В большинстве языков с развитым мета-программированием существует 2 способа написания макросов — построение синтаксического дерева из кирпичиков (ExprXXX
-блоки в daScript), и с помощью цитирования (quote/qmacro_xxx
). Примеры генерации каждым из способов — в предыдущей заметке.
Второй способ более компактный, но приводит к появлению мета-языка для внедрения кусков сгенерённого кода в цитируемый код. Проблема этого языка в том, что он разрастаётся — постепенно появляются различные макросы для деклараций блоков/функций/переменных (qmacro macros), различные способы внедрения кусков кода — подстановка выражений/идентификаторов/типа/списков аргументов (escape sequences). Этот встроенный язык (причём частично на низком уровне, в лексический парсер) постепенно усложняется, в попытках охватить весь основной язык.
Ideas for a Programming Language Part 3: No Shadow Worlds — тут это явление названо “теневыми мирами”.
Проблема менее заметна в языках с однородный синтаксисом, как в LISP, когда всё представлено в виде списков, или с динамической типизацией, в которых не жалко производительности на вызов полноценного eval
, принимающий строку кода и возвращающий строку результата. Всякие движки подстановки (templates_boost), которые работают на промежуточном уровне, решают проблему частично — они дают возможность заменять одни куски синтаксического дерева на другие, но своими правилами создают новые теневые миры.
Пример шаблона, который сейчас не осиливает разобрать templates_boost
:
мета-язык ВСЕГДА требует ещё какое-нибудь мелкое расширение
spoof — более мощные текстовые подстановки в daScript с PEG-парсингом (создание ещё одного мета-языка, который тоже будет требовать расширения со временем).
Собственно, мой поинт в том, чтобы махнуть рукой на несовершенство теневых миров, требующих расширения, и просто часть задач с макросами решать на уровне построения синтаксического дерева “вручную”. Набросал макрос для daScript, который разбирает ast-функции и аннотирует каждую ноду текстом с названием Expr-блока, представляющего эту ноду, чтобы было проще “срисовывать” синтаксическое дерево (похожий на dumpTree в Nim).
Пример вывода:
Красивее выглядит в терминале, который понимает цветовый коды:
full-size