Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Курсовая работа2 / Docs / KursovikOtchet00_.doc
Скачиваний:
27
Добавлен:
01.05.2014
Размер:
791.55 Кб
Скачать

Разработка синтаксического анализатора

Назначение синтаксического анализатора

Синтаксический анализ – наиболее трудоёмкий этап компиляции. К задачам, выполняемым на этом этапе, относятся:

- разбор цепочки токенов, сформированной лексическим анализатором;

- выявление и диагностика синтаксически некорректных цепочек;

- формирование промежуточного представления программы в виде тетрад;

- выявление и диагностика семантических ошибок

Важным этапом подготовки к синтаксическому анализу является создание формальной грамматики и управляющих таблиц анализатора. Формальная грамматика основывается на описании языка в форме Бэкуса-Наура и характеризует набор правил, применяемых для порождения текста входной программы. Согласно условию, формальная грамматика, описывающая язык, должна принадлежать классу SLR(1).

Стратегии проектирования синтаксического анализатора

Синтаксический анализатор класса SLR(1) может быть реализован на основе конечного детерминированного распознавателя со стековой организацией магазинной памяти. Важным элементом такого распознавателя является управляющая таблица, которая ставит в соответствие текущему символу разбираемой цепочки и текущему состоянию автомата набор действий, позволяющих принять решение о допуске входной цепочки, ошибке, либо переходу автомата в другое состояние. Сам автомат и его управляющая таблица могут проектироваться независимо, поскольку алгоритм распознавания цепочек не зависит от конкретной управляющей таблицы.

SLR(1)-автомат осуществляет реализацию алгоритма типа “перенос-свёртка”. Таким образом, он выполняет восходящий разбор цепочки и стремится свернуть входную цепочку к стартовому символу грамматики. Стратегической операцией данного алгоритма является свёртка выявленной во входной цепочке грамматической основы к порождающему её символу. В рамках проектирования компилятора, можно построить такую грамматику входного языка, свёртки которой можно использовать для генерации промежуточного представления текста программы. Для этого на основе грамматики формируется атрибутно-транслирующая грамматика, а распознающий автомат дополняется возможностями по работе с атрибутами своих состояний.

Центральной проблемой разработки синтаксического анализатора является формирование управляющих таблиц, обеспечивающих разбор грамматики входного языка. Существует несколько принципиально разных решений этой проблемы:

  • использование общепризнанных генераторов кода по входным грамматикам (наиболее распространены Yacc и Bison). Этот подход очевиден, однако он неприемлем в рамках курсовой работы, поскольку подобные генераторы осуществляют LALR(1) разбор грамматики (условием курсовой работы является SLR(1) разбор). Класс грамматик LALR(1) не является подмножеством класса SLR(1), в свою очередь, класс SLR(1) не является подмножеством класса LALR(1);

  • разбиение грамматики входного языка на некоторое количество подграмматик меньшего размера. Для каждой из подграмматик управляющие таблицы можно построить вручную или используя имеющиеся старые программы на МОЭВМ. Результирующие таблицы необходимо вручную перенести в код языка программирования;

  • разработать инструментальное средство, позволяющее формировать управляющие таблицы анализатора для грамматик произвольного размера. В инструментальном средстве предусмотреть автоматическую генерацию кода языка программирования.

Первый вариант формирования таблиц невозможен по условию. Недостатки второго варианта очевидны: необходимо использовать крайне неудобные программы генерации таблиц, а затем переносить таблицы в программный код, что может привести к возникновению довольно большого числа сложностей. Исходя из вышесказанного, необходимо разработать гибкое и эффективное средство, реализующее генерацию таблиц произвольных грамматик и автоматизирующее генерацию исполняемого кода по этим таблицам.

Разработка вспомогательных средств генерации таблиц

Требования к разрабатываемой программе:

  • программа должна осуществлять считывание грамматики из файла и формирование пополненной грамматики, необходимой для реализации алгоритма. Формат файла с входными данными должен быть достаточно простым, чтобы обеспечить заполнение и редактирование вручную. В программе принят достаточно простой текстовый формат xml, который содержит описание терминалов, нетерминалов и порождающих правил грамматики;

  • программа должна осуществлять анализ входной грамматики на предмет принадлежности классу SLR(1). В случае принадлежности программа должна формировать управляющие таблицы разбора, в противном случае – выводить диагностические сообщения о невозможности сформировать таблицы;

  • в случае успешного формирования таблиц программа должна осуществлять генерацию их представления в программном коде компилятора. Полученные файлы должны подключаться к проекту компилятора и осуществлять управление движком синтаксического анализатора.

