2.29. Reification

Expression reification is used to generate AST expression trees in a convenient way. It provides a collection of escaping sequences to allow for different types of expression substitutions. At the top level, reification is supported by multiple call macros, which are used to generate different AST objects.

Reification is implemented in daslib/templates_boost.

2.29.1. Simple example

Let’s review the following example:

var foo = "foo"
var fun <- qmacro_function("madd") <| $ ( a, b )
    return $i(foo) * a + b
print(describe(fun))

The output would be:

def public madd (  a:auto const;  b:auto const ) : auto
    return (foo * a) + b

What happens here is that call to macro qmacro_function generates a new function named madd. The arguments and body of that function are taken from the block, which is passed to the function. The escape sequence $i takes its argument in the form of a string and converts it to an identifier (ExprVar).

2.29.2. Quote macros

Reification macros are similar to quote expressions because the arguments are not going through type inference. Instead, an ast tree is generated and operated on.

2.29.2.1. qmacro

qmacro is the simplest reification. The input is returned as is, after escape sequences are resolved:

var expr <- qmacro(2+2)
print(describe(expr))

prints:

(2+2)

2.29.2.2. qmacro_block

qmacro_block takes a block as an input and returns unquoted block. To illustrate the difference between qmacro and qmacro_block, let’s review the following example:

var blk1 <- qmacro <| $ ( a, b ) { return a+b; }
var blk2 <- qmacro_block <| $ ( a, b ) { return a+b; }
print("{blk1.__rtti}\n{blk2.__rtti}\n")

The output would be:

ExprMakeBlock
ExprBlock

This is because the block sub-expression is decorated, i.e. (ExprMakeBlock(ExprBlock (…))), and qmacro_block removes such decoration.

2.29.2.3. qmacro_expr

qmacro_expr takes a block with a single expression as an input and returns that expression as the result. Certain expressions like return and such can’t be an argument to a call, so they can’t be passed to qmacro directly. The work around is to pass them as first line of a block:

var expr <- qmacro_block <|
    return 13
print(describe(expr))

prints:

return 13

2.29.2.4. qmacro_type

qmacro_type takes a type expression (type<…>) as an input and returns the subtype as a TypeDeclPtr, after resolving the escape sequences. Consider the following example:

var foo <- typeinfo(ast_typedecl type<int>)
var typ <- qmacro_type <| type<$t(foo)?>
print(describe(typ))

TypeDeclPtr foo is passed as a reification sequence to qmacro_type, and a new pointer type is generated. The output is:

int?

2.29.2.5. qmacro_function

qmacro_function takes two arguments. The first one is the generated function name. The second one is a block with a function body and arguments. By default, the generated function only has the FunctionFlags generated flag set.

2.29.2.6. qmacro_variable

qmacro_variable takes a variable name and type expression as an input, and returns the variable as a VariableDeclPtr, after resolving the escape sequences:

var vdecl <- qmacro_variable("foo", type<int>)
print(describe(vdecl))

prints:

foo:int

2.29.3. Escape sequences

Reification provides multiple escape sequences, which are used for miscellaneous template substitution.

2.29.3.1. $i(ident)

$i takes a string or das_string as an argument and substitutes it with an identifier. An identifier can be substituted for the variable name in both the variable declaration and use:

var bus = "bus"
var qb <- qmacro_block <|
    let $i(bus) = "busbus"
    let t = $i(bus)
print(describe(qb))

prints:

let  bus:auto const = "busbus"
let  t:auto const = bus

2.29.3.2. $f(field-name)

$f takes a string or das_string as an argument and substitutes it with a field name:

var bar = "fieldname"
var blk <- qmacro_block <|
    foo.$f(bar) = 13
print(describe(blk))

prints:

foo.fieldname = 13

2.29.3.3. $v(value)

$v takes any value as an argument and substitutes it with an expression which generates that value. The value does not have to be a constant expression, but the expression will be evaluated before its substituted. Appropriate make infrastructure will be generated:

var t = [[auto 1,2.,"3"]]
var expr <- qmacro($v(t))
print(describe(expr))

prints:

[[1,2f,"3"]]

In the example above, a tuple is substituted with the expression that generates this tuple.

2.29.3.4. $e(expression)

$e takes any expression as an argument in form of an ExpressionPtr. The expression will be substituted as-is:

var expr <- quote(2+2)
var qb <- qmacro_block <|
    let foo = $e(expr)
print(describe(qb))

prints:

let foo:auto const = (2 + 2)

2.29.3.5. $b(array-of-expr)

$b takes an array<ExpressionPtr> or das::vector<ExpressionPtr> aka dasvector`smart_ptr`Expression as an argument and is replaced with each expression from the input array in sequential order:

var qqblk : array<ExpressionPtr>
for i in range(3)
    qqblk |> emplace_new <| qmacro(print("{$v(i)}\n"))
var blk <- qmacro_block <|
    $b(qqblk)
print(describe(blk))

prints:

print(string_builder(0, "\n"))
print(string_builder(1, "\n"))
print(string_builder(2, "\n"))

2.29.3.6. $a(arguments)

$a takes an array<ExpressionPtr> or das::vector<ExpressionPtr> aka dasvector`smart_ptr`Expression as an argument and replaces call arguments with each expression from the input array in sequential order:

var arguments <- [{ExpressionPtr quote(1+2); quote("foo")}]
var blk <- qmacro <| somefunnycall(1,$a(arguments),2)
print(describe(blk))

prints:

somefunnycall(1,1 + 2,"foo",2)

Note how the other arguments of the function are preserved, and multiple arguments can be substituted at the same time.

Arguments can be substituted in the function declaration itself. In that case $a expects array<VariablePtr>:

var foo <- [{VariablePtr
    new [[Variable() name:="v1", _type<-qmacro_type(type<int>)]];
    new [[Variable() name:="v2", _type<-qmacro_type(type<float>), init<-qmacro(1.2)]]
}]
var fun <- qmacro_function("show") <| $ ( a: int; $a(foo); b : int )
    return a + b
print(describe(fun))

prints:

def public add ( a:int const; var v1:int; var v2:float = 1.2f; b:int const ) : int
    return a + b

2.29.3.7. $t(type)

$t takes a TypeDeclPtr as an input and substitutes it with the type expression. In the following example:

var subtype <- typeinfo(ast_typedecl type<int>)
var blk <- qmacro_block <|
    var a : $t(subtype)?
print(describe(blk))

we create pointer to a subtype:

var a:int? -const

2.29.3.8. $c(call-name)

$c takes a string or das_string as an input, and substitutes the call expression name:

var cll = "somefunnycall"
var blk <- qmacro ( $c(cll)(1,2) )
print(describe(blk))

prints:

somefunnycall(1,2)