Макросы

Макросы в 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 как во время компиляции, так и во время выполнения. Необходимо добавить конструкции для определения правил символьного программрования (чистые функции?) Необходимо добавить конструкцию для вычисления выражений в символьном программировании.