Сегодня вы узнаете как же было хорошо что названия этой статьи было не «Как сделать компилятор C на Python» ведь когда я делал его то я не знал как сделать компилятор C на Python.
Информация о компиляторе
Вот все правила которые я запомнил:
-
Типы данных int и char (второй мне кажется багованный)
-
Куча циклов: do-while, while
-
Условие if-else
-
Функции только с помощью void
-
Если к char добавить строку «hello world» то сделайте с помощью переменных
-
Эти операторы: +, -, /, *, <, >, ==, !=
-
Присваивание переменных (имя = значение;)
-
Декларация с инициализацией (тип_данных имя = значение;)
-
А препроцессор… Ещё подумаю.
BNF для этого компилятора слишком сложное. Так что сразу кидаю пример кода:
void fac(n) { int f = 1; while (n > 1) { f = f * n; n = n - 1; } } void main() { fac(5); }
Правда вывода не какого кроме Execution finished. Почему? Потому что когда виртуальная машина выводит результат работы программы то она выполняет с помощью eval переменную stdout которая уже есть автоматически.
А как вывести результат мы поговорим позже.
А вот как работает мой велосипед, тоже известно. Всего четыре этапа:
-
Лексер возвращает токены
-
Парсер строит AST
-
Компиляция функций
-
Выполнения байткода
Так и работает.
Примеры функций
Функция вывода (printf):
void printf(mes) { stdout = stdout + mes; }
Функция просить строку из пайтона чтобы потом виртуальная машина выполнила сообщение из mes. Ну и остальное stdout.
Функция факториала:
void fac(n) { int f = 1; while (n > 1) { f = f * n; n = n - 1; } }
В конец можно добавить строку stdout = stdout + "f"; чтобы выводить результат вычисления факториала.
Также можно реализовать Фибоначчи и много других функций. Но пока кроме printf, факториала и всех этих декрементов и инкрементов больше не нашел применения.
Более объясняющие примеры программ
В этом заголовке я объясню синтатикс этого компилятора ещё больше.
Начнём с if-else. Вот пример (как работает):
if (условие) { //блок кода } //если else нужен else { //блок кода }
Как вы заметили комментарии здесь тоже есть.
Вот полный пример:
void main() { int x = 5; if (x < 10) { x = 10; } else { x = 5; } }
Создаём переменную x. И проверяем: меньше 10? Тогда будет 10. Больше десяти или 10? Будет 5.
Всё легко. Да.
Теперь do-while. Вот пример работы:
do { //блок кода } while (условие);
Пример:
void main() { int a = 5; do { a = a - 1 } while (a > 6); }
Я специально сделал условие со значением false. Но «a» будет не 5 а 4. В этом весь смысл do-while.
Я думаю while можно не объяснять ведь я его показывал ранее.
Нормальный пример
Пример Фибоначчи 10:
//сама функция фибоначчи void fib(n) { int n1 = 1; int n2 = 1; int n3 = 1; int count = n - 2; while (count) { n3 = n1 + n2; n1 = n2; n2 = n3; count = count - 1; } } //функция вывода void printf(mes) { stdout = stdout + mes; } //все эти вызовы void main() { fib(10); printf("'fibonacci of 10: '+str(n2)"); }
Вот так. И всё равно главное в этом примере это while.
А вывод:
Execution finished fibonacci of 10: 55
Вот так вот. В принципе я всё показал.
Давайте я расскажу как я писал этот компилятор.
Как я писал этот компилятор
Большое начало
Как всегда, когда я делаю такие проекты то думаю не о том что «я умею и мне будет легко» а «я умею но будет сложно» или «я пишу в первый раз так что ошибка неизбежна».
Скорее всего подойдет фраза 3. Потому что я в первый раз пишу полный нормальный компилятор с блоками кода и с функциями.
Но писать я стал сразу. И с начало я нагло скопировал код первого компилятора. А лексер я взял из ещё одного проекта. Просто переопределил регулярки. А начал писать я с присваивания. Просто a = 10; как нормально. В скором я начал писать для декларации. Изначально думал сделать все эти ваши return, декларация функций. Но потом понял что не смогу так быстро это сделать. И первым прорывом в моём компиляторе стал анализ типов данных для деклараций. Так что после этого я решил снять данный проект с второстепенного статуса и начал делать его больше чем что либо.
Дальше я сделал if-else и while. С начало сделать это было просто но потом это стало самым сложным. К тому времени когда мой велосипед заработал и начал выводить правильный результат то я написал свой первый пост про этот компилятора.
Потом я начал писать функции. Получение аргументов стало самым легким. Но дальше пошла порча кода парсера номер 1 чтобы добиться правильного результата. Своего я добился и потом написал пост о том что сделал функции.
Дальше я добавил ошибки и порешал баг типизации где int x = «h»; чувствовал себя правильно. А также с этими ошибками я сделал цикл do-while который я никогда не использовал на тестах.
Большая идея
Началось всё с того что я захотел сделать функцию printf но при этом не задевая парсер новым ключевым словом.
Долго думал, думал, думал… И придумал!
Я решил придумать поток номер 1. А точнее поток стандартного вывода (stdout).
Как я реализовал его? Просто! Засунул в переменные ‘stdout’ со значением ‘ ‘.
В конце работы выполнял значение stdout с помощью пайтоновского eval.
Вот так и закончилось продолжилось создания этого проекта.
Великая серия багов, из-за чего я чуть не удалил весь проект.
Когда я начал делать идею с stdout то компилятор тут же посыпался.
Ошибка с плюсом, ошибка с аргументами и остальная фигня…
Когда я начал исправлять это в первый раз то у меня была мысль «удали это» но потом я отбросил комп и про себя сказал: «я много сделал и исправил чтобы удалить это».
Потом вечерком я снова начал исправлять. И я много чего нашёл неработающего и много чего исправил. Так я добился (полу) стабильной работы компилятора.
Вот как я нахожу баги: тестирую с крутым лицом и случайно нахожу баг. В принципе так и есть. Вроде бы😅.
Заключение
Вот такой компилятор получился. Тот кто дочитал до этого момента молодец.
Всем пока!
ссылка на оригинал статьи https://habr.com/ru/articles/904548/
Добавить комментарий