Рубрики
Без рубрики

Компиляторы – один из самых пугающих проектов

Термины “компилятор” и “Промежуточный язык” раньше были для меня несколько пугающими, то есть до тех пор, пока…

Автор оригинала: Matt Hope.

Термины “компилятор” и “Промежуточный язык” раньше были для меня несколько пугающими; то есть до тех пор, пока я не решил взять на себя задачу создания своего собственного компилятора. Еще лучше, чтобы сделать его немного интереснее, я также продолжил и разработал его на Java, что является необычным выбором, учитывая, что я в основном работаю на C#. Теперь эта задача заключалась не столько в быстром “взгляните на StackOverflow и сделайте это”. Это потребовало много чтения и исследований того, как работают компиляторы. Большая часть моего времени была потрачена на чтение “Книги о драконах”, которая, хотя и может быть немного устаревшей для более современных современных компиляторов, обладала основными концепциями, которые все еще очень актуальны.

Прежде чем мы погрузимся в подробности моего путешествия, важно понять базовую структуру компилятора. Я не буду вдаваться в подробности, так как мы можем сохранить это для другого раза, но ниже приведены основные шаги простого компилятора:

  1. Лексический анализ – компилятор обрабатывает исходный код и выводит поток токенов.
  2. Синтаксический анализатор – компилятор обрабатывает поток символов и выводит синтаксическое дерево.
  3. Генератор промежуточного кода – компилятор обрабатывает синтаксическое дерево и выводит некоторый 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”