Spiiin's blog

daScript macro - 2

Еще немного про способы кодогенерации в макросах.

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

Можно строить выражения с помощью ручной генерации абстрактного синтаксического дерева. Например, код для генерации выражения let a = 40 + 2:

var expr40 <- new [[ExprConstInt() value=40]]                               //40
var expr2 <- new [[ExprConstInt() value=2]]                                //2
var exprPlus <- new [[ExprOp2() op:="+", left := expr40, right := expr2]]  //40+2

var exprLet <- new [[ExprLet()]]                                           //let
exprLet_aSize.variables |> emplace_new() <| new [[Variable()    
    name := "a",                                                            //a
    _type <- new [[TypeDecl() baseType=Type tInt]],
    init <- exprPlus                                                        //=40+2
]]

Генерировать большие функции с использованием ExprXXX-кирпичиков утомительно, поэтому можно использовать макрос quote, который трансформирует переданное в него выражение в синтаксическое дерево этого выражения:

var exprLet <- quote <|
let a = 40 + 2
print(describe(exprLet))
//output:
let /*unused*/ a:auto const = (40 + 2);

В случае, если какую-либо часть выражения нужно сделать изменяемой, можно воспользовать макросом apply_template:

var exprLet <- quote <|
    let a = 40 + 2
print(describe(exprLet))
//output:
let  /*unused*/ a:auto const = (40 + 2);

Недавно в язык была добавлена фича по упрощению генерации правил переписываний выражений — expression reification (аналогичная фича из haxe).
Её можно описать как DSL для задания правил переписывания выражений в шаблонах. Теперь генерацию того же самого выражения можно описать так:

require daslib/templates
require daslib/templates_boost

var exprLet <- quote <|
    let VARIABLE_NAME = OP1 + OP2                                         //шаблон выражения
var exprLet_rules : Template                                              //правила переписывания выражения
exprLet_rules |> renameVariable("VARIABLE_NAME", "a")                     //замена одного имени на другое
exprLet_rules |> replaceVariable("OP1", new [[ExprConstInt() value=40]])  //замена одного выражения на другое
exprLet_rules |> replaceVariable("OP2", new [[ExprConstInt() value=2]])
apply_template(exprLet_rules, exprLet.at, exprLet)

В таком виде строчка шаблона всё ещё остаётся похожим на сам код, который будет сгенерирован этим шаблоном, а не на синтаксическое дерево или таблицу с описанием правил. Пример на все поддерживаемые правила реификации выражений - reification.das.

Переписанная кодо-генерированная функция инициализации структуры с использованием реификации получается где-то вдвое короче и проще:

let variableName = "a"
let op1 = 40
let op2 = 2
var exprLet <-qmacro <|
    let $i(variableName) = $v(op1) + $v(op2)

Макрос qmacro_expr позволяет вставить сгенерированное выражение в текущий блок, а не генерировать новый блок.
reduce - функция из стандартной библиотеки functional, позволяющая произвольным образом свернуть массив выражений с помощью функтора.
reduce_while — её дописанная версия, позволяющая задать предикат остановки свёртки выражения по условию.
qmacro_function — макрос для генерации сигнатуры функции и её определения

Получившаяся функция инициализации аналогична той, которая генерировалась в предыдущей заметке:

def generateStructureInitFunction(var st:StructurePtr; ptrsTypeIndexes:array<int>&)
    let ptrFieldsLen = ptrsTypeIndexes |> length
    var blk : array<ExpressionPtr>; defer_delete(blk)

    //-------------------------
    //memblock.a`count = aCount

    for i in range(0, ptrFieldsLen)
        let argumentName = "{st.fields[ptrsTypeIndexes[i]].name}`count"
        blk |> emplace_new <| qmacro_expr(
            ${memblock.$f(argumentName) = $i(argumentName);}
        )

    //-------------------------
    //let aSize = typeinfo(sizeof *memblock.a) * aCount

    for i in range(0, ptrFieldsLen)
        let argumentName = "{st.fields[ptrsTypeIndexes[i]].name}"
        let argumentNameSize = "{argumentName}Size"
        let argumentNameCount = "{argumentName}`count"
        blk |> emplace_new <| qmacro_expr(
            ${let $i(argumentNameSize) = typeinfo(sizeof *memblock.$f(argumentName)) * $i(argumentNameCount);}
        )
   
    //-------------------------
    //memblock.mem |> resize(aSize + bSize + cSize)

    var sumArgumentsArray: array<ExpressionPtr>
    let zero = 0;
    sumArgumentsArray |> emplace<| qmacro_expr(${$v(zero);})
    for i in range(0, ptrFieldsLen)
        let argumentName = "{st.fields[ptrsTypeIndexes[i]].name}"
        let nameSize := "{argumentName}Size"
        sumArgumentsArray |> emplace <| qmacro_expr(${$i(nameSize);})
    unsafe
        var sumExpr <- reduce(each(sumArgumentsArray), @@makeSumExpr)
        blk |> emplace_new <| qmacro_expr(
            ${memblock.mem |> resize($e(sumExpr));}
        )

    //-------------------------
    //memblock.a = reinterpret<int?> addr(memblock.mem[0])

    for i in range(0, ptrFieldsLen)
        let argumentName = "{st.fields[ptrsTypeIndexes[i]].name}"
        unsafe
            var exprStartAddress <- reduce_while(each(sumArgumentsArray), @@makeSumExpr,  @(e:ExpressionPtr; counter:int):bool => counter <= i)
            var subtype := st.fields[ptrsTypeIndexes[i]]._type
            var exprAssign <- qmacro_expr <|
                unsafe{ memblock.$f(argumentName) = reinterpret<$t(subtype)> addr(memblock.mem[$e(exprStartAddress)]); }
            blk |> emplace_new(exprAssign)
    //-------------------------

    //function signature
    var fnArguments : array<VariablePtr>;
    unsafe
        fnArguments |> emplace_new <| new [[Variable() at=st.at, name:= "memblock", _type <- new [[TypeDecl() baseType=Type tStructure, structType=addr(*st)]]]]
    var structT <- typeinfo(ast_typedecl type<int>)
    for i in range(0, ptrFieldsLen)
        let argumentName = "{st.fields[ptrsTypeIndexes[i]].name}`count"
        fnArguments |> emplace_new <| new [[Variable() at=st.at, name:= argumentName,  _type := intAstType]]
    var fn <- qmacro_function("init`struct`{st.name}") <| $ ($a(fnArguments))
        $b(blk)
    defer_delete(fn)
    compiling_module() |> add_function(fn)

Код примера