Работа программы по построению таблиц разбора заключается в выполнении следующих действий:

  • проверка корректности входной грамматики. Программа способна выявить некоторые ошибки входной грамматики, которые делают дальнейшую работу бессмысленной;

  • формирование пополненной грамматики. Тривиальное действие по добавлению одного нетерминала и одного порождающего правила в грамматику;

  • формирование списка грамматических вхождений (LR-Items). Тривиальное действие по нумерации символов, входящих в порождающие правила грамматики;

  • построение функции переходов конечного автомата. В программе реализован модифицированный алгоритм, в результате работы которого автомат получается детерминированным;

  • определение отношений «ПЕРВ» и «СЛЕД» («FIRST» и «FOLLOW»). Предположительно, на данном этапе анализа проявляется ошибка, в соответствии с которой стартовый символ грамматики должен быть указан первым во множестве нетерминалов. В противном случае анализ грамматики получается некорректным;

  • определение функции действий автомата. Заключительный этап анализа, на котором принимается решение о принадлежности входной грамматики классу SLR(1). В результате работы в программе формируется полная управляющая таблица разбора грамматики.

После того, как программа сформирует внутреннее представление управляющей таблицы, происходит формирование представления этих таблиц в коде произвольного языка программирования. В соответствии с принятой спецификацией синтаксического анализатора, для управляющей таблицы формируется следующая информация:

  • константы, определяющие количество правил грамматики, а также количество записей в разделах управляющей таблицы;

  • константы, представляющие символические имена нетерминалов и состояний автомата, а также константы, определяющие уникальные имена для правил грамматики (такое имя зависит от самого правила, а не порядка его появления в грамматике);

  • перечисление всех используемых терминалов в грамматике;

  • массив, содержащий информацию о правилах грамматики;

  • массив, описывающий функцию переходов конечного автомата;

  • массив, описывающий функцию действий конечного автомата.

Итогом всего вышеперечисленного является создание средства, позволяющего подключать к проекту компилятора SLR(1) грамматику. Реализуется следующая схема:

Пример использования вспомогательных средств генерации таблиц

В приложении 1 приводится листинг xml файла, соответствующего языку ПАСКАЛЬ.

Данная грамматика принадлежит классу SLR(1).

Текст данной грамматики подаётся на вход программе, которая осуществляет построение управляющих таблиц. Ниже приведён вид работы программы:

В приложении 2 приведен листинг файлов, сгенерированных программой и готовых для подключения к синтаксическому анализатору.

Спецификация синтаксического анализатора

Синтаксический анализатор исполнен в виде класса, заголовок которого приводится ниже:

class CSyntAnalyzer

{

protected:

// таблица переходов g(x)

Map2D<CStackSymbol, CSymbol, CStackSymbol> m_TableTrans;

// таблица действий f(x)

Map2D<CStackSymbol, CSymbol, int> m_TableAct;

// Магазин

// std::stack<CStackSymbol> m_Stack;

Stack<CStackSymbol> m_Stack;

// Хранит длину правой части каждого правила

std::vector<RuleInfo> m_vRulesInfo;

// Допустимые терминалы грамматики

std::set<ENUM_TOKENS> m_Tokens;

// функция действий f(x)

int f( const CSymbol &sym );

// функция переходов g(x)

CStackSymbol g( const CSymbol& sym );

public:

void InitTables(FInfo FV[], unsigned FVLen, GInfo GV[], unsigned GVLen);

void InitRules(RuleInfo RI[], unsigned RulesCount);

void InitTokens(ENUM_TOKENS Tokens[], unsigned Count);

virtual bool Analyze( CTokenStream::iterator it_token,

CTokenStream::iterator it_end );

// "Семантическая процедура", вычисляет атрибуты и пишет промежуточный код

virtual void Operation(int RuleNum, SAttrSet &LeftAttrSet) { };

};

Центральной функцией данного класса является функция Analyze, вызов которой осуществляет разбор текста входной программы - синтаксический анализатор потребляет входной поток токенов (сформированный на предыдущем этапе компиляции) и принимает решение о синтаксической корректности программы. Особый интерес представляет функция Operation, предназначенная для реализации проверки семантической корректности программы и осуществления генерации её промежуточного представления. Функция объявлена виртуальной и имеет пустое тело. Таким образом, данный класс реализует только синтаксический анализ, а проверка семантики и генерация кода осуществляется в классе-наследнике CCodeGen, объявление которого представлено ниже.

class CCodeGen: public CSyntAnalyzer

