5.4.17. Macro Tutorial 17: Quasi-quotation Reference

Tutorial 16 used qmacro_template_class to generate a struct from a template. This tutorial is a comprehensive reference — it exercises every quasi-quotation variant and reification operator provided by daslib/templates_boost in a single macro module.

5.4.17.1. Quasi-quotation variants

All functions below are [call_macro] helpers (or the underlying runtime functions) from templates_boost. They build AST nodes at macro expansion time:

Variant

Description

quote(expr)

Plain AST capture, no reification. Use for literal constants.

qmacro(expr)

Expression with reification splices ($v, $e, etc.).

qmacro_type(type<T>)

Build a TypeDeclPtr. $t(td) splices another TypeDeclPtr inside.

qmacro_variable("name", type<T>)

Build a VariablePtr. Used for function parameters.

qmacro_expr(${ stmt; })

Single statement (e.g. let / var declaration).

qmacro_block() { stmts }

Multiple statements as an ExprBlock. $b(stmts) splices an array.

qmacro_block_to_array() { stmts }

Like qmacro_block but returns array<ExpressionPtr>.

qmacro_function("name") $(args) { body }

Complete function. $a(args) splices the parameter list.

qmacro_template_class("Name", type<T>)

Clone a struct template, rename it, apply substitution rules. Also clones and renames any methods defined on the template.

qmacro_method("Cls\`name", cls) $(self…) { }

Class method. Used here to add get_tuple to the cloned struct.

qmacro_template_function(@@fn)

Clone a def template function. Types fixed up manually.

5.4.17.2. Reification operators

Inside qmacro(…) and friends, dollar-prefixed splices inject compile-time values into the generated AST:

Operator

Meaning

Example

$v(val)

Compile-time value → AST constant

$v("hello")ExprConstString

$e(expr)

Splice an existing ExpressionPtr

$e(existing_ast) → inserts that node

$i(name)

String → identifier reference

$i("tag")ExprVar { name="tag" }

$c(name)

String → function call name

$c("print")ExprCall { name="print" }

$f(name)

String → field access name

pair.$f("first")pair.first

$t(td)

Splice a TypeDeclPtr in type position

type<array<$t(int_type)>>type<array<int>>

$a(args)

Splice array<VariablePtr> as parameters

$($a(fn_args)) in qmacro_function

$b(body)

Splice array<ExpressionPtr> as block body

{ $b(fn_body) } in qmacro_function

5.4.17.3. Section 1 — The macro module

The entire macro is a single [call_macro] named build_demo. Its visit method exercises every variant listed above, including qmacro_method — used to add a get_tuple method to the cloned DemoIntFloat struct.

tutorials/macros/qmacro_mod.das
options gen2
options no_aot

// Tutorial macro module: quasi-quotation reference (qmacro variants).
//
// This module demonstrates every qmacro variant and reification
// operator through a single [call_macro] named `build_demo`.
// When the user writes `build_demo()` the macro generates:
//
//   • A struct via qmacro_template_class  — DemoIntFloat (with method)
//   • A function via qmacro_function      — demo_run
//   • A function clone via qmacro_template_function — demo_add_int
//
// Every other qmacro variant (qmacro, qmacro_block, qmacro_block_to_array,
// qmacro_expr, qmacro_type, qmacro_variable, quote) and every reification
// splice ($v, $e, $i, $c, $f, $t, $a, $b) is exercised in the process.
//
// Every variant is exercised, including qmacro_method — used here
// to generate a `get_tuple` method on the cloned struct.

module qmacro_mod public

require ast
require daslib/ast_boost
require daslib/templates_boost

// -----------------------------------------------------------------------
// Template struct — used by qmacro_template_class.
// `struct template` prevents direct instantiation.  TFirst / TSecond
// are alias names resolved when the template is cloned.
//
// The describe() method is defined inside the struct body.  When
// qmacro_template_class clones DemoPair into DemoIntFloat it also
// clones this method, renames it to DemoIntFloat`describe, and applies
// the same type substitution rules (TFirst → int, TSecond → float) to
// the method's self parameter and body.
// -----------------------------------------------------------------------
struct template DemoPair {
    first  : TFirst
    second : TSecond

    def describe() {
        print("first  = {self.first}\n")
        print("second = {self.second}\n")
    }
}

// -----------------------------------------------------------------------
// Template function — used by qmacro_template_function.
// The $t(t_value) tag types in the signature are resolved automatically
// when qmacro_template_function clones the function — it reads the tag
// and substitutes whatever TypeDeclPtr `t_value` holds at expansion time.
// -----------------------------------------------------------------------
def template demo_add(a, b : $t(t_value)) : $t(t_value) {
    return a + b
}

