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:
@@ -107,6 +107,7 @@ const ekMatch: int = 22;
|
||||
const ekSpawn: int = 24;
|
||||
const ekAwait: int = 25;
|
||||
const ekStringInterp: int = 26;
|
||||
const ekClosure: int = 27;
|
||||
|
||||
struct ExprList {
|
||||
expr: *Expr,
|
||||
@@ -139,6 +140,8 @@ struct Expr {
|
||||
// Struct init fields
|
||||
structName: String,
|
||||
structFieldCount: int,
|
||||
// Closure params (for ekClosure)
|
||||
closureParams: *Decl,
|
||||
// Call arguments (linked list for multi-arg support)
|
||||
callArgs: *ExprList,
|
||||
callArgCount: int,
|
||||
|
||||
@@ -384,6 +384,23 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
||||
return n;
|
||||
}
|
||||
|
||||
// Closure: generate function and return address-of
|
||||
if kind == ekClosure {
|
||||
let f: *HirFunc = Lcx_LowerClosureFunc(ctx, expr);
|
||||
n.kind = hUnary;
|
||||
n.intValue = tkAmp;
|
||||
let varNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
varNode.kind = hVar;
|
||||
varNode.strValue = f.name;
|
||||
varNode.typeKind = tyFunc;
|
||||
n.child1 = varNode;
|
||||
n.typeKind = tyFunc;
|
||||
if expr.refType != null as *TypeExpr {
|
||||
n.typeName = Lcx_BuildFuncTypeName(expr.refType);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// Cast
|
||||
if kind == ekCast {
|
||||
n.kind = hCast;
|
||||
@@ -779,6 +796,93 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
|
||||
return f;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Closure lowering — generate a global function for a closure expression
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Lcx_LowerClosureFunc(ctx: *LowerCtx, expr: *Expr) -> *HirFunc {
|
||||
let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc;
|
||||
|
||||
// Generate unique name
|
||||
let numStr: String = String_FromInt(ctx.funcCount);
|
||||
f.name = String_Concat("__closure_", numStr);
|
||||
f.isPublic = false;
|
||||
|
||||
let params: *Decl = expr.closureParams;
|
||||
if params != null as *Decl {
|
||||
f.paramCount = params.paramCount;
|
||||
if params.paramCount > 0 { f.param0 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param0, ¶ms.param0); }
|
||||
if params.paramCount > 1 { f.param1 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param1, ¶ms.param1); }
|
||||
if params.paramCount > 2 { f.param2 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param2, ¶ms.param2); }
|
||||
if params.paramCount > 3 { f.param3 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param3, ¶ms.param3); }
|
||||
if params.paramCount > 4 { f.param4 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param4, ¶ms.param4); }
|
||||
if params.paramCount > 5 { f.param5 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param5, ¶ms.param5); }
|
||||
if params.paramCount > 6 { f.param6 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param6, ¶ms.param6); }
|
||||
if params.paramCount > 7 { f.param7 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param7, ¶ms.param7); }
|
||||
if params.paramCount > 8 { f.param8 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param8, ¶ms.param8); }
|
||||
}
|
||||
|
||||
if expr.refType != null as *TypeExpr && expr.refType.kind == tekFunc && expr.refType.funcRet != null as *TypeExpr {
|
||||
f.retTypeName = expr.refType.funcRet.typeName;
|
||||
f.retTypeKind = Lcx_ResolveTypeKind(expr.refType.funcRet);
|
||||
} else {
|
||||
f.retTypeName = "";
|
||||
f.retTypeKind = 0;
|
||||
}
|
||||
|
||||
// Create function scope
|
||||
var funcScope: Scope = Scope_NewChild(ctx.scope);
|
||||
var pi: int = 0;
|
||||
while pi < params.paramCount {
|
||||
var p: *Param = null as *Param;
|
||||
if pi == 0 { p = ¶ms.param0; }
|
||||
else if pi == 1 { p = ¶ms.param1; }
|
||||
else if pi == 2 { p = ¶ms.param2; }
|
||||
else if pi == 3 { p = ¶ms.param3; }
|
||||
else if pi == 4 { p = ¶ms.param4; }
|
||||
else if pi == 5 { p = ¶ms.param5; }
|
||||
else if pi == 6 { p = ¶ms.param6; }
|
||||
else if pi == 7 { p = ¶ms.param7; }
|
||||
else if pi == 8 { p = ¶ms.param8; }
|
||||
if p != null as *Param && p.refParamType != null as *TypeExpr {
|
||||
var sym: Symbol;
|
||||
sym.kind = skVar;
|
||||
sym.name = p.name;
|
||||
sym.typeKind = Lcx_ResolveTypeKind(p.refParamType);
|
||||
sym.refType = p.refParamType;
|
||||
if p.refParamType.kind == tekFunc {
|
||||
sym.typeName = Lcx_BuildFuncTypeName(p.refParamType);
|
||||
} else if !String_Eq(p.refParamType.typeName, "") {
|
||||
sym.typeName = p.refParamType.typeName;
|
||||
} else if p.refParamType.kind == tekPointer && p.refParamType.pointerPointee != null as *TypeExpr {
|
||||
sym.typeName = String_Concat(p.refParamType.pointerPointee.typeName, "*");
|
||||
} else {
|
||||
sym.typeName = "";
|
||||
}
|
||||
sym.isMutable = false;
|
||||
sym.isPublic = false;
|
||||
sym.decl = null as *Decl;
|
||||
discard Scope_Define(&funcScope, sym);
|
||||
}
|
||||
pi = pi + 1;
|
||||
}
|
||||
|
||||
let prevScope: *Scope = ctx.scope;
|
||||
ctx.scope = &funcScope;
|
||||
if expr.refBlock != null as *Block {
|
||||
f.body = Lcx_LowerBlock(ctx, expr.refBlock, -1);
|
||||
} else {
|
||||
f.body = null as *HirNode;
|
||||
}
|
||||
ctx.scope = prevScope;
|
||||
|
||||
// Add to module functions
|
||||
ctx.funcs[ctx.funcCount] = *f;
|
||||
ctx.funcCount = ctx.funcCount + 1;
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Module lowering — main entry point
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -384,10 +384,89 @@ func parserParsePrimary(p: *Parser) -> *Expr {
|
||||
return e;
|
||||
}
|
||||
|
||||
// Closure: |params| -> Ret { body }
|
||||
if kind == tkPipe {
|
||||
return parserParseClosure(p);
|
||||
}
|
||||
|
||||
parserEmitDiag(p, line, col, "expected expression");
|
||||
return parserMakeExpr(ekLiteral, line, col);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Closure: |params| -> Ret { body }
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func parserParseClosure(p: *Parser) -> *Expr {
|
||||
let line: uint32 = parserCurToken(p).line;
|
||||
let col: uint32 = parserCurToken(p).column;
|
||||
discard parserExpect(p, tkPipe, "expected '|' to start closure params");
|
||||
|
||||
let e: *Expr = parserMakeExpr(ekClosure, line, col);
|
||||
let params: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
||||
params.kind = dkFunc;
|
||||
params.paramCount = 0;
|
||||
|
||||
// Parse params: name: Type
|
||||
while !parserCheck(p, tkPipe) && parserPeek(p, 0) != tkEndOfFile {
|
||||
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
|
||||
discard parserAdvance(p);
|
||||
}
|
||||
if parserCheck(p, tkPipe) || parserPeek(p, 0) == tkEndOfFile {
|
||||
break;
|
||||
}
|
||||
if params.paramCount >= 9 { break; }
|
||||
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name in closure");
|
||||
discard parserExpect(p, tkColon, "expected ':' in closure parameter");
|
||||
let ptype: *TypeExpr = parserParseType(p);
|
||||
|
||||
let idx: int = params.paramCount;
|
||||
if idx == 0 {
|
||||
params.param0.name = nameTok.text;
|
||||
params.param0.refParamType = ptype;
|
||||
} else if idx == 1 {
|
||||
params.param1.name = nameTok.text;
|
||||
params.param1.refParamType = ptype;
|
||||
} else if idx == 2 {
|
||||
params.param2.name = nameTok.text;
|
||||
params.param2.refParamType = ptype;
|
||||
} else if idx == 3 {
|
||||
params.param3.name = nameTok.text;
|
||||
params.param3.refParamType = ptype;
|
||||
} else if idx == 4 {
|
||||
params.param4.name = nameTok.text;
|
||||
params.param4.refParamType = ptype;
|
||||
} else if idx == 5 {
|
||||
params.param5.name = nameTok.text;
|
||||
params.param5.refParamType = ptype;
|
||||
} else if idx == 6 {
|
||||
params.param6.name = nameTok.text;
|
||||
params.param6.refParamType = ptype;
|
||||
} else if idx == 7 {
|
||||
params.param7.name = nameTok.text;
|
||||
params.param7.refParamType = ptype;
|
||||
} else if idx == 8 {
|
||||
params.param8.name = nameTok.text;
|
||||
params.param8.refParamType = ptype;
|
||||
}
|
||||
params.paramCount = params.paramCount + 1;
|
||||
if parserMatch(p, tkComma) { continue; }
|
||||
break;
|
||||
}
|
||||
|
||||
discard parserExpect(p, tkPipe, "expected '|' to close closure params");
|
||||
e.closureParams = params;
|
||||
|
||||
// Optional return type: -> Type
|
||||
if parserMatch(p, tkArrow) {
|
||||
e.refType = parserParseType(p);
|
||||
}
|
||||
|
||||
// Body: { ... }
|
||||
e.refBlock = parserParseBlock(p);
|
||||
return e;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Postfix: call, index, field access, as, is, ?
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -564,6 +564,105 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
||||
return tyVoid;
|
||||
}
|
||||
|
||||
// Closure: |params| -> Ret { body }
|
||||
if kind == ekClosure {
|
||||
let savedRetType: int = sema.currentRetType;
|
||||
let savedScope: *Scope = sema.scope;
|
||||
let childScope: Scope = Scope_NewChild(sema.scope);
|
||||
sema.scope = &childScope;
|
||||
|
||||
// Set return type from annotation, or unknown for inference
|
||||
var closureRetType: int = tyUnknown;
|
||||
if expr.refType != null as *TypeExpr {
|
||||
closureRetType = Sema_ResolveType(sema, expr.refType);
|
||||
}
|
||||
sema.currentRetType = closureRetType;
|
||||
|
||||
// Register params in child scope
|
||||
let params: *Decl = expr.closureParams;
|
||||
if params != null as *Decl {
|
||||
var i: int = 0;
|
||||
while i < params.paramCount {
|
||||
var p: Param;
|
||||
if i == 0 { p = params.param0; }
|
||||
else if i == 1 { p = params.param1; }
|
||||
else if i == 2 { p = params.param2; }
|
||||
else if i == 3 { p = params.param3; }
|
||||
else if i == 4 { p = params.param4; }
|
||||
else if i == 5 { p = params.param5; }
|
||||
else if i == 6 { p = params.param6; }
|
||||
else if i == 7 { p = params.param7; }
|
||||
else if i == 8 { p = params.param8; }
|
||||
|
||||
if !String_Eq(p.name, "") {
|
||||
var sym: Symbol;
|
||||
sym.kind = skVar;
|
||||
sym.name = p.name;
|
||||
sym.typeKind = tyUnknown;
|
||||
if p.refParamType != null as *TypeExpr {
|
||||
sym.typeKind = Sema_ResolveType(sema, p.refParamType);
|
||||
sym.refType = p.refParamType;
|
||||
}
|
||||
sym.typeName = "";
|
||||
sym.isMutable = false;
|
||||
sym.isPublic = false;
|
||||
sym.decl = null as *Decl;
|
||||
discard Scope_Define(sema.scope, sym);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Check body
|
||||
if expr.refBlock != null as *Block {
|
||||
Sema_CheckBlock(sema, expr.refBlock);
|
||||
}
|
||||
|
||||
// Restore
|
||||
sema.scope = savedScope;
|
||||
sema.currentRetType = savedRetType;
|
||||
|
||||
// Build function type expression for later use
|
||||
let funcType: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||
funcType.kind = tekFunc;
|
||||
funcType.funcRet = expr.refType;
|
||||
funcType.funcParamCount = params.paramCount;
|
||||
// Build param type list
|
||||
if params.paramCount > 0 {
|
||||
var head: *TypeExprList = null as *TypeExprList;
|
||||
var tail: *TypeExprList = null as *TypeExprList;
|
||||
var i: int = 0;
|
||||
while i < params.paramCount {
|
||||
var p: Param;
|
||||
if i == 0 { p = params.param0; }
|
||||
else if i == 1 { p = params.param1; }
|
||||
else if i == 2 { p = params.param2; }
|
||||
else if i == 3 { p = params.param3; }
|
||||
else if i == 4 { p = params.param4; }
|
||||
else if i == 5 { p = params.param5; }
|
||||
else if i == 6 { p = params.param6; }
|
||||
else if i == 7 { p = params.param7; }
|
||||
else if i == 8 { p = params.param8; }
|
||||
|
||||
let node: *TypeExprList = bux_alloc(sizeof(TypeExprList)) as *TypeExprList;
|
||||
node.te = p.refParamType;
|
||||
node.next = null as *TypeExprList;
|
||||
if head == null as *TypeExprList {
|
||||
head = node;
|
||||
tail = node;
|
||||
} else {
|
||||
tail.next = node;
|
||||
tail = node;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
funcType.funcParams = head;
|
||||
}
|
||||
expr.refType = funcType;
|
||||
|
||||
return tyFunc;
|
||||
}
|
||||
|
||||
return tyUnknown;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user