From a67271b08cbbfa362753e7283f67cfecd590d760 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Mon, 8 Jun 2026 20:59:27 +0300 Subject: [PATCH] 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/ --- bootstrap/ast.nim | 3 + bootstrap/c_backend.nim | 40 +++++++++ bootstrap/hir.nim | 6 ++ bootstrap/hir_lower.nim | 33 ++++++- bootstrap/parser.nim | 6 ++ bootstrap/sema.nim | 3 + bootstrap/token.nim | 5 +- docs/ROADMAP.md | 192 ++++++++++++++++++++++++++++++++++++++++ src/ast.bux | 1 + src/c_backend.bux | 75 +++++++++++++++- src/hir.bux | 1 + src/hir_lower.bux | 9 ++ src/lexer.bux | 1 + src/parser.bux | 13 +++ src/token.bux | 1 + 15 files changed, 385 insertions(+), 4 deletions(-) create mode 100644 docs/ROADMAP.md diff --git a/bootstrap/ast.nim b/bootstrap/ast.nim index 2356f20..09c5fa0 100644 --- a/bootstrap/ast.nim +++ b/bootstrap/ast.nim @@ -238,6 +238,7 @@ type skStaticAssert skComptime skEmit + skDefer skDecl ElseIf* = object @@ -298,6 +299,8 @@ type of skEmit: stmtEmitExpr*: Expr stmtEmitEvaluated*: string ## filled by sema CTFE + of skDefer: + stmtDeferBody*: Expr of skDecl: stmtDecl*: Decl diff --git a/bootstrap/c_backend.nim b/bootstrap/c_backend.nim index fbe3754..7a0d4aa 100644 --- a/bootstrap/c_backend.nim +++ b/bootstrap/c_backend.nim @@ -8,6 +8,7 @@ type varCounter*: int declaredVars*: seq[string] sliceTypeDefs*: seq[tuple[name: string, elem: string]] ## Generated Slice_ typedefs + deferStack*: seq[seq[string]] ## Function-level deferred statements (LIFO) proc cEscape(s: string): string = ## Escape a string for use as a C string literal. @@ -45,6 +46,28 @@ proc freshVar(be: var CBackend): string = inc be.varCounter result = &"__tmp_{be.varCounter}" +# --------------------------------------------------------------------------- +# Defer helpers +# --------------------------------------------------------------------------- +proc pushDeferScope(be: var CBackend) = + be.deferStack.add(@[]) + +proc popDeferScope(be: var CBackend) = + if be.deferStack.len > 0: + be.deferStack.setLen(be.deferStack.len - 1) + +proc emitCurrentDefers(be: var CBackend) = + ## Emit all deferred statements in current scope, LIFO order + if be.deferStack.len == 0: return + let idx = be.deferStack.len - 1 + for i in countdown(be.deferStack[idx].len - 1, 0): + be.emitLine(be.deferStack[idx][i]) + +proc clearCurrentDefers(be: var CBackend) = + if be.deferStack.len > 0: + let idx = be.deferStack.len - 1 + be.deferStack[idx].setLen(0) + # Type conversion: Bux Type → C type string proc typeToC*(be: var CBackend, typ: Type): string = if typ == nil: return "void" @@ -358,7 +381,14 @@ proc emitExpr(be: var CBackend, node: HirNode): string = proc emitStmt(be: var CBackend, node: HirNode) = if node == nil: return case node.kind + of hDefer: + let expr = be.emitExpr(node.deferBody) + if be.deferStack.len > 0: + let idx = be.deferStack.len - 1 + be.deferStack[idx].add(expr & ";") + of hReturn: + be.emitCurrentDefers() if node.returnValue != nil: let val = be.emitExpr(node.returnValue) be.emitLine(&"return {val};") @@ -394,9 +424,11 @@ proc emitStmt(be: var CBackend, node: HirNode) = be.emitLine("}") of hBreak: + be.emitCurrentDefers() be.emitLine("break;") of hContinue: + be.emitCurrentDefers() be.emitLine("continue;") of hEmit: @@ -453,15 +485,23 @@ proc emitFunc*(be: var CBackend, hfunc: HirFunc) = let paramsStr = params.join(", ") be.emitLine(&"{retType} {hfunc.name}({paramsStr}) {{") inc be.indent + be.pushDeferScope() if hfunc.body != nil: if hfunc.body.kind == hBlock: for stmt in hfunc.body.blockStmts: be.emitStmt(stmt) if hfunc.body.blockExpr != nil and hfunc.retType.kind != tkVoid: + be.emitCurrentDefers() let val = be.emitExpr(hfunc.body.blockExpr) be.emitLine(&"return {val};") + else: + be.emitCurrentDefers() else: be.emitStmt(hfunc.body) + be.emitCurrentDefers() + else: + be.emitCurrentDefers() + be.popDeferScope() dec be.indent be.emitLine("}") be.emitLine("") diff --git a/bootstrap/hir.nim b/bootstrap/hir.nim index 20e2da9..79a73fc 100644 --- a/bootstrap/hir.nim +++ b/bootstrap/hir.nim @@ -17,6 +17,7 @@ type hBreak hContinue hReturn + hDefer # Memory hAlloca hLoad @@ -84,6 +85,8 @@ type continueLabel*: string of hReturn: returnValue*: HirNode + of hDefer: + deferBody*: HirNode of hAlloca: allocaType*: Type allocaName*: string @@ -200,6 +203,9 @@ proc hirCall*(callee: string, args: seq[HirNode], typ: Type, loc: SourceLocation proc hirReturn*(value: HirNode, loc: SourceLocation): HirNode = HirNode(kind: hReturn, returnValue: value, typ: makeVoid(), loc: loc) +proc hirDefer*(body: HirNode, loc: SourceLocation): HirNode = + HirNode(kind: hDefer, deferBody: body, typ: makeVoid(), loc: loc) + proc hirBlock*(stmts: seq[HirNode], expr: HirNode, typ: Type, loc: SourceLocation, isScope: bool = false): HirNode = HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr, typ: typ, loc: loc, isScope: isScope) diff --git a/bootstrap/hir_lower.nim b/bootstrap/hir_lower.nim index dbd0de2..322a5c7 100644 --- a/bootstrap/hir_lower.nim +++ b/bootstrap/hir_lower.nim @@ -11,6 +11,7 @@ type varCounter*: int tryCounter*: int pendingStmts*: seq[HirNode] + deferStmts*: seq[HirNode] typeSubst*: Table[string, Type] # Type parameter substitution for generics importTable*: Table[string, string] # Local name → fully qualified name for imports genericStructs*: Table[string, Decl] # Generic struct declarations @@ -1005,7 +1006,13 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode = of skReturn: let value = if stmt.stmtReturnValue != nil: ctx.lowerExpr(stmt.stmtReturnValue) else: nil - return ctx.flushPending(hirReturn(value, loc)) + var stmts = ctx.pendingStmts + ctx.pendingStmts = @[] + # Add defers in reverse order (LIFO) + for i in countdown(ctx.deferStmts.len - 1, 0): + stmts.add(ctx.deferStmts[i]) + stmts.add(hirReturn(value, loc)) + return hirBlock(stmts, nil, makeVoid(), loc) of skIf: let cond = ctx.lowerExpr(stmt.stmtIfCond) @@ -1120,6 +1127,11 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode = return ctx.flushPending(HirNode(kind: hMatch, matchSubject: subject, matchArms: arms, typ: makeVoid(), loc: loc)) + of skDefer: + let body = ctx.lowerExpr(stmt.stmtDeferBody) + ctx.deferStmts.add(body) + return nil + of skDecl: return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc), typ: makeVoid(), loc: loc) @@ -1188,7 +1200,24 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc = ctx.currentFuncRetType = retType ctx.currentFuncDecl = decl ctx.varTypeExprs = initTable[string, TypeExpr]() # Clear local vars for new function - let body = if funcBody != nil: ctx.lowerBlock(funcBody) else: nil + var body = if funcBody != nil: ctx.lowerBlock(funcBody) else: nil + + # Inject remaining defers at end of function (for implicit return) + if ctx.deferStmts.len > 0 and body != nil and body.kind == hBlock: + # Only add if last statement is not already a return (defers already injected there) + var hasReturn = false + if body.blockStmts.len > 0 and body.blockStmts[^1].kind == hReturn: + hasReturn = true + elif body.blockStmts.len > 0 and body.blockStmts[^1].kind == hBlock: + # Check nested block's last statement + let last = body.blockStmts[^1] + if last.blockStmts.len > 0 and last.blockStmts[^1].kind == hReturn: + hasReturn = true + if not hasReturn: + for i in countdown(ctx.deferStmts.len - 1, 0): + body.blockStmts.add(ctx.deferStmts[i]) + ctx.deferStmts = @[] + ctx.currentFuncDecl = oldFuncDecl ctx.currentFuncRetType = oldFuncRetType ctx.varTypeExprs = oldVarTypeExprs diff --git a/bootstrap/parser.nim b/bootstrap/parser.nim index 45b712e..891af57 100644 --- a/bootstrap/parser.nim +++ b/bootstrap/parser.nim @@ -959,6 +959,12 @@ proc parseStmt(p: var Parser): Stmt = # No-op: emit literal 0 as expression statement let zeroTok = Token(kind: tkIntLiteral, text: "0", loc: loc) return Stmt(kind: skExpr, loc: loc, stmtExpr: Expr(kind: ekLiteral, loc: loc, exprLit: zeroTok)) + of tkDefer: + discard p.advance() + let expr = p.parseExpr() + if p.check(tkSemicolon): + discard p.advance() + return Stmt(kind: skDefer, loc: loc, stmtDeferBody: expr) of tkFunc, tkStruct, tkEnum, tkUnion, tkInterface, tkExtend, tkModule, tkImport, tkConst, tkType, tkExtern, tkPub: # Local declaration diff --git a/bootstrap/sema.nim b/bootstrap/sema.nim index eeba627..8b89df5 100644 --- a/bootstrap/sema.nim +++ b/bootstrap/sema.nim @@ -1335,6 +1335,9 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type = elif not exprType.isUnknown and exprType.kind != tkStr: sema.emitError(stmt.loc, "#emit requires a string expression") return makeVoid() + of skDefer: + discard sema.checkExpr(stmt.stmtDeferBody, scope) + return makeVoid() of skDecl: # Local declaration inside block case stmt.stmtDecl.kind diff --git a/bootstrap/token.nim b/bootstrap/token.nim index 5dda5a2..ab582e9 100644 --- a/bootstrap/token.nim +++ b/bootstrap/token.nim @@ -59,6 +59,7 @@ type tkStaticAssert # static_assert tkComptime # comptime tkDyn # dyn + tkDefer # defer tkLifetime # 'a (lifetime parameter) ##Punctuation @@ -152,7 +153,7 @@ proc isKeyword*(kind: TokenKind): bool = tkBreak, tkContinue, tkReturn, tkMatch, tkFunc, tkLet, tkVar, tkConst, tkType, tkStruct, tkEnum, tkUnion, tkInterface, tkExtend, tkModule, tkImport, - tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkMut, tkBorrow, tkDiscard, tkAsync, tkAwait, tkSpawn, tkStaticAssert, tkComptime, tkDyn: + tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkMut, tkBorrow, tkDiscard, tkAsync, tkAwait, tkSpawn, tkStaticAssert, tkComptime, tkDyn, tkDefer: true else: false @@ -216,6 +217,7 @@ proc keywordKind*(text: string): TokenKind = of "static_assert": tkStaticAssert of "comptime": tkComptime of "dyn": tkDyn + of "defer": tkDefer of "true", "false": tkBoolLiteral else: tkIdent @@ -269,6 +271,7 @@ proc tokenKindName*(kind: TokenKind): string = of tkStaticAssert: "'static_assert'" of tkComptime: "'comptime'" of tkDyn: "'dyn'" + of tkDefer: "'defer'" of tkLifetime: "lifetime" of tkLParen: "'('" of tkRParen: "')'" diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md new file mode 100644 index 0000000..2f2cb39 --- /dev/null +++ b/docs/ROADMAP.md @@ -0,0 +1,192 @@ +# Bux Language Roadmap — New Constructs + +> **Updated:** 2026-06-08 | **Status:** In Progress + +This document tracks planned language constructs beyond Phase 8 strategy. + +--- + +## P0 — Critical (Unlocks Major Use Cases) + +### 1. `defer` Statement +**Why:** No GC + no destructors = manual `Free` everywhere. `defer` is the pragmatic fix. + +**Syntax:** +```bux +func ReadFile(path: String) -> String { + let fd: int = Open(path); + defer Close(fd); // runs on any exit from scope + defer PrintLine("done"); // LIFO order + let data: String = ReadAll(fd); + return data; // both defers run before return +} +``` + +**Implementation Steps:** +1. Add `tkDefer` token (or reuse `tkDefer` if exists) +2. Add `DeferStmt` AST node (`child: *Stmt`) +3. Parser: parse `defer ;` or `defer { }` +4. C backend: collect all `defer`s per block, emit cleanup code before every exit point (return, break, continue) +5. Handle LIFO ordering for multiple defers in same scope + +**Complexity:** Low — localized to parser + C backend. No type system changes. + +--- + +### 2. Closures / Anonymous Functions +**Why:** Callbacks, iterators, functional APIs. Currently only named functions exist. + +**Syntax:** +```bux +let add: func(int, int) -> int = |a, b| { return a + b; }; +let nums: Array = Array_New(); +Array_Filter(nums, |x| { return x > 10; }); +``` + +**Implementation Steps:** +1. New AST node: `ClosureExpr` with `params`, `body`, `captures` +2. Parser: parse `|param1, param2| -> Type { body }` +3. Type system: closure type as `func(Args) -> Ret` + implicit capture struct +4. C backend: generate struct with captured vars + function pointer +5. Lifetime: ensure captures outlive closure usage + +**Complexity:** High — touches parser, sema, type system, C backend. + +--- + +### 3. `for x in collection` Iterator Loops +**Why:** Currently only `for i in 0..10` works. No way to iterate arrays/channels/maps. + +**Syntax:** +```bux +for item in arr { + PrintLine(item); +} +for msg in channel { + Process(msg); +} +``` + +**Implementation Steps:** +1. Parser: extend `for` to accept `for in { ... }` +2. Desugar to while loop using `Iter_HasNext` / `Iter_Next` or trait-based iterator +3. C backend: generate standard while loop with iterator state + +**Complexity:** Medium — needs either trait system enhancement or hardcoded desugaring. + +--- + +## P1 — High Impact + +### 4. Operator Overloading +**Why:** Can't write `arr[i]`, `a + b`, `s1 == s2` for user types. + +**Syntax:** +```bux +extend Array { + func operator[](self: Array, idx: uint) -> T { ... } + func operator+(self: Array, other: Array) -> Array { ... } +} +``` + +**Implementation Steps:** +1. Parser: allow `operator[]`, `operator+`, etc. as function names +2. Sema: resolve operator calls to user-defined functions when available +3. C backend: emit regular function call + +**Complexity:** Medium — mainly sema + parser changes. + +--- + +### 5. Destructors / `Drop` Trait +**Why:** `own T` exists but nothing cleans up automatically. Complements `defer`. + +**Syntax:** +```bux +extend Array { + func Drop(self: own Array) { + Array_Free(self); + } +} +``` + +**Implementation Steps:** +1. Define `Drop` interface in stdlib +2. C backend: emit `Drop(value)` before variable goes out of scope +3. Respect move semantics — don't drop moved values + +**Complexity:** High — needs ownership tracking + move semantics. + +--- + +### 6. String Interpolation +**Why:** `Fmt_Fmt1("hello {0}", name)` is verbose. + +**Syntax:** +```bux +let name: String = "Bux"; +let msg: String = "Hello, {name}!"; +let num: int = 42; +let msg2: String = "Count: {num}"; +``` + +**Implementation Steps:** +1. Lexer: detect `{` inside string literals, parse interpolation expressions +2. Parser: create string concatenation AST node +3. Desugar to `String_Concat` calls or `Fmt_FmtN` + +**Complexity:** Low — lexer/parser changes only. + +--- + +## P2 — Nice to Have + +### 7. Native `switch` / `case` +**Why:** `match` is powerful but overkill for simple integer dispatch. Jump tables are faster. + +**Syntax:** +```bux +switch statusCode { + case 200: PrintLine("OK"); + case 404: PrintLine("Not Found"); + case 500: PrintLine("Server Error"); + default: PrintLine("Unknown"); +} +``` + +**Implementation Steps:** +1. Parser: `switch expr { case literal: stmts ... default: stmts }` +2. C backend: emit `switch(expr) { case N: ... }` + +**Complexity:** Low — straightforward C mapping. + +--- + +### 8. Named / Default Parameters +**Why:** API ergonomics. + +**Syntax:** +```bux +func HttpResponse(code: int = 200, contentType: String = "text/plain", body: String = "") -> Response { ... } +let r: Response = HttpResponse(body: "hello"); // code=200, contentType=default +``` + +**Implementation Steps:** +1. Parser: allow `param: Type = defaultExpr` +2. Sema: fill missing args at call sites +3. C backend: emit args in correct order with defaults + +**Complexity:** Medium — sema changes for call resolution. + +--- + +## Recommended Order + +1. **`defer`** — Low complexity, huge impact, unlocks safe resource management immediately. +2. **String interpolation** — Low complexity, big ergonomics win. +3. **`switch`/`case`** — Low complexity, complements `match` for numeric dispatch. +4. **Named/default parameters** — Medium complexity, improves stdlib APIs. +5. **Operator overloading** — Medium complexity, transforms stdlib ergonomics. +6. **Closures** — High complexity, unlocks iterators and functional style. +7. **`for x in collection`** — Depends on closures or trait system. +8. **Destructors / Drop** — High complexity, needs ownership + move semantics. diff --git a/src/ast.bux b/src/ast.bux index 6d3fce1..ab815e2 100644 --- a/src/ast.bux +++ b/src/ast.bux @@ -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; diff --git a/src/c_backend.bux b/src/c_backend.bux index eca736a..ea6f759 100644 --- a/src/c_backend.bux +++ b/src/c_backend.bux @@ -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; } diff --git a/src/hir.bux b/src/hir.bux index 312cfcf..9f9e3b9 100644 --- a/src/hir.bux +++ b/src/hir.bux @@ -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 diff --git a/src/hir_lower.bux b/src/hir_lower.bux index c6d07f2..90aa950 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -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; } diff --git a/src/lexer.bux b/src/lexer.bux index 91e02f1..c4b2bc3 100644 --- a/src/lexer.bux +++ b/src/lexer.bux @@ -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; } diff --git a/src/parser.bux b/src/parser.bux index f3e56b9..ecb3a6a 100644 --- a/src/parser.bux +++ b/src/parser.bux @@ -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); diff --git a/src/token.bux b/src/token.bux index f20fd7e..f1284d3 100644 --- a/src/token.bux +++ b/src/token.bux @@ -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