// -----------------------------------------------------------------------
// The main call macro.
// -----------------------------------------------------------------------

[call_macro(name="build_demo")]
class BuildDemoMacro : AstCallMacro {
    def override visit(prog : ProgramPtr; mod : Module?; var expr : smart_ptr<ExprCallMacro>) : ExpressionPtr {

        // ===============================================================
        // 1. quote(expr) — plain AST capture, NO reification.
        //    Returns ExpressionPtr.  Use for simple literal constants.
        // ===============================================================
        var inscope literal_true <- quote(true)   // ExprConstBool
        var inscope literal_zero <- quote(0)      // ExprConstInt

        // ===============================================================
        // 2. qmacro(expr) — expression with reification splices.
        //    Returns ExpressionPtr.
        //
        //    Reification operators used here:
        //      $v(value) — splice compile-time VALUE as a constant
        //      $e(expr)  — splice an existing ExpressionPtr
        // ===============================================================
        let greeting = "hello from qmacro"

        // $v — compile-time string becomes a constant in the AST.
        var inscope print_greeting <- qmacro(print($v("{greeting}!\n")))

        // $e — insert an already-built ExpressionPtr into the tree.
        var inscope msg_expr <- qmacro("the literal is: ")
        var inscope print_literal <- qmacro(print($e(msg_expr) + "{$e(literal_true)}\n"))

        // ===============================================================
        // 3. qmacro_type(type<T>) — build a TypeDeclPtr.
        //    $t(typeDecl) — splice a TypeDeclPtr in type position.
        // ===============================================================
        var inscope int_type   <- qmacro_type(type<int>)
        var inscope float_type <- qmacro_type(type<float>)
        // $t — compose types: array<$t(int_type)> → array<int>
        var inscope arr_int_type <- qmacro_type(type<array<$t(int_type)>>)

        // ===============================================================
        // 4. qmacro_variable("name", type<T>) — build a VariablePtr.
        //    Used to create function arguments programmatically.
        // ===============================================================
        var inscope tag_var <- qmacro_variable("tag", type<string>)

        // ===============================================================
        // 5. qmacro_expr(${ statement; }) — single statement.
        //    Returns ExpressionPtr.  Useful for let/var declarations
        //    that need statement syntax in an expression context.
        // ===============================================================
        var inscope let_stmt <- qmacro_expr(${ let title = "items"; })

        // ===============================================================
        // 6. qmacro_block() { stmts } — multiple statements as ExprBlock.
        //    $b(stmts) — splice array<ExpressionPtr> as a block body.
        // ===============================================================
        var inscope steps : array<ExpressionPtr>
        steps |> emplace_new <| qmacro(print("  step 1\n"))
        steps |> emplace_new <| qmacro(print("  step 2\n"))
        steps |> emplace_new <| qmacro(print("  step 3\n"))

        var inscope block_stmts <- qmacro_block() {
            print("qmacro_block body:\n")
            $b(steps)
        }

        // ===============================================================
        // 7. qmacro_block_to_array() { stmts }
        //    Like qmacro_block but returns array<ExpressionPtr> —
        //    each statement becomes a separate element.
        // ===============================================================
        var inscope extra <- qmacro_block_to_array() {
            print("  extra A\n")
            print("  extra B\n")
        }

        // ===============================================================
        // 8. qmacro_function("name") $(args) : RetType { body }
        //    Generates a complete FunctionPtr.
        //
        //    Reification operators used here:
        //      $a(args) — splice array<VariablePtr> as parameter list
        //      $b(body) — splice array<ExpressionPtr> as function body
        //      $i(name) — splice a string as an IDENTIFIER reference
        //      $c(name) — splice a string as a function CALL name
        //      $f(name) — splice a string as a field access name
        // ===============================================================
        let fn_name = "demo_run"
        var inscope fn_args : array<VariablePtr>
        fn_args |> emplace_new <| clone_variable(tag_var)

        // Build the function body — each statement demonstrates
        // a different reification operator or qmacro variant.
        var inscope fn_body : array<ExpressionPtr>

        // $v — greeting is baked in as a string constant.
        fn_body |> emplace_new <| clone_expression(print_greeting)

        // $e — two sub-expressions spliced into a larger expression.
        fn_body |> emplace_new <| clone_expression(print_literal)

        // $c — string becomes a function call name.
        //      $c("print") → ExprCall { name = "print" }
        let print_fn = "print"
        fn_body |> emplace_new <| qmacro($c(print_fn)($v("called via $c\n")))

        // $i — string becomes an identifier (variable reference).
        //      $i("tag") → ExprVar { name = "tag" }
        let tag_name = "tag"
        fn_body |> emplace_new <| qmacro(print("$i resolved tag = {$i(tag_name)}\n"))

        // $f — string becomes a field access name.
        //      pair.$f("first") → pair.first
        let field_name = "first"
        fn_body |> emplace_new <| qmacro_expr(${
            var pair = DemoIntFloat(first = 42, second = 3.14);
        })
        fn_body |> emplace_new <| qmacro(print("pair.$f first = {pair.$f(field_name)}\n"))

        // qmacro_block result — three steps spliced via $b.
        fn_body |> emplace_new <| clone_expression(block_stmts)

        // qmacro_block_to_array — extra statements appended.
        for (s in extra) {
            fn_body |> emplace_new <| clone_expression(s)
        }

        // qmacro_expr — a let declaration as a single statement.
        fn_body |> emplace_new <| clone_expression(let_stmt)
        fn_body |> emplace_new <| qmacro(print("title = {title}\n"))

        // $a — splice argument list; $b — splice body statements.
        var inscope demo_fn <- qmacro_function(fn_name) $($a(fn_args)) {
            $b(fn_body)
        }
        demo_fn.flags |= FunctionFlags.generated
        add_function(compiling_module(), demo_fn)

        // ===============================================================
        // 9. qmacro_template_class("Name", type<Template>)
        //    Clones a struct template, renames it, clears the template
        //    flag, and returns a TypeDeclPtr pointing to the new struct.
        //
        //    Type aliases are resolved via add_structure_alias — this
        //    registers alias types on the cloned struct so the compiler
        //    replaces TFirst/TSecond with real types during compilation.
        //
        //    Also clones DemoPair`describe → DemoIntFloat`describe,
        //    applying the same alias substitution to the method body.
        // ===============================================================
        var inscope pair_type <- qmacro_template_class("DemoIntFloat", type<DemoPair>)
        pair_type.structType |> add_structure_alias("TFirst", int_type)
        pair_type.structType |> add_structure_alias("TSecond", float_type)

        // ===============================================================
        // 10. qmacro_template_function(@@template_fn)
        //     Clones a template function, strips the template flag.
        //     Takes a function address (@@), not a string name.
        //     The template uses $t(t_value) tag types in its signature,
        //     so qmacro_template_function auto-generates substitution
        //     rules — no manual type fixup is needed.
        // ===============================================================
        var inscope t_value <- clone_type(int_type)
        var inscope add_fn <- qmacro_template_function(@@demo_add)
        add_fn.name := "demo_add_int"
        add_fn.flags |= FunctionFlags.generated
        add_function(compiling_module(), add_fn)

        // ===============================================================
        // 11. qmacro_method("Cls`name", cls) $(var self : T) { body }
        //     Generates a FunctionPtr marked as a class method.
        //     Here we add a `get_tuple` method to DemoIntFloat that
        //     returns (first, second) as a tuple.
        // ===============================================================

        var inscope pair_struct_type <- add_ptr_ref(pair_type.structType)
        var inscope method <- qmacro_method("get_tuple", pair_struct_type) $(var self : $t(pair_type)) {
            return (self.first, self.second)
        }
        add_function(compiling_module(), method)

        // ===============================================================
        // Return value — all real work was done via add_function /
        // qmacro_template_class.  Return a trivial expression.
        // ===============================================================
        return <- quote(true)
    }
}

