Макросы

В Ü есть механизм расширения синтаксиса программы, с использованием конструкций, определяемых программистом. Данный механизм реализуется через макросы.

Программист может объявить макрос, начинающийся с некоторого имени. При встрече этого имени в тексте программы синтаксический анализатор компилятора применит правила, заданные в макросе, для последующего разбора текста и произведёт замену на синтаксические конструкции, указанные в макросе.

Простейший пример:

?macro <? Double:expr ( ?e:expr ) ?>  ->  <? ?e * 2 ?>
var i32 x= Double(10);

В данном примере объявляется макрос с именем Double, который будет состоять из выражения в круглых скобках. Данный макрос раскроется в выражение, умноженное на 2.

Объявление макросов

Макросы указываются в начале файла исходного кода, после списка импортируемых модулей, но до любого другого кода. Макросы импортируются из импортируемых модулей. Они глобальны - доступны везде в коде и расположены логически в общем пространстве имён (с разделением по контекстам).

Итак, из чего же состоят макросы? Объявление макроса начинается с идентификатора ?macro, за которым следует пара скобок <? и ?>. Первым элементов в этих скобках должно следовать имя макроса и его контекст после :. Далее следует набор правил сопоставления макроса. После следует блок правил раскрытия макроса в <? ?> скобках, следующий после лексемы ->.

Контексты использования макросов

У макроса обязательно указать контекст, в котором он будет использоваться. Существуют следующие контексты:

  • namespace - контекст внутри пространств имён (в том числе в корневом пространстве имён).
  • class - контекст внутри структуры или класса.
  • block - контекст внутри блока.
  • expr - контекст внутри выражения.

Все контексты макросов независимы друг от друга. В разных контекстах возможно объявления макросов с одним именем. Внутри же одного контекста объявление нескольких макросов с одним и тем же именем не допускается.

Элементы блока сопоставления

Самый простой элемент - это регулярные лексемы. Они включают в себя идентификаторы, различные скобки, символы операторов и т. д. Сопоставляются они напрямую.

Остальные элементы являются именованными сущностями. Начинаются они с символа `?` и последующего идентификатора. После идёт :, после которого указывается класс элемента. Внутри одного блока сопоставления не допускается существование элементов с одинаковыми именами. Но допускается использование элементов с одинаковыми именами в разных (в том числе вложенных) блоках сопоставления.

Элементы бывают простые и составные. Простые элементы включают в себя:

  • ident - одиночный идентификатор.
  • ty - имя типа.
  • expr - выражение.
  • block - блок.

Составные элементы это опциональные элементы и последовательности.

Опциональные элементы имеют класс opt. После объявления опционального элемента идёт набор его вложенных элементов в <? ?> скобках.

Элементы последовательностей имеют класс rep. После объявления элемента последовательности идёт набор его вложенных элементов в <? ?> скобках. Также может быть указан опциональный разделитель элементов последовательности - одиночная лексема в <? ?> скобках.

Опциональные элементы и последовательности требуют чтобы в начале списка их внутренних элементов была указана фиксированная лексема, или же чтобы после них была указана фиксированная лексема. Это необходимо, чтобы при обработке сопоставления выбрать, перейти ли к элементу после опционального/последовательности или же сделать выборку опционального элемента/элемента последовательности. Фиксированная лексема начала опционального элемента/последователньости не должна совпадать с фиксированной лексемой после опционального/последовательности.

Примеры элементов:

