feat: try/unwrap payload types, LSP format, macro paste, freestanding runtime
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
This commit is contained in:
2026-07-28 16:56:35 +03:00
parent db7ba1dff2
commit ec5984762b
22 changed files with 1332 additions and 68 deletions
+71 -3
View File
@@ -17,6 +17,17 @@ type
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*)
# ---------------------------------------------------------------------------
@@ -152,9 +163,12 @@ proc cloneExpr*(e: Expr): Expr =
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: e.exprGenericTypeArgs)
exprGenericTypeArgs: gtas)
of ekIndex:
result = Expr(kind: ekIndex, loc: e.loc,
exprIndexObj: cloneExpr(e.exprIndexObj),
@@ -165,9 +179,12 @@ proc cloneExpr*(e: Expr): Expr =
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: e.exprStructInitTypeArgs,
exprStructInitTypeArgs: stas,
exprStructInitFields: @[])
for f in e.exprStructInitFields:
result.exprStructInitFields.add((f.name, cloneExpr(f.value)))
@@ -895,6 +912,12 @@ proc substExpr(e: Expr, env: MacroEnv, callLoc: SourceLocation): Expr =
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] = @[]
@@ -950,12 +973,37 @@ proc substExpr(e: Expr, env: MacroEnv, callLoc: SourceLocation): Expr =
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)))
@@ -1247,6 +1295,25 @@ proc expandOneCall(call: Expr, macros: Table[string, Decl],
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
@@ -1257,7 +1324,8 @@ proc expandOneCall(call: Expr, macros: Table[string, Decl],
var gi = 0
var ai = 0
let useGroups = nReps > 1 and groups.len > 1
let flat = juxtaCallSplit(rule, args)
var flat = juxtaCallSplit(rule, args)
flat = juxtaBinarySplit(rule, flat)
for frag in rule.frags:
if failed: break