Key observations:

  • ``quote`` vs ``qmacro`` — use quote when no $… splices are needed; use qmacro when you need reification.

  • ``qmacro_function`` + ``$a`` + ``$b`` — builds a complete function from dynamically constructed argument and body arrays.

  • ``qmacro_template_class`` + ``add_structure_alias`` — the call macro clones the struct template, renames it, and returns a TypeDeclPtr. add_structure_alias then registers alias types (TFirstint, TSecondfloat) on the cloned struct so the compiler resolves them during compilation. The template’s describe() method is also cloned and renamed to DemoIntFloat`describe with the same alias substitution applied.

  • ``qmacro_method`` — generates a standalone method (get_tuple) and attaches it to the cloned DemoIntFloat struct. Uses add_ptr_ref to obtain the StructurePtr and $t(pair_type) to splice the struct type into the self parameter.

  • ``qmacro_template_function`` — clones a def template function. The template uses $t(t_value) tag types in its signature, so qmacro_template_function auto-generates substitution rules from the tags — no manual type fixup is needed after cloning.

5.4.17.4. Section 2 — Using the generated artifacts

tutorials/macros/17_qmacro.das
options gen2
options no_aot

// Tutorial 17 — Quasi-quotation reference (qmacro variants).
//
// This tutorial demonstrates ALL qmacro variants and reification
// operators available in `daslib/templates_boost`.
//
// The companion module `qmacro_mod.das` defines a single
// [call_macro] named `build_demo` that uses every variant:
//
//   qmacro variants (10 + quote):
//     quote(expr)                       — plain AST capture, no reification
//     qmacro(expr)                      — expression with reification
//     qmacro_type(type<T>)              — build TypeDeclPtr
//     qmacro_variable("n", type<T>)     — build VariablePtr
//     qmacro_expr(${ stmt })            — single statement
//     qmacro_block() { stmts }          — block of statements
//     qmacro_block_to_array() { stmts } — statements → array<ExpressionPtr>
//     qmacro_function("name") $(args) { body }       — full function
//     qmacro_template_class("Name", type<Template>)  — clone struct template
//     qmacro_method("Cls`meth", cls) $(self...) { }   — class method (*)
//     qmacro_template_function(@@fn)                   — clone template fn
//
//   qmacro_method is used here to generate a `get_tuple` method
//   on the cloned DemoIntFloat struct.
//
//   Reification operators:
//     $v(value)   — compile-time value → AST constant
//     $e(expr)    — splice existing ExpressionPtr
//     $i(name)    — string → identifier
//     $c(name)    — string → function call name
//     $f(name)    — string → field access name
//     $t(type)    — splice TypeDeclPtr in type position
//     $a(args)    — splice array<VariablePtr> as parameters
//     $b(body)    — splice array<ExpressionPtr> as block body
//
// See `qmacro_mod.das` for the implementation of each variant.
// The code below exercises the artefacts that the macro generates
// at compile time.

