feat: capture-less anonymous functions (closures)
Implement MVP closures — anonymous functions without captures.
Syntax:
|a: int, b: int| -> int { return a + b; }
Changes:
- ast.bux + ast.nim: add ekClosure AST node
- parser.bux + parser.nim: parse |params| -> Ret { body }
- sema.bux + sema.nim: type-check closure params/body, return tyFunc
- hir_lower.bux + hir_lower.nim: generate __closure_N function + hAddrOf
- lir_c_backend.nim: fix function-pointer variable declaration (cParamDecl)
- C backend: closures compile to global functions with unique names
Test: _test_closure/src/Main.bux
- Closure as variable
- Closure passed to higher-order function
- Address of named function as function pointer
Both bootstrap and selfhost compilers build and pass the test.
This commit is contained in:
@@ -132,6 +132,7 @@ type
|
||||
ekBlock
|
||||
ekMatch
|
||||
ekStringInterp
|
||||
ekClosure
|
||||
|
||||
MatchArm* = object
|
||||
loc*: SourceLocation
|
||||
@@ -228,6 +229,10 @@ type
|
||||
of ekStringInterp:
|
||||
exprInterpTexts*: seq[string]
|
||||
exprInterpExprs*: seq[Expr]
|
||||
of ekClosure:
|
||||
exprClosureParams*: seq[Param]
|
||||
exprClosureBody*: Block
|
||||
exprClosureReturnType*: TypeExpr
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Statements
|
||||
|
||||
@@ -276,6 +276,7 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
|
||||
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode
|
||||
proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode
|
||||
proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode
|
||||
proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc
|
||||
|
||||
proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
if expr == nil: return makeUnknown()
|
||||
@@ -1139,6 +1140,10 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
resultNode = hirCall("String_Concat", @[resultNode, lastTextNode], makeStr(), loc)
|
||||
return resultNode
|
||||
|
||||
of ekClosure:
|
||||
let f = ctx.lowerClosureFunc(expr)
|
||||
return hirUnary(tkAmp, hirVar(f.name, makeFunc(@[], makeVoid()), loc), typ, loc)
|
||||
|
||||
else:
|
||||
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
|
||||
typ: makeVoid(), loc: loc)
|
||||
@@ -1167,6 +1172,12 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
of tekSlice:
|
||||
let elemType = ctx.resolveTypeExpr(stmt.stmtLetType.sliceElement)
|
||||
makeSlice(elemType)
|
||||
of tekFunc:
|
||||
var params: seq[Type] = @[]
|
||||
for p in stmt.stmtLetType.funcParams:
|
||||
params.add(ctx.resolveTypeExpr(p))
|
||||
let ret = if stmt.stmtLetType.funcRet != nil: ctx.resolveTypeExpr(stmt.stmtLetType.funcRet) else: makeVoid()
|
||||
makeFunc(params, ret)
|
||||
else: makeUnknown()
|
||||
elif stmt.stmtLetInit != nil:
|
||||
ctx.resolveExprType(stmt.stmtLetInit)
|
||||
@@ -1475,6 +1486,25 @@ proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs:
|
||||
ctx.generatedFuncInsts[mangledName] = true
|
||||
return mangledName
|
||||
|
||||
proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc =
|
||||
let loc = expr.loc
|
||||
let name = "__closure_" & $ctx.varCounter
|
||||
inc ctx.varCounter
|
||||
var f = HirFunc(name: name, isPublic: false)
|
||||
# Params
|
||||
for p in expr.exprClosureParams:
|
||||
f.params.add((name: p.name, typ: if p.ptype != nil: ctx.resolveTypeExpr(p.ptype) else: makeUnknown()))
|
||||
# Return type
|
||||
if expr.exprClosureReturnType != nil:
|
||||
f.retType = ctx.resolveTypeExpr(expr.exprClosureReturnType)
|
||||
else:
|
||||
f.retType = makeVoid()
|
||||
# Body
|
||||
if expr.exprClosureBody != nil:
|
||||
f.body = ctx.lowerBlock(expr.exprClosureBody)
|
||||
ctx.extraFuncs.add(f)
|
||||
return f
|
||||
|
||||
proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
var ctx = initLowerCtx(module, sema)
|
||||
var funcs: seq[HirFunc] = @[]
|
||||
|
||||
@@ -58,6 +58,13 @@ proc typeFromValue(be: var LirCBackend, v: LirValue): string =
|
||||
proc setTempType(be: var LirCBackend, temp: string, cType: string) =
|
||||
be.tempTypes[temp] = cType
|
||||
|
||||
proc cParamDecl(cType, name: string): string =
|
||||
## Emit a C parameter declaration, handling function-pointer syntax.
|
||||
if cType.contains("(*)"):
|
||||
return cType.replace("(*)", "(*" & name & ")")
|
||||
else:
|
||||
return cType & " " & name
|
||||
|
||||
# ── Per-instruction emission ──
|
||||
|
||||
proc emitInstr(be: var LirCBackend, instr: LirInstr) =
|
||||
@@ -183,7 +190,7 @@ proc emitInstr(be: var LirCBackend, instr: LirInstr) =
|
||||
let inferred = be.tempTypes[instr.dst.strVal]
|
||||
if inferred != "" and inferred != ct:
|
||||
ct = inferred
|
||||
be.emitLine(&"{ct} {v(instr.dst)};")
|
||||
be.emitLine(cParamDecl(ct, v(instr.dst)) & ";")
|
||||
|
||||
# ── Pointers ──
|
||||
of lirAddrOf:
|
||||
@@ -241,13 +248,6 @@ proc emitInstr(be: var LirCBackend, instr: LirInstr) =
|
||||
|
||||
# ── Function emission ──
|
||||
|
||||
proc cParamDecl(cType, name: string): string =
|
||||
## Emit a C parameter declaration, handling function-pointer syntax.
|
||||
if cType.contains("(*)"):
|
||||
return cType.replace("(*)", "(*" & name & ")")
|
||||
else:
|
||||
return cType & " " & name
|
||||
|
||||
proc emitFunc(be: var LirCBackend, f: LirFunc, funcRetTypes: Table[string, string], funcPtrTypes: Table[string, string]) =
|
||||
var paramsStr = ""
|
||||
for i, p in f.params:
|
||||
|
||||
@@ -587,6 +587,26 @@ proc parsePrimary(p: var Parser): Expr =
|
||||
of tkNull:
|
||||
discard p.advance()
|
||||
return newLiteralExpr(Token(kind: tkNull, text: "null", loc: loc))
|
||||
of tkPipe:
|
||||
# Closure: |params| -> Ret { body }
|
||||
discard p.advance() # |
|
||||
var params: seq[Param] = @[]
|
||||
while not p.check(tkPipe) and not p.isAtEnd:
|
||||
let nameTok = p.expect(tkIdent, "expected parameter name in closure")
|
||||
discard p.expect(tkColon, "expected ':' in closure parameter")
|
||||
let ptype = p.parseType()
|
||||
params.add(Param(name: nameTok.text, ptype: ptype))
|
||||
if p.check(tkComma):
|
||||
discard p.advance()
|
||||
else:
|
||||
break
|
||||
discard p.expect(tkPipe, "expected '|' to close closure params")
|
||||
var retType: TypeExpr = nil
|
||||
if p.check(tkArrow):
|
||||
discard p.advance() # ->
|
||||
retType = p.parseType()
|
||||
let body = p.parseBlock()
|
||||
return Expr(kind: ekClosure, loc: loc, exprClosureParams: params, exprClosureBody: body, exprClosureReturnType: retType)
|
||||
else:
|
||||
p.emitError(loc, "expected expression")
|
||||
discard p.advance()
|
||||
|
||||
@@ -44,6 +44,7 @@ type
|
||||
checkedFunc*: bool ## true inside @[Checked] function
|
||||
currentFuncIsAsync*: bool ## true inside async func
|
||||
movedVars*: seq[string] ## variables moved in current checked function
|
||||
currentRetType*: Type ## return type of the function being checked
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
@@ -1337,6 +1338,25 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
for e in expr.exprInterpExprs:
|
||||
discard sema.checkExpr(e, scope)
|
||||
return makeStr()
|
||||
of ekClosure:
|
||||
let savedRetType = sema.currentRetType
|
||||
let childScope = Scope(parent: scope)
|
||||
sema.currentRetType = if expr.exprClosureReturnType != nil: sema.resolveType(expr.exprClosureReturnType) else: makeUnknown()
|
||||
# Register params
|
||||
for p in expr.exprClosureParams:
|
||||
let ptype = if p.ptype != nil: sema.resolveType(p.ptype) else: makeUnknown()
|
||||
discard childScope.define(Symbol(kind: skVar, name: p.name, typ: ptype))
|
||||
# Check body
|
||||
if expr.exprClosureBody != nil:
|
||||
for stmt in expr.exprClosureBody.stmts:
|
||||
discard sema.checkStmt(stmt, childScope)
|
||||
sema.currentRetType = savedRetType
|
||||
# Build function type
|
||||
var params: seq[Type] = @[]
|
||||
for p in expr.exprClosureParams:
|
||||
params.add(if p.ptype != nil: sema.resolveType(p.ptype) else: makeUnknown())
|
||||
let retType = if expr.exprClosureReturnType != nil: sema.resolveType(expr.exprClosureReturnType) else: makeVoid()
|
||||
return makeFunc(params, retType)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Statement type checking
|
||||
|
||||
Reference in New Issue
Block a user