feat: Phase 8.3 Concurrency — tasks, channels, spawn

- Add async/await/spawn keywords to lexer
- C runtime: pthread wrappers (bux_task_spawn, bux_task_join, bux_task_sleep)
- C runtime: mutex/condvar channel implementation (bux_channel_new/send/recv/close/free)
- Build command adds -pthread flag for C compilation
- Std::Task module: TaskHandle, Task_Spawn, Task_Join, Task_Sleep
- Std::Channel module: generic Channel<T> with Send, Recv, Close, Free
- Parser: spawn Expr() expression
- Sema: ekSpawn returns *void
- HIR: hSpawn node lowered from ekSpawn
- C backend: emit bux_task_spawn(FuncName, arg) for spawn expressions
- Example: examples/concurrency.bux (thread + channel demo)
- Example: examples_pkg/concurrency working project
This commit is contained in:
2026-06-01 00:04:48 +03:00
parent 8e255b2125
commit ac8b25935f
12 changed files with 271 additions and 2 deletions
+4
View File
@@ -117,6 +117,7 @@ type
ekIs
ekTry
ekUnwrap ## expr! — unwrap or panic
ekSpawn ## spawn expr — create a new task
ekBlock
ekMatch
@@ -196,6 +197,9 @@ type
exprTryType*: TypeExpr # nil for Result?, or explicit target type
of ekUnwrap:
exprUnwrapOperand*: Expr
of ekSpawn:
exprSpawnCallee*: Expr
exprSpawnArgs*: seq[Expr]
of ekBlock:
exprBlock*: Block
of ekMatch:
+10
View File
@@ -252,6 +252,16 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
let typ = typeToC(node.sizeOfType)
return &"sizeof({typ})"
of hSpawn:
var argsStr = ""
if node.spawnArgs.len > 0:
# Package arguments into a heap-allocated struct (simplified: single arg)
let arg = be.emitExpr(node.spawnArgs[0])
argsStr = &"(void*){arg}"
else:
argsStr = "NULL"
return &"bux_task_spawn({node.spawnCallee}, {argsStr})"
of hIf:
# Ternary expression
let cond = be.emitExpr(node.ifCond)
+1 -1
View File
@@ -536,7 +536,7 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
# Compile with cc
let outputName = if man.name != "": man.name else: "bux_out"
let outputFile = buildDir / outputName
let ccCmd = &"cc -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm 2>&1"
let ccCmd = &"cc -pthread -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm 2>&1"
if opts.verbose:
printInfo(&"running: {ccCmd}", useColor)
let (output, exitCode) = execCmdEx(ccCmd)
+5
View File
@@ -31,6 +31,8 @@ type
hCast
hIs
hSizeOf
# Concurrency
hSpawn
# Composite
hBlock
hStructInit
@@ -107,6 +109,9 @@ type
isType*: Type
of hSizeOf:
sizeOfType*: Type
of hSpawn:
spawnCallee*: string
spawnArgs*: seq[HirNode]
of hBlock:
blockStmts*: seq[HirNode]
blockExpr*: HirNode
+12
View File
@@ -756,6 +756,18 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
return HirNode(kind: hLit, litToken: Token(kind: tkStringLiteral, text: "\"\"", loc: loc),
typ: makeStr(), loc: loc)
of ekSpawn:
var calleeName = ""
if expr.exprSpawnCallee.kind == ekIdent:
calleeName = expr.exprSpawnCallee.exprIdent
elif expr.exprSpawnCallee.kind == ekPath:
calleeName = expr.exprSpawnCallee.exprPath.join("_")
var args: seq[HirNode] = @[]
for arg in expr.exprSpawnArgs:
args.add(ctx.lowerExpr(arg))
return HirNode(kind: hSpawn, spawnCallee: calleeName, spawnArgs: args,
typ: makePointer(makeVoid()), loc: loc)
else:
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
typ: makeVoid(), loc: loc)
+12
View File
@@ -444,6 +444,18 @@ proc parsePrimary(p: var Parser): Expr =
let ty = p.parseType()
discard p.expect(tkRParen, "expected ')'")
return Expr(kind: ekSizeOf, loc: loc, exprSizeOfType: ty)
of tkSpawn:
discard p.advance() # spawn
let callee = p.parsePrimary()
var args: seq[Expr] = @[]
if p.check(tkLParen):
discard p.advance() # (
while not p.check(tkRParen) and not p.isAtEnd:
args.add(p.parseExpr())
if p.check(tkComma):
discard p.advance()
discard p.expect(tkRParen, "expected ')' after spawn arguments")
return Expr(kind: ekSpawn, loc: loc, exprSpawnCallee: callee, exprSpawnArgs: args)
of tkHashLine:
discard p.advance()
return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikLine)
+5
View File
@@ -1000,6 +1000,11 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
case expr.exprIntrinsic
of ikLine, ikColumn: return makeInt()
of ikFile, ikFunction, ikDate, ikTime, ikModule: return makeStr()
of ekSpawn:
discard sema.checkExpr(expr.exprSpawnCallee, scope)
for arg in expr.exprSpawnArgs:
discard sema.checkExpr(arg, scope)
return makePointer(makeVoid())
of ekSpread:
return sema.checkExpr(expr.exprSpreadOperand, scope)
+10 -1
View File
@@ -52,6 +52,9 @@ type
tkOwn # own (gradual ownership transfer)
tkMut # mut (mutable reference)
tkDiscard # discard (evaluate and throw away)
tkAsync # async
tkAwait # await
tkSpawn # spawn
##Punctuation
tkLParen # (
@@ -143,7 +146,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, tkDiscard:
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkMut, tkDiscard, tkAsync, tkAwait, tkSpawn:
true
else:
false
@@ -200,6 +203,9 @@ proc keywordKind*(text: string): TokenKind =
of "own": tkOwn
of "mut": tkMut
of "discard": tkDiscard
of "async": tkAsync
of "await": tkAwait
of "spawn": tkSpawn
of "true", "false": tkBoolLiteral
else: tkIdent
@@ -246,6 +252,9 @@ proc tokenKindName*(kind: TokenKind): string =
of tkOwn: "'own'"
of tkMut: "'mut'"
of tkDiscard: "'discard'"
of tkAsync: "'async'"
of tkAwait: "'await'"
of tkSpawn: "'spawn'"
of tkLParen: "'('"
of tkRParen: "')'"
of tkLBrace: "'{'"