require qmacro_mod

[export]
def main() {
    // build_demo() triggers the call macro.  All functions and structs
    // are registered at compile time via add_function / add_structure.
    // The return value (a trivial constant) is discarded.
    build_demo()

    // 1. demo_run — generated by qmacro_function.
    //    Its body was assembled from qmacro, qmacro_expr, qmacro_block,
    //    qmacro_block_to_array, and reification operators
    //    $v, $e, $i, $c, $f, $a, $b.
    print("--- demo_run ---\n")
    demo_run("test")

    // 2. DemoIntFloat — generated by qmacro_template_class.
    //    Cloned from struct template DemoPair with TFirst=int, TSecond=float.
    print("\n--- DemoIntFloat (qmacro_template_class) ---\n")
    var pair = DemoIntFloat(first = 42, second = 3.14)
    // describe() was a template method on DemoPair — it was cloned
    // and type-substituted automatically by qmacro_template_class.
    pair.describe()

    let (a, b) = pair.get_tuple()
    print("get_tuple() = ({a}, {b})\n")

    // 3. demo_add_int — generated by qmacro_template_function.
    //    Cloned from template demo_add, types fixed to int.
    print("\n--- demo_add_int (qmacro_template_function) ---\n")
    let sum = demo_add_int(10, 20)
    print("demo_add_int(10, 20) = {sum}\n")
}

build_demo() triggers the call macro. The generated artefacts:

  1. ``demo_run(tag)`` — a function whose body was assembled from qmacro, qmacro_expr, qmacro_block, qmacro_block_to_array, and all eight reification operators.

  2. ``DemoIntFloat`` — a struct cloned from the DemoPair template via qmacro_template_class. Type aliases TFirst and TSecond are registered on the cloned struct with add_structure_alias, so the compiler resolves them to int and float. The template’s describe() method is also cloned and renamed to DemoIntFloat`describe. Additionally, qmacro_method adds a get_tuple() method that returns (first, second) as a tuple.

  3. ``demo_add_int`` — a function cloned from the demo_add template. The template’s $t(t_value) tag types are resolved to int automatically by qmacro_template_function.

Running the tutorial:

$ daslang tutorials/macros/17_qmacro.das
--- demo_run ---
hello from qmacro!
the literal is: true
called via $c
$i resolved tag = test
pair.$f first = 42
qmacro_block body:
  step 1
  step 2
  step 3
  extra A
  extra B
title = items

--- DemoIntFloat (qmacro_template_class) ---
first  = 42
second = 3.14
get_tuple() = (42, 3.14)

--- demo_add_int (qmacro_template_function) ---
demo_add_int(10, 20) = 30

See also

Full source: 17_qmacro.das, qmacro_mod.das

Previous tutorial: tutorial_macro_template_type_macro

Standard library: daslib/templates_boost.das — quasi-quotation infrastructure (all qmacro_* variants, Template, apply_template)

Language reference: Macros — full macro system documentation