{

CNameTable m_NameTable;

CTetradStream m_Tetrads;

unsigned m_NextName;

unsigned m_NextTempNameInt;

unsigned m_NextTempNameReal;

unsigned m_NextTempNameBool;

unsigned m_NextTempNameStr;

unsigned m_NextTempNameChar;

unsigned m_NextLabel;

public:

CNameTable & GetNameTable() { return m_NameTable; }

CTetradStream & GetTetrads() { return m_Tetrads; }

CCodeGen();

bool Analyze(CTokenStream::iterator it_token, CTokenStream::iterator it_end, const char *FileName);

// Выполняет "семантические процедуры" при свертке по правилу № RuleNum

virtual void Operation(int RuleNum, SAttrSet &LeftAttrSet);

// Создаёт новую переменную

std::string NewName(VarInfo vi);

// Создает новую временную переменную

std::string NewNameTemp(VarInfo vi);

// Создает новую метку

std::string NewLabel();

// Создает новое имя для литерала

SAttrSet ProcessLiter(TYPES t, unsigned TableIndex);

// Необходимо вызвать после использования имени, для учета временных имен

void VarHasBeenUsed(std::string name);

// проверяет может ли тип использоваться в арифметических операциях

bool IsTypeArithm(TYPES typ);

// проверяет могут ли эти типы использоваться в арифметических операциях

bool AreTypesArithm(TYPES typ1, TYPES typ2);

// Выполняем бинарную логическую операцию

SAttrSet PerformBoolOperBin(ENUM_TOKENS op, SAttrSet &left, SAttrSet &right);

void WriteBoolOperationBin(ENUM_TOKENS op, std::string op1, std::string op2, std::string res);

// Выполняем бинарную арифметическую операцию

SAttrSet PerformArithmOperBin(SAttrSet &left, SAttrSet &right);

void WriteArithmOperBin(ENUM_TOKEN_VAL op, TYPES TypeOfOperands, std::string op1, std::string op2, std::string res);

// Выполняет арифметическое сравнение

SAttrSet PerformComparisonArithm(SAttrSet &left, SAttrSet &right);

// Анализирует типы переменных и при необходимости выполняет приведение типов

// Возвращает тип результата операции

TYPES ArithmCast(ENUM_TOKEN_VAL op, SAttrSet &left, SAttrSet &right, std::string &op1, std::string &op2);

// Выполняет присваивание

void PerformAssignment(SAttrSet &id, SAttrSet &expr);

// Выполняет преобразование Integer в Real

std::string ITOF(std::string operand);

// Выполняет преобразование Char в String

std::string CharToStr(std::string operand);

// Включаем в потом новую тетраду

void AddTet(TET_OPERATION op, std::string op1, std::string op2, std::string res);

// Проверяет объявлен ли идентификатор

bool IsIdDeclared(SAttrSet &attr);

void ErrorIdRedefinition(SAttrSet &attr);

void ErrorIdUndeclared(SAttrSet &attr);

SAttrSet PerformStrConcat(SAttrSet &left, SAttrSet &right);

void ErrorTypeUnsupported(unsigned line);

};

Как видно, в данном классе переопределена виртуальная функция Operation. Поля с именами «m_Next*» хранят числовые значения, используемые для генерации уникальных идентификаторов для меток, переменных и констант, используемых в промежуточном и ассемблерном коде.

Члены-данные m_NameTable и m_Tetrads предназначены для накопления информации об используемых идентификаторах и формирования выходного потока тетрад соответственно.

Подготовка полной грамматики входного языка

Для корректного функционирования компилятора требуется грамотно описать грамматику входного языка. Требование о принадлежности классу SLR(1) является необходимым, но не достаточным условием правильного функционирования синтаксического анализатора (так как помимо разбора, синтаксический анализатор выполняет генерацию промежуточного представления программы). Исходя из этих соображений, можно выдвинуть ряд дополнительных требований к грамматике:

  • грамматика должна быть однозначной;

  • грамматика должна быть леворекурсивной;

  • свёртка по любому правилу должна однозначно определять набор семантических операций, необходимых для вычисления атрибутов всех её символов.

В результате проведённой экспериментальной работы, в компиляторе принята следующая грамматика:

// Группа описания самой программы

<_Program> ::= <ProgName> <Declarations> <StructStatement> TKN_TOCHKA

<ProgName> ::=

<ProgName> ::= TKN_PROGRAM TKN_ID TKN_ TOCHKA - ZPT

// Группа описания секций объявений

<Declarations> ::=

<Declarations> ::= <Declarations> <DeclarationSection>

<DeclarationSection> ::= <DeclarationsVar>

<DeclarationSection> ::= <DeclarationsConst>

// Группа описания констант

<DeclarationsConst> ::= TKN_CONST <ConstDecs>

<ConstDecs> ::= <SingleConstDec>

<ConstDecs> ::= <ConstDecs> <SingleConstDec>

<SingleConstDec> ::= TKN_ID TKN_REL_EQUAL <Expression> TKN_ TOCHKA - ZPT

// Группа описания объявлений переменных

<DeclarationsVar> ::= TKN_VAR <VarDecs>

<VarDecs> ::= <SingleVarDec>

<VarDecs> ::= <VarDecs> <SingleVarDec>

<SingleVarDec> ::= TKN_ID TKN_COLON <Type> TKN_ TOCHKA - ZPT

