Автор оригинала: Matt Hope.
Термины “компилятор” и “Промежуточный язык” раньше были для меня несколько пугающими; то есть до тех пор, пока я не решил взять на себя задачу создания своего собственного компилятора. Еще лучше, чтобы сделать его немного интереснее, я также продолжил и разработал его на Java, что является необычным выбором, учитывая, что я в основном работаю на C#. Теперь эта задача заключалась не столько в быстром “взгляните на StackOverflow и сделайте это”. Это потребовало много чтения и исследований того, как работают компиляторы. Большая часть моего времени была потрачена на чтение “Книги о драконах”, которая, хотя и может быть немного устаревшей для более современных современных компиляторов, обладала основными концепциями, которые все еще очень актуальны.
Прежде чем мы погрузимся в подробности моего путешествия, важно понять базовую структуру компилятора. Я не буду вдаваться в подробности, так как мы можем сохранить это для другого раза, но ниже приведены основные шаги простого компилятора:
- Лексический анализ – компилятор обрабатывает исходный код и выводит поток токенов.
- Синтаксический анализатор – компилятор обрабатывает поток символов и выводит синтаксическое дерево.
- Генератор промежуточного кода – компилятор обрабатывает синтаксическое дерево и выводит некоторый IL.
Как он был построен
После установки полезной платформы “компилятор-конструктор” под названием “ANTLR” первым шагом было создание моей грамматики. Эта грамматика точно определила бы, как ведет себя мой пользовательский язык. Я решил использовать строго типизированный подход к дизайну моего языка, очень похожий на C#, поскольку он обеспечивает немного большую защиту от этих простых семантических ошибок. Мы все знаем, насколько запутанными могут быть корпоративные проекты JavaScript. Полную грамматику можно найти здесь .
Язык, который я предложил, выглядит примерно так:
int b; b = factorial(8); int factorial(int n) { int val; int counter; val = 1; counter = 1; while(counter <= n) { val = val * counter; counter = counter + 1; }; return val; }; print(b);
Как только грамматика была завершена, пришло время использовать ANTLR для анализа моего исходного кода с использованием грамматики и создания дерева выражений, которое я мог бы преобразовать в свое собственное AST (абстрактное синтаксическое дерево). Так вот, это был не самый простой процесс но я следовал шаблону “посетитель”, чтобы проходить по дереву выражений по одному узлу за раз, а затем переводил эти узлы в свой собственный AST. Код для перевода можно найти здесь .
Теперь, создав свой собственный AS, следующим логическим шагом было выполнить семантический анализ, чтобы проверить, нет ли каких-либо ошибок в программном коде, который я компилировал. Это может включать такие ошибки, как попытка присвоить строковому значению целое число или использовать переменную до ее объявления. Код для семантического анализатора можно найти здесь .
Небольшая дополнительная задача, которую я поставил перед собой, состояла в том, чтобы позволить компилятору обрабатывать несвязанные объявления, то есть вы можете вызвать функцию до ее объявления. Теперь для этого потребовалось немного больше исследований, но, в конце концов, я решил решить эту проблему с помощью простого графика зависимостей. Мой компилятор генерирует график зависимостей после выполнения семантического анализа, а затем переупорядочивает AST на основе отображенных зависимостей. Чтобы изменить порядок AST, необходимо выполнить два прохода. При первом прохождении график зависимостей привязывается к ACT, чтобы убедиться, что найдены все объявления переменных и функций. Затем график зависимостей повторно привязывается к ACT для второго прохождения, чтобы создать зависимости от переменных и функций, обнаруженных в первом прохождении. Код для графика зависимостей можно найти здесь .
На данный момент мы находимся на заключительном (и, вероятно, самом легком) этапе моего путешествия – пройдемся по AST, чтобы создать что-то полезное. Теперь это было так же просто, как рекурсивно обойти каждый из узлов, используя шаблон посетителя, о котором я упоминал ранее. Я сгенерировал несколько “СВОИХ посетителей”; наиболее интересным из них был “Pythonvisitor”, который выводил АКТ в исполняемом коде Python, и “ТОЧЕЧНЫЙ посетитель”, который выводил AST в виде точечного графика.
Все репозитории компилятора можно найти здесь
Что я узнал
Создание этого компилятора было непростой задачей, но это дало мне гораздо более глубокое понимание языков высокого уровня, которые мы все считаем само собой разумеющимися. Лично я никогда не видел, чтобы мне приходилось беспокоиться о таблицах символов и зависимостях на низком уровне языка. Это заставляет вас сделать шаг назад и оценить работу, проделанную некоторыми умными умами, стоящими за популярными компиляторами, такими как Rosyln и Javac. Это невероятные американские горки знаний, позволяющие понять, как именно код, который мы пишем ежедневно, компилируется в машинный код и выполняется. Если бы я мог сделать что-то по-другому, я бы избегал использования ANTLR и вместо этого создал свой собственный лексер и синтаксический анализатор грамматики. Хотя ANTLR был великолепен и помог мне в этом путешествии, я чувствую, что пропустил некоторые уроки, которые мог бы получить. Я бы тоже остановился на C#, так как он находится в пределах моей зоны комфорта. Ява была в порядке но у меня просто было несколько незначительных проблем с этим, одной из которых было отсутствие перегрузки динамического метода, поскольку это упростило бы реализацию посетителей AST.
Оригинал: “https://www.codementor.io/@atleastitry/compilers-one-of-the-more-intimidating-projects-1eptl7k8ju”