Макросы
Макросы в TrustLang являются элементами метапрограммирования.
Макрос - это один или несколько последовательных терминов, которые заменяются на другой термин или на целую синтаксическую конструкцию (последовательность лексем) и выступают в роли специализированных регулярных выражений для поиска и замены последовательностей терминов.
Макросы обработываются лексером перед отправкой токенов в парсер для синтаксического анализа, что позволяет изменять даже синтаксис языка, например, превратить синтаксис TrustLang на основе грамматических правил в более привычный синтаксис на основе ключевых слов, так как такой текст легче воспринимается при последующем чтении исходного кода программы.
Определение макросов
Определение макросов аналогично определению других объектов и состоит из трех частей <имя макроса> <оператор создания/присвоения> <тело макроса> и завершающая точка с запятой “;”, т.е. применяются обычные операторы :=(::-), = или :=(:-) для создания нового или переопределения уже существующего объекта, а имя макроса указывается между двумя символами "@@" и может содержать одну или нескольких лексем (терминов).
Все макросы относятся к глобальному пространству имен, поэтому первый термин в имени макроса должен быть уникальным, иначе он будет прекрывать локальные и глобальные переменные при разрешении имен, если они записаны в тексте программы без квалификаторов (сигилов).
С помощью операторов ::- и :- создаются чистые (гигиеничные) макросы, аргументы и переменные в которых гарантированно не пересекаются с пространством имен программы.
Телом макроса могут быть корректное выражение языка, последовательность лексем (которые заключается в двойные собачки "@@",
т.е. @@ лексема1 лексема1 @@) или обычная текстовая строка (которую нужно указать между тройными собачками "@@@", т.е. @@@ текстовая строка @@@).
В имени макроса после первого термина могут присутствовать один или несколько образцов. Образец — это термин, который при сопоставлении последовательности лексем с идентификатором макроса может заменяться любым другим одиночным термином (т.е. фактически это сопоставление по заданному шаблонному образцу).
Для создания термина-образца в начале его идентификатора нужно поставить знак доллара (что соответствует квалификатору локальной переменой),
т.е. имя макроса @@ FUNC $name @@ будет соответствовать последовательности лексем как FUNC my_func_name, так и FUNC other_name_func.
Для удаления макроса используется специальный синтаксис: @@@@ name @@@@; или @@@@ два термина @@@@;,
т.е. необходимо указать идентификатор макроса между четырмя символами "@@@@".
# Тело макроса из текстовой строки (как в препроцессоре С/С++)
@@macro_str@@ := @@@ строка - тело макроса @@@; # Строка для лексера
# Удаления макроса @macro_str
@@@@ macro_str @@@@;
Аргументы макросов и их раскрытие
Макросы можно определять как с аргументами (параметрами в скобках), так и без них. Если макрос был определен с аргументами, то их проверка будет выполнятся макропроцессором при определении и раскрытии макроса. Если макрос был определен без аргументов, то их наличие макропроцессором игнорируется.
Первый терм имени макроса является ключом группы макросов: в одной группе может быть много
макросов с одинаковым первым именем, но разной арностью (разным числом/составом дополнительных
термов), например break, break $label, break $a $b. Такие макросы сосуществуют и не конфликтуют.
При раскрытии из макросов группы выбирается самый длинный (наиболее специфичный) — тот, который потребляет больше всего термов входного буфера. Диагностика «duplication» выдается только при полном совпадении сигнатуры (всех термов), а не при совпадении одного лишь первого имени. Разные арности одной группы дубликатами не являются.
Макросы с аргументами в скобках и без них — тоже разные сигнатуры: форма-вызов и бесскобочная форма с одним первым именем могут сосуществовать (бесскобочная форма сопоставляется и вызову, потребляя только первый терм; форма-вызов — всему вызову целиком).
@@ macro @@ := term; # Макрос без аргументов
@@ macro $value @@ := term(@$value); # Макрос с дополнительным термом-шаблоном (другая арность)
macro; # ОК -> term; (форма арности 1)
macro 42; # ОК -> term(42); (форма арности 2, longest-match)
# Но
@@ call() @@ := term();
call(); # ОК -> term();
call; # ОК -> term; (бесскобочная форма `call`, если она определена)
Если при определении макроса указаны аргументы, то место для их вставки в теле макроса записывается как имя локальной переменой, перед которой добавлен символ "@", т.е. @$arg.
Место для вставки числа реально переданных аргументов отмечается лексемой "@$#". Если требуется вставить переданные аргументы в виде словаря, то место для вставки отмечается лексемой "@$*".
Если макрос принимае произвольное количество аргументов (аргуметы макроса завершает многоточие), то место их вставки в тело макроса отмечается лексемой "@$…".
По аналогии с препроцессором С/С++, для соединения двух лексем в одну, в теле макроса используется оператор "@##",
а для преобразование лексемы в текстову строку применяется операторы @#, @#" или @#’, например,
@@macro($arg)@@ := @@ func_ @## @$arg( @#" arg ) @;, тогда вызов macro(name); будет преобразован в func_name ("name");
Примеры использования макросов:
# Обычные макросы (тело макроса корректное выражение)
@@ macro @@ := replace();
@@ macro2(arg) @@ := { call(@$arg); call()};
# Тело макросов из последовательности лексем
@@ if(...) @@ := @@ [ @$... ]--> @@; # Выражение может быть не полным
@@ elif(...) @@ := @@ ,[ @$... ]--> @@;
@@ else @@ := @@ ,[...]--> @@;
# Запись условного оператора с использованием
# определенных выше макросов
@if( condition ){
...
} @elif( condition2 ) {
...
} @else {
...
};
Например цикл до 5:
count := 1;
[ 1 ] <-> {
[ count > 5 ] --> {
-- 42 --;
};
count+=1;
};
Будет выглядеть более привычно:
count := 1;
@while( true ) {
@if( count > 5 ) {
@return 42;
};
count += 1;
};
Идеи на будущее
Макропроцессор может выполнять статические вычисления (аналог consteval в С++).
В таких блоках доступно синтаксическое дерево AST и атрибуты его узлов.
В них не могут содержаться определения функций, но можно изменять опции среды выполенения для вызова уже существующий функции,
функции, которая была скомпилирована ранее и/или загружена из динамической библиотеки.
⚠️ НЕ РЕАЛИЗОВАНО: статическое вычисление (consteval) `@(...)@`, атрибуты `@[[...]]@` и атрибуты-свойства `@[ %func ]@.deprecated` не реализованы в текущей версии. Данный подраздел относится к разделу 'Идеи на будущее'
@[[deprecated]]@ %func():Integer := { 42 } ; # Annotation [[deprecated]] %func
const := @{ %func(); }@; # consteval 42
value := @{ const * 2 }@; # consteval 84
@flag := 1;
%func2():Integer := ... ;
%func3():Integer := ... ;
@{ # Eval at compile time
@if( @flag ){
@[ %func2 ]@.deprecated = 1;
@[ %func3 ]@.deprecated = 1;
}
}@
Символьное программирование
Различие между символьным программированием и препроцессором
- Задача препроцессора - раскрыть(расширить) макрос, тогда как при символьном программировании нужно сокращать (сворачивать) выражения
- Препроцессор обрабатывает последовательности лексем (плоские данные), тогда как символьное программирование оперирует выражениями (деревом лексем)
- Макрос у препроцессора идентифицируется первым термином, и если макрос не может быть раскрыт, то возникает ошибка. В символьном программировани требуется точное соответствие всего выражения и только в этом случае производится сокращение выражения.
Предпосылки для реализации символьного программирования Компилятор - интерепретатор с REPL и обработка AST как во время компиляции, так и во время выполнения. Необходимо добавить конструкции для определения правил символьного программрования (чистые функции?) Необходимо добавить конструкцию для вычисления выражений в символьном программировании.