Всем привет, и, сегодня вы узнаете как я писал компилятор Си.
Вот части статьи:
1 - Что за компилятор
2 - Как я его создал
Ну, как понимаете он чуть-чуть плохой но ладно.
Что за компилятор?
Я его назвал pycc. Как вы поняли, он написан на python. Ведь pycc переводится как Python C Compiler.
Он поддерживает такие правила:
- Два типа данных (int и char) 
- Цикл while и цикл do-while. 
- Условие if-else. 
- Минимальный препроцессор (только директива #include) 
- Функции через void. 
Их мало, но и чтобы эти правила работали я пропотел.
Где то щас 700 - 724 строчек кода (вроде бы).
Вот пример кода (C в хабре нету):
#include <stdio.h>
void fac(n) {
  int f = 1;
  while (n > 0) {
    f = f * n;
    n = n - 1;
  }
}
void main() {
  fac(5);
  printf("'factorial of 5: '+str(f)");
}Из этого примера кода вот что мы видим:
- Все переменные - глобальные что и хорошо, что и плохо. 
- Функция printf принимает питонью строку. 
Вывод:
factorial of 5: 120Как и ожидалось.
Да. Пока что нечего больше.
Вот так мы и прошлись по его правилам.
О том как я писал этот компилятор
Это мне далось очень не легко.
И я это знал за ранее.
Начал я с лексера. Написать Скопировать лексер с одного сайта очень легко.
Да и парсер дался достаточно легко.
Ну. По началу было всё хорошо. Я легко справлялся. И багов пока не было.
Но всё началось с void. Я реально понял что оказывается я делал всё с багами.
Когда я делал такой код:
void main() {
  int n = 5;
  while (n > 0) {
    n = n - 1;
  }
}То, парсеру это не нравилось. Но в скором я начал писать данный код:
d = Parser()
d.parse(...)
block.append(...)Это всё ОЧЕНЬ ПЛОХО. Но я делал этот проект не из-за красивого кода, а чтобы всё работало. Чего я добился.
В скором ошибка типизации... int x = "h"; это было нормально... Ну ладно эта типизация (я быстро решил) но потом и ошибка с char.
Которая была из-за пробелов. Да. Но... К концу я всё таки справился и исправил все баги кроме того самого (с пробелами, но я был доволен).
Сейчас я работаю над другими изменениями.
Да, данный заголовок был очень маленьким но я не умею объяснять истории.
CHANGELOG (только у меня, больше не у кого)
Пре-альфа 1.0
Очень мало было.
Циклы, условия и переменные.
При этом было очень примитивно и не работающее.
Альфа 1.0
Добавился сам полный функционал. Больше багов было устранено.
Но, они были. За то вы могли написать такой код:
void main() {
  int n = 5;
  while (n > 0) {
    n = n - 1;
  }
}И он мог работать. Что самое главное.
Бета 1.0
Ну прям все баги исправлены.
И добавлена возможность выводить другие данные.
Релиз 1.0
Официальный выпуск. Тогда я опубликовал это на гитхаб.
Релиз 1.1
Добавлен препроцессор.
К примеру:
#include <stdio.h>Теперь можно было импортировать заголовочные обычные файлы.
Релиз 1.1.2 (Последний)
Я исправил баг с больше и меньше.
Раньше было не так:
if stack[-2] > stack[-1]:
  ...
else:
  ...А так:
if stack[-2] >= stack[-1]:
  ...
else:
  ...И я это исправил (да, самое легкое исправление)
Итог
Статья получилась плохой и большой, но, вот ссылка на проект:
SystemSoftware2/pycc: Компилятор C на Python
В скором я добавлю больше всего. И возможно компилироваться будет в x86 ASM.
UPD: компилируется в байткод
Комментарии (37)
 - alex5jb17.05.2025 18:28- Но зачем?  - SystemSoft Автор17.05.2025 18:28- Скорее для опыта. Для развлечения. Мне оно нравится.  - saipr17.05.2025 18:28- Я бы вам рекомендовал обратить внимание то на книгу Эндрю Таненбаума «Operating Systems Design and Implementation» на ее первое издание и на то, как в ней представлен язык Си. И может это описание Си и положить в основу вашего компилятора. Удачи вам. 
 
 
 - pnmv17.05.2025 18:28- Прошу поощения. Возможно, я невнимательно прочёл, но что и во что он "компилирует"? Если это перевод кода, написанного на си, в код на питоне, то почему именно компилятор, а не транслятор?  - SystemSoft Автор17.05.2025 18:28- Оу, я и правда забыл это. Он компилируется в собственный набор инструкций (или проще говоря байткод) который выполняется виртуальной машине  - SystemSoft Автор17.05.2025 18:28- созданной тоже мной (для понимания)  - HyperWin17.05.2025 18:28- У нас тоже есть подобный проект, я разрабатываю свою ISA с эмулятором и ассемблером, а один чувак в нашей команде пишет компилятор ANSI C для него. 
 
 
 
 - JBFW17.05.2025 18:28- "Но зачем?" - в смысле, зачем именно C? - Что если вместо всем известного C попробовать придумать другой язык, решающий какую-то в принципе иную задачу? - Не A * B, а что-нибудь из статистического анализа, например, или еще что-то довольно специальное, то, что сейчас решается через программирование хотя бы на том же С, а могло бы быть записано на своем языке в несколько операторов? - И вот к нему - компилятор в байткод?  - pnmv17.05.2025 18:28- Такую задачу вредно навешивать на единственного разработчика.  - JBFW17.05.2025 18:28- Почему нет? ) - Если бы тот же Линус Торвальдс начал с подбора команды разработчиков - никакого линукса вообще бы не получилось никогда, завязли бы в согласованиях, утверждениях, бюджетировании, зависли на митингах и признали бы экономически необоснованным и нецелесообразным. 
 
 
 - CatAssa17.05.2025 18:28- Посмотрите на старую книжку Р.Берри и Б.Микинза, там и компилятор С есть: - Скрытый текст 
 - CrazyHackGUT17.05.2025 18:28- Увидев printf, можно понять, что это не Си. Вовсе. gcc это скомпилировать - скомпилирует вполне, но по факту работать не будет так же. 
 - imsyava17.05.2025 18:28- Аж захотелось узнать, как эта реализация факториала - #include <stdio.h>- void- fac(n) {- int- f = 0;- while- (n > 0) { f = f * n; n = n - 1; } }- void- main() {- fac(5);- printf("'factorial of 5: '+str(f)");- }- Смогла выдать - factorial of 5: 120- Там же что ни подставь f будет 0. Либо этот самодельный компилятор умеет распознавать человеческий фактор и корректировать вычисления для соответствия результата ожиданиям, либо при инициализации там всё же f = 1. 
 - kmatveev17.05.2025 18:28- Я почитал исходники. Питона не знаю, но что-то попробовал понять. - Лексер нужно улучшать. Во-первых, он принципиально неправильно написан: вы тупо пытаетесь регулярки применять к вводу, из-за чего, встретив, например, "iframe", он выделит "if" как ключевое слово. Правильный способ - конечный автомат, то есть у лексера появляется состояние. Ну и напоследок, вы бы хоть компиляцию регулярок не делали каждый раз, очень уж тормознуто. - С компилятором не понял вот чего: все функции, получается, инлайнятся? - С виртуальной машиной много непоняток. Почему-то FETCH, который у вас для сохранения глобальной переменной на стеке, в случае, если переменная неинициализированна (или тупо опечатка), пушит 0. А STORE наоборот, ругается, если глобальной переменной нет. Я бы сделал наоборот. Не очень понял смысла операции POP, и мне кажется, что она pc недостаточно увеличивает.  - SystemSoft Автор17.05.2025 18:28- Все функции инлайнятся потому что к тому времени я не понимал как это делать. - С виртуальной машиной я убрал эти непонятности. - Лексер... Ну... Да, согласен. - С парсером тоже согласен. Там теги просто тратят смысл. 
 
 - GambitOZ17.05.2025 18:28- Писать компилятор скриптового движка си написанного на скриптовом движке питона, который в свою очередь изначально написан на си - это оригинально. Браво)) 
 - SkylineXXX17.05.2025 18:28- Кажется первые версии компиляторов gcc, RMS писал "одну каску". И ничего...не помер. При чем не с двумя типами данных, а полные. 
 
           
 



SystemSoft Автор
CHANGELOG бонусом.