?macro <? Skip:expr ?some_ident:ident ?>  ->  <? ?> // идентификатор
?macro <? Skip:namespace ?some_type:ty ?>  ->  <? ?> // имя типа
?macro <? Skip:expr ?some_expression:expr ?>  ->  <? ?> // выражение
?macro <? Skip:block ?some_block:block ?>  ->  <? ?> // блок
?macro <? Skip:expr ?some_opt:opt<? ?i:ident ?> ?>  ->  <? ?> // опциональный идентификатор
?macro <? Skip:expr ?some_opt:opt<? ( ?b:block ) ?> ?>  ->  <? ?> // опциональный блок в круглых скобках
?macro <? Skip:expr ( ?some_sequence:rep<? ?e:expr ?> ) ?>  ->  <? ?> // последовательность выражений в круглых скобках
?macro <? Skip:expr START ?some_sequence:rep<? ?e:expr ?><?,?> END ?>  ->  <? ?> // последовательность из нуля или более выражений, разделённых запятой, расположенная внутри пары слов START/END

Раскрытие макросов

Макросы раскрываются в соответствии с правилами, указанными в блоке раскрытия объявления макроса.

В этом блоке могут встречаться фиксированные лексемы, которые будут раскрыты напрямую.

Также в блоке могут встречаться макро-переменные, объявленные в блоке сопоставления. Начинаются эти макро-переменные с символа ?. При раскрытии макроса они заменяются аргументами, переданными в макрос.

Простые элементы раскрываются напрямую.

Составные элементы раскрываются вложенно. Для них надо указывать макро-блок в <? ?> скобках, в которых уже указывать правила раскрытия внутренних элементов составных элементов.

Внутренний блок опционального элемента раскрывается в случае, если был передан входной опциональный элемент.

Внутренний блок последовательности раскрывается столько раз, сколько раз элемент полседовательности был передан в макрос. При этом для каждого раскрытия внутренние переменные блока раскрытия имеют свои значения, соотвествующие входным значениям.

Примеры раскрытия:

?macro <? DECLARE_VAR:namespace ?name:ident ?init:expr ?t:ty ?>  ->  <? var ?t ?name = ?init; ?>

DECLARE_VAR pi 3.14f f32
// Раскроется в
var i32 pi = 3.14f;
?macro <? DECLARE_VAR:namespace ?name:ident ?init:expr ?t:ty ?m:opt<?MUT?> ?>  ->  <? var ?t ?m<?mut?> ?name = ?init; ?>

DECLARE_VAR pi 3.14f f32
// Раскроется в
var i32 f32 = 3.14f;

DECLARE_VAR x 0 i32 MUT
// Раскроется в
var mut i32 pi = 0;
?macro <? DECLARE_FOO:namespace ( ?params:rep<? ?t:ty ?name:ident ?><?,?> ) ?>  ->  <? fn Foo( ?params<? ?t ?name ?><?,?> ); ?>

DECLARE_FOO()
// Раскроется в
fn Foo()

DECLARE_FOO(i32 x, f32 y)
// Раскроется в
fn Foo(i32 x, f32 y)

Уникальные макро-идентификаторы

В блоке раскрытия макроса можно указывать уникальные макро-идентификаторы, начинающиеся с ??. Данные идентификаторы при раскрытии будут заменены на идентикфикаторы, уникальные в рамках данного раскрытия макроса и гарантированно не пересекающиеся с именами других идентификаторов.

Данные уникальные макро-идентификаторы позволяют производить раскрытие макроса без опасения совпадения имён, объявленных в раскрытии макроса, с именами, объявленными в других местах.

Пример:

?macro <? FOR:block ?count:expr ?b:block ?>  ->
<?
{
        var size_type mut ??counter= 0s;
        while( ??counter < size_type(?count) )
        {
            ?b
            ++??counter;
        }
}
?>

fn Foo();

fn Bar()
{
    FOR 32
    {
        var i32 counter= 0;
        Foo();
    }
    // Макрос будет раскрыт во что-то вроде
    // var size_type mut ??counter= 0s;
    // while( _macro_ident_counter_140734899778672_0 < size_type(32) )
    // {
    //     {
    //         var i32 counter= 0; // Пересечения имён не будет
    //         Foo();
    //     }
    //     ++_macro_ident_counter_140734899778672_0;
    // }
}