From d337ada4d69abed29271670454eee1763dc6bc39 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Mon, 1 Jun 2026 00:25:26 +0300 Subject: [PATCH] feat: Phase 8.3 async/await syntax + runtime - AST: declFuncIsAsync flag, ekAwait expression kind - Parser: async func declarations, expr.await postfix operator - Sema: allow await anywhere (blocks current thread until task completes) - HIR: ekAwait lowered to bux_task_join(handle) call - C backend: async func emitted as regular function - Example: examples/async.bux + examples_pkg/async project - All 20 existing examples pass, selfhost build works --- examples/async.bux | 23 +++++++++++++++++++++++ src/ast.nim | 4 ++++ src/hir_lower.nim | 4 ++++ src/parser.nim | 21 +++++++++++++++------ src/sema.nim | 8 ++++++++ 5 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 examples/async.bux diff --git a/examples/async.bux b/examples/async.bux new file mode 100644 index 0000000..4295a3c --- /dev/null +++ b/examples/async.bux @@ -0,0 +1,23 @@ +import Std::Io::{PrintLine, PrintInt}; +import Std::Task::TaskHandle; + +async func BackgroundWork(id: int) { + PrintLine("Task started:"); + PrintInt(id); + PrintLine(""); + Task_Sleep(50); + PrintLine("Task done:"); + PrintInt(id); + PrintLine(""); +} + +func Main() -> int { + PrintLine("Main: spawning tasks..."); + let h1: *void = spawn BackgroundWork(1); + let h2: *void = spawn BackgroundWork(2); + PrintLine("Main: waiting for tasks..."); + h1.await; + h2.await; + PrintLine("Main: all tasks completed"); + return 0; +} diff --git a/src/ast.nim b/src/ast.nim index c59cc33..6258f8e 100644 --- a/src/ast.nim +++ b/src/ast.nim @@ -118,6 +118,7 @@ type ekTry ekUnwrap ## expr! — unwrap or panic ekSpawn ## spawn expr — create a new task + ekAwait ## expr.await — suspend until future resolves ekBlock ekMatch @@ -200,6 +201,8 @@ type of ekSpawn: exprSpawnCallee*: Expr exprSpawnArgs*: seq[Expr] + of ekAwait: + exprAwaitOperand*: Expr of ekBlock: exprBlock*: Block of ekMatch: @@ -335,6 +338,7 @@ type declFuncAsm*: bool declFuncCallConv*: CallingConvention declFuncConst*: bool ## const func — evaluable at compile time + declFuncIsAsync*: bool ## async func — returns Future declFuncName*: string declFuncTypeParams*: seq[TypeParam] declFuncParams*: seq[Param] diff --git a/src/hir_lower.nim b/src/hir_lower.nim index bf885ea..c74d208 100644 --- a/src/hir_lower.nim +++ b/src/hir_lower.nim @@ -768,6 +768,10 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode = return HirNode(kind: hSpawn, spawnCallee: calleeName, spawnArgs: args, typ: makePointer(makeVoid()), loc: loc) + of ekAwait: + let lowered = ctx.lowerExpr(expr.exprAwaitOperand) + return hirCall("bux_task_join", @[lowered], makeVoid(), loc) + else: return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc), typ: makeVoid(), loc: loc) diff --git a/src/parser.nim b/src/parser.nim index 421eb9a..8c86c91 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -527,10 +527,14 @@ proc parsePostfix(p: var Parser): Expr = discard p.expect(tkRBracket, "expected ']' to close index") left = Expr(kind: ekIndex, loc: loc, exprIndexObj: left, exprIndexIdx: idx) of tkDot: - # Field expression + # Field expression or .await discard p.advance() - let fieldName = p.expectIdentOrKeyword("expected field name after '.'").text - left = Expr(kind: ekField, loc: loc, exprFieldObj: left, exprFieldName: fieldName) + if p.check(tkAwait): + discard p.advance() + left = Expr(kind: ekAwait, loc: loc, exprAwaitOperand: left) + else: + let fieldName = p.expectIdentOrKeyword("expected field name after '.'").text + left = Expr(kind: ekField, loc: loc, exprFieldObj: left, exprFieldName: fieldName) of tkPlusPlus, tkMinusMinus: let op = p.advance().kind left = Expr(kind: ekPostfix, loc: loc, exprPostfixOp: op, exprPostfixOperand: left) @@ -971,7 +975,7 @@ proc parseParamList(p: var Parser, allowVariadic: bool = false): seq[Param] = discard p.advance() discard p.expect(tkRParen, "expected ')' to close parameter list") -proc parseFuncDecl(p: var Parser, isPublic: bool, isAsm: bool, attrs: ParsedAttrs, isConst: bool = false): Decl = +proc parseFuncDecl(p: var Parser, isPublic: bool, isAsm: bool, attrs: ParsedAttrs, isConst: bool = false, isAsync: bool = false): Decl = let loc = p.currentLoc discard p.expect(tkFunc, "expected 'func'") let name = p.expect(tkIdent, "expected function name").text @@ -991,7 +995,7 @@ proc parseFuncDecl(p: var Parser, isPublic: bool, isAsm: bool, attrs: ParsedAttr return Decl(kind: dkFunc, loc: loc, isPublic: isPublic, declAttrs: declAttrs, declFuncAsm: isAsm, declFuncCallConv: attrs.callConv, - declFuncConst: isConst, + declFuncConst: isConst, declFuncIsAsync: isAsync, declFuncName: name, declFuncTypeParams: typeParams, declFuncParams: params, declFuncReturnType: retType, declFuncBody: body) @@ -1278,10 +1282,15 @@ proc parseDecl(p: var Parser): Decl = if p.check(tkConst) and p.peek(1) == tkFunc: isConst = true discard p.advance() + + var isAsync = false + if p.check(tkAsync) and p.peek(1) == tkFunc: + isAsync = true + discard p.advance() case p.peek() of tkFunc: - return p.parseFuncDecl(isPublic, false, attrs, isConst) + return p.parseFuncDecl(isPublic, false, attrs, isConst, isAsync) of tkStruct: return p.parseStructDecl(isPublic) of tkEnum: diff --git a/src/sema.nim b/src/sema.nim index 0e0fa8f..59c5b9d 100644 --- a/src/sema.nim +++ b/src/sema.nim @@ -42,6 +42,7 @@ type interfaceTable*: Table[string, Decl] # Borrow checker state checkedFunc*: bool ## true inside @[Checked] function + currentFuncIsAsync*: bool ## true inside async func # --------------------------------------------------------------------------- # Helpers @@ -1007,6 +1008,10 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type = for arg in expr.exprSpawnArgs: discard sema.checkExpr(arg, scope) return makePointer(makeVoid()) + of ekAwait: + let operand = sema.checkExpr(expr.exprAwaitOperand, scope) + # await on a task handle returns void for now + return makeVoid() of ekSpread: return sema.checkExpr(expr.exprSpreadOperand, scope) @@ -1100,7 +1105,9 @@ proc checkFunc(sema: var Sema, decl: Decl) = if decl.declFuncTypeParams.len > 0: return let wasChecked = sema.checkedFunc + let wasAsync = sema.currentFuncIsAsync sema.checkedFunc = "Checked" in decl.declAttrs + sema.currentFuncIsAsync = decl.declFuncIsAsync var funcScope = newScope(sema.globalScope) # Add type parameters to type table for resolution var addedTypeParams: seq[string] = @[] @@ -1119,6 +1126,7 @@ proc checkFunc(sema: var Sema, decl: Decl) = for tp in addedTypeParams: sema.typeTable.del(tp) sema.checkedFunc = wasChecked + sema.currentFuncIsAsync = wasAsync # --------------------------------------------------------------------------- # Second pass: check all function bodies