SerafimArts
@SerafimArts
Кратко о себе

На какие слои должен (может) разделяться ЯП и его компилятор?

Доброго времени суток!

Ситуация следующая: В наличии есть GraphQL SDL (Schema Definition Language). Это декларативный предметноориентированный ЯП под строго заточенную платформу (встраиваемый в другой язык). Примерно как "XML" + "парсер XML".

Всё уже есть, всё готовое. Как выглядит всё сейчас в целом:
1) EBNF-Like грамматика.

2) Самопальный лексер: Делит исходники по этой грамматике на токены.

3) Самопальный LL(k) парсер: Выстраивает AST.

4) Объектная модель и Reflection: Далее происходит выстраивание базовой объектной модели типов (классов рефлексии, как в Java или PHP). Например, мы находим тип X, и создаём в "словаре", помечая, что он ещё не собран до конца и отправляемся дальше.

5) Линкер: После предварительного билда проходит линковка, находим все типы и выстраиваем отношения между ними. Например, берём те типы, которые ещё не были собраны и начинаем их файтюнить. Как только находим неизвестную связь с другим - ищем его в системе и пытаемся дособрать, если он так же не собран до конца. В противном случае вылетают ошибки, вида "Undefined type X".

6) Coercion: Допустимые приведения типов и исправляторы кода. Например, если аргумент помечен как "Nullable" (т.е. может содержать NULL), то к нему принудительно добавляется NULL в качестве значения по-умолчанию, если программа не содержит обратного.

7) Валидатор: Проверка типов, правил наследования, аргументов и прочего. Например, у нас есть тип-объект "X", который содержит два поля с одинаковыми названиями. Надо проверить это и сказать человеку, мол, нельзя так.

8) Препроцессинг: Выполняются константные скалярные выражения, перегрузки. Например, мы указываем какую-то директиву (аннотация или аттрибут в другом языке), это invocation инструкция, т.е. вызов существующей задекларированной директивы.

9) Билдер (или кеш): Результат собирается в набор файлов, которые потом умеют грузиться в память в будущем, минуя все эти этапы с проверками и линковкой.

В итоге мы на выходе получаем АПИ (например, на псевдоджаве):
Compiler compiler = new Compiler();

// Собирает или берёт уже скомпиленный файл и возвращает результат - класс рефлексии этого файла (документа)
ReflectionDocument document = compiler.compile('interface Example {}');

// Массив типов в документе (в нашем случае он один)
document.getTypes(); 

// Массив полей у типа (интерфейса) Example
document.getType('Example').getFields();


А теперь к вопросам:
1) Насколько правильна такая модель работы? У меня есть подозрения, что превращаение из AST в классы рефлексии не совсем перспективно в т.ч. для поддержки. Но т.к. это декларативный язык - хз. Если есть какой-то опыт запиливания подобных штук - хорошо бы услышать советы.

2) Может имеет смысл вначале сборки в опкод (или байткод), для переносимости между платформами (т.е. возможности напрямую считывать императивные команды, вроде "создай тип Х", "добавь ему поле Y", уже выстроенные в нужном порядке и отвалидированные) и уже потом из этих инструкций генерировать и Reflection API, и какие-нибудь XML схемки?
  • Вопрос задан
  • 934 просмотра
Пригласить эксперта
Ответы на вопрос 1
  • Насколько правильна такая модель работы?

    Расскажите подробнее, какая задача решается. Если бы вы спрашивали про обработку ЯП общего назначения, указав целевой язык для компиляции (например, "TypeScript в JavaScript" или "Java в JVM"), то ваши цели были бы понятны. Но у вас декларативный язык, и другие цели, не указанные в вопросе. Судя по примеру API цель - предоставить объектную модель? Или сгенерировать выходное представление на другом языке?

    возможности напрямую считывать императивные команды, вроде "создай тип Х", "добавь ему поле Y"

    Хоть я до конца не понял ваших целей, мне кажется это плохой идеей - не вижу смысла добавлять императивность там, где есть декларативное описание, тем самым вы потеряете часть информации о вашем декларативном представлении и привнесёте паразитную - например, зависимость от порядка исполнения этих опкодов. Оно вам зачем? Ведь мы как раз стараемся избавляться от такой паразитной информации, когда используем DSL с собственным синтаксисом вместо ЯП общего назначения (вспоминаются всякие ORM маппинги записанные кодом на C#, где код как бы императивный, но порядок декларативных инструкций на самом деле значения не имеет).
    В этом ВОЗМОЖНО был бы смысл если ваша цель - сгенерировать императивный же код на разных целевых языках. Ну не знаю, например валидаторы. Валидатор на C#, валидатор на JavaScript, валидатор на PHP. Тогда да, возможно стоило бы использовать такое промежуточное представление, и то непонятно был бы от него толк или вред.
    Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через TM ID
Похожие вопросы
Вакансии с Моего Круга Все вакансии
Заказы с Фрилансим Все заказы