Files
bux-lang/bootstrap/macroexpand.nim
T
dimgigov fe3b1e8b6a
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
feat: macros (multi-rep, hygiene), Drop field-move, lean multi-OS CI
Sessions 56–69: declarative macro! with rep/zip/literal/block and
unhygienic var $name binders; partial field-move skip Drop; @[Release]
polish; LSP type hierarchy; CI Nim cache + lean macOS + Windows smoke.
2026-07-20 17:19:46 +03:00

1181 lines
44 KiB
Nim

## Declarative macro! expansion (session 59).
## Expands `name!(args)` using `macro! name { ($x:expr) => { … } }` rules.
## Hygiene: substitute clones args at call-site, graft call-site SourceLocation
## onto expanded template nodes (Ast_QuoteCallSite policy from QUALITY_PLAN).
import std/[tables, sequtils, sets]
import ast, token, source_location
type
MacroDiagnostic* = object
loc*: SourceLocation
message*: string
MacroExpandResult* = object
diagnostics*: seq[MacroDiagnostic]
proc emitErr(res: var MacroExpandResult, loc: SourceLocation, msg: string) =
res.diagnostics.add(MacroDiagnostic(loc: loc, message: msg))
# ---------------------------------------------------------------------------
# Deep clone (bootstrap has no Ast_Clone*)
# ---------------------------------------------------------------------------
proc cloneExpr*(e: Expr): Expr
proc cloneStmt*(s: Stmt): Stmt
proc cloneBlock*(b: Block): Block
proc cloneBlock*(b: Block): Block =
if b == nil: return nil
result = Block(loc: b.loc, stmts: @[])
for s in b.stmts:
result.stmts.add(cloneStmt(s))
proc clonePattern(p: Pattern): Pattern =
if p == nil: return nil
case p.kind
of pkWildcard:
result = Pattern(kind: pkWildcard, loc: p.loc)
of pkLiteral:
result = Pattern(kind: pkLiteral, loc: p.loc, patLit: p.patLit)
of pkIdent:
result = Pattern(kind: pkIdent, loc: p.loc, patIdent: p.patIdent)
of pkRange:
result = Pattern(kind: pkRange, loc: p.loc,
patRangeLo: clonePattern(p.patRangeLo),
patRangeHi: clonePattern(p.patRangeHi),
patRangeInclusive: p.patRangeInclusive)
of pkEnum:
result = Pattern(kind: pkEnum, loc: p.loc, patEnumPath: p.patEnumPath,
patEnumArgs: @[], patEnumNamed: @[])
for a in p.patEnumArgs:
result.patEnumArgs.add(clonePattern(a))
for nf in p.patEnumNamed:
result.patEnumNamed.add((nf.name, clonePattern(nf.pattern)))
of pkStruct:
result = Pattern(kind: pkStruct, loc: p.loc, patStructName: p.patStructName,
patStructFields: @[])
for f in p.patStructFields:
result.patStructFields.add((f.name, clonePattern(f.pattern)))
of pkTuple:
result = Pattern(kind: pkTuple, loc: p.loc, patTupleElements: @[])
for el in p.patTupleElements:
result.patTupleElements.add(clonePattern(el))
of pkGuarded:
result = Pattern(kind: pkGuarded, loc: p.loc,
patGuardedInner: clonePattern(p.patGuardedInner),
patGuardedExpr: cloneExpr(p.patGuardedExpr))
proc cloneExpr*(e: Expr): Expr =
if e == nil: return nil
case e.kind
of ekLiteral:
result = Expr(kind: ekLiteral, loc: e.loc, exprLit: e.exprLit)
of ekIdent:
result = Expr(kind: ekIdent, loc: e.loc, exprIdent: e.exprIdent)
of ekSelf:
result = Expr(kind: ekSelf, loc: e.loc)
of ekPath:
result = Expr(kind: ekPath, loc: e.loc, exprPath: e.exprPath)
of ekSizeOf:
result = Expr(kind: ekSizeOf, loc: e.loc, exprSizeOfType: e.exprSizeOfType)
of ekIntrinsic:
result = Expr(kind: ekIntrinsic, loc: e.loc, exprIntrinsic: e.exprIntrinsic)
of ekUnary:
result = Expr(kind: ekUnary, loc: e.loc, exprUnaryOp: e.exprUnaryOp,
exprUnaryOperand: cloneExpr(e.exprUnaryOperand))
of ekPostfix:
result = Expr(kind: ekPostfix, loc: e.loc, exprPostfixOp: e.exprPostfixOp,
exprPostfixOperand: cloneExpr(e.exprPostfixOperand))
of ekBinary:
result = Expr(kind: ekBinary, loc: e.loc, exprBinaryOp: e.exprBinaryOp,
exprBinaryLeft: cloneExpr(e.exprBinaryLeft),
exprBinaryRight: cloneExpr(e.exprBinaryRight))
of ekAssign:
result = Expr(kind: ekAssign, loc: e.loc, exprAssignOp: e.exprAssignOp,
exprAssignTarget: cloneExpr(e.exprAssignTarget),
exprAssignValue: cloneExpr(e.exprAssignValue))
of ekTernary:
result = Expr(kind: ekTernary, loc: e.loc,
exprTernaryCond: cloneExpr(e.exprTernaryCond),
exprTernaryThen: cloneExpr(e.exprTernaryThen),
exprTernaryElse: cloneExpr(e.exprTernaryElse))
of ekRange:
result = Expr(kind: ekRange, loc: e.loc,
exprRangeLo: cloneExpr(e.exprRangeLo),
exprRangeHi: cloneExpr(e.exprRangeHi),
exprRangeInclusive: e.exprRangeInclusive)
of ekCall:
result = Expr(kind: ekCall, loc: e.loc,
exprCallCallee: cloneExpr(e.exprCallCallee),
exprCallArgs: @[], exprCallArgNames: e.exprCallArgNames,
exprCallInferredTypeArgs: e.exprCallInferredTypeArgs)
for a in e.exprCallArgs:
result.exprCallArgs.add(cloneExpr(a))
of ekGenericCall:
result = Expr(kind: ekGenericCall, loc: e.loc,
exprGenericCallee: e.exprGenericCallee,
exprGenericTypeArgs: e.exprGenericTypeArgs)
of ekIndex:
result = Expr(kind: ekIndex, loc: e.loc,
exprIndexObj: cloneExpr(e.exprIndexObj),
exprIndexIdx: cloneExpr(e.exprIndexIdx),
exprIndexBoundsCheck: e.exprIndexBoundsCheck)
of ekField:
result = Expr(kind: ekField, loc: e.loc,
exprFieldObj: cloneExpr(e.exprFieldObj),
exprFieldName: e.exprFieldName)
of ekStructInit:
result = Expr(kind: ekStructInit, loc: e.loc,
exprStructInitName: e.exprStructInitName,
exprStructInitTypeArgs: e.exprStructInitTypeArgs,
exprStructInitFields: @[])
for f in e.exprStructInitFields:
result.exprStructInitFields.add((f.name, cloneExpr(f.value)))
of ekSlice:
result = Expr(kind: ekSlice, loc: e.loc, exprSliceElements: @[])
for el in e.exprSliceElements:
result.exprSliceElements.add(cloneExpr(el))
of ekSpread:
result = Expr(kind: ekSpread, loc: e.loc,
exprSpreadOperand: cloneExpr(e.exprSpreadOperand))
of ekTuple:
result = Expr(kind: ekTuple, loc: e.loc, exprTupleElements: @[])
for el in e.exprTupleElements:
result.exprTupleElements.add(cloneExpr(el))
of ekCast:
result = Expr(kind: ekCast, loc: e.loc,
exprCastOperand: cloneExpr(e.exprCastOperand),
exprCastType: e.exprCastType)
of ekIs:
result = Expr(kind: ekIs, loc: e.loc,
exprIsOperand: cloneExpr(e.exprIsOperand),
exprIsType: e.exprIsType)
of ekTry:
result = Expr(kind: ekTry, loc: e.loc,
exprTryOperand: cloneExpr(e.exprTryOperand),
exprTryType: e.exprTryType)
of ekUnwrap:
result = Expr(kind: ekUnwrap, loc: e.loc,
exprUnwrapOperand: cloneExpr(e.exprUnwrapOperand))
of ekSpawn:
result = Expr(kind: ekSpawn, loc: e.loc,
exprSpawnCallee: cloneExpr(e.exprSpawnCallee),
exprSpawnArgs: @[], exprSpawnAsync: e.exprSpawnAsync)
for a in e.exprSpawnArgs:
result.exprSpawnArgs.add(cloneExpr(a))
of ekAwait:
result = Expr(kind: ekAwait, loc: e.loc,
exprAwaitOperand: cloneExpr(e.exprAwaitOperand))
of ekBorrow:
result = Expr(kind: ekBorrow, loc: e.loc,
exprBorrowOperand: cloneExpr(e.exprBorrowOperand),
exprBorrowMutable: e.exprBorrowMutable)
of ekBlock:
result = Expr(kind: ekBlock, loc: e.loc, exprBlock: cloneBlock(e.exprBlock))
of ekMatch:
result = Expr(kind: ekMatch, loc: e.loc,
exprMatchSubject: cloneExpr(e.exprMatchSubject),
exprMatchArms: @[])
for arm in e.exprMatchArms:
result.exprMatchArms.add(MatchArm(loc: arm.loc,
pattern: clonePattern(arm.pattern), body: cloneExpr(arm.body)))
of ekStringInterp:
result = Expr(kind: ekStringInterp, loc: e.loc,
exprInterpTexts: e.exprInterpTexts, exprInterpExprs: @[])
for ie in e.exprInterpExprs:
result.exprInterpExprs.add(cloneExpr(ie))
of ekClosure:
result = Expr(kind: ekClosure, loc: e.loc,
exprClosureParams: e.exprClosureParams,
exprClosureBody: cloneBlock(e.exprClosureBody),
exprClosureReturnType: e.exprClosureReturnType,
captureCount: 0, captureNames: @[], captureTypeKinds: @[])
of ekMacroCall:
result = Expr(kind: ekMacroCall, loc: e.loc,
exprMacroName: e.exprMacroName, exprMacroArgs: @[])
for a in e.exprMacroArgs:
result.exprMacroArgs.add(cloneExpr(a))
proc cloneStmt*(s: Stmt): Stmt =
if s == nil: return nil
case s.kind
of skExpr:
result = Stmt(kind: skExpr, loc: s.loc, stmtExpr: cloneExpr(s.stmtExpr))
of skLet:
result = Stmt(kind: skLet, loc: s.loc, stmtLetMut: s.stmtLetMut,
stmtLetName: s.stmtLetName, stmtLetPattern: clonePattern(s.stmtLetPattern),
stmtLetType: s.stmtLetType, stmtLetInit: cloneExpr(s.stmtLetInit))
of skIf:
result = Stmt(kind: skIf, loc: s.loc,
stmtIfCond: cloneExpr(s.stmtIfCond),
stmtIfThen: cloneBlock(s.stmtIfThen),
stmtIfElseIfs: @[],
stmtIfElse: cloneBlock(s.stmtIfElse))
for ei in s.stmtIfElseIfs:
result.stmtIfElseIfs.add(ElseIf(loc: ei.loc, cond: cloneExpr(ei.cond),
blk: cloneBlock(ei.blk)))
of skWhile:
result = Stmt(kind: skWhile, loc: s.loc, stmtWhileLabel: s.stmtWhileLabel,
stmtWhileCond: cloneExpr(s.stmtWhileCond),
stmtWhileBody: cloneBlock(s.stmtWhileBody))
of skDoWhile:
result = Stmt(kind: skDoWhile, loc: s.loc, stmtDoWhileLabel: s.stmtDoWhileLabel,
stmtDoWhileBody: cloneBlock(s.stmtDoWhileBody),
stmtDoWhileCond: cloneExpr(s.stmtDoWhileCond))
of skLoop:
result = Stmt(kind: skLoop, loc: s.loc, stmtLoopLabel: s.stmtLoopLabel,
stmtLoopBody: cloneBlock(s.stmtLoopBody))
of skFor:
result = Stmt(kind: skFor, loc: s.loc, stmtForLabel: s.stmtForLabel,
stmtForVar: s.stmtForVar, stmtForIter: cloneExpr(s.stmtForIter),
stmtForBody: cloneBlock(s.stmtForBody))
of skMatch:
result = Stmt(kind: skMatch, loc: s.loc,
stmtMatchSubject: cloneExpr(s.stmtMatchSubject), stmtMatchArms: @[])
for arm in s.stmtMatchArms:
result.stmtMatchArms.add(MatchArm(loc: arm.loc,
pattern: clonePattern(arm.pattern), body: cloneExpr(arm.body)))
of skReturn:
result = Stmt(kind: skReturn, loc: s.loc,
stmtReturnValue: cloneExpr(s.stmtReturnValue))
of skBreak:
result = Stmt(kind: skBreak, loc: s.loc, stmtBreakLabel: s.stmtBreakLabel)
of skContinue:
result = Stmt(kind: skContinue, loc: s.loc, stmtContinueLabel: s.stmtContinueLabel)
of skStaticAssert:
result = Stmt(kind: skStaticAssert, loc: s.loc,
stmtStaticAssertCond: cloneExpr(s.stmtStaticAssertCond),
stmtStaticAssertMsg: cloneExpr(s.stmtStaticAssertMsg))
of skComptime:
result = Stmt(kind: skComptime, loc: s.loc,
stmtComptimeBlock: cloneBlock(s.stmtComptimeBlock))
of skEmit:
result = Stmt(kind: skEmit, loc: s.loc, stmtEmitExpr: cloneExpr(s.stmtEmitExpr),
stmtEmitEvaluated: s.stmtEmitEvaluated)
of skDefer:
result = Stmt(kind: skDefer, loc: s.loc, stmtDeferBody: cloneExpr(s.stmtDeferBody))
of skSwitch:
result = Stmt(kind: skSwitch, loc: s.loc,
stmtSwitchExpr: cloneExpr(s.stmtSwitchExpr),
stmtSwitchCases: @[],
stmtSwitchDefault: cloneBlock(s.stmtSwitchDefault))
for c in s.stmtSwitchCases:
result.stmtSwitchCases.add(SwitchCase(loc: c.loc,
caseValue: cloneExpr(c.caseValue), caseBody: cloneBlock(c.caseBody)))
of skDecl:
# Nested decl — share pointer (macros don't template decls)
result = Stmt(kind: skDecl, loc: s.loc, stmtDecl: s.stmtDecl)
of skMacroRep:
result = Stmt(kind: skMacroRep, loc: s.loc,
stmtMacroRepBody: cloneBlock(s.stmtMacroRepBody))
# ---------------------------------------------------------------------------
# Call-site graft (overwrite locations)
# ---------------------------------------------------------------------------
proc graftExprLoc(e: Expr, loc: SourceLocation)
proc graftStmtLoc(s: Stmt, loc: SourceLocation)
proc graftBlockLoc(b: Block, loc: SourceLocation)
proc graftExprLoc(e: Expr, loc: SourceLocation) =
if e == nil: return
e.loc = loc
case e.kind
of ekUnary: graftExprLoc(e.exprUnaryOperand, loc)
of ekPostfix: graftExprLoc(e.exprPostfixOperand, loc)
of ekBinary:
graftExprLoc(e.exprBinaryLeft, loc)
graftExprLoc(e.exprBinaryRight, loc)
of ekAssign:
graftExprLoc(e.exprAssignTarget, loc)
graftExprLoc(e.exprAssignValue, loc)
of ekTernary:
graftExprLoc(e.exprTernaryCond, loc)
graftExprLoc(e.exprTernaryThen, loc)
graftExprLoc(e.exprTernaryElse, loc)
of ekRange:
graftExprLoc(e.exprRangeLo, loc)
graftExprLoc(e.exprRangeHi, loc)
of ekCall:
graftExprLoc(e.exprCallCallee, loc)
for a in e.exprCallArgs: graftExprLoc(a, loc)
of ekIndex:
graftExprLoc(e.exprIndexObj, loc)
graftExprLoc(e.exprIndexIdx, loc)
of ekField: graftExprLoc(e.exprFieldObj, loc)
of ekStructInit:
for f in e.exprStructInitFields: graftExprLoc(f.value, loc)
of ekSlice:
for el in e.exprSliceElements: graftExprLoc(el, loc)
of ekSpread: graftExprLoc(e.exprSpreadOperand, loc)
of ekTuple:
for el in e.exprTupleElements: graftExprLoc(el, loc)
of ekCast: graftExprLoc(e.exprCastOperand, loc)
of ekIs: graftExprLoc(e.exprIsOperand, loc)
of ekTry: graftExprLoc(e.exprTryOperand, loc)
of ekUnwrap: graftExprLoc(e.exprUnwrapOperand, loc)
of ekSpawn:
graftExprLoc(e.exprSpawnCallee, loc)
for a in e.exprSpawnArgs: graftExprLoc(a, loc)
of ekAwait: graftExprLoc(e.exprAwaitOperand, loc)
of ekBorrow: graftExprLoc(e.exprBorrowOperand, loc)
of ekBlock: graftBlockLoc(e.exprBlock, loc)
of ekMatch:
graftExprLoc(e.exprMatchSubject, loc)
for arm in e.exprMatchArms: graftExprLoc(arm.body, loc)
of ekStringInterp:
for ie in e.exprInterpExprs: graftExprLoc(ie, loc)
of ekClosure: graftBlockLoc(e.exprClosureBody, loc)
of ekMacroCall:
for a in e.exprMacroArgs: graftExprLoc(a, loc)
else: discard
proc graftStmtLoc(s: Stmt, loc: SourceLocation) =
if s == nil: return
s.loc = loc
case s.kind
of skExpr: graftExprLoc(s.stmtExpr, loc)
of skLet: graftExprLoc(s.stmtLetInit, loc)
of skIf:
graftExprLoc(s.stmtIfCond, loc)
graftBlockLoc(s.stmtIfThen, loc)
for ei in s.stmtIfElseIfs:
graftExprLoc(ei.cond, loc)
graftBlockLoc(ei.blk, loc)
graftBlockLoc(s.stmtIfElse, loc)
of skWhile:
graftExprLoc(s.stmtWhileCond, loc)
graftBlockLoc(s.stmtWhileBody, loc)
of skDoWhile:
graftBlockLoc(s.stmtDoWhileBody, loc)
graftExprLoc(s.stmtDoWhileCond, loc)
of skLoop: graftBlockLoc(s.stmtLoopBody, loc)
of skFor:
graftExprLoc(s.stmtForIter, loc)
graftBlockLoc(s.stmtForBody, loc)
of skMatch:
graftExprLoc(s.stmtMatchSubject, loc)
for arm in s.stmtMatchArms: graftExprLoc(arm.body, loc)
of skReturn: graftExprLoc(s.stmtReturnValue, loc)
of skStaticAssert:
graftExprLoc(s.stmtStaticAssertCond, loc)
graftExprLoc(s.stmtStaticAssertMsg, loc)
of skComptime: graftBlockLoc(s.stmtComptimeBlock, loc)
of skEmit: graftExprLoc(s.stmtEmitExpr, loc)
of skDefer: graftExprLoc(s.stmtDeferBody, loc)
of skSwitch:
graftExprLoc(s.stmtSwitchExpr, loc)
for c in s.stmtSwitchCases:
graftExprLoc(c.caseValue, loc)
graftBlockLoc(c.caseBody, loc)
graftBlockLoc(s.stmtSwitchDefault, loc)
of skMacroRep:
graftBlockLoc(s.stmtMacroRepBody, loc)
else: discard
proc graftBlockLoc(b: Block, loc: SourceLocation) =
if b == nil: return
b.loc = loc
for s in b.stmts:
graftStmtLoc(s, loc)
# ---------------------------------------------------------------------------
# Substitution of $frags (singles + list bindings for $(…)*)
# ---------------------------------------------------------------------------
type
MacroEnv = object
singles: Table[string, Expr]
lists: Table[string, seq[Expr]]
var macroGensymCounter = 0
# Call-site binders introduced via `var $name` / `for $i` (skip gensym)
var expandUnhygienic: HashSet[string]
proc binderIdentFromFrag(env: MacroEnv, name: string): string =
## If `name` is a $frag bound to a bare ident, return that ident (unhygienic binder).
if name.len == 0 or not env.singles.hasKey(name): return ""
let e = env.singles[name]
if e != nil and e.kind == ekIdent and e.exprIdent.len > 0:
return e.exprIdent
""
proc gensymLocals(b: Block, callLoc: SourceLocation): Block
proc renameIdents(e: Expr, map: Table[string, string]): Expr
proc renameIdentsStmt(s: Stmt, map: Table[string, string]): Stmt
proc renameIdentsBlock(b: Block, map: Table[string, string]): Block
proc renameIdents(e: Expr, map: Table[string, string]): Expr =
if e == nil: return nil
let c = cloneExpr(e)
if c.kind == ekIdent and map.hasKey(c.exprIdent):
c.exprIdent = map[c.exprIdent]
case c.kind
of ekUnary: c.exprUnaryOperand = renameIdents(c.exprUnaryOperand, map)
of ekBinary:
c.exprBinaryLeft = renameIdents(c.exprBinaryLeft, map)
c.exprBinaryRight = renameIdents(c.exprBinaryRight, map)
of ekAssign:
c.exprAssignTarget = renameIdents(c.exprAssignTarget, map)
c.exprAssignValue = renameIdents(c.exprAssignValue, map)
of ekCall:
c.exprCallCallee = renameIdents(c.exprCallCallee, map)
var args: seq[Expr] = @[]
for a in c.exprCallArgs: args.add(renameIdents(a, map))
c.exprCallArgs = args
of ekBlock: c.exprBlock = renameIdentsBlock(c.exprBlock, map)
of ekTernary:
c.exprTernaryCond = renameIdents(c.exprTernaryCond, map)
c.exprTernaryThen = renameIdents(c.exprTernaryThen, map)
c.exprTernaryElse = renameIdents(c.exprTernaryElse, map)
else: discard
result = c
proc renameIdentsStmt(s: Stmt, map: Table[string, string]): Stmt =
if s == nil: return nil
let c = cloneStmt(s)
case c.kind
of skLet:
if map.hasKey(c.stmtLetName):
c.stmtLetName = map[c.stmtLetName]
c.stmtLetInit = renameIdents(c.stmtLetInit, map)
of skExpr: c.stmtExpr = renameIdents(c.stmtExpr, map)
of skIf:
c.stmtIfCond = renameIdents(c.stmtIfCond, map)
c.stmtIfThen = renameIdentsBlock(c.stmtIfThen, map)
c.stmtIfElse = renameIdentsBlock(c.stmtIfElse, map)
of skWhile:
c.stmtWhileCond = renameIdents(c.stmtWhileCond, map)
c.stmtWhileBody = renameIdentsBlock(c.stmtWhileBody, map)
of skFor:
if map.hasKey(c.stmtForVar):
c.stmtForVar = map[c.stmtForVar]
c.stmtForIter = renameIdents(c.stmtForIter, map)
c.stmtForBody = renameIdentsBlock(c.stmtForBody, map)
of skReturn: c.stmtReturnValue = renameIdents(c.stmtReturnValue, map)
of skMacroRep:
c.stmtMacroRepBody = renameIdentsBlock(c.stmtMacroRepBody, map)
else: discard
result = c
proc renameIdentsBlock(b: Block, map: Table[string, string]): Block =
if b == nil: return nil
result = Block(loc: b.loc, stmts: @[])
for s in b.stmts:
result.stmts.add(renameIdentsStmt(s, map))
proc collectLetNames(blk: Block, map: var Table[string, string])
proc collectLetNamesExpr(e: Expr, map: var Table[string, string])
proc collectLetNamesExpr(e: Expr, map: var Table[string, string]) =
if e == nil: return
case e.kind
of ekBlock:
collectLetNames(e.exprBlock, map)
of ekUnary:
collectLetNamesExpr(e.exprUnaryOperand, map)
of ekBinary:
collectLetNamesExpr(e.exprBinaryLeft, map)
collectLetNamesExpr(e.exprBinaryRight, map)
of ekCall:
collectLetNamesExpr(e.exprCallCallee, map)
for a in e.exprCallArgs: collectLetNamesExpr(a, map)
of ekAssign:
collectLetNamesExpr(e.exprAssignTarget, map)
collectLetNamesExpr(e.exprAssignValue, map)
else:
discard
proc collectLetNames(blk: Block, map: var Table[string, string]) =
if blk == nil: return
for s in blk.stmts:
if s == nil: continue
if s.kind == skLet and s.stmtLetName.len > 0 and not map.hasKey(s.stmtLetName):
# Unhygienic: call-site binder from `var $name` — keep the name
if s.stmtLetName notin expandUnhygienic:
inc macroGensymCounter
map[s.stmtLetName] = "__m" & $macroGensymCounter & "_" & s.stmtLetName
if s.kind == skFor and s.stmtForVar.len > 0 and not map.hasKey(s.stmtForVar):
if s.stmtForVar notin expandUnhygienic:
inc macroGensymCounter
map[s.stmtForVar] = "__m" & $macroGensymCounter & "_" & s.stmtForVar
if s.kind == skLet:
collectLetNamesExpr(s.stmtLetInit, map)
if s.kind == skExpr:
collectLetNamesExpr(s.stmtExpr, map)
if s.kind == skMacroRep:
collectLetNames(s.stmtMacroRepBody, map)
if s.kind == skIf:
collectLetNames(s.stmtIfThen, map)
collectLetNames(s.stmtIfElse, map)
if s.kind == skWhile:
collectLetNames(s.stmtWhileBody, map)
if s.kind == skFor:
collectLetNamesExpr(s.stmtForIter, map)
collectLetNames(s.stmtForBody, map)
proc gensymLocals(b: Block, callLoc: SourceLocation): Block =
## Rename template let/var locals so multiple expansions don't collide in CBE.
if b == nil: return nil
var map = initTable[string, string]()
collectLetNames(b, map)
if map.len == 0:
return cloneBlock(b)
result = renameIdentsBlock(b, map)
if result != nil:
result.loc = callLoc
proc substExpr(e: Expr, env: MacroEnv, callLoc: SourceLocation): Expr
proc substStmt(s: Stmt, env: MacroEnv, callLoc: SourceLocation): Stmt
proc substBlock(b: Block, env: MacroEnv, callLoc: SourceLocation): Block
proc substStmtsFlat(stmts: seq[Stmt], env: MacroEnv, callLoc: SourceLocation): seq[Stmt]
proc substBlock(b: Block, env: MacroEnv, callLoc: SourceLocation): Block =
if b == nil: return nil
result = Block(loc: callLoc, stmts: substStmtsFlat(b.stmts, env, callLoc))
proc collectListNames(e: Expr, env: MacroEnv, into: var seq[string]) =
if e == nil: return
if e.kind == ekIdent and env.lists.hasKey(e.exprIdent):
if e.exprIdent notin into:
into.add(e.exprIdent)
case e.kind
of ekUnary: collectListNames(e.exprUnaryOperand, env, into)
of ekBinary:
collectListNames(e.exprBinaryLeft, env, into)
collectListNames(e.exprBinaryRight, env, into)
of ekCall:
collectListNames(e.exprCallCallee, env, into)
for a in e.exprCallArgs: collectListNames(a, env, into)
of ekAssign:
collectListNames(e.exprAssignTarget, env, into)
collectListNames(e.exprAssignValue, env, into)
of ekBlock:
if e.exprBlock != nil:
for st in e.exprBlock.stmts:
if st == nil: continue
if st.kind == skExpr: collectListNames(st.stmtExpr, env, into)
elif st.kind == skLet: collectListNames(st.stmtLetInit, env, into)
else: discard
proc collectListNamesStmt(st: Stmt, env: MacroEnv, into: var seq[string]) =
if st == nil: return
case st.kind
of skExpr: collectListNames(st.stmtExpr, env, into)
of skLet: collectListNames(st.stmtLetInit, env, into)
of skIf: collectListNames(st.stmtIfCond, env, into)
of skReturn: collectListNames(st.stmtReturnValue, env, into)
of skMacroRep:
if st.stmtMacroRepBody != nil:
for inner in st.stmtMacroRepBody.stmts:
collectListNamesStmt(inner, env, into)
else: discard
proc substStmtsFlat(stmts: seq[Stmt], env: MacroEnv, callLoc: SourceLocation): seq[Stmt] =
## Flatten skMacroRep into repeated statements (zip lists / once for singles).
result = @[]
for s in stmts:
if s == nil: continue
if s.kind == skMacroRep:
var listNames: seq[string] = @[]
if s.stmtMacroRepBody != nil:
for st in s.stmtMacroRepBody.stmts:
collectListNamesStmt(st, env, listNames)
# Nested same-list: if no list names but singles used, expand once
if listNames.len == 0:
let body = substBlock(s.stmtMacroRepBody, env, callLoc)
if body != nil:
for st in body.stmts:
result.add(st)
continue
# Zip all referenced lists by index
var n = 0
for ln in listNames:
if env.lists.hasKey(ln):
n = max(n, env.lists[ln].len)
if n == 0:
continue
for i in 0 ..< n:
var singles = initTable[string, Expr]()
for k, v in env.singles.pairs: singles[k] = v
var lists = initTable[string, seq[Expr]]()
for k, v in env.lists.pairs:
if k notin listNames:
lists[k] = v
for ln in listNames:
if env.lists.hasKey(ln) and i < env.lists[ln].len:
singles[ln] = env.lists[ln][i]
let subEnv = MacroEnv(singles: singles, lists: lists)
let body = substBlock(s.stmtMacroRepBody, subEnv, callLoc)
if body != nil:
for st in body.stmts:
result.add(st)
else:
result.add(substStmt(s, env, callLoc))
proc substStmt(s: Stmt, env: MacroEnv, callLoc: SourceLocation): Stmt =
if s == nil: return nil
if s.kind == skMacroRep:
# Should be flattened by substStmtsFlat; expand empty as no-op expr
return Stmt(kind: skExpr, loc: callLoc,
stmtExpr: newLiteralExpr(Token(kind: tkIntLiteral, text: "0", loc: callLoc)))
let c = cloneStmt(s)
case c.kind
of skExpr:
c.stmtExpr = substExpr(c.stmtExpr, env, callLoc)
of skLet:
# Unhygienic binder: `var $name: T = …` with $name:ident → call-site name
let letBn = binderIdentFromFrag(env, c.stmtLetName)
if letBn.len > 0:
c.stmtLetName = letBn
expandUnhygienic.incl(letBn)
c.stmtLetInit = substExpr(c.stmtLetInit, env, callLoc)
of skIf:
c.stmtIfCond = substExpr(c.stmtIfCond, env, callLoc)
c.stmtIfThen = substBlock(c.stmtIfThen, env, callLoc)
var eifs: seq[ElseIf] = @[]
for ei in c.stmtIfElseIfs:
eifs.add(ElseIf(loc: callLoc, cond: substExpr(ei.cond, env, callLoc),
blk: substBlock(ei.blk, env, callLoc)))
c.stmtIfElseIfs = eifs
c.stmtIfElse = substBlock(c.stmtIfElse, env, callLoc)
of skWhile:
c.stmtWhileCond = substExpr(c.stmtWhileCond, env, callLoc)
c.stmtWhileBody = substBlock(c.stmtWhileBody, env, callLoc)
of skDoWhile:
c.stmtDoWhileBody = substBlock(c.stmtDoWhileBody, env, callLoc)
c.stmtDoWhileCond = substExpr(c.stmtDoWhileCond, env, callLoc)
of skLoop:
c.stmtLoopBody = substBlock(c.stmtLoopBody, env, callLoc)
of skFor:
let forBn = binderIdentFromFrag(env, c.stmtForVar)
if forBn.len > 0:
c.stmtForVar = forBn
expandUnhygienic.incl(forBn)
c.stmtForIter = substExpr(c.stmtForIter, env, callLoc)
c.stmtForBody = substBlock(c.stmtForBody, env, callLoc)
of skMatch:
c.stmtMatchSubject = substExpr(c.stmtMatchSubject, env, callLoc)
var arms: seq[MatchArm] = @[]
for arm in c.stmtMatchArms:
arms.add(MatchArm(loc: callLoc, pattern: arm.pattern,
body: substExpr(arm.body, env, callLoc)))
c.stmtMatchArms = arms
of skReturn:
c.stmtReturnValue = substExpr(c.stmtReturnValue, env, callLoc)
of skStaticAssert:
c.stmtStaticAssertCond = substExpr(c.stmtStaticAssertCond, env, callLoc)
c.stmtStaticAssertMsg = substExpr(c.stmtStaticAssertMsg, env, callLoc)
of skComptime:
c.stmtComptimeBlock = substBlock(c.stmtComptimeBlock, env, callLoc)
of skEmit:
c.stmtEmitExpr = substExpr(c.stmtEmitExpr, env, callLoc)
of skDefer:
c.stmtDeferBody = substExpr(c.stmtDeferBody, env, callLoc)
of skSwitch:
c.stmtSwitchExpr = substExpr(c.stmtSwitchExpr, env, callLoc)
var cases: seq[SwitchCase] = @[]
for sc in c.stmtSwitchCases:
cases.add(SwitchCase(loc: callLoc,
caseValue: substExpr(sc.caseValue, env, callLoc),
caseBody: substBlock(sc.caseBody, env, callLoc)))
c.stmtSwitchCases = cases
c.stmtSwitchDefault = substBlock(c.stmtSwitchDefault, env, callLoc)
of skMacroRep:
discard
else:
discard
c.loc = callLoc
result = c
proc substExpr(e: Expr, env: MacroEnv, callLoc: SourceLocation): Expr =
if e == nil: return nil
# Fragment splice: $x → clone of bound argument (already call-site loc)
if e.kind == ekIdent and env.singles.hasKey(e.exprIdent):
result = cloneExpr(env.singles[e.exprIdent])
graftExprLoc(result, callLoc)
return
# Bare use of list frag outside $(…)* → first element if any, else 0
if e.kind == ekIdent and env.lists.hasKey(e.exprIdent):
let items = env.lists[e.exprIdent]
if items.len > 0:
result = cloneExpr(items[0])
graftExprLoc(result, callLoc)
return
return newLiteralExpr(Token(kind: tkIntLiteral, text: "0", loc: callLoc))
let c = cloneExpr(e)
case c.kind
of ekUnary:
c.exprUnaryOperand = substExpr(c.exprUnaryOperand, env, callLoc)
of ekPostfix:
c.exprPostfixOperand = substExpr(c.exprPostfixOperand, env, callLoc)
of ekBinary:
c.exprBinaryLeft = substExpr(c.exprBinaryLeft, env, callLoc)
c.exprBinaryRight = substExpr(c.exprBinaryRight, env, callLoc)
of ekAssign:
c.exprAssignTarget = substExpr(c.exprAssignTarget, env, callLoc)
c.exprAssignValue = substExpr(c.exprAssignValue, env, callLoc)
of ekTernary:
c.exprTernaryCond = substExpr(c.exprTernaryCond, env, callLoc)
c.exprTernaryThen = substExpr(c.exprTernaryThen, env, callLoc)
c.exprTernaryElse = substExpr(c.exprTernaryElse, env, callLoc)
of ekRange:
c.exprRangeLo = substExpr(c.exprRangeLo, env, callLoc)
c.exprRangeHi = substExpr(c.exprRangeHi, env, callLoc)
of ekCall:
c.exprCallCallee = substExpr(c.exprCallCallee, env, callLoc)
var args: seq[Expr] = @[]
for a in c.exprCallArgs:
args.add(substExpr(a, env, callLoc))
c.exprCallArgs = args
of ekIndex:
c.exprIndexObj = substExpr(c.exprIndexObj, env, callLoc)
c.exprIndexIdx = substExpr(c.exprIndexIdx, env, callLoc)
of ekField:
c.exprFieldObj = substExpr(c.exprFieldObj, env, callLoc)
of ekStructInit:
var fields: seq[tuple[name: string, value: Expr]] = @[]
for f in c.exprStructInitFields:
fields.add((f.name, substExpr(f.value, env, callLoc)))
c.exprStructInitFields = fields
of ekSlice:
var els: seq[Expr] = @[]
for el in c.exprSliceElements:
els.add(substExpr(el, env, callLoc))
c.exprSliceElements = els
of ekSpread:
c.exprSpreadOperand = substExpr(c.exprSpreadOperand, env, callLoc)
of ekTuple:
var els: seq[Expr] = @[]
for el in c.exprTupleElements:
els.add(substExpr(el, env, callLoc))
c.exprTupleElements = els
of ekCast:
c.exprCastOperand = substExpr(c.exprCastOperand, env, callLoc)
of ekIs:
c.exprIsOperand = substExpr(c.exprIsOperand, env, callLoc)
of ekTry:
c.exprTryOperand = substExpr(c.exprTryOperand, env, callLoc)
of ekUnwrap:
c.exprUnwrapOperand = substExpr(c.exprUnwrapOperand, env, callLoc)
of ekSpawn:
c.exprSpawnCallee = substExpr(c.exprSpawnCallee, env, callLoc)
var args: seq[Expr] = @[]
for a in c.exprSpawnArgs:
args.add(substExpr(a, env, callLoc))
c.exprSpawnArgs = args
of ekAwait:
c.exprAwaitOperand = substExpr(c.exprAwaitOperand, env, callLoc)
of ekBorrow:
c.exprBorrowOperand = substExpr(c.exprBorrowOperand, env, callLoc)
of ekBlock:
c.exprBlock = substBlock(c.exprBlock, env, callLoc)
of ekMatch:
c.exprMatchSubject = substExpr(c.exprMatchSubject, env, callLoc)
var arms: seq[MatchArm] = @[]
for arm in c.exprMatchArms:
arms.add(MatchArm(loc: callLoc, pattern: arm.pattern,
body: substExpr(arm.body, env, callLoc)))
c.exprMatchArms = arms
of ekStringInterp:
var ies: seq[Expr] = @[]
for ie in c.exprInterpExprs:
ies.add(substExpr(ie, env, callLoc))
c.exprInterpExprs = ies
of ekClosure:
c.exprClosureBody = substBlock(c.exprClosureBody, env, callLoc)
of ekMacroCall:
# Nested macro call — expand outer pass will re-walk; still subst args
var args: seq[Expr] = @[]
for a in c.exprMacroArgs:
args.add(substExpr(a, env, callLoc))
c.exprMacroArgs = args
else:
discard
c.loc = callLoc
result = c
# ---------------------------------------------------------------------------
# Expand one call
# ---------------------------------------------------------------------------
# Mutual recursion
proc expandExpr(e: Expr, macros: Table[string, Decl], res: var MacroExpandResult,
depth: int): Expr
proc expandBlock(b: Block, macros: Table[string, Decl], res: var MacroExpandResult,
depth: int): Block
proc expandStmt(s: Stmt, macros: Table[string, Decl], res: var MacroExpandResult,
depth: int): Stmt
proc expandDecl(d: Decl, macros: Table[string, Decl], res: var MacroExpandResult,
depth: int)
proc expandOneCall(call: Expr, macros: Table[string, Decl],
res: var MacroExpandResult, depth: int): Expr =
if call == nil or call.kind != ekMacroCall:
return call
if depth > 32:
res.emitErr(call.loc, "macro expansion depth exceeded")
return newLiteralExpr(Token(kind: tkIntLiteral, text: "0", loc: call.loc))
let name = call.exprMacroName
# Built-in quote!(e) — identity with call-site graft (hygiene API demo)
if name == "quote":
if call.exprMacroArgs.len != 1:
res.emitErr(call.loc, "quote! expects exactly 1 argument")
return newLiteralExpr(Token(kind: tkIntLiteral, text: "0", loc: call.loc))
result = cloneExpr(call.exprMacroArgs[0])
# Expand nested macros inside quoted expr first
result = expandExpr(result, macros, res, depth + 1)
graftExprLoc(result, call.loc)
return
if not macros.hasKey(name):
res.emitErr(call.loc, "unknown macro '" & name & "'")
return newLiteralExpr(Token(kind: tkIntLiteral, text: "0", loc: call.loc))
let mdecl = macros[name]
let nargs = call.exprMacroArgs.len
# Expand args first
var args: seq[Expr] = @[]
for a in call.exprMacroArgs:
args.add(expandExpr(a, macros, res, depth + 1))
# Build arg groups: m!(a,b; c,d) → [[a,b],[c,d]]
var groups: seq[seq[Expr]] = @[]
if call.exprMacroGroupLens.len == 0:
groups.add(args)
else:
var off = 0
for glen in call.exprMacroGroupLens:
var g: seq[Expr] = @[]
var j = 0
while j < glen and off < args.len:
g.add(args[off])
inc off
inc j
groups.add(g)
# leftover args append to last group
while off < args.len:
if groups.len == 0: groups.add(@[])
groups[^1].add(args[off])
inc off
proc fragNames(f: MacroFragment): seq[string] =
if f.names.len > 0: return f.names
if f.name.len > 0: return @[f.name]
@[]
proc fragKinds(f: MacroFragment): seq[MacroFragKind] =
if f.kinds.len > 0: return f.kinds
@[f.kind]
proc fragMatches(k: MacroFragKind, arg: Expr): bool =
## Kind constraint at match time (after arg expand).
if arg == nil: return false
case k
of mfkIdent: arg.kind == ekIdent
of mfkLiteral: arg.kind == ekLiteral
of mfkBlock: arg.kind == ekBlock
of mfkExpr, mfkTt: true
var matched: MacroRule
var env: MacroEnv
var found = false
for rule in mdecl.declMacroRules:
var e = MacroEnv(singles: initTable[string, Expr](), lists: initTable[string, seq[Expr]]())
var failed = false
let nReps = rule.frags.countIt(it.isRep)
var gi = 0
var ai = 0
let useGroups = nReps > 1 and groups.len > 1
let flat = args
for frag in rule.frags:
if failed: break
let ns = fragNames(frag)
let ks = fragKinds(frag)
if frag.isRep:
let chunk = max(1, ns.len)
for n in ns:
e.lists[n] = @[]
if ns.len == 0:
failed = true
break
if useGroups:
if gi >= groups.len:
continue # empty rep
let g = groups[gi]
inc gi
if g.len mod chunk != 0:
failed = true
break
var i = 0
while i < g.len:
for c in 0 ..< chunk:
let arg = g[i + c]
let k = if c < ks.len: ks[c] else: mfkExpr
if not fragMatches(k, arg):
failed = true
break
e.lists[ns[c]].add(arg)
if failed: break
i += chunk
else:
if (flat.len - ai) mod chunk != 0:
failed = true
break
while ai < flat.len:
for c in 0 ..< chunk:
let arg = flat[ai]
let k = if c < ks.len: ks[c] else: mfkExpr
if not fragMatches(k, arg):
failed = true
break
e.lists[ns[c]].add(arg)
inc ai
if failed: break
else:
var arg: Expr = nil
if useGroups:
if gi >= groups.len or ai >= groups[gi].len:
failed = true
break
arg = groups[gi][ai]
inc ai
if ai >= groups[gi].len:
inc gi
ai = 0
else:
if ai >= flat.len:
failed = true
break
arg = flat[ai]
inc ai
let k = if ks.len > 0: ks[0] else: frag.kind
if not fragMatches(k, arg):
failed = true
break
let n = if ns.len > 0: ns[0] else: frag.name
e.singles[n] = arg
if not failed:
if useGroups:
if gi < groups.len: failed = true
else:
if ai != flat.len: failed = true
if failed: continue
matched = rule
env = e
found = true
break
if not found:
res.emitErr(call.loc, "macro '" & name & "' has no matching rule for " &
$nargs & " argument(s)")
return newLiteralExpr(Token(kind: tkIntLiteral, text: "0", loc: call.loc))
if matched.body == nil:
res.emitErr(call.loc, "macro '" & name & "' rule has empty body")
return newLiteralExpr(Token(kind: tkIntLiteral, text: "0", loc: call.loc))
# Splice $frags / $(…)* , then gensym hygienic locals (skip unhygienic binders)
expandUnhygienic = initHashSet[string]()
let body = substBlock(matched.body, env, call.loc)
let body2 = gensymLocals(body, call.loc)
result = Expr(kind: ekBlock, loc: call.loc, exprBlock: body2)
# Expand any macro calls introduced by substitution
result = expandExpr(result, macros, res, depth + 1)
# ---------------------------------------------------------------------------
# Walk + expand trees
# ---------------------------------------------------------------------------
proc expandExpr(e: Expr, macros: Table[string, Decl], res: var MacroExpandResult,
depth: int): Expr =
if e == nil: return nil
if e.kind == ekMacroCall:
return expandOneCall(e, macros, res, depth)
case e.kind
of ekUnary:
e.exprUnaryOperand = expandExpr(e.exprUnaryOperand, macros, res, depth)
of ekPostfix:
e.exprPostfixOperand = expandExpr(e.exprPostfixOperand, macros, res, depth)
of ekBinary:
e.exprBinaryLeft = expandExpr(e.exprBinaryLeft, macros, res, depth)
e.exprBinaryRight = expandExpr(e.exprBinaryRight, macros, res, depth)
of ekAssign:
e.exprAssignTarget = expandExpr(e.exprAssignTarget, macros, res, depth)
e.exprAssignValue = expandExpr(e.exprAssignValue, macros, res, depth)
of ekTernary:
e.exprTernaryCond = expandExpr(e.exprTernaryCond, macros, res, depth)
e.exprTernaryThen = expandExpr(e.exprTernaryThen, macros, res, depth)
e.exprTernaryElse = expandExpr(e.exprTernaryElse, macros, res, depth)
of ekRange:
e.exprRangeLo = expandExpr(e.exprRangeLo, macros, res, depth)
e.exprRangeHi = expandExpr(e.exprRangeHi, macros, res, depth)
of ekCall:
e.exprCallCallee = expandExpr(e.exprCallCallee, macros, res, depth)
for i in 0 ..< e.exprCallArgs.len:
e.exprCallArgs[i] = expandExpr(e.exprCallArgs[i], macros, res, depth)
of ekIndex:
e.exprIndexObj = expandExpr(e.exprIndexObj, macros, res, depth)
e.exprIndexIdx = expandExpr(e.exprIndexIdx, macros, res, depth)
of ekField:
e.exprFieldObj = expandExpr(e.exprFieldObj, macros, res, depth)
of ekStructInit:
for i in 0 ..< e.exprStructInitFields.len:
e.exprStructInitFields[i].value =
expandExpr(e.exprStructInitFields[i].value, macros, res, depth)
of ekSlice:
for i in 0 ..< e.exprSliceElements.len:
e.exprSliceElements[i] = expandExpr(e.exprSliceElements[i], macros, res, depth)
of ekSpread:
e.exprSpreadOperand = expandExpr(e.exprSpreadOperand, macros, res, depth)
of ekTuple:
for i in 0 ..< e.exprTupleElements.len:
e.exprTupleElements[i] = expandExpr(e.exprTupleElements[i], macros, res, depth)
of ekCast:
e.exprCastOperand = expandExpr(e.exprCastOperand, macros, res, depth)
of ekIs:
e.exprIsOperand = expandExpr(e.exprIsOperand, macros, res, depth)
of ekTry:
e.exprTryOperand = expandExpr(e.exprTryOperand, macros, res, depth)
of ekUnwrap:
e.exprUnwrapOperand = expandExpr(e.exprUnwrapOperand, macros, res, depth)
of ekSpawn:
e.exprSpawnCallee = expandExpr(e.exprSpawnCallee, macros, res, depth)
for i in 0 ..< e.exprSpawnArgs.len:
e.exprSpawnArgs[i] = expandExpr(e.exprSpawnArgs[i], macros, res, depth)
of ekAwait:
e.exprAwaitOperand = expandExpr(e.exprAwaitOperand, macros, res, depth)
of ekBorrow:
e.exprBorrowOperand = expandExpr(e.exprBorrowOperand, macros, res, depth)
of ekBlock:
e.exprBlock = expandBlock(e.exprBlock, macros, res, depth)
of ekMatch:
e.exprMatchSubject = expandExpr(e.exprMatchSubject, macros, res, depth)
for i in 0 ..< e.exprMatchArms.len:
e.exprMatchArms[i].body = expandExpr(e.exprMatchArms[i].body, macros, res, depth)
of ekStringInterp:
for i in 0 ..< e.exprInterpExprs.len:
e.exprInterpExprs[i] = expandExpr(e.exprInterpExprs[i], macros, res, depth)
of ekClosure:
e.exprClosureBody = expandBlock(e.exprClosureBody, macros, res, depth)
else:
discard
result = e
proc expandBlock(b: Block, macros: Table[string, Decl], res: var MacroExpandResult,
depth: int): Block =
if b == nil: return nil
for i in 0 ..< b.stmts.len:
b.stmts[i] = expandStmt(b.stmts[i], macros, res, depth)
result = b
proc expandStmt(s: Stmt, macros: Table[string, Decl], res: var MacroExpandResult,
depth: int): Stmt =
if s == nil: return nil
case s.kind
of skExpr:
s.stmtExpr = expandExpr(s.stmtExpr, macros, res, depth)
of skLet:
s.stmtLetInit = expandExpr(s.stmtLetInit, macros, res, depth)
of skIf:
s.stmtIfCond = expandExpr(s.stmtIfCond, macros, res, depth)
s.stmtIfThen = expandBlock(s.stmtIfThen, macros, res, depth)
for i in 0 ..< s.stmtIfElseIfs.len:
s.stmtIfElseIfs[i].cond = expandExpr(s.stmtIfElseIfs[i].cond, macros, res, depth)
s.stmtIfElseIfs[i].blk = expandBlock(s.stmtIfElseIfs[i].blk, macros, res, depth)
s.stmtIfElse = expandBlock(s.stmtIfElse, macros, res, depth)
of skWhile:
s.stmtWhileCond = expandExpr(s.stmtWhileCond, macros, res, depth)
s.stmtWhileBody = expandBlock(s.stmtWhileBody, macros, res, depth)
of skDoWhile:
s.stmtDoWhileBody = expandBlock(s.stmtDoWhileBody, macros, res, depth)
s.stmtDoWhileCond = expandExpr(s.stmtDoWhileCond, macros, res, depth)
of skLoop:
s.stmtLoopBody = expandBlock(s.stmtLoopBody, macros, res, depth)
of skFor:
s.stmtForIter = expandExpr(s.stmtForIter, macros, res, depth)
s.stmtForBody = expandBlock(s.stmtForBody, macros, res, depth)
of skMatch:
s.stmtMatchSubject = expandExpr(s.stmtMatchSubject, macros, res, depth)
for i in 0 ..< s.stmtMatchArms.len:
s.stmtMatchArms[i].body = expandExpr(s.stmtMatchArms[i].body, macros, res, depth)
of skReturn:
s.stmtReturnValue = expandExpr(s.stmtReturnValue, macros, res, depth)
of skStaticAssert:
s.stmtStaticAssertCond = expandExpr(s.stmtStaticAssertCond, macros, res, depth)
s.stmtStaticAssertMsg = expandExpr(s.stmtStaticAssertMsg, macros, res, depth)
of skComptime:
s.stmtComptimeBlock = expandBlock(s.stmtComptimeBlock, macros, res, depth)
of skEmit:
s.stmtEmitExpr = expandExpr(s.stmtEmitExpr, macros, res, depth)
of skDefer:
s.stmtDeferBody = expandExpr(s.stmtDeferBody, macros, res, depth)
of skSwitch:
s.stmtSwitchExpr = expandExpr(s.stmtSwitchExpr, macros, res, depth)
for i in 0 ..< s.stmtSwitchCases.len:
s.stmtSwitchCases[i].caseValue =
expandExpr(s.stmtSwitchCases[i].caseValue, macros, res, depth)
s.stmtSwitchCases[i].caseBody =
expandBlock(s.stmtSwitchCases[i].caseBody, macros, res, depth)
s.stmtSwitchDefault = expandBlock(s.stmtSwitchDefault, macros, res, depth)
of skDecl:
expandDecl(s.stmtDecl, macros, res, depth)
else:
discard
result = s
proc expandDecl(d: Decl, macros: Table[string, Decl], res: var MacroExpandResult,
depth: int) =
if d == nil: return
case d.kind
of dkFunc:
if d.declFuncBody != nil:
d.declFuncBody = expandBlock(d.declFuncBody, macros, res, depth)
of dkImpl:
for m in d.declImplMethods:
expandDecl(m, macros, res, depth)
of dkModule:
for it in d.declModuleItems:
expandDecl(it, macros, res, depth)
of dkConst:
d.declConstValue = expandExpr(d.declConstValue, macros, res, depth)
of dkInterface:
for m in d.declInterfaceMethods:
expandDecl(m, macros, res, depth)
of dkExternBlock:
for it in d.declExtBlockItems:
expandDecl(it, macros, res, depth)
else:
discard
proc collectMacroDeclsFrom(d: Decl, tab: var Table[string, Decl]) =
if d == nil: return
case d.kind
of dkMacro:
if not tab.hasKey(d.declMacroName):
tab[d.declMacroName] = d
of dkModule:
for it in d.declModuleItems:
collectMacroDeclsFrom(it, tab)
else:
discard
proc collectMacroDecls(modu: Module): Table[string, Decl] =
result = initTable[string, Decl]()
for it in modu.items:
collectMacroDeclsFrom(it, result)
proc expandMacros*(modu: Module): MacroExpandResult =
## Expand all declarative macro! invocations in the module (in place).
result = MacroExpandResult(diagnostics: @[])
let macros = collectMacroDecls(modu)
for d in modu.items:
expandDecl(d, macros, result, 0)