feat: defer statement support in both compilers

- Add  statement for function-level deferred execution
- Deferred expressions run in LIFO order on function exit (return or implicit)
- Bootstrap: desugar defers in hir_lower.nim (inject before return + end of func)
- Selfhost: emit defers in c_backend.bux via CEmitter defer stack
- Both: add tkDefer token, skDefer AST node, hDefer HIR node
- Parser, lexer, sema, token files updated in both bootstrap/ and src/
This commit is contained in:
2026-06-08 20:59:27 +03:00
parent cf92b63ba6
commit a67271b08c
15 changed files with 385 additions and 4 deletions
+1
View File
@@ -157,6 +157,7 @@ const skReturn: int = 8;
const skBreak: int = 9;
const skContinue: int = 10;
const skDecl: int = 11;
const skDefer: int = 12;
struct ElseIf {
line: uint32;
+74 -1
View File
@@ -66,6 +66,15 @@ struct CEmitter {
sb: StringBuilder,
indent: int,
mod: *HirModule,
deferCount: int,
defer0: *HirNode,
defer1: *HirNode,
defer2: *HirNode,
defer3: *HirNode,
defer4: *HirNode,
defer5: *HirNode,
defer6: *HirNode,
defer7: *HirNode,
}
func CBE_Init() -> CEmitter {
@@ -73,6 +82,49 @@ func CBE_Init() -> CEmitter {
return CEmitter { sb: sb, indent: 0, mod: null as *HirModule };
}
func CBE_PushDefer(cbe: *CEmitter, node: *HirNode) {
if cbe.deferCount == 0 { cbe.defer0 = node; }
if cbe.deferCount == 1 { cbe.defer1 = node; }
if cbe.deferCount == 2 { cbe.defer2 = node; }
if cbe.deferCount == 3 { cbe.defer3 = node; }
if cbe.deferCount == 4 { cbe.defer4 = node; }
if cbe.deferCount == 5 { cbe.defer5 = node; }
if cbe.deferCount == 6 { cbe.defer6 = node; }
if cbe.deferCount == 7 { cbe.defer7 = node; }
cbe.deferCount = cbe.deferCount + 1;
}
func CBE_EmitDefers(cbe: *CEmitter) -> int {
if cbe.deferCount == 0 { return 0; }
var i: int = cbe.deferCount - 1;
while i >= 0 {
StringBuilder_Append(&cbe.sb, "\n");
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
var dn: *HirNode = null as *HirNode;
if i == 0 { dn = cbe.defer0; }
if i == 1 { dn = cbe.defer1; }
if i == 2 { dn = cbe.defer2; }
if i == 3 { dn = cbe.defer3; }
if i == 4 { dn = cbe.defer4; }
if i == 5 { dn = cbe.defer5; }
if i == 6 { dn = cbe.defer6; }
if i == 7 { dn = cbe.defer7; }
CBE_EmitExpr(cbe, dn);
StringBuilder_Append(&cbe.sb, ";");
i = i - 1;
}
cbe.deferCount = 0;
return 1;
}
func CBE_ClearDefers(cbe: *CEmitter) {
cbe.deferCount = 0;
}
func CBE_Emit(cbe: *CEmitter, text: String) {
var i: int = 0;
while i < cbe.indent {
@@ -231,8 +283,23 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
return;
}
// Defer
if kind == hDefer {
CBE_PushDefer(cbe, node.child1);
return;
}
// Return
if kind == hReturn {
let hadDefers: int = CBE_EmitDefers(cbe);
if hadDefers != 0 {
StringBuilder_Append(&cbe.sb, "\n");
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
}
StringBuilder_Append(&cbe.sb, "return");
if node.child1 != null as *HirNode {
StringBuilder_Append(&cbe.sb, " ");
@@ -500,6 +567,11 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
if kind == hBlock {
var child: *HirNode = node.child1;
while child != null as *HirNode {
if child.kind == hDefer {
CBE_EmitExpr(cbe, child);
child = child.child3;
continue;
}
// Indent
var sp: int = 0;
while sp < cbe.indent {
@@ -971,6 +1043,7 @@ func CBackend_Generate(mod: *HirModule) -> String {
var hasReturn: bool = false;
cbe.indent = 1;
CBE_EmitExpr(cbe, body);
CBE_EmitDefers(cbe);
cbe.indent = 0;
// Check if body has a return statement
var stmt: *HirNode = body.child1;
@@ -982,7 +1055,7 @@ func CBackend_Generate(mod: *HirModule) -> String {
if String_Eq(mod.funcs[i].retTypeName, "int") && !hasReturn {
StringBuilder_Append(&cbe.sb, " return 0;\n");
}
StringBuilder_Append(&cbe.sb, "}\n\n");
StringBuilder_Append(&cbe.sb, "\n}\n\n");
i = i + 1;
}
+1
View File
@@ -33,6 +33,7 @@ const hTupleInit: int = 27;
const hMatch: int = 28;
const hSpawn: int = 29;
const hAwait: int = 30;
const hDefer: int = 31;
// ---------------------------------------------------------------------------
// HirArgList — linked list for call arguments beyond 2
+9
View File
@@ -482,6 +482,15 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
return n;
}
// Defer
if kind == skDefer {
n.kind = hDefer;
if stmt.child1 != null as *Expr {
n.child1 = Lcx_LowerExpr(ctx, stmt.child1);
}
return n;
}
return n;
}
+1
View File
@@ -301,6 +301,7 @@ func lexKeywordKind(text: String) -> int {
if String_Eq(text, "super") { return tkSuper; }
if String_Eq(text, "sizeof") { return tkSizeOf; }
if String_Eq(text, "discard") { return tkDiscard; }
if String_Eq(text, "defer") { return tkDefer; }
if String_Eq(text, "async") { return tkAsync; }
if String_Eq(text, "await") { return tkAwait; }
if String_Eq(text, "spawn") { return tkSpawn; }
+13
View File
@@ -704,6 +704,19 @@ func parserParseStmt(p: *Parser) -> *Stmt {
return s;
}
// defer expr
if kind == tkDefer {
discard parserAdvance(p);
let val: *Expr = parserParseExpr(p);
parserMatch(p, tkSemicolon);
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skDefer;
s.line = line;
s.column = col;
s.child1 = val;
return s;
}
// if
if kind == tkIf {
discard parserAdvance(p);
+1
View File
@@ -138,6 +138,7 @@ const tkAsync: int = 102;
const tkAwait: int = 103;
const tkSpawn: int = 104;
const tkDiscard: int = 105;
const tkDefer: int = 106;
// ---------------------------------------------------------------------------
// Token struct