// Группа описания типов

<Type> ::= TKN_BOOLEAN

<Type> ::= TKN_CHAR

<Type> ::= TKN_INTEGER

<Type> ::= TKN_REAL

<Type> ::= TKN_STACK

<Type> ::= TKN_STRING

// Группа описания операторов

<Statement> ::= <StatementMatched>

<Statement> ::= <StatementUnmatched>

<StatementMatched> ::= <PartIfMatched> <MatchedElse>

<StatementMatched> ::= <StatementOther>

<StatementUnmatched> ::= <PartIf> <UnmatchedThen>

<StatementUnmatched> ::= <PartIfMatched> <UnmatchedElse>

<PartIf> ::= TKN_IF <Expression>

<MatchedThen> ::= TKN_THEN <StatementMatched>

<MatchedElse> ::= TKN_ELSE <StatementMatched>

<UnmatchedThen> ::= TKN_THEN <Statement>

<UnmatchedElse> ::= TKN_ELSE <StatementUnmatched>

<Statement> ::=

<StatementMatched> ::= <ForInit> <StatementMatched>

<StatementUnmatched> ::= <ForInit> <StatementUnmatched>

<PartIfMatched> ::= <PartIf> <MatchedThen>

<StatementMatched> ::= <WhileInit> <StatementMatched>

<StatementUnmatched> ::= <WhileInit> <StatementUnmatched>

// Группа описания других операторов

<StatementOther> ::= <StructStatement>

<StatementOther> ::= <Assignment>

<StatementOther> ::= <StatementIO>

<StatementOther> ::= <Procedure>

// Группа описания структурного оператора

<StructStatement> ::= TKN_BEGIN <StatementListNonEmpty> TKN_END

<StatementListNonEmpty> ::= <Statement>

<StatementListNonEmpty> ::= <StatementListNonEmpty> TKN_ TOCHKA - ZPT <Statement>

// Группа описания оператора присваивания

<Assignment> ::= TKN_ID TKN_ASSIGN <Expression>

// Группа описания цикла for

<ForInit> ::= TKN_FOR TKN_ID TKN_ASSIGN <Expression> TKN_TO <Expression> TKN_DO

// Группа описания цикла while

<WhileInit> ::= <WhileKeyword> <Expression> TKN_DO

<WhileKeyword> ::= TKN_WHILE

// Группа описания выражений

<Expression> ::= <Expression> TKN_OR <ExprDisjunctor>

<Expression> ::= <ExprDisjunctor>

<ExprDisjunctor> ::= <ExprDisjunctor> TKN_AND <ExprConjunctor>

<ExprDisjunctor> ::= <ExprConjunctor>

<ExprConjunctor> ::= <ExprConjunctor> <ExprRelation> <ExprRelator>

<ExprConjunctor> ::= <ExprRelator>

<ExprRelation> ::= TKN_REL_EQUAL

<ExprRelation> ::= TKN_REL_NOT_EQUAL

<ExprRelator> ::= <ExprRelator> TKN_PLUS <ExprConstituent>

<ExprRelator> ::= <ExprConstituent>

<ExprConstituent> ::= <ExprConstituent> TKN_MULT <ExprMultiplier>

<ExprConstituent> ::= <ExprMultiplier>

<ExprMultiplier> ::= TKN_BRACKET_OPEN <Expression> TKN_BRACKET_CLOSE

<ExprMultiplier> ::= TKN_NOT <ExprMultiplier>

<ExprMultiplier> ::= TKN_PLUS <ExprMultiplier>

<ExprMultiplier> ::= <ExprAtom>

<ExprAtom> ::= TKN_ID

<ExprAtom> ::= TKN_LITER_CHAR

<ExprAtom> ::= TKN_LITER_INT

<ExprAtom> ::= TKN_LITER_REAL

<ExprAtom> ::= TKN_LITER_STRING

<ExprAtom> ::= TKN_TRUE

<ExprAtom> ::= TKN_FALSE

<ExprAtom> ::= <Function>

// Группа описания операторов ввода-вывода

<StatementIO>::= TKN_READ TKN_BRACKET_OPEN TKN_ID TKN_BRACKET_CLOSE

<StatementIO>::= TKN_WRITE TKN_BRACKET_OPEN <Expression> TKN_BRACKET_CLOSE

<StatementIO>::= TKN_WRITELN

// Группа описания работы со стеком

<Procedure> ::= TKN_PUSH TKN_BRACKET_OPEN TKN_ID TKN_ZPT <Expression> TKN_BRACKET_CLOSE

<Function> ::= TKN_TOP TKN_BRACKET_OPEN TKN_ID TKN_BRACKET_CLOSE

<Procedure> ::= TKN_POP TKN_BRACKET_OPEN TKN_ID TKN_BRACKET_CLOSE

