Генерировать вывод вместе с парсером рекурсивного спуска
Написание простых парсеров тривиально, и я реализовал несколько за эти годы. В колледже нам тоже пришлось написать один. Но нам никогда не приходилось генерировать значимые результаты, используя этот метод; мы никогда не учились создавать бэкэнд.
Если у меня есть работающий парсер рекурсивного спуска для упрощенного Паскаля, и я хотел бы перевести код на C++, как бы я поступил так? Я не вижу необходимости в промежуточном шаге, таком как создание абстрактного синтаксического дерева.
Итак, как мне вывести вывод скомпилированного или переведенного кода? Единственный полезный пример, который я нашел в этом, находится в учебнике Джека Креншоу, но он больше фокусируется на интерфейсе, как и большинство других ресурсов. Связь между моим парсерным кодом и моей грамматикой очень очевидна. Как насчет связи между методами парсера и выводом? Мой метод парсера для объявления функции может относиться только к одному вызову EmitLn(здесь код C++). Но как насчет методов синтаксического анализа, которые не так просты, такие как выражения. Выражения разбиты на, возможно, еще много вызовов, так что намекает на необходимость функции Emit(), которая позволяет разбивать выходной код для выражения по частям. Есть ли какой-нибудь код котельной пластины для вывода кода, такой как функция EmitLn в книге Джека Креншоу "Построить компилятор"? Это также показывает, что мне нужно поддерживать базовую таблицу символов, что часто упускается в большинстве примеров.
Я прав? Что еще я должен знать? Какие-либо советы / предложения или ресурсы? Мой большой вопрос в том, что существует так много учебных пособий по внешним интерфейсам компилятора, но как насчет объяснения в конце? Я могу анализировать языки, теперь я хочу развить это, чтобы иметь возможность переводить и компилировать их в другие языки.
2 ответа
Существуют тривиальные компиляторы, использующие генераторы кода "на лету", которые выплевывают код при разборе, как вы, похоже, пытаетесь это сделать. Хорошей новостью является то, что они выглядят легко.
Проблема в том, что компиляция в основном заключается в распространении информации из одной части кода в другую, чтобы обеспечить хорошую генерацию кода. Для этого люди-компиляторы давно решили отделить разбор от генерации кода. Анализатор анализирует код и создает другую промежуточную структуру данных (часто абстрактное синтаксическое дерево или тройки), которая представляет программу.
Зачастую на промежуточной структуре проводится очень глубокий анализ, чтобы определить, как генерировать хороший код, с последующей соответствующей генерацией хорошего кода. Методы для этого хорошо сложны, но хорошо мотивированы необходимостью создавать хороший (и правильный) код.
Попробуйте проверить стандартные ресурсы компилятора. Учимся писать компилятор
Стоит потратить время на подробное чтение некоторых из этих ссылок, а не на взлом. Если вы настаиваете только на одном, Aho и Ullman - классическая книга.
Если вы хотите увидеть, как генерация кода на лету может работать достаточно хорошо, ознакомьтесь с метакомпиляторами.
Краткий ответ После сопоставления правила в вашей грамматике, вызовите функцию, которая делает что-то на основе ввода.
Длинный ответ: Из Шаблонов реализации языка от Теренса Парра, обсуждающих синтаксически-ориентированных переводчиков:
интерпретатор, ориентированный на синтаксис, не создает AST и не переводит исходный код в байт-коды... Интерпретатор напрямую передает синтаксис для выполнения операторов.
Именно об этой идее я и думал в своем первоначальном вопросе. Продолжая в книге, он описывает технику, как она работает, из чего она состоит, и ограничения. Он работает для DSL и небольших языков, а не для языков общего назначения.
Он заявляет, что такие конструкции как IF и циклы неудобны для реализации. Хотя это, скорее всего, правда, у меня не было проблем с реализацией таких функций на моем языке Peay BASIC. Языки, которые являются последовательностями инструкций или простых утверждений, идеально подходят для этого шаблона; он приводит языки обработки текста в качестве примера.
Грамматика расширена и состоит из пар "сопоставь, позвони". Таким образом, с необходимыми правилами в вашей грамматике должно быть связано действие. Пример оператора присваивания:
assignment : ID '=' expr {interp.assign($ID, $expr.value);};
expr returns [Object value] : ... ;