ec5984762b
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
- Type `?`/`!` as Result/Option Ok payload (not always int); fix unwrap C types - LSP 0.18 document formatting (bux fmt) + VS Code format-on-save - Macro `:type` generics (Array_New<$t>) and operators-only tt paste - Ship runtime_freestanding.c + BUX_RUNTIME=freestanding + smokes/examples
1619 lines
63 KiB
Nim
1619 lines
63 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, strutils]
|
|
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))
|
|
|
|
proc isBinaryPasteOp*(k: TokenKind): bool =
|
|
## Operators allowed as `$op:tt` paste into `$op($a, $b)` → `a OP b`.
|
|
case k
|
|
of tkPlus, tkMinus, tkStar, tkSlash, tkPercent, tkStarStar,
|
|
tkAmp, tkPipe, tkCaret, tkShl, tkShr,
|
|
tkAmpAmp, tkPipePipe,
|
|
tkEq, tkNe, tkLt, tkLe, tkGt, tkGe:
|
|
true
|
|
else:
|
|
false
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Deep clone (bootstrap has no Ast_Clone*)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
proc cloneExpr*(e: Expr): Expr
|
|
proc cloneStmt*(s: Stmt): Stmt
|
|
proc cloneBlock*(b: Block): Block
|
|
proc cloneTypeExpr*(t: TypeExpr): TypeExpr
|
|
|
|
proc cloneTypeExpr*(t: TypeExpr): TypeExpr =
|
|
if t == nil: return nil
|
|
case t.kind
|
|
of tekNamed:
|
|
result = TypeExpr(kind: tekNamed, loc: t.loc, typeName: t.typeName, typeArgs: @[])
|
|
for a in t.typeArgs:
|
|
result.typeArgs.add(cloneTypeExpr(a))
|
|
of tekPath:
|
|
result = TypeExpr(kind: tekPath, loc: t.loc, pathSegments: t.pathSegments)
|
|
of tekSlice:
|
|
result = TypeExpr(kind: tekSlice, loc: t.loc,
|
|
sliceElement: cloneTypeExpr(t.sliceElement), sliceSize: cloneExpr(t.sliceSize))
|
|
of tekOwn:
|
|
result = TypeExpr(kind: tekOwn, loc: t.loc,
|
|
pointerPointee: cloneTypeExpr(t.pointerPointee), refLifetime: t.refLifetime)
|
|
of tekPointer:
|
|
result = TypeExpr(kind: tekPointer, loc: t.loc,
|
|
pointerPointee: cloneTypeExpr(t.pointerPointee), refLifetime: t.refLifetime)
|
|
of tekRef:
|
|
result = TypeExpr(kind: tekRef, loc: t.loc,
|
|
pointerPointee: cloneTypeExpr(t.pointerPointee), refLifetime: t.refLifetime)
|
|
of tekMutRef:
|
|
result = TypeExpr(kind: tekMutRef, loc: t.loc,
|
|
pointerPointee: cloneTypeExpr(t.pointerPointee), refLifetime: t.refLifetime)
|
|
of tekDynRef:
|
|
result = TypeExpr(kind: tekDynRef, loc: t.loc, dynInterface: t.dynInterface)
|
|
of tekTuple:
|
|
result = TypeExpr(kind: tekTuple, loc: t.loc, tupleElements: @[])
|
|
for el in t.tupleElements:
|
|
result.tupleElements.add(cloneTypeExpr(el))
|
|
of tekSelf:
|
|
result = TypeExpr(kind: tekSelf, loc: t.loc)
|
|
of tekFunc:
|
|
result = TypeExpr(kind: tekFunc, loc: t.loc, funcParams: @[],
|
|
funcRet: cloneTypeExpr(t.funcRet))
|
|
for p in t.funcParams:
|
|
result.funcParams.add(cloneTypeExpr(p))
|
|
|
|
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:
|
|
var gtas: seq[TypeExpr] = @[]
|
|
for ta in e.exprGenericTypeArgs:
|
|
gtas.add(cloneTypeExpr(ta))
|
|
result = Expr(kind: ekGenericCall, loc: e.loc,
|
|
exprGenericCallee: e.exprGenericCallee,
|
|
exprGenericTypeArgs: gtas)
|
|
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:
|
|
var stas: seq[TypeExpr] = @[]
|
|
for ta in e.exprStructInitTypeArgs:
|
|
stas.add(cloneTypeExpr(ta))
|
|
result = Expr(kind: ekStructInit, loc: e.loc,
|
|
exprStructInitName: e.exprStructInitName,
|
|
exprStructInitTypeArgs: stas,
|
|
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: @[],
|
|
exprMacroGroupLens: e.exprMacroGroupLens)
|
|
for a in e.exprMacroArgs:
|
|
result.exprMacroArgs.add(cloneExpr(a))
|
|
of ekMacroStmt:
|
|
result = Expr(kind: ekMacroStmt, loc: e.loc,
|
|
exprMacroStmt: cloneStmt(e.exprMacroStmt))
|
|
of ekMacroPat:
|
|
result = Expr(kind: ekMacroPat, loc: e.loc,
|
|
exprMacroPat: clonePattern(e.exprMacroPat))
|
|
of ekMacroTt:
|
|
result = Expr(kind: ekMacroTt, loc: e.loc,
|
|
exprMacroTtInner: cloneExpr(e.exprMacroTtInner),
|
|
exprMacroTtGroup: e.exprMacroTtGroup)
|
|
of ekMacroRep:
|
|
result = Expr(kind: ekMacroRep, loc: e.loc,
|
|
exprMacroRepBody: cloneExpr(e.exprMacroRepBody))
|
|
of ekMacroType:
|
|
result = Expr(kind: ekMacroType, loc: e.loc,
|
|
exprMacroType: cloneTypeExpr(e.exprMacroType))
|
|
|
|
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)
|
|
of ekMacroStmt:
|
|
graftStmtLoc(e.exprMacroStmt, loc)
|
|
of ekMacroPat:
|
|
discard
|
|
of ekMacroTt:
|
|
graftExprLoc(e.exprMacroTtInner, loc)
|
|
of ekMacroRep:
|
|
graftExprLoc(e.exprMacroRepBody, loc)
|
|
of ekMacroType:
|
|
discard
|
|
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)
|
|
of ekMacroRep:
|
|
c.exprMacroRepBody = renameIdents(c.exprMacroRepBody, map)
|
|
of ekMacroTt:
|
|
c.exprMacroTtInner = renameIdents(c.exprMacroTtInner, 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 substType(t: TypeExpr, env: MacroEnv, callLoc: SourceLocation): TypeExpr
|
|
proc substBlock(b: Block, env: MacroEnv, callLoc: SourceLocation): Block
|
|
proc substStmtsFlat(stmts: seq[Stmt], env: MacroEnv, callLoc: SourceLocation): seq[Stmt]
|
|
proc substPattern(p: Pattern, env: MacroEnv, callLoc: SourceLocation): Pattern
|
|
|
|
proc substPattern(p: Pattern, env: MacroEnv, callLoc: SourceLocation): Pattern =
|
|
## Substitute `$p:pat` (pkIdent `$name`) with the bound pattern.
|
|
if p == nil: return nil
|
|
if p.kind == pkIdent and env.singles.hasKey(p.patIdent):
|
|
let bound = env.singles[p.patIdent]
|
|
if bound != nil and bound.kind == ekMacroPat:
|
|
result = clonePattern(bound.exprMacroPat)
|
|
if result != nil: result.loc = callLoc
|
|
return
|
|
case p.kind
|
|
of pkRange:
|
|
result = Pattern(kind: pkRange, loc: callLoc,
|
|
patRangeLo: substPattern(p.patRangeLo, env, callLoc),
|
|
patRangeHi: substPattern(p.patRangeHi, env, callLoc),
|
|
patRangeInclusive: p.patRangeInclusive)
|
|
of pkEnum:
|
|
result = Pattern(kind: pkEnum, loc: callLoc, patEnumPath: p.patEnumPath,
|
|
patEnumArgs: @[], patEnumNamed: @[])
|
|
for a in p.patEnumArgs:
|
|
result.patEnumArgs.add(substPattern(a, env, callLoc))
|
|
for nf in p.patEnumNamed:
|
|
result.patEnumNamed.add((nf.name, substPattern(nf.pattern, env, callLoc)))
|
|
of pkStruct:
|
|
result = Pattern(kind: pkStruct, loc: callLoc, patStructName: p.patStructName,
|
|
patStructFields: @[])
|
|
for f in p.patStructFields:
|
|
result.patStructFields.add((f.name, substPattern(f.pattern, env, callLoc)))
|
|
of pkTuple:
|
|
result = Pattern(kind: pkTuple, loc: callLoc, patTupleElements: @[])
|
|
for el in p.patTupleElements:
|
|
result.patTupleElements.add(substPattern(el, env, callLoc))
|
|
of pkGuarded:
|
|
result = Pattern(kind: pkGuarded, loc: callLoc,
|
|
patGuardedInner: substPattern(p.patGuardedInner, env, callLoc),
|
|
patGuardedExpr: substExpr(p.patGuardedExpr, env, callLoc))
|
|
else:
|
|
result = clonePattern(p)
|
|
if result != nil: result.loc = callLoc
|
|
|
|
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)
|
|
of ekMacroRep:
|
|
collectListNames(e.exprMacroRepBody, env, into)
|
|
of ekMacroTt:
|
|
collectListNames(e.exprMacroTtInner, 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)
|
|
elif s.kind == skExpr and s.stmtExpr != nil and s.stmtExpr.kind == ekIdent and
|
|
env.singles.hasKey(s.stmtExpr.exprIdent):
|
|
let bound = env.singles[s.stmtExpr.exprIdent]
|
|
if bound != nil and bound.kind == ekMacroStmt:
|
|
# Splice `$s:stmt` as a real statement (not an expression)
|
|
result.add(substStmt(bound.exprMacroStmt, env, callLoc))
|
|
else:
|
|
result.add(substStmt(s, env, callLoc))
|
|
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.stmtLetType = substType(c.stmtLetType, env, callLoc)
|
|
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: substPattern(arm.pattern, env, callLoc),
|
|
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 substType(t: TypeExpr, env: MacroEnv, callLoc: SourceLocation): TypeExpr =
|
|
## Substitute `$t:type` in type positions (sizeof, cast, let annotation, …).
|
|
if t == nil: return nil
|
|
if t.kind == tekNamed and t.typeName.startsWith("$") and env.singles.hasKey(t.typeName):
|
|
let bound = env.singles[t.typeName]
|
|
if bound != nil and bound.kind == ekMacroType and bound.exprMacroType != nil:
|
|
result = cloneTypeExpr(bound.exprMacroType)
|
|
if result != nil: result.loc = callLoc
|
|
return
|
|
result = cloneTypeExpr(t)
|
|
if result == nil: return
|
|
result.loc = callLoc
|
|
case result.kind
|
|
of tekNamed:
|
|
var args: seq[TypeExpr] = @[]
|
|
for a in result.typeArgs:
|
|
args.add(substType(a, env, callLoc))
|
|
result.typeArgs = args
|
|
of tekSlice:
|
|
result.sliceElement = substType(result.sliceElement, env, callLoc)
|
|
result.sliceSize = substExpr(result.sliceSize, env, callLoc)
|
|
of tekOwn, tekPointer, tekRef, tekMutRef:
|
|
result.pointerPointee = substType(result.pointerPointee, env, callLoc)
|
|
of tekTuple:
|
|
var els: seq[TypeExpr] = @[]
|
|
for el in result.tupleElements:
|
|
els.add(substType(el, env, callLoc))
|
|
result.tupleElements = els
|
|
of tekFunc:
|
|
var ps: seq[TypeExpr] = @[]
|
|
for p in result.funcParams:
|
|
ps.add(substType(p, env, callLoc))
|
|
result.funcParams = ps
|
|
result.funcRet = substType(result.funcRet, env, callLoc)
|
|
else:
|
|
discard
|
|
|
|
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])
|
|
# Value position: unwrap MacroTt wrapper (groups become tuples as values)
|
|
if result != nil and result.kind == ekMacroTt:
|
|
result = cloneExpr(result.exprMacroTtInner)
|
|
# Type fragments are not values — leave as-is only if wrongly spliced as expr
|
|
if result != nil and result.kind == ekMacroType:
|
|
# Recover as sizeof? no — invalid value splice
|
|
return newLiteralExpr(Token(kind: tkIntLiteral, text: "0", loc: callLoc))
|
|
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])
|
|
if result != nil and result.kind == ekMacroTt:
|
|
result = cloneExpr(result.exprMacroTtInner)
|
|
if result != nil and result.kind == ekMacroType:
|
|
return newLiteralExpr(Token(kind: tkIntLiteral, text: "0", loc: callLoc))
|
|
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 ekGenericCall:
|
|
# Array_New<$t>(…) / Foo<$t, $u> — substitute type fragments in type args
|
|
var gtas: seq[TypeExpr] = @[]
|
|
for ta in c.exprGenericTypeArgs:
|
|
gtas.add(substType(ta, env, callLoc))
|
|
c.exprGenericTypeArgs = gtas
|
|
of ekCall:
|
|
c.exprCallCallee = substExpr(c.exprCallCallee, env, callLoc)
|
|
var args: seq[Expr] = @[]
|
|
var argNames: seq[string] = @[]
|
|
for a in c.exprCallArgs:
|
|
# Expression-level `$( body ),*` → flatten into N call arguments
|
|
if a != nil and a.kind == ekMacroRep:
|
|
var listNames: seq[string] = @[]
|
|
collectListNames(a.exprMacroRepBody, env, listNames)
|
|
if listNames.len == 0:
|
|
args.add(substExpr(a.exprMacroRepBody, env, callLoc))
|
|
argNames.add("")
|
|
else:
|
|
var n = 0
|
|
for ln in listNames:
|
|
if env.lists.hasKey(ln):
|
|
n = max(n, env.lists[ln].len)
|
|
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)
|
|
args.add(substExpr(a.exprMacroRepBody, subEnv, callLoc))
|
|
argNames.add("")
|
|
continue
|
|
# Bare `$args:tt` that is a delimiter-balanced group → flatten elems
|
|
if a != nil and a.kind == ekIdent and env.singles.hasKey(a.exprIdent):
|
|
let bound = env.singles[a.exprIdent]
|
|
if bound != nil and bound.kind == ekMacroTt and bound.exprMacroTtGroup and
|
|
bound.exprMacroTtInner != nil:
|
|
let inner = bound.exprMacroTtInner
|
|
if inner.kind == ekTuple:
|
|
for el in inner.exprTupleElements:
|
|
let ce = cloneExpr(el)
|
|
graftExprLoc(ce, callLoc)
|
|
args.add(ce)
|
|
argNames.add("")
|
|
continue
|
|
if inner.kind == ekSlice:
|
|
for el in inner.exprSliceElements:
|
|
let ce = cloneExpr(el)
|
|
graftExprLoc(ce, callLoc)
|
|
args.add(ce)
|
|
argNames.add("")
|
|
continue
|
|
args.add(substExpr(a, env, callLoc))
|
|
argNames.add("")
|
|
c.exprCallArgs = args
|
|
c.exprCallArgNames = argNames
|
|
# Operators-only paste: `$op($a, $b)` where `$op:tt` is a bound binary operator
|
|
# → rebuild as `$a OP $b` (post-1.0).
|
|
if c.exprCallCallee != nil and c.exprCallCallee.kind == ekMacroTt and
|
|
c.exprCallCallee.exprMacroTtInner != nil and
|
|
c.exprCallCallee.exprMacroTtInner.kind == ekLiteral and
|
|
c.exprCallArgs.len == 2:
|
|
let opTok = c.exprCallCallee.exprMacroTtInner.exprLit
|
|
if opTok.kind.isBinaryPasteOp:
|
|
result = Expr(kind: ekBinary, loc: callLoc,
|
|
exprBinaryOp: opTok.kind,
|
|
exprBinaryLeft: c.exprCallArgs[0],
|
|
exprBinaryRight: c.exprCallArgs[1])
|
|
return
|
|
# Callee already unwrapped to op literal by value-position MacroTt splice
|
|
if c.exprCallCallee != nil and c.exprCallCallee.kind == ekLiteral and
|
|
c.exprCallArgs.len == 2 and c.exprCallCallee.exprLit.kind.isBinaryPasteOp:
|
|
result = Expr(kind: ekBinary, loc: callLoc,
|
|
exprBinaryOp: c.exprCallCallee.exprLit.kind,
|
|
exprBinaryLeft: c.exprCallArgs[0],
|
|
exprBinaryRight: c.exprCallArgs[1])
|
|
return
|
|
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 stas: seq[TypeExpr] = @[]
|
|
for ta in c.exprStructInitTypeArgs:
|
|
stas.add(substType(ta, env, callLoc))
|
|
c.exprStructInitTypeArgs = stas
|
|
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)
|
|
c.exprCastType = substType(c.exprCastType, env, callLoc)
|
|
of ekIs:
|
|
c.exprIsOperand = substExpr(c.exprIsOperand, env, callLoc)
|
|
c.exprIsType = substType(c.exprIsType, env, callLoc)
|
|
of ekSizeOf:
|
|
c.exprSizeOfType = substType(c.exprSizeOfType, 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: substPattern(arm.pattern, env, callLoc),
|
|
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 exprToPattern(arg: Expr): Pattern =
|
|
## Convert a call-site expression into a pattern for `$p:pat`.
|
|
if arg == nil: return nil
|
|
if arg.kind == ekMacroPat: return clonePattern(arg.exprMacroPat)
|
|
case arg.kind
|
|
of ekIdent:
|
|
if arg.exprIdent == "_":
|
|
return Pattern(kind: pkWildcard, loc: arg.loc)
|
|
return Pattern(kind: pkIdent, loc: arg.loc, patIdent: arg.exprIdent)
|
|
of ekLiteral:
|
|
return Pattern(kind: pkLiteral, loc: arg.loc, patLit: arg.exprLit)
|
|
of ekPath:
|
|
return Pattern(kind: pkEnum, loc: arg.loc, patEnumPath: arg.exprPath,
|
|
patEnumArgs: @[], patEnumNamed: @[])
|
|
of ekCall:
|
|
# Enum::Variant(args) or Variant(args)
|
|
var path: seq[string] = @[]
|
|
if arg.exprCallCallee == nil: return nil
|
|
case arg.exprCallCallee.kind
|
|
of ekIdent: path = @[arg.exprCallCallee.exprIdent]
|
|
of ekPath: path = arg.exprCallCallee.exprPath
|
|
else: return nil
|
|
var pargs: seq[Pattern] = @[]
|
|
for a in arg.exprCallArgs:
|
|
let ap = exprToPattern(a)
|
|
if ap == nil: return nil
|
|
pargs.add(ap)
|
|
return Pattern(kind: pkEnum, loc: arg.loc, patEnumPath: path,
|
|
patEnumArgs: pargs, patEnumNamed: @[])
|
|
of ekTuple:
|
|
var elems: seq[Pattern] = @[]
|
|
for el in arg.exprTupleElements:
|
|
let ep = exprToPattern(el)
|
|
if ep == nil: return nil
|
|
elems.add(ep)
|
|
return Pattern(kind: pkTuple, loc: arg.loc, patTupleElements: elems)
|
|
of ekStructInit:
|
|
var fields: seq[tuple[name: string, pattern: Pattern]] = @[]
|
|
for f in arg.exprStructInitFields:
|
|
let fp = exprToPattern(f.value)
|
|
if fp == nil: return nil
|
|
fields.add((f.name, fp))
|
|
return Pattern(kind: pkStruct, loc: arg.loc,
|
|
patStructName: arg.exprStructInitName, patStructFields: fields)
|
|
of ekRange:
|
|
let lo = exprToPattern(arg.exprRangeLo)
|
|
let hi = exprToPattern(arg.exprRangeHi)
|
|
if lo == nil or hi == nil: return nil
|
|
return Pattern(kind: pkRange, loc: arg.loc, patRangeLo: lo, patRangeHi: hi,
|
|
patRangeInclusive: arg.exprRangeInclusive)
|
|
else:
|
|
return nil
|
|
|
|
proc coerceArg(k: MacroFragKind, arg: Expr): Expr =
|
|
## Normalize arg for storage (pat → ekMacroPat). Returns nil if kind fails.
|
|
if arg == nil: return nil
|
|
case k
|
|
of mfkIdent:
|
|
if arg.kind != ekIdent: return nil
|
|
return arg
|
|
of mfkLiteral:
|
|
if arg.kind != ekLiteral: return nil
|
|
return arg
|
|
of mfkBlock:
|
|
if arg.kind != ekBlock: return nil
|
|
return arg
|
|
of mfkStmt:
|
|
if arg.kind == ekMacroStmt: return arg
|
|
# Expression as expression-statement
|
|
if arg.kind in {ekMacroPat}: return nil
|
|
return Expr(kind: ekMacroStmt, loc: arg.loc,
|
|
exprMacroStmt: Stmt(kind: skExpr, loc: arg.loc, stmtExpr: arg))
|
|
of mfkPat:
|
|
let pat = exprToPattern(arg)
|
|
if pat == nil: return nil
|
|
return Expr(kind: ekMacroPat, loc: arg.loc, exprMacroPat: pat)
|
|
of mfkExpr:
|
|
if arg.kind in {ekMacroStmt, ekMacroPat}: return nil
|
|
return arg
|
|
of mfkTt:
|
|
# Session 76/84/85: token-tree is a *superset* of expr — any single
|
|
# well-formed AST fragment. Delimiter-balanced multi-element groups
|
|
# `(a, b)` (tuple) and `[a, b]` (slice lit) flatten when spliced as
|
|
# the sole call argument: `$f($args)` → `f(a, b)`.
|
|
if arg == nil: return nil
|
|
if arg.kind == ekMacroTt: return arg
|
|
let isGroup =
|
|
arg.kind == ekTuple or
|
|
(arg.kind == ekSlice and arg.exprSliceElements.len > 0)
|
|
return Expr(kind: ekMacroTt, loc: arg.loc,
|
|
exprMacroTtInner: arg, exprMacroTtGroup: isGroup)
|
|
of mfkType:
|
|
# Session 87 — type fragment from call-site expr shape
|
|
if arg == nil: return nil
|
|
if arg.kind == ekMacroType: return arg
|
|
proc exprToType(x: Expr): TypeExpr =
|
|
if x == nil: return nil
|
|
case x.kind
|
|
of ekIdent:
|
|
return TypeExpr(kind: tekNamed, loc: x.loc, typeName: x.exprIdent, typeArgs: @[])
|
|
of ekPath:
|
|
return TypeExpr(kind: tekPath, loc: x.loc, pathSegments: x.exprPath)
|
|
of ekUnary:
|
|
if x.exprUnaryOp == tkStar:
|
|
let inner = exprToType(x.exprUnaryOperand)
|
|
if inner == nil: return nil
|
|
return TypeExpr(kind: tekPointer, loc: x.loc, pointerPointee: inner, refLifetime: "")
|
|
if x.exprUnaryOp == tkAmp:
|
|
let inner = exprToType(x.exprUnaryOperand)
|
|
if inner == nil: return nil
|
|
return TypeExpr(kind: tekRef, loc: x.loc, pointerPointee: inner, refLifetime: "")
|
|
return nil
|
|
of ekGenericCall:
|
|
# Foo<Bar> written as generic-call shape at call site (limited)
|
|
var targs: seq[TypeExpr] = @[]
|
|
for ta in x.exprGenericTypeArgs:
|
|
targs.add(cloneTypeExpr(ta))
|
|
return TypeExpr(kind: tekNamed, loc: x.loc, typeName: x.exprGenericCallee, typeArgs: targs)
|
|
else:
|
|
return nil
|
|
let ty = exprToType(arg)
|
|
if ty == nil: return nil
|
|
return Expr(kind: ekMacroType, loc: arg.loc, exprMacroType: ty)
|
|
|
|
proc fragMatches(k: MacroFragKind, arg: Expr): bool =
|
|
## Kind constraint at match time (after arg expand).
|
|
coerceArg(k, arg) != nil
|
|
|
|
## Session 86 — free-form juxta: single call arg `F(a, b)` matches
|
|
## pattern `$f:ident $args:tt` (or `$f:ident, $args:tt`) as two fragments.
|
|
proc juxtaCallSplit(rule: MacroRule, inArgs: seq[Expr]): seq[Expr] =
|
|
result = inArgs
|
|
if inArgs.len != 1 or inArgs[0] == nil: return
|
|
if inArgs[0].kind != ekCall: return
|
|
if inArgs[0].exprCallCallee == nil or inArgs[0].exprCallCallee.kind != ekIdent:
|
|
return
|
|
# Exactly two fixed frags: ident + tt (no reps)
|
|
if rule.frags.len != 2: return
|
|
if rule.frags[0].isRep or rule.frags[1].isRep: return
|
|
let k0 = if rule.frags[0].kinds.len > 0: rule.frags[0].kinds[0] else: rule.frags[0].kind
|
|
let k1 = if rule.frags[1].kinds.len > 0: rule.frags[1].kinds[0] else: rule.frags[1].kind
|
|
if k0 != mfkIdent or k1 != mfkTt: return
|
|
let callee = inArgs[0].exprCallCallee
|
|
var elems: seq[Expr] = @[]
|
|
for a in inArgs[0].exprCallArgs:
|
|
elems.add(a)
|
|
let inner = Expr(kind: ekTuple, loc: inArgs[0].loc, exprTupleElements: elems)
|
|
let group = Expr(kind: ekMacroTt, loc: inArgs[0].loc,
|
|
exprMacroTtInner: inner, exprMacroTtGroup: true)
|
|
result = @[callee, group]
|
|
|
|
## Operators-only juxta: single binary arg `1 + 2` matches
|
|
## `$a:expr, $op:tt, $b:expr` (or juxta without commas).
|
|
proc juxtaBinarySplit(rule: MacroRule, inArgs: seq[Expr]): seq[Expr] =
|
|
result = inArgs
|
|
if inArgs.len != 1 or inArgs[0] == nil: return
|
|
if inArgs[0].kind != ekBinary: return
|
|
if rule.frags.len != 3: return
|
|
if rule.frags[0].isRep or rule.frags[1].isRep or rule.frags[2].isRep: return
|
|
let k0 = if rule.frags[0].kinds.len > 0: rule.frags[0].kinds[0] else: rule.frags[0].kind
|
|
let k1 = if rule.frags[1].kinds.len > 0: rule.frags[1].kinds[0] else: rule.frags[1].kind
|
|
let k2 = if rule.frags[2].kinds.len > 0: rule.frags[2].kinds[0] else: rule.frags[2].kind
|
|
if k0 != mfkExpr or k1 != mfkTt or k2 != mfkExpr: return
|
|
if not inArgs[0].exprBinaryOp.isBinaryPasteOp: return
|
|
let opTok = Token(kind: inArgs[0].exprBinaryOp, text: "", loc: inArgs[0].loc)
|
|
let lit = Expr(kind: ekLiteral, loc: inArgs[0].loc, exprLit: opTok)
|
|
let opTt = Expr(kind: ekMacroTt, loc: inArgs[0].loc,
|
|
exprMacroTtInner: lit, exprMacroTtGroup: false)
|
|
result = @[inArgs[0].exprBinaryLeft, opTt, inArgs[0].exprBinaryRight]
|
|
|
|
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
|
|
var flat = juxtaCallSplit(rule, args)
|
|
flat = juxtaBinarySplit(rule, flat)
|
|
|
|
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
|
|
let coerced = coerceArg(k, arg)
|
|
if coerced == nil:
|
|
failed = true
|
|
break
|
|
e.lists[ns[c]].add(coerced)
|
|
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
|
|
let coerced = coerceArg(k, arg)
|
|
if coerced == nil:
|
|
failed = true
|
|
break
|
|
e.lists[ns[c]].add(coerced)
|
|
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
|
|
let coerced = coerceArg(k, arg)
|
|
if coerced == nil:
|
|
failed = true
|
|
break
|
|
let n = if ns.len > 0: ns[0] else: frag.name
|
|
e.singles[n] = coerced
|
|
|
|
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)
|