Ниже представлена грамматрика языка.
Нетерминальные символы зыключены к двойные ковычки ", за исключение выражения, описывающего STR, там они заключены
в одинарные ковычки '. Символ ANY означет любой символ.
S -> L {L}
L -> (A | I | C) {BLANK} ";"
A -> "print" (STR | NUM | ID)
I -> ["int"] ID "=" E
C -> "str" ID "=" STR
E -> T | E LBINOP T
T -> F | T HBINOP F
F -> "read" | ID | NUM | "(" E ")"
STR -> '"' {ANY}/{'"'} '"'
NUM -> ["-"] digit {digit}
ID -> char {char | "_" | digit}
char -> "a" | ... | "z" | "A" | ... | "Z"
digit -> "0" | ... | "9"
LBINOP -> "+" | "-"
HBINOP -> "*" | "/" | "%"
BLANK -> " " | "\t" | "\n"В языке предусмотрена возможность записывать комментарии. Для этого перед комментарием необходимо написать #. Тогда
лексический анализатор проигнорирует все символы после # до символа переноса строки.
Пример кода:
#Считываем целое число и записываем в x
print "Введите значение x";
int x = read;
#Считываем целое число и записываем в y
print "Введите значение y";
int y = read;
#Выполняем некоторое выражение
int z = x * y;
#Печатаем результат
print z;Лексический анализатор разбивает входной тексовый файл на список токенов, игнорируя все незначащие символы \t, \n, " ".
Токен имеет три параметра: type, line и union {str, num}.
Типы токенов:
-
ID - идентификатор
-
KWORD - ключевое слово
-
LBRC - открывающая скобка
-
RBRC - закрывающая скобка
-
BINOP - бинарная операция
+-=*/% -
NUM - целое число
-
STR - строка
-
SCLN - символ
; -
TEND - конец токенов
Для токенов ID и STR задается праметр str, а для NUM параметр num. Далее полученный список токенов скармливается
синтаксическому анализатору.
Синтаксический анализ языка довольно прост. Каждое выражение L должно начинаться с одного из следующих символов
ID, "print", "int". Поэтому можно сразу определить какие символы должны стоять дальше. Заканчиваться любое выражение L
должно символом ;. Проблема возникает при анализе арифметических выражений. Для них был использован конечный автомат.
Здесь под BINOP подразумевается {BINOP}/{"="}, а под READ - KWORD: "read". Слово init указывает на точку входа
в автомат, а exit - на точку выхода.
Семантический анализатор проверяет были ли заранее объявлены и инициализированы все переменные, которые используются в выражениях и при печати. Формирует новую структуру данных, которая удобна для обработки генератором кода. Полученная структура несет в себе следующую информацию:
-
char **strings- массив ссылок на все строки, которые встречаются в коде; -
unsigned max_depth- максимальная глубина в выражениях, чтобы знать сколько регистров понадобится; -
unsigned num_of_strings- кол-во строк (типstr) в коде; -
unsigned num_of_ints- кол-во переменных типаint; -
unsigned num_of_instructions- кол-во инструкций типаstruct Instr; -
struct Instr *instructions- указатель на инструкции.
Все эти данные собраны в структуру ForGenerator.
Генерирует ассемблер для процессора risc-v.
Принимает на вход struct ForGenerator и название файла, в который нужно будет записывать генерируемы код.
Имеет ограничения. Максимальное кол-во переменных не должно превышать 485 штук.
Данное число получено следующим образом: (2048 - 12 + 1) * 8 - 4 / 4 = 485.
Здесь 12 - кол-во регистров s0-s11, которые хранятся в стеке с выравниваем в 8 byte, к ним прибавляется еще регистр ra.
4 byte занимают записываемые слова (то есть переменные), поэтому делим на 4. Так же 4 байта в стеке резервируются для функции
read, результат ее работы всегда записывается в регистр sp + 0, потом переносится в необходимое место. 2048 - максимальное
значение которое можно использовать в команде addi для перемещения по стеку. Каждое аримфетическое выражение переводится в обратную польскую нотацию. Есть ограничея на глубину такого выражения - 18 (кол-во используемых регистров) переменных и чисел идущих подряд без операций.
Для сборки проекта необходимо выполнить следующие действия:
-
Шаг 1.
git clone https://github.com/Hiraev/Compiler -
Шаг 2.
cd Compiler -
Шаг 3.
cmake . -
Шаг 4.
make -
Шаг 5.
mv mh-compiler tools -
Шаг 6. Скачать SiFive GNU Embedded Toolchain
-
Шаг 7. Распаковавать скаченный файл
-
Шаг 8. Переместить все файлы из полученной директории в директорию compiler/riscv
После всех проделанных шагов директория tools должна содержать следующие файлы:
-
tools/mh-compiler -
tools/mh-driver -
tools/README.MD -
tools/riscv/README.MD -
tools/riscv/bin -
tools/riscv/include -
tools/riscv/lib -
tools/riscv/libexec -
tools/riscv/riscv64-unknown-elf -
tools/riscv/share
Для запуска компиляции необходимо запустить утилиту mh-driver. Можно указать в качестве опции ключ --save-temps, чтобы сохранить промежуточные .s - файлы. Ключ необходимо указывать первым. За ним идут названия файлов, которые необходимо скомпилировать, разделенные пробелеми. При успешной компиляции будет выведено сообшение Файл {$file} успешно скомпилирован или Не удалось скомпилировать файл {$file}, если возникла какая-то ошибка. В результате компиляции получатся исполняемые файлы с таким же названием, но с расширением .out.
Грамматика языка была описана выше.
Программа должна соответсвовать синтаксису языка.
Переменные можно называть латинскими буквами нижнего и верхнего регистра, начинаться название должно всегда с буквы, дальше в ней могут быть цифры и знак нижнего подчеркивания. Максимальная длина - 64 символа.
Объявив строку, ее необходимо тут же и инициализировать. Синтаксис следующий str {name} = "String"; Максимальная длина строки - 64 символа.
Числа так же, как и строки необходимо сразу же инициализировать. Синтаксис следующий int {name} = {Expression};. Expression - выражение любой длины в форме инфиксной записи, разрешено использовать числа, переменные, которые были уже объявлены и ключевое слово read, которое запрашивает ввод у пользователя. Разрешено использовать круглые скобки для выделения приоритета операций. Разрешенные операции: +, -, *, /, %.
В выражениях знак -, может означать как вычитание, так и отрицательное числа. Если между знаком "минус" и числом нет
пробела, то такая запись будет интерпритирована как отрицательное число.
Печатать можно как строки, так и числа.
После ключевого слова print идет либо название переменной, либо число, либо строка.
