В данном проекте представлен компилятор для подмножества языка Rust, написанный на C++. В нем реализованы основные стадии работы современных компиляторов.
На вход подается файл с исходным кодом. Лексический анализатор выделяет из лексем все токены. Они поступают в синтаксический анализатор, которой строит AST. Абстрактное синтаксическое дерево строится по грамматике, которая описана при помощи формальной системы определения синтаксиса EBNF. В финальной стадии к работе приступает генератор кода, который генерирует ассемблерный код по полученному ранее AST.
Каждый токен является объектом класса Token. В этом файле также содержится
строго-типизированное перечисление enum class token_type, в котором находятся все известные компилятору токены.
За лексический анализ отвечает класс Lexer. Он хранит в себе вектор
токенов vector<Token*> tokens, указатель на текущий токен size_t current_token_index и методы для анализа и дальнейшего синтаксического анализа.
Класс Parser содержит в себе три указателя. С лексером все ясно. Интерес представляют остальные члены класса.
class Parser
{
Lexer* lex;
Ast* ast;
Variable_table* var_table;
...
}
Первый из них, это объект класса синтаксического дерева Ast, внутри которого содержится указатель на объект
класса Node. Типы узлов представлены в перечислении enum class node_type в файле
Node.h. Тип узла задает действие, которое необходимо сделать с потомками:
- Объявления переменных
DCLRT; - Присваивания
SET; - Выражения
EXPR, в том числе логическиеMORE,LESS,EQUALITY; - Условные переходы
IF,ELSE; - Цикл
FOR; - Математические операции
ADD,SUB,MUL,DIV; - Переменные
VARIABLEи константные целочисленные значенияNUMBER; - Вывод значения переменной
PRINTLN;
Составление AST - один из главных процессов для последующей генерации кода. Дерево строится методом рекурсивного спуска. Грамматика языка описана с
помощью расширенной формы Бэкуса-Наура в файле EBNF.txt. Ниже показан пример описания
нетерминала условия if-else.
...
<compound_statement> ::= "{", <statement>, "}"
<selection_statement> ::= "if", <expression>, <compound_statement>,
["else", <compound_statement>]
...
В итоге для фрагмента кода:
if var2 < var1*2 {
println!("{}",var2);
}
Получим следующее AST:
+-SEQ
+-IF
+-LESS
+-VARIABLE (var2)
+-MUL
+-VARIABLE (var1)
+-NUMBER (2)
+-SEQ
+-PRINTLN
+-VARIABLE (var2)
Примечание: узел SEQuence необходим для хранения в нем последовательной инструкции.
Объект класса Generator создается в функции int main(int argc, char* argv[]) и там же
вызывается его метод Generate.
try {
generator.Generate();
}
catch (const logic_error& error) {
cout << error.what() << endl;
}
При помощи рекурсии генератор кода обходит созданное парсером AST и добавляет необходимые строчки ассемблерного кода в файл *.asm.
Все примеры с исходным кодом, лог файлом, и сгенерированным ассемблерным кодом можно просмотреть в папке Tests.
В файлах *.rs находится исходный код на языке Rust. Например, Condition.rs.
В файле log.txt можно отследить выходные данные всех этапов работы компилятора. Например,
log.txt для программы строчкой выше.
И наконец, в файле *.asm находится сгенерированный ассемблерный код. Condition.asm
для файла, приведенного в пример выше.