В данном проекте представлен компилятор для подмножества языка 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
для файла, приведенного в пример выше.