Написание простого DSL компилятора на Delphi (7. Компилятор AST)

Перевод поста Writing a Simple DSL Compiler with Delphi (7. AST Compiler).

Эта статья представляет собой описание компилятора AST используемого для проекта моего языка программирования. Если вы только начинаете читать эту серию, то я бы рекомендовал вам начать с этого поста. Как минимум вы должны прочитать предыдущий пост Intermezzo так как он разъясняет некоторые части компилятора которых я не касаюсь здесь.

В каркасе моего игрушечного компилятора, компилятор (или codegen, как он называется внутри) — часть кода которая реализует интерфейс ISimpleDSLCodegen. Этот интерфейс предоставляет только одну функцию, Generate, которая принимает абстрактное синтаксическое дерево и преобразует его в объект, который реализует интерфейс ISimpleDSLProgram, который позволяет вам вызывать любую функцию скомпилированной программы по имени.

Компилятор по умолчанию реализован классом TSimpleDSLCodegen в модуле SimpleDSLCompiler.Compiler. Методы в этом классе в основном занимаются чтением и пониманием AST пока код, фактически, создаётся в методах в модуле SimpleDSLCompiler.Compiler.Codegen.

Этот компилятор создаёт программу которая является интерфейсом класса TSimpleDSLProgram (также находящемся в SimpleDSLCompiler.Compiler).

Функционирование компилятора очень похоже на компилятор представленный в Intermezzo — с одним критичным отличием. Выражения в моём игрушечном языке могут использовать параметры функций как слагаемые. Поэтому вычислитель выражений должен иметь доступ к параметрам текущей функции.

История начинается в методе TSimpleDSLCodegen.Generate который для каждой функции в дереве сначала компилирует тело функции (CompileBlock) и затем генерирует функциональную обёртку для этого тела (CodegenFunction).

Давайте начнём с последней функции так как она даст нам больше контекста (каламбур, как вы увидите скоро).

CodegenFunction создаёт основную обёртку в виде анонимного метода для текущей функции. Этот анонимный метод получит контекст выполнения, который позволит этой функции вызывать другие функции. Затем анонимный метод построит контекст функции который грубо соответствует стековому фрейму (stack frame) производимому "нормальным" компилятором. Этот контекст хранит указатель на контекст выполнения и копию параметров (значений) переданных в функцию. Затем он вызывает block(context) для выполнения переданного блока.

Спустимся на один уровень ниже... Функция TSimpleDSLCodegen.CompileBlock компилирует каждое выражение в блоке вызовом CompileStatement и затем вызывает CodegenBlock для обёртки скомпилированных выражений в блок.

Скомпилированный код, который реализует блок, снова является анонимным методом. Он принимает контекст (передаваемый анонимным методом реализующим функцию) и просто передаёт этот же контекст во все выражения в блоке.

Это продолжается и продолжается. Большая часть кода довольна скучна и предсказуема. Например, это метод который генерирует код для оператора if.

Вещи становятся интереснее как только мы хотим скомпилировать слагаемое. Слагаемое может представлять константу (целое число), параметр (названный variable в codegen так как в будущем может быть добавлена поддержка переменных) или вызовом функции.

Компиляция константы тривиальна. Нам просто нужна функция возвращающая эту константу.

Доступ к параметрам немного сложнее. AST содержит индекс этого параметра и мы просто должны применить его к свойству Params контекста.

Генерация вызова функции немного более сложная. Она должна настроить массив параметров который будет передан в вызов функции, найти корректную функцию через контекст запуска и затем вызывать функцию.

В конце компилятор производит большой анонимный метод, использующий внутри другие анонимные методы, и который при вызове возвращает результат.

Например, эта минимальная программа ...

... генерирует что-то похожее на следующее чудовище. В реальности код даже более странный так как он должен обрабатывать захваченные переменные.

Он определённо не для слабонервных, но вы не должны смотреть скомпилированный код (конечно, исключая случая отладки компилятора).

Вы можете удивится скорости этого кода. Должен признать — не очень быстро. Я дам вам более точные цифры в следующей части этой серии которая будет описывать интерпретатор для этого языка.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *