// macroexpand.bux — declarative macro! expansion (selfhost parity, session 60) // Expands name!(args) using macro! rules; grafts call-site locations (quote hygiene). module MacroExpand { extern func bux_alloc(size: uint) -> *void; extern func bux_strlen(s: String) -> uint; extern func bux_str_slice(s: String, start: uint, len: uint) -> String; extern func bux_str_contains(haystack: String, needle: String) -> int; extern func PrintLine(s: String); extern func Print(s: String); extern func PrintInt(n: int); // --------------------------------------------------------------------------- // Fragment environment (up to 9 $frags singles + 2 rep lists) // --------------------------------------------------------------------------- struct MacroEnv { count: int; n0: String; n1: String; n2: String; n3: String; n4: String; n5: String; n6: String; n7: String; n8: String; a0: *Expr; a1: *Expr; a2: *Expr; a3: *Expr; a4: *Expr; a5: *Expr; a6: *Expr; a7: *Expr; a8: *Expr; // Up to 2 named rep lists (multi-rep + compound zip) listName0: String; listCount0: int; l0: *Expr; l1: *Expr; l2: *Expr; l3: *Expr; l4: *Expr; l5: *Expr; l6: *Expr; l7: *Expr; l8: *Expr; l9: *Expr; l10: *Expr; l11: *Expr; l12: *Expr; l13: *Expr; l14: *Expr; l15: *Expr; listName1: String; listCount1: int; m0: *Expr; m1: *Expr; m2: *Expr; m3: *Expr; m4: *Expr; m5: *Expr; m6: *Expr; m7: *Expr; m8: *Expr; m9: *Expr; m10: *Expr; m11: *Expr; m12: *Expr; m13: *Expr; m14: *Expr; m15: *Expr; // Unhygienic call-site binders from `var $name` / `for $i` (skip gensym) unhyCount: int; u0: String; u1: String; u2: String; u3: String; u4: String; u5: String; u6: String; u7: String; } func Env_New() -> MacroEnv { return MacroEnv { count: 0, n0: "", n1: "", n2: "", n3: "", n4: "", n5: "", n6: "", n7: "", n8: "", a0: null as *Expr, a1: null as *Expr, a2: null as *Expr, a3: null as *Expr, a4: null as *Expr, a5: null as *Expr, a6: null as *Expr, a7: null as *Expr, a8: null as *Expr, listName0: "", listCount0: 0, l0: null as *Expr, l1: null as *Expr, l2: null as *Expr, l3: null as *Expr, l4: null as *Expr, l5: null as *Expr, l6: null as *Expr, l7: null as *Expr, l8: null as *Expr, l9: null as *Expr, l10: null as *Expr, l11: null as *Expr, l12: null as *Expr, l13: null as *Expr, l14: null as *Expr, l15: null as *Expr, listName1: "", listCount1: 0, m0: null as *Expr, m1: null as *Expr, m2: null as *Expr, m3: null as *Expr, m4: null as *Expr, m5: null as *Expr, m6: null as *Expr, m7: null as *Expr, m8: null as *Expr, m9: null as *Expr, m10: null as *Expr, m11: null as *Expr, m12: null as *Expr, m13: null as *Expr, m14: null as *Expr, m15: null as *Expr, unhyCount: 0, u0: "", u1: "", u2: "", u3: "", u4: "", u5: "", u6: "", u7: "" }; } func Env_IsUnhy(env: *MacroEnv, name: String) -> bool { if String_Eq(name, "") { return false; } if env.unhyCount > 0 && String_Eq(env.u0, name) { return true; } if env.unhyCount > 1 && String_Eq(env.u1, name) { return true; } if env.unhyCount > 2 && String_Eq(env.u2, name) { return true; } if env.unhyCount > 3 && String_Eq(env.u3, name) { return true; } if env.unhyCount > 4 && String_Eq(env.u4, name) { return true; } if env.unhyCount > 5 && String_Eq(env.u5, name) { return true; } if env.unhyCount > 6 && String_Eq(env.u6, name) { return true; } if env.unhyCount > 7 && String_Eq(env.u7, name) { return true; } return false; } func Env_AddUnhy(env: *MacroEnv, name: String) { if String_Eq(name, "") { return; } if Env_IsUnhy(env, name) { return; } if env.unhyCount >= 8 { return; } if env.unhyCount == 0 { env.u0 = name; } else if env.unhyCount == 1 { env.u1 = name; } else if env.unhyCount == 2 { env.u2 = name; } else if env.unhyCount == 3 { env.u3 = name; } else if env.unhyCount == 4 { env.u4 = name; } else if env.unhyCount == 5 { env.u5 = name; } else if env.unhyCount == 6 { env.u6 = name; } else { env.u7 = name; } env.unhyCount = env.unhyCount + 1; } func Env_ListSet(env: *MacroEnv, which: int, idx: int, e: *Expr) { if which == 0 { if idx == 0 { env.l0 = e; } else if idx == 1 { env.l1 = e; } else if idx == 2 { env.l2 = e; } else if idx == 3 { env.l3 = e; } else if idx == 4 { env.l4 = e; } else if idx == 5 { env.l5 = e; } else if idx == 6 { env.l6 = e; } else if idx == 7 { env.l7 = e; } else if idx == 8 { env.l8 = e; } else if idx == 9 { env.l9 = e; } else if idx == 10 { env.l10 = e; } else if idx == 11 { env.l11 = e; } else if idx == 12 { env.l12 = e; } else if idx == 13 { env.l13 = e; } else if idx == 14 { env.l14 = e; } else if idx == 15 { env.l15 = e; } } else { if idx == 0 { env.m0 = e; } else if idx == 1 { env.m1 = e; } else if idx == 2 { env.m2 = e; } else if idx == 3 { env.m3 = e; } else if idx == 4 { env.m4 = e; } else if idx == 5 { env.m5 = e; } else if idx == 6 { env.m6 = e; } else if idx == 7 { env.m7 = e; } else if idx == 8 { env.m8 = e; } else if idx == 9 { env.m9 = e; } else if idx == 10 { env.m10 = e; } else if idx == 11 { env.m11 = e; } else if idx == 12 { env.m12 = e; } else if idx == 13 { env.m13 = e; } else if idx == 14 { env.m14 = e; } else if idx == 15 { env.m15 = e; } } } func Env_ListGet(env: *MacroEnv, which: int, idx: int) -> *Expr { if which == 0 { if idx == 0 { return env.l0; } if idx == 1 { return env.l1; } if idx == 2 { return env.l2; } if idx == 3 { return env.l3; } if idx == 4 { return env.l4; } if idx == 5 { return env.l5; } if idx == 6 { return env.l6; } if idx == 7 { return env.l7; } if idx == 8 { return env.l8; } if idx == 9 { return env.l9; } if idx == 10 { return env.l10; } if idx == 11 { return env.l11; } if idx == 12 { return env.l12; } if idx == 13 { return env.l13; } if idx == 14 { return env.l14; } if idx == 15 { return env.l15; } } else { if idx == 0 { return env.m0; } if idx == 1 { return env.m1; } if idx == 2 { return env.m2; } if idx == 3 { return env.m3; } if idx == 4 { return env.m4; } if idx == 5 { return env.m5; } if idx == 6 { return env.m6; } if idx == 7 { return env.m7; } if idx == 8 { return env.m8; } if idx == 9 { return env.m9; } if idx == 10 { return env.m10; } if idx == 11 { return env.m11; } if idx == 12 { return env.m12; } if idx == 13 { return env.m13; } if idx == 14 { return env.m14; } if idx == 15 { return env.m15; } } return null as *Expr; } func Env_ListWhich(env: *MacroEnv, name: String) -> int { if !String_Eq(env.listName0, "") && String_Eq(env.listName0, name) { return 0; } if !String_Eq(env.listName1, "") && String_Eq(env.listName1, name) { return 1; } return -1; } func Env_ListCountOf(env: *MacroEnv, which: int) -> int { if which == 0 { return env.listCount0; } if which == 1 { return env.listCount1; } return 0; } func Env_CopySingles(dst: *MacroEnv, src: *MacroEnv) { dst.n0 = src.n0; dst.a0 = src.a0; dst.n1 = src.n1; dst.a1 = src.a1; dst.n2 = src.n2; dst.a2 = src.a2; dst.n3 = src.n3; dst.a3 = src.a3; dst.n4 = src.n4; dst.a4 = src.a4; dst.n5 = src.n5; dst.a5 = src.a5; dst.n6 = src.n6; dst.a6 = src.a6; dst.n7 = src.n7; dst.a7 = src.a7; dst.n8 = src.n8; dst.a8 = src.a8; dst.count = src.count; } func Env_Set(env: *MacroEnv, idx: int, name: String, arg: *Expr) { if idx == 0 { env.n0 = name; env.a0 = arg; } else if idx == 1 { env.n1 = name; env.a1 = arg; } else if idx == 2 { env.n2 = name; env.a2 = arg; } else if idx == 3 { env.n3 = name; env.a3 = arg; } else if idx == 4 { env.n4 = name; env.a4 = arg; } else if idx == 5 { env.n5 = name; env.a5 = arg; } else if idx == 6 { env.n6 = name; env.a6 = arg; } else if idx == 7 { env.n7 = name; env.a7 = arg; } else if idx == 8 { env.n8 = name; env.a8 = arg; } if idx + 1 > env.count { env.count = idx + 1; } } func Env_Lookup(env: *MacroEnv, name: String) -> *Expr { if String_Eq(env.n0, name) { return env.a0; } if String_Eq(env.n1, name) { return env.a1; } if String_Eq(env.n2, name) { return env.a2; } if String_Eq(env.n3, name) { return env.a3; } if String_Eq(env.n4, name) { return env.a4; } if String_Eq(env.n5, name) { return env.a5; } if String_Eq(env.n6, name) { return env.a6; } if String_Eq(env.n7, name) { return env.a7; } if String_Eq(env.n8, name) { return env.a8; } return null as *Expr; } // If binder name is a $frag bound to ekIdent, return that ident text func Env_BinderFromFrag(env: *MacroEnv, name: String) -> String { let bound: *Expr = Env_Lookup(env, name); if bound == null as *Expr { return ""; } if bound.kind != ekIdent { return ""; } return bound.strValue; } // Find free single slot (prefer 0..7, then 8) func Env_SetNamed(env: *MacroEnv, name: String, arg: *Expr) { var i: int = 0; while i < 9 { var existing: String = ""; if i == 0 { existing = env.n0; } else if i == 1 { existing = env.n1; } else if i == 2 { existing = env.n2; } else if i == 3 { existing = env.n3; } else if i == 4 { existing = env.n4; } else if i == 5 { existing = env.n5; } else if i == 6 { existing = env.n6; } else if i == 7 { existing = env.n7; } else { existing = env.n8; } if String_Eq(existing, "") || String_Eq(existing, name) { Env_Set(env, i, name, arg); return; } i = i + 1; } Env_Set(env, 8, name, arg); } func Rule_FragName(rule: *Decl, idx: int) -> String { if idx == 0 { return rule.param0.name; } if idx == 1 { return rule.param1.name; } if idx == 2 { return rule.param2.name; } if idx == 3 { return rule.param3.name; } if idx == 4 { return rule.param4.name; } if idx == 5 { return rule.param5.name; } if idx == 6 { return rule.param6.name; } if idx == 7 { return rule.param7.name; } if idx == 8 { return rule.param8.name; } return ""; } func Macro_RenameIdentsInExpr(e: *Expr, oldN: String, newN: String) { if e == null as *Expr { return; } if e.kind == ekIdent && String_Eq(e.strValue, oldN) { e.strValue = newN; } Macro_RenameIdentsInExpr(e.child1, oldN, newN); Macro_RenameIdentsInExpr(e.child2, oldN, newN); Macro_RenameIdentsInExpr(e.child3, oldN, newN); if e.refBlock != null as *Block { Macro_GensymBlockApply(e.refBlock, oldN, newN); } var args: *ExprList = e.callArgs; while args != null as *ExprList { Macro_RenameIdentsInExpr(args.expr, oldN, newN); args = args.next; } } func Macro_GensymBlockApply(b: *Block, oldN: String, newN: String) { if b == null as *Block { return; } var s: *Stmt = b.firstStmt; while s != null as *Stmt { // let/var and for-loop binder if (s.kind == skLet || s.kind == skFor) && String_Eq(s.strValue, oldN) { s.strValue = newN; } Macro_RenameIdentsInExpr(s.child1, oldN, newN); Macro_RenameIdentsInExpr(s.child2, oldN, newN); Macro_RenameIdentsInExpr(s.child3, oldN, newN); if s.refStmtBlock != null as *Block { Macro_GensymBlockApply(s.refStmtBlock, oldN, newN); } if s.refStmtElse != null as *Block { Macro_GensymBlockApply(s.refStmtElse, oldN, newN); } s = s.nextStmt; } } // Collect template-introduced binders (let/var + for) then rename whole body. // Skip unhygienic call-site binders from `var $name` (tracked on env). func Macro_GensymBlock(ex: *MacroExpander, b: *Block, env: *MacroEnv) { if b == null as *Block || ex == null as *MacroExpander { return; } var s: *Stmt = b.firstStmt; while s != null as *Stmt { if (s.kind == skLet || s.kind == skFor) && !String_Eq(s.strValue, "") { var skip: bool = false; if env != null as *MacroEnv && Env_IsUnhy(env, s.strValue) { skip = true; } if !skip { ex.gensymCounter = ex.gensymCounter + 1; let neu: String = String_Concat(String_Concat("__m", String_FromInt(ex.gensymCounter)), String_Concat("_", s.strValue)); let oldN: String = s.strValue; Macro_GensymBlockApply(b, oldN, neu); } } if s.refStmtBlock != null as *Block { Macro_GensymBlock(ex, s.refStmtBlock, env); } if s.refStmtElse != null as *Block { Macro_GensymBlock(ex, s.refStmtElse, env); } if s.child1 != null as *Expr && s.child1.kind == ekBlock { Macro_GensymBlock(ex, s.child1.refBlock, env); } s = s.nextStmt; } } // Flatten ekIdent / ekField(::) chain to "A::B::C" (selfhost path style) func Macro_PathFromExpr(e: *Expr) -> String { if e == null as *Expr { return ""; } if e.kind == ekIdent { return e.strValue; } if e.kind == ekPath { return e.strValue; } if e.kind == ekField { let base: String = Macro_PathFromExpr(e.child1); if String_Eq(base, "") { return e.strValue; } return String_Concat(base, String_Concat("::", e.strValue)); } return ""; } // Convert call-site expr → pattern for `$p:pat` (ident/lit/path/call/field) func Macro_ExprToPattern(aexp: *Expr) -> *Pattern { if aexp == null as *Expr { return null as *Pattern; } if aexp.kind == ekMacroPat { return Ast_ClonePattern(aexp.macroPat); } if aexp.kind == ekIdent { if String_Eq(aexp.strValue, "_") { let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern; p.kind = pkWildcard; p.line = aexp.line; p.column = aexp.column; return p; } let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern; p.kind = pkIdent; p.line = aexp.line; p.column = aexp.column; p.patIdent = aexp.strValue; return p; } if aexp.kind == ekLiteral { let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern; p.kind = pkLiteral; p.line = aexp.line; p.column = aexp.column; p.patLitKind = aexp.tokKind; p.patLitText = aexp.tokText; return p; } if aexp.kind == ekPath || aexp.kind == ekField { let path: String = Macro_PathFromExpr(aexp); if String_Eq(path, "") { return null as *Pattern; } let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern; p.kind = pkEnum; p.line = aexp.line; p.column = aexp.column; p.patEnumPath = path; return p; } if aexp.kind == ekCall { // Opt::Some(v) — callee is ekField chain var path: String = Macro_PathFromExpr(aexp.child1); if String_Eq(path, "") { return null as *Pattern; } let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern; p.kind = pkEnum; p.line = aexp.line; p.column = aexp.column; p.patEnumPath = path; var last: *Pattern = null as *Pattern; var arg: *ExprList = aexp.callArgs; while arg != null as *ExprList { let ap: *Pattern = Macro_ExprToPattern(arg.expr); if ap == null as *Pattern { return null as *Pattern; } if last == null as *Pattern { p.patArgs = ap; last = ap; } else { last.patNext = ap; last = ap; } arg = arg.next; } return p; } return null as *Pattern; } // Coerce arg for kind; returns normalized expr or null on mismatch func Macro_CoerceArg(kindStr: String, aexp: *Expr) -> *Expr { if aexp == null as *Expr { return null as *Expr; } if String_Eq(kindStr, "ident") { if aexp.kind != ekIdent { return null as *Expr; } return aexp; } if String_Eq(kindStr, "literal") { if aexp.kind != ekLiteral { return null as *Expr; } return aexp; } if String_Eq(kindStr, "block") { if aexp.kind != ekBlock { return null as *Expr; } return aexp; } if String_Eq(kindStr, "stmt") { if aexp.kind == ekMacroStmt { return aexp; } if aexp.kind == ekMacroPat { return null as *Expr; } // Wrap expression as expression-statement let st: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt; st.kind = skExpr; st.line = aexp.line; st.column = aexp.column; st.child1 = aexp; let e: *Expr = bux_alloc(sizeof(Expr)) as *Expr; e.kind = ekMacroStmt; e.line = aexp.line; e.column = aexp.column; e.macroStmt = st; return e; } if String_Eq(kindStr, "pat") { let pat: *Pattern = Macro_ExprToPattern(aexp); if pat == null as *Pattern { return null as *Expr; } let e: *Expr = bux_alloc(sizeof(Expr)) as *Expr; e.kind = ekMacroPat; e.line = aexp.line; e.column = aexp.column; e.macroPat = pat; return e; } // expr rejects stmt/pat wrappers if String_Eq(kindStr, "expr") { if aexp.kind == ekMacroStmt || aexp.kind == ekMacroPat { return null as *Expr; } return aexp; } // tt — any single call-site AST fragment (session 76/84/85). // Delimiter-balanced multi-element groups (tuple `(a,b)` / slice `[a,b]`) // flatten when spliced as sole call arg: `$f($args)` → `f(a, b)`. if String_Eq(kindStr, "tt") { if aexp.kind == ekMacroTt { return aexp; } let wrap: *Expr = bux_alloc(sizeof(Expr)) as *Expr; wrap.kind = ekMacroTt; wrap.line = aexp.line; wrap.column = aexp.column; wrap.sourceFile = aexp.sourceFile; wrap.child1 = aexp; // group flag: tuple or non-empty slice lit wrap.boolValue = aexp.kind == ekTuple || (aexp.kind == ekSlice && aexp.callArgCount > 0); return wrap; } // type — session 87+: named / pointer / generic Array from call-site if String_Eq(kindStr, "type") { if aexp.kind == ekMacroType { return aexp; } var te: *TypeExpr = null as *TypeExpr; if aexp.kind == ekIdent || aexp.kind == ekGenericCall { te = bux_alloc(sizeof(TypeExpr)) as *TypeExpr; te.kind = tekNamed; te.line = aexp.line; te.column = aexp.column; if aexp.kind == ekGenericCall && !String_Eq(aexp.genericCallee, "") { te.typeName = aexp.genericCallee; } else { te.typeName = aexp.strValue; } // Generic type args: Array / Map (selfhost: up to 2 string args) var gcount: int = aexp.genericTypeArgCount; if gcount > 0 { te.typeArgName0 = aexp.genericTypeArg0; te.typeArgCount = 1; if gcount > 1 { te.typeArgName1 = aexp.genericTypeArg1; te.typeArgCount = 2; } } } else if aexp.kind == ekUnary && aexp.intValue == tkStar && aexp.child1 != null as *Expr { // *T from unary star let inner: *Expr = Macro_CoerceArg("type", aexp.child1); if inner == null as *Expr || inner.refType == null as *TypeExpr { return null as *Expr; } te = bux_alloc(sizeof(TypeExpr)) as *TypeExpr; te.kind = tekPointer; te.line = aexp.line; te.column = aexp.column; te.pointerPointee = inner.refType; if inner.refType != null as *TypeExpr { te.typeName = String_Concat(inner.refType.typeName, "*"); } } else { return null as *Expr; } let tw: *Expr = bux_alloc(sizeof(Expr)) as *Expr; tw.kind = ekMacroType; tw.line = aexp.line; tw.column = aexp.column; tw.refType = te; return tw; } // default: treat as expr if aexp.kind == ekMacroStmt || aexp.kind == ekMacroPat { return null as *Expr; } return aexp; } // Substitute `$t:type` in TypeExpr (named type starting with $) func Macro_SubstType(te: *TypeExpr, env: *MacroEnv) -> *TypeExpr { if te == null as *TypeExpr { return null as *TypeExpr; } if te.kind == tekNamed && String_StartsWith(te.typeName, "$") { let bound: *Expr = Env_Lookup(env, te.typeName); if bound != null as *Expr && bound.kind == ekMacroType && bound.refType != null as *TypeExpr { return bound.refType; } } // Array<$t> / Map<$k,$v> — substitute type-arg name slots if te.kind == tekNamed && te.typeArgCount > 0 { if String_StartsWith(te.typeArgName0, "$") { let b0: *Expr = Env_Lookup(env, te.typeArgName0); if b0 != null as *Expr && b0.kind == ekMacroType && b0.refType != null as *TypeExpr { // Flatten simple named type arg (int, String, …) if b0.refType.kind == tekNamed && b0.refType.typeArgCount == 0 { te.typeArgName0 = b0.refType.typeName; } else if b0.refType.kind == tekNamed { // Nested generic: store mangled-ish "Array_int" for mono te.typeArgName0 = b0.refType.typeName; if b0.refType.typeArgCount > 0 { te.typeArgName0 = String_Concat(te.typeArgName0, "_"); te.typeArgName0 = String_Concat(te.typeArgName0, b0.refType.typeArgName0); } } else if b0.refType.kind == tekPointer && b0.refType.pointerPointee != null as *TypeExpr { te.typeArgName0 = String_Concat(b0.refType.pointerPointee.typeName, "p"); } } } if te.typeArgCount > 1 && String_StartsWith(te.typeArgName1, "$") { let b1: *Expr = Env_Lookup(env, te.typeArgName1); if b1 != null as *Expr && b1.kind == ekMacroType && b1.refType != null as *TypeExpr { if b1.refType.kind == tekNamed { te.typeArgName1 = b1.refType.typeName; } } } } if te.pointerPointee != null as *TypeExpr { te.pointerPointee = Macro_SubstType(te.pointerPointee, env); if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr { te.typeName = String_Concat(te.pointerPointee.typeName, "*"); } } if te.sliceElement != null as *TypeExpr { te.sliceElement = Macro_SubstType(te.sliceElement, env); } if te.funcRet != null as *TypeExpr { te.funcRet = Macro_SubstType(te.funcRet, env); } return te; } // Substitute `$t` in Expr generic type-arg slots (Array_New<$t>) func Macro_SubstGenericTypeArgs(e: *Expr, env: *MacroEnv) { if e == null as *Expr { return; } if e.genericTypeArgCount > 0 && String_StartsWith(e.genericTypeArg0, "$") { let b0: *Expr = Env_Lookup(env, e.genericTypeArg0); if b0 != null as *Expr && b0.kind == ekMacroType && b0.refType != null as *TypeExpr { if b0.refType.kind == tekNamed { e.genericTypeArg0 = b0.refType.typeName; } } } if e.genericTypeArgCount > 1 && String_StartsWith(e.genericTypeArg1, "$") { let b1: *Expr = Env_Lookup(env, e.genericTypeArg1); if b1 != null as *Expr && b1.kind == ekMacroType && b1.refType != null as *TypeExpr { if b1.refType.kind == tekNamed { e.genericTypeArg1 = b1.refType.typeName; } } } } // Fragment kind check: "ident" | "literal" | "block" | "stmt" | "pat" | "expr" | "tt" func Macro_FragMatches(kindStr: String, aexp: *Expr) -> bool { return Macro_CoerceArg(kindStr, aexp) != null as *Expr; } // kinds encoded as "expr;ident;rep:expr," — return fi-th segment func Macro_KindAt(kinds: String, fi: int) -> String { if kinds == null as String || String_Eq(kinds, "") { return "expr"; } var start: int = 0; var idx: int = 0; let n: int = bux_strlen(kinds) as int; var i: int = 0; while i <= n { if i == n || kinds[i] == 59 as char8 { // ';' if idx == fi { let len: int = i - start; if len <= 0 { return "expr"; } return bux_str_slice(kinds, start as uint, len as uint); } idx = idx + 1; start = i + 1; } i = i + 1; } return "expr"; } // Does body (or nested MacroRep) reference list name as an ident? func Macro_BodyUsesListName(b: *Block, name: String) -> bool { if b == null as *Block || String_Eq(name, "") { return false; } var s: *Stmt = b.firstStmt; while s != null as *Stmt { if Macro_ExprUsesListName(s.child1, name) { return true; } if Macro_ExprUsesListName(s.child2, name) { return true; } if Macro_ExprUsesListName(s.child3, name) { return true; } if s.refStmtBlock != null as *Block { if Macro_BodyUsesListName(s.refStmtBlock, name) { return true; } } if s.refStmtElse != null as *Block { if Macro_BodyUsesListName(s.refStmtElse, name) { return true; } } s = s.nextStmt; } return false; } func Macro_ExprUsesListName(e: *Expr, name: String) -> bool { if e == null as *Expr { return false; } if e.kind == ekIdent && String_Eq(e.strValue, name) { return true; } if Macro_ExprUsesListName(e.child1, name) { return true; } if Macro_ExprUsesListName(e.child2, name) { return true; } if Macro_ExprUsesListName(e.child3, name) { return true; } if e.refBlock != null as *Block { if Macro_BodyUsesListName(e.refBlock, name) { return true; } } var args: *ExprList = e.callArgs; while args != null as *ExprList { if Macro_ExprUsesListName(args.expr, name) { return true; } args = args.next; } return false; } func Macro_AppendBlockStmts(n: *Block, part: *Block) { if part == null as *Block { return; } var ps: *Stmt = part.firstStmt; while ps != null as *Stmt { let nxt: *Stmt = ps.nextStmt; ps.nextStmt = null as *Stmt; if n.firstStmt == null as *Stmt { n.firstStmt = ps; n.lastStmt = ps; } else { n.lastStmt.nextStmt = ps; n.lastStmt = ps; } n.stmtCount = n.stmtCount + 1; ps = nxt; } } // Flatten $(…)* while substituting — zip multi-lists; expand once when no lists left func Subst_Block_Flat(b: *Block, env: *MacroEnv, file: String, line: uint32, col: uint32) -> *Block { if b == null as *Block { return null as *Block; } let n: *Block = bux_alloc(sizeof(Block)) as *Block; n.line = line; n.column = col; n.sourceFile = file; n.stmtCount = 0; n.firstStmt = null as *Stmt; n.lastStmt = null as *Stmt; var s: *Stmt = b.firstStmt; while s != null as *Stmt { if s.kind == skMacroRep && s.refStmtBlock != null as *Block { let use0: bool = Macro_BodyUsesListName(s.refStmtBlock, env.listName0); let use1: bool = Macro_BodyUsesListName(s.refStmtBlock, env.listName1); // Nested same-list after outer bound items as singles: expand once if !use0 && !use1 { let partOnce: *Block = Subst_Block(s.refStmtBlock, env, file, line, col); Macro_AppendBlockStmts(n, partOnce); } else { var nRep: int = 0; if use0 && env.listCount0 > nRep { nRep = env.listCount0; } if use1 && env.listCount1 > nRep { nRep = env.listCount1; } var li: int = 0; while li < nRep { var inner: MacroEnv = Env_New(); Env_CopySingles(&inner, env); // Keep unreferenced lists for nested MacroRep siblings if !use0 && !String_Eq(env.listName0, "") { inner.listName0 = env.listName0; inner.listCount0 = env.listCount0; var ci: int = 0; while ci < env.listCount0 { Env_ListSet(&inner, 0, ci, Env_ListGet(env, 0, ci)); ci = ci + 1; } } if !use1 && !String_Eq(env.listName1, "") { inner.listName1 = env.listName1; inner.listCount1 = env.listCount1; var cj: int = 0; while cj < env.listCount1 { Env_ListSet(&inner, 1, cj, Env_ListGet(env, 1, cj)); cj = cj + 1; } } if use0 && li < env.listCount0 { Env_SetNamed(&inner, env.listName0, Env_ListGet(env, 0, li)); } if use1 && li < env.listCount1 { Env_SetNamed(&inner, env.listName1, Env_ListGet(env, 1, li)); } let part: *Block = Subst_Block(s.refStmtBlock, &inner, file, line, col); Macro_AppendBlockStmts(n, part); li = li + 1; } } } else if s.kind == skExpr && s.child1 != null as *Expr && s.child1.kind == ekIdent { // Splice `$s:stmt` bound to ekMacroStmt as a real statement let bound: *Expr = Env_Lookup(env, s.child1.strValue); if bound != null as *Expr && bound.kind == ekMacroStmt && bound.macroStmt != null as *Stmt { let one: *Stmt = Subst_Stmt(bound.macroStmt, env, file, line, col); if one != null as *Stmt { one.nextStmt = null as *Stmt; if n.firstStmt == null as *Stmt { n.firstStmt = one; n.lastStmt = one; } else { n.lastStmt.nextStmt = one; n.lastStmt = one; } n.stmtCount = n.stmtCount + 1; } } else { let one2: *Stmt = Subst_Stmt(s, env, file, line, col); if one2 != null as *Stmt { one2.nextStmt = null as *Stmt; if n.firstStmt == null as *Stmt { n.firstStmt = one2; n.lastStmt = one2; } else { n.lastStmt.nextStmt = one2; n.lastStmt = one2; } n.stmtCount = n.stmtCount + 1; } } } else { let one: *Stmt = Subst_Stmt(s, env, file, line, col); if one != null as *Stmt { one.nextStmt = null as *Stmt; if n.firstStmt == null as *Stmt { n.firstStmt = one; n.lastStmt = one; } else { n.lastStmt.nextStmt = one; n.lastStmt = one; } n.stmtCount = n.stmtCount + 1; } } s = s.nextStmt; } return n; } // --------------------------------------------------------------------------- // Diagnostics // --------------------------------------------------------------------------- const maxMacroDiags: int = 64; struct MacroDiag { line: uint32; column: uint32; message: String; } struct MacroEntry { name: String; decl: *Decl; } struct MacroExpander { diagCount: int; diags: *MacroDiag; // Flat macro table — max 64 macros macroCount: int; entries: *MacroEntry; gensymCounter: int; } func MacroExpand_New() -> *MacroExpander { let ex: *MacroExpander = bux_alloc(sizeof(MacroExpander)) as *MacroExpander; ex.diagCount = 0; ex.diags = bux_alloc((maxMacroDiags * sizeof(MacroDiag)) as uint) as *MacroDiag; ex.macroCount = 0; ex.entries = bux_alloc((64 * sizeof(MacroEntry)) as uint) as *MacroEntry; ex.gensymCounter = 0; return ex; } func MacroExpand_Err(ex: *MacroExpander, line: uint32, col: uint32, msg: String) { if ex.diagCount < maxMacroDiags { ex.diags[ex.diagCount] = MacroDiag { line: line, column: col, message: msg }; ex.diagCount = ex.diagCount + 1; } } func MacroExpand_Register(ex: *MacroExpander, name: String, d: *Decl) { if ex.macroCount >= 64 { return; } ex.entries[ex.macroCount] = MacroEntry { name: name, decl: d }; ex.macroCount = ex.macroCount + 1; } func MacroExpand_Lookup(ex: *MacroExpander, name: String) -> *Decl { var i: int = 0; while i < ex.macroCount { if String_Eq(ex.entries[i].name, name) { return ex.entries[i].decl; } i = i + 1; } return null as *Decl; } // --------------------------------------------------------------------------- // Call-site graft: force line/col + sourceFile on tree // --------------------------------------------------------------------------- func Graft_Expr(e: *Expr, file: String, line: uint32, col: uint32) { if e == null as *Expr { return; } e.line = line; e.column = col; if file != null as String && !String_Eq(file, "") { e.sourceFile = file; } Graft_Expr(e.child1, file, line, col); Graft_Expr(e.child2, file, line, col); Graft_Expr(e.child3, file, line, col); if e.refBlock != null as *Block { Graft_Block(e.refBlock, file, line, col); } var args: *ExprList = e.callArgs; while args != null as *ExprList { Graft_Expr(args.expr, file, line, col); args = args.next; } var arm: *MatchArm = e.matchArms; while arm != null as *MatchArm { Graft_Expr(arm.body, file, line, col); arm = arm.next; } } func Graft_Stmt(s: *Stmt, file: String, line: uint32, col: uint32) { if s == null as *Stmt { return; } s.line = line; s.column = col; if file != null as String && !String_Eq(file, "") { s.sourceFile = file; } Graft_Expr(s.child1, file, line, col); Graft_Expr(s.child2, file, line, col); Graft_Expr(s.child3, file, line, col); if s.refStmtBlock != null as *Block { Graft_Block(s.refStmtBlock, file, line, col); } if s.refStmtElse != null as *Block { Graft_Block(s.refStmtElse, file, line, col); } Graft_Stmt(s.nextStmt, file, line, col); } func Graft_Block(b: *Block, file: String, line: uint32, col: uint32) { if b == null as *Block { return; } b.line = line; b.column = col; if file != null as String && !String_Eq(file, "") { b.sourceFile = file; } Graft_Stmt(b.firstStmt, file, line, col); } // --------------------------------------------------------------------------- // Substitute $frags in a cloned tree // --------------------------------------------------------------------------- // Build callArgs for a call/macro-call, flattening MacroRep and MacroTt groups. func Macro_SubstCallArgs(oldArgs: *ExprList, env: *MacroEnv, file: String, line: uint32, col: uint32, outCount: *int) -> *ExprList { var first: *ExprList = null as *ExprList; var last: *ExprList = null as *ExprList; var count: int = 0; var args: *ExprList = oldArgs; while args != null as *ExprList { let a: *Expr = args.expr; var didFlat: bool = false; // Expression-level `$( body ),*` if a != null as *Expr && a.kind == ekMacroRep && a.child1 != null as *Expr { let body: *Expr = a.child1; let use0: bool = Macro_ExprUsesListName(body, env.listName0); let use1: bool = Macro_ExprUsesListName(body, env.listName1); if !use0 && !use1 { let node: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList; node.expr = Subst_Expr(body, env, file, line, col); node.next = null as *ExprList; node.argName = ""; if first == null as *ExprList { first = node; last = node; } else { last.next = node; last = node; } count = count + 1; } else { var nRep: int = 0; if use0 && env.listCount0 > nRep { nRep = env.listCount0; } if use1 && env.listCount1 > nRep { nRep = env.listCount1; } var li: int = 0; while li < nRep { var inner: MacroEnv = Env_New(); Env_CopySingles(&inner, env); if use0 && li < env.listCount0 { Env_SetNamed(&inner, env.listName0, Env_ListGet(env, 0, li)); } if use1 && li < env.listCount1 { Env_SetNamed(&inner, env.listName1, Env_ListGet(env, 1, li)); } let node2: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList; node2.expr = Subst_Expr(body, &inner, file, line, col); node2.next = null as *ExprList; node2.argName = ""; if first == null as *ExprList { first = node2; last = node2; } else { last.next = node2; last = node2; } count = count + 1; li = li + 1; } } didFlat = true; } else if a != null as *Expr && a.kind == ekIdent { let bound: *Expr = Env_Lookup(env, a.strValue); // Bare `$args:tt` group → flatten tuple/slice elements as call args if bound != null as *Expr && bound.kind == ekMacroTt && bound.boolValue && bound.child1 != null as *Expr && (bound.child1.kind == ekTuple || bound.child1.kind == ekSlice) { var tel: *ExprList = bound.child1.callArgs; while tel != null as *ExprList { let ce: *Expr = Ast_CloneExpr(tel.expr); Graft_Expr(ce, file, line, col); let node3: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList; node3.expr = ce; node3.next = null as *ExprList; node3.argName = ""; if first == null as *ExprList { first = node3; last = node3; } else { last.next = node3; last = node3; } count = count + 1; tel = tel.next; } didFlat = true; } } if !didFlat { let node4: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList; node4.expr = Subst_Expr(a, env, file, line, col); node4.next = null as *ExprList; node4.argName = ""; if first == null as *ExprList { first = node4; last = node4; } else { last.next = node4; last = node4; } count = count + 1; } args = args.next; } *outCount = count; return first; } func Subst_Expr(e: *Expr, env: *MacroEnv, file: String, line: uint32, col: uint32) -> *Expr { if e == null as *Expr { return null as *Expr; } if e.kind == ekIdent { let bound: *Expr = Env_Lookup(env, e.strValue); if bound != null as *Expr { // Value position: unwrap MacroTt wrapper if bound.kind == ekMacroTt && bound.child1 != null as *Expr { let n: *Expr = Ast_CloneExpr(bound.child1); Graft_Expr(n, file, line, col); return n; } // Type fragments are not values — leave a zero literal if bound.kind == ekMacroType { let z: *Expr = bux_alloc(sizeof(Expr)) as *Expr; z.kind = ekLiteral; z.line = line; z.column = col; z.tokKind = 0; z.tokText = "0"; z.intValue = 0; return z; } let n2: *Expr = Ast_CloneExpr(bound); Graft_Expr(n2, file, line, col); return n2; } } // In-place subst on clone of e let c: *Expr = Ast_CloneExpr(e); if c == null as *Expr { return null as *Expr; } c.child1 = Subst_Expr(c.child1, env, file, line, col); c.child2 = Subst_Expr(c.child2, env, file, line, col); c.child3 = Subst_Expr(c.child3, env, file, line, col); // Array_New<$t> / Foo<$t,$u> Macro_SubstGenericTypeArgs(c, env); // sizeof / cast / is type annotations if c.refType != null as *TypeExpr { c.refType = Macro_SubstType(c.refType, env); } if c.refBlock != null as *Block { c.refBlock = Subst_Block_Flat(c.refBlock, env, file, line, col); } // callArgs: flatten MacroRep + MacroTt groups for calls if c.kind == ekCall || c.kind == ekMacroCall { var nCount: int = 0; c.callArgs = Macro_SubstCallArgs(c.callArgs, env, file, line, col, &nCount); c.callArgCount = nCount; } else { var args2: *ExprList = c.callArgs; while args2 != null as *ExprList { args2.expr = Subst_Expr(args2.expr, env, file, line, col); args2 = args2.next; } } var arm: *MatchArm = c.matchArms; while arm != null as *MatchArm { arm.pattern = Subst_Pattern(arm.pattern, env, file, line, col); arm.body = Subst_Expr(arm.body, env, file, line, col); arm = arm.next; } c.line = line; c.column = col; if file != null as String && !String_Eq(file, "") { c.sourceFile = file; } return c; } func Subst_Pattern(p: *Pattern, env: *MacroEnv, file: String, line: uint32, col: uint32) -> *Pattern { if p == null as *Pattern { return null as *Pattern; } // `$p:pat` as whole pattern (pkIdent name `$p`) if p.kind == pkIdent && !String_Eq(p.patIdent, "") { let bound: *Expr = Env_Lookup(env, p.patIdent); if bound != null as *Expr && bound.kind == ekMacroPat && bound.macroPat != null as *Pattern { let np: *Pattern = Ast_ClonePattern(bound.macroPat); if np != null as *Pattern { np.line = line; np.column = col; } return np; } } // MVP: other patterns kept as cloned (no nested `$p` rewrite) let c: *Pattern = Ast_ClonePattern(p); if c != null as *Pattern { c.line = line; c.column = col; } return c; } func Subst_Stmt(s: *Stmt, env: *MacroEnv, file: String, line: uint32, col: uint32) -> *Stmt { if s == null as *Stmt { return null as *Stmt; } let c: *Stmt = Ast_CloneStmt(s); // Ast_CloneStmt clones nextStmt chain — break to single and rebuild c.nextStmt = null as *Stmt; // Unhygienic binder: `var $name` / `for $i` with $frag:ident → call-site name if (c.kind == skLet || c.kind == skFor) && !String_Eq(c.strValue, "") { let bn: String = Env_BinderFromFrag(env, c.strValue); if !String_Eq(bn, "") { c.strValue = bn; Env_AddUnhy(env, bn); } } c.child1 = Subst_Expr(c.child1, env, file, line, col); c.child2 = Subst_Expr(c.child2, env, file, line, col); c.child3 = Subst_Expr(c.child3, env, file, line, col); // let x: $t = … if c.refStmtType != null as *TypeExpr { c.refStmtType = Macro_SubstType(c.refStmtType, env); } if c.refStmtBlock != null as *Block { c.refStmtBlock = Subst_Block(c.refStmtBlock, env, file, line, col); } if c.refStmtElse != null as *Block { c.refStmtElse = Subst_Block(c.refStmtElse, env, file, line, col); } c.line = line; c.column = col; if file != null as String && !String_Eq(file, "") { c.sourceFile = file; } // Do NOT walk nextStmt — Subst_Block_Flat iterates the chain itself c.nextStmt = null as *Stmt; return c; } func Subst_Block(b: *Block, env: *MacroEnv, file: String, line: uint32, col: uint32) -> *Block { // Always flatten $(…)* so nested expression-blocks expand correctly return Subst_Block_Flat(b, env, file, line, col); } // --------------------------------------------------------------------------- // Expand trees // --------------------------------------------------------------------------- func Expand_Expr(ex: *MacroExpander, e: *Expr, depth: int) -> *Expr { if e == null as *Expr { return null as *Expr; } if depth > 32 { MacroExpand_Err(ex, e.line, e.column, "macro expansion depth exceeded"); return e; } if e.kind == ekMacroCall { return Expand_OneCall(ex, e, depth); } e.child1 = Expand_Expr(ex, e.child1, depth); e.child2 = Expand_Expr(ex, e.child2, depth); e.child3 = Expand_Expr(ex, e.child3, depth); if e.refBlock != null as *Block { e.refBlock = Expand_Block(ex, e.refBlock, depth); } var args: *ExprList = e.callArgs; while args != null as *ExprList { args.expr = Expand_Expr(ex, args.expr, depth); args = args.next; } var arm: *MatchArm = e.matchArms; while arm != null as *MatchArm { arm.body = Expand_Expr(ex, arm.body, depth); arm = arm.next; } return e; } func Expand_OneCall(ex: *MacroExpander, call: *Expr, depth: int) -> *Expr { let name: String = call.strValue; let siteLine: uint32 = call.line; let siteCol: uint32 = call.column; let siteFile: String = call.sourceFile; // Built-in quote!(e) if String_Eq(name, "quote") { if call.callArgCount != 1 || call.callArgs == null as *ExprList { MacroExpand_Err(ex, siteLine, siteCol, "quote! expects exactly 1 argument"); return call; } let arg: *Expr = Expand_Expr(ex, call.callArgs.expr, depth + 1); let n: *Expr = Ast_CloneExpr(arg); Graft_Expr(n, siteFile, siteLine, siteCol); return n; } let mdecl: *Decl = MacroExpand_Lookup(ex, name); if mdecl == null as *Decl { MacroExpand_Err(ex, siteLine, siteCol, String_Concat("unknown macro '", String_Concat(name, "'"))); return call; } // Match rule: fixed, trailing rep, multi-rep groups (;), compound zip (chunk>1). // Group lengths encoded on call.genericCallee as "n0;n1;…" (empty = one flat group). var rule: *Decl = mdecl.childDecl1; var matched: *Decl = null as *Decl; var env: MacroEnv = Env_New(); // Pre-expand all args once var expArgs: *ExprList = null as *ExprList; var expTail: *ExprList = null as *ExprList; var nargs: int = 0; var rawArg: *ExprList = call.callArgs; while rawArg != null as *ExprList { let aexp: *Expr = Expand_Expr(ex, rawArg.expr, depth + 1); let node: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList; node.expr = aexp; node.next = null as *ExprList; node.argName = ""; if expArgs == null as *ExprList { expArgs = node; expTail = node; } else { expTail.next = node; expTail = node; } nargs = nargs + 1; rawArg = rawArg.next; } // Parse group lengths from genericCallee ("2;3") — empty means one group of all args var g0: int = nargs; var g1: int = 0; var nGroups: int = 1; let glens: String = call.genericCallee; if glens != null as String && !String_Eq(glens, "") { var gstart: int = 0; var gidx: int = 0; let glen: int = bux_strlen(glens) as int; var gi: int = 0; while gi <= glen { if gi == glen || glens[gi] == 59 as char8 { let gpart: String = bux_str_slice(glens, gstart as uint, (gi - gstart) as uint); let gv: int = String_ToInt(gpart) as int; if gidx == 0 { g0 = gv; } else if gidx == 1 { g1 = gv; nGroups = 2; } gidx = gidx + 1; gstart = gi + 1; } gi = gi + 1; } if gidx > 0 { nGroups = gidx; } } while rule != null as *Decl { let kinds: String = rule.useNames; var nSeg: int = 0; let klen: int = bux_strlen(kinds) as int; if klen == 0 { nSeg = 0; } else { nSeg = 1; var ci: int = 0; while ci < klen { if kinds[ci] == 59 as char8 { nSeg = nSeg + 1; } ci = ci + 1; } } var nReps: int = 0; var sgi: int = 0; while sgi < nSeg { let ks0: String = Macro_KindAt(kinds, sgi); if String_StartsWith(ks0, "rep:") { nReps = nReps + 1; } sgi = sgi + 1; } let useGroups: bool = nReps > 1 && nGroups > 1; // Session 86 — juxta free-form: single arg F(a,b) → $f:ident + $args:tt var ruleArgs: *ExprList = expArgs; var ruleNargs: int = nargs; if !useGroups && nReps == 0 && nSeg == 2 && nargs == 1 && expArgs != null as *ExprList { let k0: String = Macro_KindAt(kinds, 0); let k1: String = Macro_KindAt(kinds, 1); let only: *Expr = expArgs.expr; if String_Eq(k0, "ident") && String_Eq(k1, "tt") && only != null as *Expr && only.kind == ekCall && only.child1 != null as *Expr && only.child1.kind == ekIdent { let callee: *Expr = only.child1; // Build MacroTt group from call args (tuple of elements) let inner: *Expr = bux_alloc(sizeof(Expr)) as *Expr; inner.kind = ekTuple; inner.line = only.line; inner.column = only.column; inner.callArgs = only.callArgs; inner.callArgCount = only.callArgCount; let group: *Expr = bux_alloc(sizeof(Expr)) as *Expr; group.kind = ekMacroTt; group.line = only.line; group.column = only.column; group.child1 = inner; group.boolValue = true; let n0: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList; n0.expr = callee; n0.next = null as *ExprList; n0.argName = ""; let n1: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList; n1.expr = group; n1.next = null as *ExprList; n1.argName = ""; n0.next = n1; ruleArgs = n0; ruleNargs = 2; } } env = Env_New(); var ok: bool = true; var argList: *ExprList = ruleArgs; var flatLeft: int = ruleNargs; var gIdx: int = 0; var gOff: int = 0; // offset within current group when useGroups var paramIdx: int = 0; var listSlot: int = 0; // next free list slot (0 or 1) var seg: int = 0; while seg < nSeg && ok { var kindStr: String = Macro_KindAt(kinds, seg); if String_StartsWith(kindStr, "rep:") { var rest: String = bux_str_slice(kindStr, 4, bux_strlen(kindStr) - 4); var chunk: int = 1; var atPos: int = -1; var ri: int = 0; let rlen: int = bux_strlen(rest) as int; while ri < rlen { if rest[ri] == 64 as char8 { atPos = ri; break; } ri = ri + 1; } if atPos >= 0 { let numStr: String = bux_str_slice(rest, (atPos + 1) as uint, (rlen - atPos - 1) as uint); chunk = String_ToInt(numStr) as int; if chunk < 1 { chunk = 1; } } // Determine how many args this rep consumes var take: int = 0; if useGroups { if gIdx >= nGroups { take = 0; // empty trailing rep } else { if gIdx == 0 { take = g0; } else { take = g1; } gIdx = gIdx + 1; gOff = 0; } } else { take = flatLeft; } if take % chunk != 0 { ok = false; break; } // Compound zip: de-interleave into parallel lists if chunk == 1 { if listSlot > 1 { ok = false; break; } let nm: String = Rule_FragName(rule, paramIdx); if listSlot == 0 { env.listName0 = nm; env.listCount0 = 0; } else { env.listName1 = nm; env.listCount1 = 0; } var ti: int = 0; while ti < take && argList != null as *ExprList { Env_ListSet(&env, listSlot, Env_ListCountOf(&env, listSlot), argList.expr); if listSlot == 0 { env.listCount0 = env.listCount0 + 1; } else { env.listCount1 = env.listCount1 + 1; } argList = argList.next; flatLeft = flatLeft - 1; ti = ti + 1; } if ti != take { ok = false; break; } listSlot = listSlot + 1; paramIdx = paramIdx + 1; } else if chunk == 2 { // Parallel lists for $a and $b if listSlot > 0 { ok = false; break; } // need both free slots let nm0: String = Rule_FragName(rule, paramIdx); let nm1: String = Rule_FragName(rule, paramIdx + 1); env.listName0 = nm0; env.listName1 = nm1; env.listCount0 = 0; env.listCount1 = 0; var ti2: int = 0; while ti2 < take && argList != null as *ExprList { // even → list0, odd → list1 if ti2 % 2 == 0 { Env_ListSet(&env, 0, env.listCount0, argList.expr); env.listCount0 = env.listCount0 + 1; } else { Env_ListSet(&env, 1, env.listCount1, argList.expr); env.listCount1 = env.listCount1 + 1; } argList = argList.next; flatLeft = flatLeft - 1; ti2 = ti2 + 1; } if ti2 != take { ok = false; break; } listSlot = 2; paramIdx = paramIdx + 2; } else { ok = false; break; } } else { // Fixed fragment if useGroups { // advance group if exhausted var gAvail: int = 0; if gIdx == 0 { gAvail = g0 - gOff; } else if gIdx == 1 { gAvail = g1 - gOff; } else { gAvail = 0; } if gAvail <= 0 { gIdx = gIdx + 1; gOff = 0; if gIdx == 0 { gAvail = g0; } else if gIdx == 1 { gAvail = g1; } else { gAvail = 0; } } if gAvail <= 0 || argList == null as *ExprList { ok = false; break; } gOff = gOff + 1; } else { if argList == null as *ExprList { ok = false; break; } } let aexp: *Expr = argList.expr; let coerced: *Expr = Macro_CoerceArg(kindStr, aexp); if coerced == null as *Expr { ok = false; break; } Env_Set(&env, paramIdx, Rule_FragName(rule, paramIdx), coerced); argList = argList.next; flatLeft = flatLeft - 1; paramIdx = paramIdx + 1; } seg = seg + 1; } if ok { if useGroups { if gIdx < nGroups { ok = false; } // remaining empty groups ok only if trailing empty reps handled } else { if argList != null as *ExprList || flatLeft != 0 { ok = false; } } } if ok { matched = rule; break; } rule = rule.childDecl2; } if matched == null as *Decl { MacroExpand_Err(ex, siteLine, siteCol, String_Concat("macro has no matching rule for '", String_Concat(name, "'"))); return call; } if matched.refBody == null as *Block { MacroExpand_Err(ex, siteLine, siteCol, "macro rule has empty body"); return call; } let body: *Block = Subst_Block_Flat(matched.refBody, &env, siteFile, siteLine, siteCol); // Hygiene: unique let names per expansion (CBE is function-scoped) Macro_GensymBlock(ex, body, &env); let blk: *Expr = bux_alloc(sizeof(Expr)) as *Expr; // zero via MakeExpr-like let made: Expr = Ast_MakeExpr(ekBlock, siteLine, siteCol); blk.kind = made.kind; blk.line = siteLine; blk.column = siteCol; blk.sourceFile = siteFile; blk.strValue = ""; blk.intValue = 0; blk.boolValue = false; blk.tokKind = 0; blk.tokText = ""; blk.child1 = null as *Expr; blk.child2 = null as *Expr; blk.child3 = null as *Expr; blk.refType = null as *TypeExpr; blk.refBlock = body; blk.callArgs = null as *ExprList; blk.callArgCount = 0; blk.matchArms = null as *MatchArm; blk.matchArmCount = 0; blk.genericCallee = ""; blk.genericTypeArgCount = 0; blk.structName = ""; blk.structFieldCount = 0; blk.closureParams = null as *Decl; blk.captureCount = 0; // Nested macros inside expansion return Expand_Expr(ex, blk, depth + 1); } func Expand_Block(ex: *MacroExpander, b: *Block, depth: int) -> *Block { if b == null as *Block { return null as *Block; } var s: *Stmt = b.firstStmt; while s != null as *Stmt { Expand_Stmt(ex, s, depth); s = s.nextStmt; } return b; } func Expand_Stmt(ex: *MacroExpander, s: *Stmt, depth: int) { if s == null as *Stmt { return; } s.child1 = Expand_Expr(ex, s.child1, depth); s.child2 = Expand_Expr(ex, s.child2, depth); s.child3 = Expand_Expr(ex, s.child3, depth); if s.refStmtBlock != null as *Block { Expand_Block(ex, s.refStmtBlock, depth); } if s.refStmtElse != null as *Block { Expand_Block(ex, s.refStmtElse, depth); } if s.refStmtDecl != null as *Decl { Expand_Decl(ex, s.refStmtDecl, depth); } } func Expand_Decl(ex: *MacroExpander, d: *Decl, depth: int) { if d == null as *Decl { return; } if d.kind == dkFunc { if d.refBody != null as *Block { Expand_Block(ex, d.refBody, depth); } } else if d.kind == dkImpl { var m: *Decl = d.childDecl1; while m != null as *Decl { Expand_Decl(ex, m, depth); m = m.childDecl2; } } else if d.kind == dkModule { var it: *Decl = d.childDecl1; while it != null as *Decl { Expand_Decl(ex, it, depth); it = it.childDecl2; } } else if d.kind == dkConst { d.constValue = Expand_Expr(ex, d.constValue, depth); } // do not expand into nested macros' templates (rules stay as templates) } func Collect_Macros(ex: *MacroExpander, d: *Decl) { if d == null as *Decl { return; } if d.kind == dkMacro { // Only top-level macros have non-empty name; rules have empty strValue if !String_Eq(d.strValue, "") { MacroExpand_Register(ex, d.strValue, d); } } else if d.kind == dkModule { var it: *Decl = d.childDecl1; while it != null as *Decl { Collect_Macros(ex, it); it = it.childDecl2; } } } /// Expand all macro! calls in module. Returns number of errors. func MacroExpand_ExpandModule(mod: *Module) -> *MacroExpander { let ex: *MacroExpander = MacroExpand_New(); if mod == null as *Module { return ex; } var d: *Decl = mod.firstItem; while d != null as *Decl { Collect_Macros(ex, d); d = d.childDecl2; } d = mod.firstItem; while d != null as *Decl { Expand_Decl(ex, d, 0); d = d.childDecl2; } return ex; } func MacroExpand_DiagCount(ex: *MacroExpander) -> int { if ex == null as *MacroExpander { return 0; } return ex.diagCount; } func MacroExpand_GetDiag(ex: *MacroExpander, i: int) -> MacroDiag { return ex.diags[i]; } }