3.35. AST Matching

AST matching is reverse reification — instead of building AST from values, it matches compiled AST against structural patterns and extracts values from it.

The module daslib/ast_match provides three macros: qmatch, qmatch_block, and qmatch_function. They use the same tag system as qmacro ($e, $v, $i, $t, $c, $f, $b, $a) but in reverse — tags extract values from the matched expression instead of substituting values in.

require daslib/ast_match

3.35.1. Simple example

Match a compiled expression and extract parts of it:

var inscope expr <- qmacro(a + b)
var left_name : string
let r = qmatch(expr, $i(left_name) + b)
assert(r.matched)
assert(left_name == "a")

qmatch compiles the pattern $i(left_name) + b into a series of checks: verify the expression is ExprOp2 with op="+" and right operand ExprVar(name="b"), then extract the left operand’s identifier name into left_name.

3.35.2. Macros

3.35.2.1. qmatch

qmatch(expr, pattern) matches a single expression against a structural pattern. Returns a QMatchResult.

var inscope expr <- qmacro(x * 2 + 1)
let r = qmatch(expr, x * 2 + 1)
assert(r.matched)

The pattern is not type-inferred — it stays as raw AST, the same way qmacro patterns work. This means identifier names in the pattern refer to AST variable names, not local variables.

3.35.2.2. qmatch_block

qmatch_block(expr) $ { stmts } matches a block’s statement list with wildcard support. Optionally matches block arguments and return type:

var inscope blk <- qmacro_block() <| $ {
    var x = 1
    print("{x}\n")
    return x
}
let r = qmatch_block(blk) $ {
    _wildcard()
    return x
}
assert(r.matched)

With argument and return type matching:

let r = qmatch_block(blk) $(x : int) : int {
    _wildcard()
    return x
}

Arguments are matched strictly — if the pattern specifies zero arguments, the target must also have zero arguments. Use $a(var) to match any remaining arguments (see below). Omitting the return type matches any return type.

3.35.2.3. qmatch_function

qmatch_function(func) $(args) : RetType { stmts } matches a compiled function’s arguments, return type, and body:

[export]
def target_add(a, b : int) : int {
    return a + b
}

[test]
def test_add(t : T?) {
    var inscope func <- find_module_function_via_rtti(compiling_module(), @@target_add)
    let r = qmatch_function(func) $(a : int; b : int) : int {
        return a + b
    }
    assert(r.matched)
}

The function has been through compilation and optimization, so the AST may differ from the source. Compiler-injected fakeContext and fakeLineInfo arguments are filtered automatically.

3.35.3. Tags

Tags extract values from matched AST positions. They mirror the reification escape sequences.

3.35.3.1. $e(var) — expression capture

Captures a matched sub-expression as a cloned ExpressionPtr.

var inscope cond, then_val, else_val : ExpressionPtr
let r = qmatch(expr, $e(cond) ? $e(then_val) : $e(else_val))

3.35.3.2. $v(var) — value extraction

Extracts a constant value from ExprConst* nodes. The variable type determines which constant type is expected:

var val : int
let r = qmatch(expr, a + $v(val))
// r.matched if the right operand is ExprConstInt; val receives the value

Supported types: int, float, bool, string, int64, uint, double, uint64, int8, uint8, int16, uint16.

3.35.3.3. $i(var) — identifier name

Extracts an identifier name as string:

var name : string
let r = qmatch(expr, $i(name) + b)    // captures variable name

Also works on let declarations and for loop iterators:

let r = qmatch_block(blk) $ {
    var $i(var_name) = 5
}

3.35.3.4. $t(var) — type capture

Captures a TypeDeclPtr from type positions:

var inscope captured_type : TypeDeclPtr
let r = qmatch(expr, type<$t(captured_type)>)

3.35.3.5. $c(var) — call name capture

Matches call arguments structurally, captures the call name:

var call_name : string
let r = qmatch(expr, $c(call_name)(a, b))

When the call targets a generic function instance, $c returns the original generic name (before mangling).

3.35.3.6. $f(var) — field name capture

Matches the field value, captures the field name:

var field : string
let r = qmatch(expr, obj.$f(field))

