Skip to content
162 changes: 159 additions & 3 deletions daslib/ast_match.das
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ options no_unused_function_arguments = false
options strict_smart_pointers = true
options stack = 1_048_576

// this module is not AOT compiled. there are major issues with current ExpressionPtr and TypeDeclPtr
// return-call patterns, which we are not going to address - until we get rid of smart_ptr altogether on das side
options no_aot

module ast_match shared public

//! AST pattern matching — reverse reification.
Expand Down Expand Up @@ -282,6 +286,7 @@ def qm_real_call_arg_index(args : dasvector`smart_ptr`Expression; logical_idx :
def qm_extract_stmts(var blk_expr : ExpressionPtr; from_pos : int; to_pos : int; var result : array<ExpressionPtr>&) : void {
//! Extract statements [from_pos, to_pos) from a block into an array (cloned).
var blk = blk_expr as ExprBlock
result |> reserve(to_pos - from_pos)
for (i in range(from_pos, to_pos)) {
{
var inscope cloned <- clone_expression(blk.list[i])
Expand Down Expand Up @@ -361,6 +366,47 @@ def qm_make_zero_const(call_name : string) : ExpressionPtr {
return <- default<ExpressionPtr>
}

def qm_convert_local_function(var expr : ExpressionPtr) : ExpressionPtr {
//! Reverse generateLocalFunction: ExprAddr → ExprMakeBlock(isLocalFunction=true).
//! Returns null if the expression is not a generated local function.
if (!(expr is ExprAddr)) {
return <- default<ExpressionPtr>
}
let ea = expr as ExprAddr
if (ea.func == null || !ea.func.flags.generated) {
return <- default<ExpressionPtr>
}
if (find(string(ea.func.name), "`function") < 0) {
return <- default<ExpressionPtr>
}
let fn = ea.func
// Reconstruct ExprBlock with args + return type + body
var inscope blk <- new ExprBlock(at = fn.at)
blk.returnType |> move_new <| clone_type(fn.result)
for (arg in fn.arguments) {
blk.arguments |> emplace_new <| clone_variable(arg)
}
// Clone body statements
var inscope fn_body <- clone_expression(fn.body)
let fn_blk = fn_body as ExprBlock
if (fn_blk != null) {
for (stmt in fn_blk.list) {
blk.list |> emplace_new <| clone_expression(stmt)
}
}
// Wrap in ExprMakeBlock with isLocalFunction flag
return <- new ExprMakeBlock(at = fn.at, _block <- blk, mmFlags = ExprMakeBlockFlags.isLocalFunction)
}

def qm_resolve_local_function(var expr : ExpressionPtr) : ExpressionPtr {
//! If expr is ExprMakeBlock, clone it. If ExprAddr (post-inference local function),
//! convert back to ExprMakeBlock. Returns null if neither works.
if (expr is ExprMakeBlock) {
return <- clone_expression(expr)
}
return <- qm_convert_local_function(expr)
}

// ── Compile-time AST builders ───────────────────────────────────────────

[macro_function]
Expand Down Expand Up @@ -795,7 +841,7 @@ def private has_tags_in_args(call : ExprCall?) : bool {
// would prevent constant folding, so the call stays as ExprCall and hits the
// normal matching path instead of reaching here.
for (arg in call.arguments) {
if (arg != null && arg.__rtti == "ExprTag") {
if (arg != null && arg is ExprTag) {
return true
}
}
Expand Down Expand Up @@ -1040,12 +1086,46 @@ def private generate_match(pattern : Expression?; actual_var : string; var body

// ── ExprMakeBlock — lightweight inline block matching ───────
if (pattern is ExprMakeBlock) {
body |> emplace_new <| qm_rtti_guard(at, actual_var, "ExprMakeBlock")
let pat_mkblk = pattern as ExprMakeBlock
// For local function patterns, also accept ExprAddr (post-inference form)
var eff_actual = actual_var
if (pat_mkblk.mmFlags.isLocalFunction) {
let eff_var = next_var(index)
body |> emplace_new <| qmacro_expr(${ var inscope $i(eff_var) <- qm_resolve_local_function($i(actual_var)); })
var inscope nc <- qmacro($i(eff_var) == null)
var inscope nf <- qmacro(qm_fail_rtti($i(actual_var)))
body |> emplace_new <| qm_guard(at, nc, nf)
eff_actual = eff_var
} else {
body |> emplace_new <| qm_rtti_guard(at, actual_var, "ExprMakeBlock")
}
// Match mmFlags (isLambda, isLocalFunction) to distinguish block/lambda/local-function
let mkblk_cast = next_var(index)
var inscope as_val_mkb <- make_var_ref(at, eff_actual)
var inscope as_cast_mkb <- new ExprAsVariant(at = at, value <- as_val_mkb, name := "ExprMakeBlock")
body |> emplace_new <| qmacro_expr(${ var $i(mkblk_cast) = $e(as_cast_mkb); })
if (pat_mkblk.mmFlags.isLambda) {
var inscope cl <- qmacro(!$i(mkblk_cast).mmFlags.isLambda)
var inscope fl <- qmacro(qm_fail_field($i(actual_var)))
body |> emplace_new <| qm_guard(at, cl, fl)
} else {
var inscope cl <- qmacro($i(mkblk_cast).mmFlags.isLambda)
var inscope fl <- qmacro(qm_fail_field($i(actual_var)))
body |> emplace_new <| qm_guard(at, cl, fl)
}
if (pat_mkblk.mmFlags.isLocalFunction) {
var inscope clf <- qmacro(!$i(mkblk_cast).mmFlags.isLocalFunction)
var inscope flf <- qmacro(qm_fail_field($i(actual_var)))
body |> emplace_new <| qm_guard(at, clf, flf)
} else {
var inscope clf <- qmacro($i(mkblk_cast).mmFlags.isLocalFunction)
var inscope flf <- qmacro(qm_fail_field($i(actual_var)))
body |> emplace_new <| qm_guard(at, clf, flf)
}
var pat_block = get_ptr(pat_mkblk._block) as ExprBlock
// Unwrap ExprMakeBlock → ExprBlock at runtime
let blk_var = next_var(index)
body |> emplace_new <| qmacro_expr(${ var inscope $i(blk_var) <- clone_expression(($i(actual_var) as ExprMakeBlock)._block); })
body |> emplace_new <| qmacro_expr(${ var inscope $i(blk_var) <- clone_expression(($i(eff_actual) as ExprMakeBlock)._block); })
body |> emplace_new <| qm_rtti_guard(at, blk_var, "ExprBlock")
let cast_var = next_var(index)
var inscope as_val_b <- make_var_ref(at, blk_var)
Expand All @@ -1071,6 +1151,82 @@ def private generate_match(pattern : Expression?; actual_var : string; var body
return
}

// ── ExprArrayComprehension — array/table/generator comprehension ─
if (pattern is ExprArrayComprehension) {
let pat_ac = pattern as ExprArrayComprehension
let pat_gen = pat_ac.generatorSyntax
let pat_tbl = pat_ac.tableSyntax
// RTTI guard
body |> emplace_new <| qm_rtti_guard(at, actual_var, "ExprArrayComprehension")
// Cast actual to ExprArrayComprehension
let cast_var = next_var(index)
var inscope as_val_ac <- make_var_ref(at, actual_var)
var inscope as_cast_ac <- new ExprAsVariant(at = at, value <- as_val_ac, name := "ExprArrayComprehension")
body |> emplace_new <| qmacro_expr(${ var $i(cast_var) = $e(as_cast_ac); })
// Match generatorSyntax (both true and false enforced)
if (pat_gen) {
var inscope cg <- qmacro($i(cast_var).generatorSyntax == false)
var inscope fg <- qmacro(qm_fail_field($i(actual_var)))
body |> emplace_new <| qm_guard(at, cg, fg)
} else {
var inscope cg <- qmacro($i(cast_var).generatorSyntax)
var inscope fg <- qmacro(qm_fail_field($i(actual_var)))
body |> emplace_new <| qm_guard(at, cg, fg)
}
// Match tableSyntax (both true and false enforced)
if (pat_tbl) {
var inscope ct <- qmacro($i(cast_var).tableSyntax == false)
var inscope ft <- qmacro(qm_fail_field($i(actual_var)))
body |> emplace_new <| qm_guard(at, ct, ft)
} else {
var inscope ct <- qmacro($i(cast_var).tableSyntax)
var inscope ft <- qmacro(qm_fail_field($i(actual_var)))
body |> emplace_new <| qm_guard(at, ct, ft)
}
// Recurse into exprFor
let pat_for = get_ptr(pat_ac.exprFor)
if (pat_for != null) {
let for_var = next_var(index)
body |> emplace_new <| qmacro_expr(${ var inscope $i(for_var) <- clone_expression($i(cast_var).exprFor); })
if (!is_wildcard_call(pat_for) && !(pat_for is ExprTag)) {
var inscope nc <- qmacro($i(for_var) == null)
var inscope nf <- qmacro(qm_fail_null())
body |> emplace_new <| qm_guard(at, nc, nf)
}
generate_match(pat_for, for_var, body, index, at)
}
// Recurse into subexpr
let pat_sub = get_ptr(pat_ac.subexpr)
if (pat_sub != null) {
let sub_var = next_var(index)
body |> emplace_new <| qmacro_expr(${ var inscope $i(sub_var) <- clone_expression($i(cast_var).subexpr); })
if (!is_wildcard_call(pat_sub) && !(pat_sub is ExprTag)) {
var inscope nc <- qmacro($i(sub_var) == null)
var inscope nf <- qmacro(qm_fail_null())
body |> emplace_new <| qm_guard(at, nc, nf)
}
generate_match(pat_sub, sub_var, body, index, at)
}
// Handle exprWhere
if (pat_ac.exprWhere != null) {
let pat_where = get_ptr(pat_ac.exprWhere)
let where_var = next_var(index)
body |> emplace_new <| qmacro_expr(${ var inscope $i(where_var) <- clone_expression($i(cast_var).exprWhere); })
if (!is_wildcard_call(pat_where) && !(pat_where is ExprTag)) {
var inscope nc <- qmacro($i(where_var) == null)
var inscope nf <- qmacro(qm_fail_null())
body |> emplace_new <| qm_guard(at, nc, nf)
}
generate_match(pat_where, where_var, body, index, at)
} else {
// Pattern has no where clause — actual shouldn't either
var inscope nc <- qmacro($i(cast_var).exprWhere != null)
var inscope nf <- qmacro(qm_fail_field($i(actual_var)))
body |> emplace_new <| qm_guard(at, nc, nf)
}
return
}

// ── ExprFor — handle iteratorsTags for $i capture ─────────
if (pattern is ExprFor) {
var pat_for = pattern as ExprFor
Expand Down
Loading
Loading