Приведенная грамматика описывает возможности входного языка компилятора и, кроме того, она удовлетворяет всем поставленным требованиям. Она принадлежит классу SLR(1) и успешно обрабатывается анализатором грамматик.

В результате генерации управляющих таблиц, синтаксический анализатор получает возможность распознавать цепочки, принадлежащие входному языку и отсекать цепочки, языку не принадлежащие. Выходом синтаксического анализатора является последовательность свёрток, сворачивающих корректную входную цепочку к стартовому символу грамматики. Для того, чтобы получить внутреннее представление программы, необходимо связать эти свёртки с семантическими процедурами, формирующими результирующий поток тетрад.

Описание схемы атрибутной трансляции

При описании атрибутной трансляции будут использоваться следующие семантические процедуры:

  • NewName – создает переменную с уникальным идентификатором, соответствующую переменной, константе или литералу исходной программы;

  • NewNameTemp – создает переменную с уникальным идентификатором, соответствующую результату промежуточных вычислений;

  • NewLabel – создает имя новой метки;

Рассмотрим наиболее важные правила грамматики, где происходит передача значений атрибутов или выполняются семантические процедуры. Используемые операционные символы будут описаны ниже.

  • объявление константы:

<SingleConstDec>::=TKN_IDaTKN_REL_EQUAL<Expression>b,c,constTKN_SEMICOLON{DC}n,p,q,const

n  a // индекс в таблице идентификаторов

p  b // тип выражения

q  с // имя переменной

  • объявление переменной:

<SingleVarDec> ::= TKN_IDa TKN_COLON <Type>b TKN_ TOCHKA - ZPT{DVar}p,q

p  a // индекс в таблице идентификаторов

q  b // тип

  • все свертки типов выполняются идентично, поэтому рассмотрим только одну:

<Type>a ::= TKN_BOOLEANb

a  b // тип

  • правила вывода для условного оператора:

<PartIf>LabelElse ::= TKN_IF <Expression>var,t {CHBT}t {GOTO_ZERO}var,l

l  NewLab

LabelElse  l

v var

<StatementUnmatched> ::= <PartIf>LabelElse <UnmatchedThen>{DEFL}l

l  LabelElse

<StatementUnmatched> ::= <PartIfMatched>LabelElse <UnmatchedElse>{DEFL}l

l  LabelElse

<PartIfMatched>lab ::= <PartIf>LabelElse <MatchedThen>{GOTO}EndL{DEFL}lelse

EndL  NewLabel // метка для конца условного оператора

lab  EndL

lelse  LabelElse

<StatementMatched> ::= <PartIfMatched>lab <MatchedElse>{DEFL}label

label  lab

  • правила вывода цикла с параметром:

<ForInit>var,LastVar,BL,EL ::= TKN_FOR TKN_IDname,idt TKN_ASSIGN <Expression>expr1,t1 TKN_TO <Expression>expr2,t2 TKN_DO {ASSIGN}name,idt,expr1,t1 {GOTO}endl {DEFL}beginl

LastVar  expr1 // имя переменной со значением вернего предела

Var  name // имя переменной-параметра цикла

Endl  NewLabel // метка конца

EL  Endl

Beginl  NewLabel // метка начала

BL  Beginl

<StatementMatched> ::= <ForInit> var,LastVar,BL,EL <StatementMatched>{INC}v{DEFL}E {GREATER}v1,v2,res{GOTO_ZERO}arg,lab

<StatementUnmatched> ::= <ForInit> var,LastVar,BL,EL <StatementUnmatched>{INC}v{DEFL}E {GREATER}v1,v2,res{GOTO_ZERO}arg,lab

v var // имя переменной-параметра цикла

E  EL // метка конца

v1  var

v2  LastVar // верхний предел для переменной цикла

arg  res // результат сравнения переменной и верхнего предела

lab  BL // метка начала

  • правила вывода цикла с условием:

<WhileKeyword>LB,LE ::= {DEFL}lab

LB  NewLabel

LE  NewLabel

lab  LB

<WhileInit>Lbeg,Lend ::= <WhileKeyword>LB,LE <Expression>expr TKN_DO {GOTO_ZERO}arg,lab

arg  expr // имя переменной, хранящей значение выражения

lab  LE //

Lbeg  LB // метка начала

Lend  LE // метка конца

<StatementMatched> ::= <WhileInit> Lbeg,Lend <StatementMatched>{GOTO}lab1{DEFL}lab2

<StatementUnmatched> ::= <WhileInit> Lbeg,Lend <StatementUnmatched>{GOTO}lab1{DEFL}lab2

lab1  Lbeg // метка начала

lab2  Lend // метка конца

  • присваивание:

<Assignment> ::= TKN_IDn TKN_ASSIGN <Expression>e {ASSIGN}name, expr

