Разделы сайта находятся в разработке.
Материалы могут быть неполными или содержать ошибки. Содержимое сайта будет синхронизировано с реализацией при выходе следующей версии, тогда как текущая (0.6.0) предназначена для прототипирования синтаксиса и создания песочницы.
Макросы и DSL
Макросы в TrustLang являются элементами метапрограммирования.
Макрос — это один или несколько последовательных терминов, которые заменяются на другой термин или на целую синтаксическую конструкцию (последовательность лексем) и выступают в роли специализированных регулярных выражений для поиска и замены последовательностей терминов.
Макросы обрабатываются лексером перед отправкой токенов в парсер для синтаксического анализа, что позволяет изменять даже синтаксис языка, например, превратить синтаксис TrustLang на основе грамматических правил в более привычный синтаксис на основе ключевых слов, так как такой текст легче воспринимается при последующем чтении исходного кода программы.
Определение макросов
Определение макросов аналогично определению других объектов и состоит из трёх частей <имя макроса> <оператор создания/присвоения> <тело макроса> и завершается точкой с запятой “;”, т. е. применяются обычные операторы := или = для создания нового или переопределения уже существующего объекта, а имя макроса указывается между двумя символами "@@" и может содержать одну или несколько лексем (терминов).
Все макросы относятся к глобальному пространству имен, поэтому первый термин в имени макроса должен быть уникальным, иначе он будет перекрывать локальные и глобальные переменные при разрешении имен, если они записаны в тексте программы без квалификаторов (сигилов).
Телом макроса может быть корректное выражение языка, последовательность лексем (которая заключается в двойные собачки "@@",
т. е. @@ лексема1 лексема2 @@) или обычная текстовая строка (которую нужно указать между тройными собачками "@@@", т. е. @@@ текстовая строка @@@@).
В имени макроса после первого термина могут присутствовать один или несколько образцов. Образец — это термин, который при сопоставлении последовательности лексем с идентификатором макроса может заменяться любым другим одиночным термином (т. е. фактически это сопоставление по заданному шаблонному образцу).
Для создания термина-образца в начале его идентификатора нужно поставить знак доллара (что соответствует квалификатору локальной переменной),
т. е. имя макроса @@ 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.
Место для вставки числа реально переданных аргументов отмечается лексемой "@$#". Если требуется вставить переданные аргументы в виде словаря, то место для вставки отмечается лексемой "@$*".
Если макрос принимает произвольное количество аргументов (аргументы макроса завершает многоточие), то место их вставки в тело макроса отмечается лексемой "@$…".
По аналогии с препроцессором C/C++ для соединения двух лексем в одну в теле макроса используется оператор "@##",
а для преобразования лексемы в текстовую строку применяются операторы @#, @#" или @#’, например,
@@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» — двумя формами:
| Макросы DSL | Базовый синтаксис |
|---|---|
| |
Отладочный вывод компилятора (@__DEBUG__, @__DEBUG_SCOPE__)
Встроенные системные макросы для отладки самого компилятора (работают только в debug-сборке компилятора; в релизной сборке вызов не даёт вывода и сопровождается предупреждением). Оба макроса не генерируют код — анализатор применяет эффект и удаляет маркер.
@__DEBUG__(<masks>)— управляет фильтром сообщений, которые создаёт компилятор черезTRUST_DEBUG;@__DEBUG__()— выключить. Применение фильтра дублируется в самом выводе эхом (@__DEBUG__: debug messages ENABLED, filter='…'/DISABLED (empty filter)) — видно, что параметры фильтрации применились/изменились/выключены. На дамп состояния эхо и фильтр не влияют.@__DEBUG_SCOPE__([<маски имён>...] [, key=value...])— печатает текущее состояние анализатора в этой точке (независимо от фильтра@__DEBUG__). Позиционные аргументы — только фильтр имён (маски через запятую); пустой вызов@__DEBUG_SCOPE__()печатает полный дамп. Именованные опции (значения без кавычек):level=current|all,types=on|off,max=<N>,count=only.
Префиксы строк вывода (каждая строка): для сообщений TRUST_DEBUG — .../файл:строка места
вызова в компиляторе; для вывода @__DEBUG__/@__DEBUG_SCOPE__ — .../файл:строка самого
макроса в .src. Каталоги в пути заменяются многоточием (.../file.src:12: ).
Именованные опции проверяются при разборе макроса: при неизвестном имени/значении выдаётся диагностика с полным списком поддерживаемых опций и допустимых значений.
Фильтрация сообщений обязательна: без заданного @__DEBUG__-фильтра сообщения TRUST_DEBUG не
печатаются. Фильтровать можно по ключевому слову, по компоненту, по файлу и по пути компилятора.
@__DEBUG__("scope,resolve"); # включить сообщения TRUST_DEBUG по этим ключевым словам
x := 1;
@__DEBUG_SCOPE__(); # полный дамп состояния анализатора
@__DEBUG_SCOPE__("x*", level=all, types=on, max=10); # с фильтром имён и опциями
@__DEBUG__(); # выключить сообщения (на дамп не влияет)
Вывод (фрагмент): prog.src:1: @__DEBUG__: debug messages ENABLED, filter='scope,resolve',
src/semantic/name_resolution.cpp:129: enter FuncDecl depth=3,
prog.src:3: scope: depth=2 names=1, prog.src:3: [1] ModuleDecl \prog.src`: x`.
Keyword syntax (DSL): привычный синтаксис на ключевых словах
Особенности ассоциативной памяти
Синтаксис языка TrustLang основан на строгих правилах без использования ключевых слов, и как бы он ни выглядел логичным, ассоциация по ключевым словам вспоминается гораздо легче, например if, чем комбинация минус минус правая угловая скобка –>. Из-за этого имеет смысл использовать не “чистый” базовый синтаксис, а более привычный диалект с использованием ключевых слов.
TrustLang уже содержит набор макросов, которые расширяют базовый синтаксис TrustLang, основанный на правилах, набором предопределённых ключевых слов как в классических языках программирования, которые можно адаптировать или дополнить под собственную предметную область.
Мнемонические команды (@func)
Чтобы не запоминать правила грамматики и специальные символы, часть DSL-макросов оформлена как мнемонические команды — команды-макросы с удобным именем вместо комбинаций специальных символов.
Команда @func — определение функции без необходимости помнить оператор :=:
@func myfn ( a:Int32 ) { @print('{}', a); };
@func add ( a:Int32, b:Int32 ): Int32 { @return a + b; };
# → void myfn(int32_t a); int32_t add(int32_t a, int32_t b);
@func <имя> ( <аргументы> ) { <тело> }— определение функции (Void). Раскрывается в<имя>( <аргументы> ) := { <тело> }.@func <имя> ( <аргументы> ): <тип> { <тело> }— определение функции с типом возврата. Раскрывается в<имя>( <аргументы> ): <тип> := { <тело> }.<тип>может быть составным (напримерTuple(Int32, Int32)).
Внимание: имя
funcзарезервировано DSL (какmain,module,return,ifи др.), поэтому не используйте его как имя функции/переменной.
Константы
- @true — 1:Bool
- @yes — 1:Bool
- @false — 0:Bool
- @no — 0:Bool
Операторы
-
@if(…) — Первый условный оператор
-
@elif(…) — Второй и все последующие условные операторы
-
@else — Оператор иначе
-
@while(…) — Оператор цикла с предусловием
-
@dowhile(…) — Оператор цикла с постусловием
-
@loop — Оператор бесконечного цикла
-
@break [метка] — Выход из ближайшего цикла или из именованного блока
метка(один идентификатор, без::). Без метки:@break; -
@continue [метка] — Переход на начало ближайшего цикла или именованного блока
метка(один идентификатор, без::). Без метки:@continue; -
@return [значение] — Выход из текущей функции (аналог break по имени функции
@__FUNCTION__). Void-форма:@return;. Со значением:@return <значение>;(один rvalue: имя, литерал, строка, вызов или словарь(a, b,)в скобках) -
@match( … ) — Оператор оценки выражения
-
@case( … ) — Оператор сравнения с образцом
-
@default — Оператор выбора по умолчанию
Встроенные функции и проверки
- @assert( cond ) — Вычислить выражение и проверить его на истинность во время выполнения
- @assert( cond, ‘fmt’, args… ) — Проверка с сообщением: при провале вместо текста условия выводится
std::format(fmt, args...)(форматная строка — узкий литерал'...') - @verify( cond ) — Вычислить выражение и проверить его на истинность во время выполнения; в отличие от
@assert, условие вычисляется даже при отключённых проверках - @verify( cond, ‘fmt’, args… ) — То же, но с форматным сообщением при провале
Если запустить компилятор с флагом -Wno-assert, тогда рантайм проверки @assert удаляются из текста программы,
а вычисления внутри @verify выполняются, но их результат игнорируется.
Предопределённые макросы
При работе парсера TrustLang автоматически формируются несколько зарезервированных макросов, часть из которых соответствует макросам препроцессора у C/C++. Данные предопределённые макросы можно использовать как обычные константы.