Генерация кода для простейших арифметических операций, работа с ассемблерным стеком. Генерация загрузки и сохранения переменных. Разбор и генерация оператора печати.
Для реализации арифметических (и не только) операций в языке ассемблер используется работа с регистровой памятью. Так, чтобы поместить значение (числовую константу или переменную) в регистр, используется инструкция mov. Команды для арифметических операций приведены в таблице ниже.
Операция | Инструкция ассемблера |
+ | add |
- | sub |
* | mul |
/ | div |
Таким образом, код для выражения a + b будет выглядеть следующим образом:
mov ax, a
mov bx b
add ax, bx
Результат этой последовательности команд будет сохранен в регистре ax. Но в ситуации со сложными выражениями необходимо где-то сохранять промежуточные результаты. Для этих целей мы будем использовать ассемблерный стек: инструкция push - помещение значения регистра на верхушку стека, pop – извлечение из стека в регистр. После каждой атомарной операции мы будем сохранять результат в стек, чтобы не потерять его. Ниже приведен доработанный псевдокод методов синтаксического анализатора: добавленные строки выделены.
|
|
разобратьИнструкцию()
{
если(LexicalAnalyzer.текущаяЛексема == Lexems. Идентификатор)
{
Идентификатор Х = NameTable.найтиПоИмени(LexicalAnalyzer.текущееИмя);
если(!Х. эквивалентно(новый Идентификатор()))
{
разобратьИнструкциюПрисваивания();
CodeGenerator.добавитьИнструкцию("pop ax");
CodeGenerator.добавитьИнструкцию("mov " + X. Имя + ", ax");
}
иначе
ошибка();
}
}
tType разобратьСложениеИлиВычитание()
{
tType т;
Lexems оператор;
если(LexicalAnalyzer.текущаяЛексема == Lexems.Плюс
или LexicalAnalyzer.текущаяЛексема == Lexems.Минус)
{
оператор = LexicalAnalyzer.текущаяЛексема;
LexicalAnalyzer.разобратьСледующуюЛексему();
т = разобратьУмножениеИлиДеление();
}
иначе
т = разобратьУмножениеИлиДеление();
если(LexicalAnalyzer.текущаяЛексема == Lexems.Плюс
или LexicalAnalyzer.текущаяЛексема == Lexems.Минус)
{
выполнять
{
оператор = LexicalAnalyzer.текущаяЛексема;
LexicalAnalyzer.разобратьСледующуюЛексему();
т = разобратьУмножениеИлиДеление();
switch(оператор)
{
case Lexems.Плюс:
CodeGenerator.добавитьИнструкцию("pop bx");
CodeGenerator.добавитьИнструкцию("pop ax");
CodeGenerator.добавитьИнструкцию("add ax, bx");
CodeGenerator.добавитьИнструкцию("push ax");
break;
case Lexems.Минус:
CodeGenerator.добавитьИнструкцию("pop bx");
CodeGenerator.добавитьИнструкцию("pop ax");
CodeGenerator.добавитьИнструкцию("sub ax, bx");
CodeGenerator.добавитьИнструкцию("push ax");
break;
}
}
пока(LexicalAnalyzer.текущаяЛексема == Lexems.Плюс
или LexicalAnalyzer.текущаяЛексема == Lexems.Минус);
}
вернуть т;
}
tType разобратьУмножениеИлиДеление()
{
Lexems оператор;
tType т = разобратьПодвыражение();
|
|
если(LexicalAnalyzer.текущаяЛексема == Lexems.Умножить
или LexicalAnalyzer.текущаяЛексема == Lexems.Делить)
{
выполнять
{
оператор = LexicalAnalyzer.текущаяЛексема;
LexicalAnalyzer.разобратьСледующуюЛексему();
т = разобратьПодвыражение();
switch(оператор)
{
case Lexems.Умножить:
CodeGenerator.добавитьИнструкцию("pop bx");
CodeGenerator.добавитьИнструкцию("pop ax");
CodeGenerator.добавитьИнструкцию("mul bx");
CodeGenerator.добавитьИнструкцию("push ax");
break;
case Lexems.Делить:
CodeGenerator.добавитьИнструкцию("pop bx");
CodeGenerator.добавитьИнструкцию("pop ax");
CodeGenerator.добавитьИнструкцию("cwd");
CodeGenerator.добавитьИнструкцию("div bl");
CodeGenerator.добавитьИнструкцию("push ax");
break;
}
}
пока(LexicalAnalyzer.текущаяЛексема == Lexems.Умножить
или LexicalAnalyzer.текущаяЛексема == Lexems.Делить);
}
вернуть т;
}
tType разобратьПодвыражение()
{
Идентификатор х;
tType т = tType.None;
если(LexicalAnalyzer.текущаяЛексема == Lexems. Идентификатор)
{
х = NameTable.найтиПоИмени(LexicalAnalyzer.текущееИмя);
если(!Х. эквивалентно(новый Идентификатор()) и х.категория == tCat.Var)
{
CodeGenerator.добавитьИнструкцию("mov ax, " + LexicalAnalyzer.текущееИмя );
CodeGenerator.добавитьИнструкцию("push ax");
LexicalAnalyzer. разобратьСледующуюЛексему ();
вернуть х.тип;
}
иначе
ошибка();
}
иначе если(LexicalAnalyzer.текущаяЛексема == Lexems.Число)
{
CodeGenerator.добавитьИнструкцию("mov ax, " + LexicalAnalyzer.текущееЧисло );
CodeGenerator.добавитьИнструкцию("push ax");
LexicalAnalyzer.разобратьСледующуюЛексему();
вернуть tType.Int;
}
иначе если(LexicalAnalyzer.текущаяЛексема == Lexems.ЛеваяСкобка)
{
LexicalAnalyzer.разобратьСледующуюЛексему();
т = разобратьВыражение();
проверитьЛексему(Lexems.ПраваяСкобка);
}
иначе
ошибка();
вернуть т;
}
После доработки текущих методов наш транслятор будет генерировать ассемблерный код, однако, проверить работоспособность этого кода можно проверить разве что отладчиком, ведь в синтаксисе нашего языка до сих пор нет оператора вывода на печать. Закрепим за оператором вывода на печать ключевое слово print. В синтаксическом анализаторе необходимо реализовать метод разбора этого оператора.
разобратьИнструкциюВыводаНаПечать()
{
проверитьЛексему(Lexems.ВыводНаПечать);
если(LexicalAnalyzer.текущаяЛексема == Lexems.Идентификатор)
{
Идентификатор X = NameTable.НайтиПоИмени(LexicalAnalyzer.текущееИмя);
CodeGenerator.добавитьИнструкцию("push ax");
CodeGenerator.добавитьИнструкцию("mov ax, " + LexicalAnalyzer.текущееИмя);
CodeGenerator.добавитьИнструкцию("CALL PRINT");
CodeGenerator.добавитьИнструкцию("pop ax");
LexicalAnalyzer.разобратьСледующуюЛексему();
}
иначе
ошибка();
}
Внутри этого метода мы сохраняем содержимое регистра аккумулятора в стек, вызываем процедуру вывода на печать, о которой речь пойдет далее, и восстанавливаем содержимое аккумулятора. Далее дополним метод разобратьИнструкцию().
разобратьИнструкцию()
{
если(LexicalAnalyzer.текущаяЛексема == Lexems. Идентификатор)
{
Идентификатор Х = NameTable.найтиПоИмени(LexicalAnalyzer.текущееИмя);
если(!Х. эквивалентно(новый Идентификатор()))
разобратьИнструкциюПрисваивания();
иначе
ошибка();
}
иначе если(LexicalAnalyzer.текущаяЛексема == Lexems.ВыводНаПечать)
разобратьИнструкциюВыводаНаПечать();
}
В генераторе кода реализуем объявление процедуры вывода на печать, а в методе компилировать() синтаксического анализатора вызовем реализованный метод.
декларироватьПроцедуруВыводаНаПечать()
{
добавитьИнструкцию("PRINT PROC NEAR");
добавитьИнструкцию("MOV CX, 10");
добавитьИнструкцию("MOV DI, BUFEND - PRINT_BUF");
добавитьИнструкцию("PRINT_LOOP:");
добавитьИнструкцию("MOV DX, 0");
добавитьИнструкцию("DIV CX");
добавитьИнструкцию("ADD DL, '0'");
добавитьИнструкцию("MOV [PRINT_BUF + DI - 1], DL");
добавитьИнструкцию("DEC DI");
добавитьИнструкцию("CMP AL, 0");
добавитьИнструкцию("JNE PRINT_LOOP");
добавитьИнструкцию("LEA DX, PRINT_BUF");
добавитьИнструкцию("ADD DX, DI");
|
|
добавитьИнструкцию("MOV AH, 09H");
добавитьИнструкцию("INT 21H");
добавитьИнструкцию("RET");
добавитьИнструкцию("PRINT ENDP");
}
компилировать()
{
………………………………………………………………………………………………………
CodeGenerator.декларироватьЗавершениеОсновнойПроцедуры();
CodeGenerator. декларироватьПроцедуруВыводаНаПечать();
CodeGenerator.декларироватьЗавершениеКода();
}
Для тестирования работы транслятора можно использовать любой код на исходном языке, например, такой:
int a,b,c
begin
a=7
b=4
a=a*a-(7*b)
c=(a-b)*a+b
print c
end
После компиляции, линкования и запуска сгенерированного ассемблерного кода программа должна вывести на печать правильный результат – 361.
Примеры кода:
Файлы CodeGenerator.cs, SyntaxAnalyzer.cs в проекте.
Содержание отчета
3.1. Название работы и ее исполнители.
3.2. Цель работы.
3.3. Краткое (по 2-3 предложения) описание процедур (функций) программы генерации кода.
3.4. Листинг программы.
3.5. В случае необходимости, информация о доработке программ лексического и синтаксического анализа.
3.6. Распечатки контрольных примеров и результатов их выполнения.
3.7. Выводы по проделанной работе.
Лабораторная работа № 10. Тестирование приложения.
ТЕСТИРОВАНИЕ ПРОЕКТА
Тестирование проходит по ошибкам, целенаправленно внесенным во входной файл. Ниже приведены возможные варианты ошибок в различных блоках транслятора.