name  n // имя переменной

expr  e // имя переменной, хранящей выражение

  • правила вывода для выражений:

<Expression>res,t,const::=<Expression>v1,t1,c1TKN_OR<ExprDisjunctor>v2,t2,c2{CHBT}t1,t2{OR}a,b,c

a  v1 // значение первого выражения

b  v2 // значение второго выражения

res  c // результат

t  Boolean

const  c1 && c2 // константность

<Expression>attributes ::= <ExprDisjunctor>att

attributes  att // все атрибуты

<ExprDisjunctor>res,t,xonst ::=<ExprDisjunctor>v1,t1,c1TKN_AND<ExprConjunctor>v2,t2,c2{CHBT}t1,t2{AND}a,b,c

a  v1 // значение первого выражения

b  v2 // значение второго выражения

res  c // результат

t  Boolean

const  c1 && c2 // константность

<ExprDisjunctor>attributes ::= <ExprConjunctor>att

attributes  att // все атрибуты

<ExprConjunctor>res,t,c::= <ExprConjunctor>v1,t1,c1<ExprRelation>op<ExprRelator>v2,t2,c2{CHT}t1,t2{OP}op,v1,v2,r

res  r // результат

c  c1 && c2 // константность

<ExprConjunctor>attributes ::= <ExprRelator>att

attributes  att // все атрибуты

<ExprRelation>oper ::= TKN_REL_EQUAL

oper  VAL_REL_E // из кода операции токена

<ExprRelation>oper ::= TKN_REL_NOT_EQUAL

oper  VAL_REL_NE // из кода операции токена

Для остальных отношений аналогично.

<ExprRelator>res,t,c ::= <ExprRelator>v1,t1,c1 TKN_PLUSop <ExprConstituent>v2,t2,c2 {CHT}t1,t2 {+}op,v1,v2,r

res  r

c  c1 && c2 // константность

<ExprRelator>attributes::=<ExprConstituent>att

attributes  att // все атрибуты

<ExprConstituent>res,t,c::=<ExprConstituent>t1,v1,c1 TKN_MULTop <ExprMultiplier>v2,t2,c2 {CHT}t1,t2 {*}op,v1,v2,r

res  r

c  c1 && c2 // константность

<ExprConstituent>attributes ::= <ExprMultiplier> att

attributes  att // все атрибуты

<ExprMultiplier>attrubutes ::= TKN_BRACKET_OPEN <Expression>att TKN_BRACKET_CLOSE

attributes  att // все атрибуты

<ExprMultiplier>t,res,c ::= TKN_NOT <ExprMultiplier>t1,v1,c1 {CHBT}t1{NOT}v1,r

res r

c  c1 // константность

<ExprMultiplier>t,res,c ::= TKN_PLUSop <ExprMultiplier>v1,t1,c1{CHT}t1{UPLUS}op,v1,r

res  r

c  c1 // константность

<ExprMultiplier>attributes ::= <ExprAtom>att

attributes  att // все атрибуты

<ExprAtom>t,v,c ::= TKN_IDv1,t1,c1

t  t1 // тип

v  v1 // имя

c  c1 // константность (константа или переменная?)

<ExprAtom> t,v,c ::= TKN_LITER_CHAR

t  TYPE_CHAR // тип

v  NewName // имя

c  true // литерал есть константное выражение

Описание использованных операционных символов:

  • объявление константы - {DC}n,p,q,const

  • если (const == false) Ошибка: «Неконстантное выражение»

  • иначе создаем переменную с заданным типом, в таблице идентификаторов помечаем тип и выставляем флаг константности.

  • объявление переменной - {DVar}p,q

  • Создаем переменную с заданным типов, в таблице идентификаторов помечаем тип.

  • проверка типа - {CHBT}t

  • Если (t != TYPE_BOOLEAN) Ошибка: «Несоответствие типов»

  • переход на метку если условие ложно - {GOTO_ZERO}var,label

Создает тетраду:

TET_OP_GOTO_ZERO

var

Label

  • определение метки - {DEFL}label

Создает тетраду:

TET_OP_DEF_LABEL

label

  • безусловный переход по метке - {GOTO}label

Создает тетраду:

TET_OP_GOTO

Label

  • присваивание - {ASSIGN}IDname,IDtype,Expr,ExprType

Проверяет соответствие типов IDType и ExprType, при возможности выполняет приведение значения выражения к типу идентификатора. В итоге создает тетраду вида:

TET_OP_ASSIGN_*

Expr

IDname

  • инкремент целого числа - {INC}var

Создает тетраду:

TET_OP_INC_INT

var

  • целочисленное больше - {GREATER}v1,v2,res

Создает тетраду:

TET_OP_REL_G

V1

V2

Res

  • проверка соответствия типов - {CHT}t1,t2