3.35.3.7. $a(var) — remaining arguments

Captures remaining arguments after the explicitly listed ones as array<VariablePtr>. Works in both qmatch_function and qmatch_block. Fixed arguments before $a are matched by name and type as usual; everything after goes into the array. $a must be the last argument in the pattern.

Capture remaining function arguments:

var inscope rest : array<VariablePtr>
let r = qmatch_function(func) $(a : int; $a(rest)) {
    _wildcard()
}
// rest contains all arguments after `a`

Capture all arguments (no fixed prefix):

var inscope rest : array<VariablePtr>
let r = qmatch_function(func) $($a(rest)) {
    _wildcard()
}
// rest contains every argument

Works the same on block arguments:

var inscope rest : array<VariablePtr>
let r = qmatch_block(blk) $(a : int; $a(rest)) {
    return a
}

If all fixed arguments match but no arguments remain, $a captures an empty array and the match succeeds. If there are fewer actual arguments than fixed ones in the pattern, the match fails.

3.35.3.8. $b(var) — statement range capture

Passed as an argument to a wildcard (_wildcard, _wildcard1, _optional, _any), captures the matched statements as array<ExpressionPtr>. Each statement is cloned.

Capture trailing statements (everything after foo()):

var inscope stmts : array<ExpressionPtr>
let r = qmatch_block(blk) $ {
    foo()
    _wildcard($b(stmts))
}

Capture leading statements (everything before baz()):

var inscope stmts : array<ExpressionPtr>
let r = qmatch_block(blk) $ {
    _wildcard($b(stmts))
    baz()
}

Capture a middle range between two fixed statements:

var inscope stmts : array<ExpressionPtr>
let r = qmatch_block(blk) $ {
    foo()
    _wildcard($b(stmts))
    bar()
}
// stmts contains everything between foo() and bar()

When the wildcard matches zero statements, $b captures an empty array. The $b tag is optional — wildcards work without it, they just don’t capture.

3.35.4. Wildcards

Wildcards match variable numbers of statements in block and function patterns:

Wildcard

Matches

_wildcard()

0 or more statements

_wildcard1()

1 or more statements

_optional()

0 or 1 statement (fails if 2+)

_any()

Exactly 1 statement

All wildcards accept an optional $b(var) argument to capture the matched statements.

// Match: any prefix, then if(...), then any suffix
let r = qmatch_block(blk) $ {
    _wildcard()
    if (a < 5) { return b }
    _wildcard()
}

3.35.5. Type matching

Types in patterns are matched structurally — base type, const/ref/pointer flags, struct/enum names, and child types are all compared recursively.

3.35.5.1. auto (type wildcard)

Use auto in any type position to match any type:

// Matches any array type
let r = qmatch(expr, type<array<auto>>)

// Matches any table with string keys
let r2 = qmatch(expr, type<table<string; auto>>)

3.35.6. Constant constructors and folding

The compiler folds constant constructor calls like range(1, 10) into ExprConstRange values. ast_match handles this transparently — a pattern range(1, 10) matches the folded constant.

Tags work inside constant constructors:

var x_val : float
let r = qmatch(expr, float4($v(x_val), 2.0, 3.0, 4.0))
// Matches ExprConstFloat4, captures the x component

Supported constructors: range, urange, range64, urange64, int2, int3, int4, uint2, uint3, uint4, float2, float3, float4.

3.35.7. Generic function matching

When matching calls to generic function instances, the compiler mangles the call name (e.g. generic_add`12345). ast_match resolves the original name via fromGeneric, so patterns like generic_add(a, b) match transparently.

$c(name) on a generic call also returns the original (unmangled) name.

3.35.8. Result type

enum QMatchError : int {
    ok
    rtti_mismatch
    field_mismatch
    const_type_mismatch
    type_mismatch
    list_length
    wildcard_not_found
    null_expression
}

struct QMatchResult {
    matched : bool
    error   : QMatchError
    expr    : Expression const?
}

When a match fails, error indicates why and expr points to the mismatched AST node (useful for diagnostics).

See also

Reification for the forward direction — building AST from values using the same tag system, Macros for the compilation lifecycle where AST matching is used, AST pattern matching module for the full API reference.