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
This commit is contained in:
2026-06-01 00:25:26 +03:00
parent 837fbb0487
commit d337ada4d6
5 changed files with 54 additions and 6 deletions
+4
View File
@@ -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<T>
declFuncName*: string
declFuncTypeParams*: seq[TypeParam]
declFuncParams*: seq[Param]
+4
View File
@@ -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)
+15 -6
View File
@@ -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:
+8
View File
@@ -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