Если типы t1 и t2 несовместимы, то ошибка «Несоответствие типов»

  • операция сравнения чисел - {OP}op,v1,v2,r

  • При необходимости выполняется преобразование целочисленного операнда к вещественному - создается переменная tempVar для промежуточного результата и тетрада вида:

    TET_OP_ITOF

    Var

    tempVar

  • Создается тетрада вида

op (код операции)

Var1

Var2

Res

  • Операция сложения (или вычитания) - {+}op,v1,v2,r

Аналогично предудущей.

  • Операция умножения (или деления) - {*}op,v1,v2,r

Аналогична предыдущим за тем исключением, что в случае деления двух целых чисел они оба приводятся к вещественным значениям.

  • Логическое НЕ - {NOT}value,result

Создает тетраду:

TET_OP_NOT

value

result

  • Унарный плюс (или минус) {UPLUS}op,value,result

  • если (op == VAL_PLUS_P), то есть унарный плюс, то result = value.

  • иначе (если унарный минус) создается тетрада:

TET_OP_UMINUS

value

result

Представление атрибутов в памяти ЭВМ

Передача значений атрибутов во время работы атрибутного ДПМ-процессора осуществляется через магазинные символы. Так как для перевода различных конструкций языка требуются различные наборы атрибутов, возникает проблема - как хранить эти разные наборы в объектах одного и того же класса, представляющего магазинный символ.

Одним из возможных решений данной проблемы является создание иерархии, базовый класс которой представляет общий интерфейс для работы с наборами атрибутов, а производные классы – реализуют разные наборы атрибутов. Преимуществом данного подхода является минимальный расход памяти. Недостатками являются необходимость аккуратного отслеживания объектов в динамической памяти и относительная сложность работы с полиморфными объектами, имеющими совершенно разные наборы полей.

Другим более простым решением данной проблемы представляется использование объединений (union, записей с вариантами по терминологии Паскаля). В данной работе хранения наборов атрибутов в магазинных символах используется структура данных, представленная ниже.

union SAttrSet

{

struct // для представления типов

{

TYPES T;

} SType;

struct // Для представления атрибутов выражений

{

TYPES type;

char name[INTERNAL_NAME_LEN+1];

// Если в выражении только константы и литералы, оно константное

// и может быть использовано для инициализации констант

bool bConst; // выражение константное

} SExpr;

ENUM_TOKEN_VAL Operation; // Для сверток к ExprRelation

// Для магазинных символов, представляющих токены

CToken *pToken;

// Для if () then ...

char label[INTERNAL_NAME_LEN+1];

struct // Для цикла for

{

char LabBegin [INTERNAL_NAME_LEN+1]; // метка начала цикла

char LabEnd [INTERNAL_NAME_LEN+1]; // метка конца цикла

char VarName [INTERNAL_NAME_LEN+1]; // имя переменная цикла

char LastName [INTERNAL_NAME_LEN+1]; // конечное значение переменной

} For;

struct

{

char LabBegin [INTERNAL_NAME_LEN+1]; // метка начала цикла

char LabEnd [INTERNAL_NAME_LEN+1]; // метка конца цикла

} While;

};

Описание промежуточного представления программы

Согласно заданию, промежуточным языком являются тетрады (трехадресный код). Ниже перечислены все используемые в работе тетрады с указанием их формата.

Таблица 5

Описание тетрады

Синтаксис

Семантика

Код операции

Оп1

Оп2

Рез.

TET_OP_DEF_LABEL

Label

Определение метки Label

TET_OP_GOTO

Label

Безусловный переход на метку Label

TET_OP_GOTO_ZERO

R

Label

Переход на Label, если R = 0

Целочисленные операции

TET_OP_ADD

V1

V2

Res

Сложение V1 и V2

TET_OP_SUBTRACT

V1

V2

Res

Вычитание V2 из V1

TET_OP_MULT

V1

V2

Res

Произведение V1 и V2

TET_OP_UMINUS

V

Res

Унарный минус

Операции с вещественными числами

TET_OP_ADD_F

V1

V2

Res

Сложение V1 и V2

TET_OP_SUBTRACT_F

V1

V2

Res

Вычитание V2 из V1

TET_OP_MULT_F

V1

V2

Res

Произведение V1 и V2

TET_OP_DIV_F

V1

V2

Res

Деление V1 на V2

TET_OP_UMINUS_F

V

Res

Унарный минус

Сравнение целых

TET_OP_REL_E

V1

V2

Res

V1 равно V2

TET_OP_REL_G

V1

V2

Res

V1 больше V2

TET_OP_REL_GE

V1

V2

Res

V1 больше или равно V2

TET_OP_REL_NE

V1

V2

Res

V1 не равно V2

TET_OP_REL_L

V1

V2

Res

V1 меньше V2

TET_OP_REL_LE

V1

V2

Res

