Разработка синтаксического анализатора
Назначение синтаксического анализатора
Синтаксический анализ – наиболее трудоёмкий этап компиляции. К задачам, выполняемым на этом этапе, относятся:
- разбор цепочки токенов, сформированной лексическим анализатором;
- выявление и диагностика синтаксически некорректных цепочек;
- формирование промежуточного представления программы в виде тетрад;
- выявление и диагностика семантических ошибок
Важным этапом подготовки к синтаксическому анализу является создание формальной грамматики и управляющих таблиц анализатора. Формальная грамматика основывается на описании языка в форме Бэкуса-Наура и характеризует набор правил, применяемых для порождения текста входной программы. Согласно условию, формальная грамматика, описывающая язык, должна принадлежать классу 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.