V1 меньше или равно V2

Сравнение вещественных

TET_OP_REL_E_F

V1

V2

Res

V1 равно V2

TET_OP_REL_G_F

V1

V2

Res

V1 больше V2

TET_OP_REL_GE_F

V1

V2

Res

V1 больше или равно V2

TET_OP_REL_NE_F

V1

V2

Res

V1 не равно V2

TET_OP_REL_L_F

V1

V2

Res

V1 меньше V2

TET_OP_REL_LE_F

V1

V2

Res

V1 меньше или равно V2

Преобразования типов

TET_OP_ITOF

i

r

Преобразование целого i к вещ. r

TET_OP_CHAR_TO_STR

сh

s

Преобразование символа ch в строку

Логические операции

TET_OP_AND

V1

V2

Res

Логическое И

TET_OP_OR

V1

V2

Res

Логическое ИЛИ

TET_OP_NOT

V1

Res

Логическое НЕ

Присваивания

TET_OP_ASSIGN_INT

val

id

Присваивание целого числа

TET_OP_ASSIGN_FP

val

id

Присваивание вещественного числа

TET_OP_ASSIGN_CH

val

id

Присваивание символа

TET_OP_ASSIGN_STR

val

id

Присваивание строки

TET_OP_ASSIGN_BOOL

val

id

Присваивание булевского значения

TET_OP_ASSIGN_STACK

val

id

Присваивание стека

Операции ввода

TET_OP_READ_INT

Res

Ввод целого числа

TET_OP_READ_REAL

Res

Ввод вещественного числа

TET_OP_READ_CHAR

Res

Ввод символа

TET_OP_READ_STR

Res

Ввод втроки

Операции вывода

TET_OP_WRITE_INT

val

Вывод на консоль целого числа

TET_OP_WRITE_REAL

Val

Вывод на консоль веществ. Числа

TET_OP_WRITE_CHAR

Val

Вывод на консоль символа

TET_OP_WRITE_STR

Val

Вывод на консоль строки

TET_OP_WRITELN

Перевод каретки

TET_OP_INC_INT

Val

Инкремент целого числа

TET_OP_STR_CONCAT

Str1

Str2

Res

Конкатенация строк Str1 и Str2

Операции со стеком

TET_OP_STACK_PUSH

Id

Val

Втолкнуть символ в стек Id

TET_OP_STACK_POP

Id

Вытолкнуть символ из стека Id

TET_OP_STACK_TOP

Id

Res

Считать вершину стека Id

Обработка ошибок

Для обработки возможных ошибок разработан класс CErrorHandler. Программе существует только один статический объект этого класса, доступный на всех этапах работы программы.

class CErrorHandler

{

std::list<CError> m_ErrorList;

const char* ErrorDesc(ENUM_ERRORS ErrNum);

public:

typedef std::list<CError>::iterator iterator;

void Error(CError err) throw(CCompilerException);

iterator begin() { return m_ErrorList.begin(); }

iterator end() { return m_ErrorList.end(); }

void Reset() { m_ErrorList.clear(); }

unsigned ErrorsCount() { return m_ErrorList.size(); }

};

При обнаружении первой ошибки программа останавливает процесс компиляции и выводит диагностическое сообщение, в котором содержится номер строки, где, с большой вероятностью, находится ошибка, и в некоторых случаях лексема, вызвавшая ошибку.

Программа может распознать и выдать сообщения о следующих типах ошибок:

  • "Неизвестный символ" – лексический анализатор встретил символ, не используемый в языке;

  • "Синтаксическая ошибка" – выдается синтаксическим анализатором, когда значение функции действий или переходов равно ОШИБКА.

  • "Необъявленный идентификатор" – необъявленный идентификатор использован в выражении или в присваивании.

  • "Переопределение идентификатора" – дважды объявлен один и тот же идентификатор;

  • "Нечисловой операнд в арифметической операции" – сообщение выдается, когда один из операндов арифметического выражения имеет нечисловой тип (строка, символ и т.д.)

  • "Нелогический операнд в логической операции" – в операции AND, OR или NOT использован операнд не булевского типа;

  • "Присваивание константе"

  • "Несоответствие типов"

  • "Незавершенная строка" – выдается лексическим анализатором, когда при считывании строкового литерала до закрывающейся кавычки обнаружен конец строки;

  • "Не константное выражение" – для инициализации константы использовано не константное выражение, то есть содержащее не только литералы и константы;

  • "Операция не определена для данного типа" – выдается, например, если в программ найдена операция read с параметром типа Boolean;

  • "Значение не входит в допустимый диапазон" – выдается, если целочисленный литерал имеет значение, выходящее за диапазон значений типа integer;

  • "Длина строки превышает максимально допустимую" – выдается, если длина строкового литерала превышает 255.

Соседние файлы в папке Docs