feat(bootstrap): function pointer types + C emission

- Add tekFunc AST node and parser support for func(Params) -> Ret
- Add tkFunc type resolution in sema and hir_lower
- Add &FuncName address-taking yielding tkMutRef(tkFunc)
- Fix C emission: function pointer params use Ret (*name)(Params) syntax
  via cParamDecl helper that embeds the name inside (*)
- Fix forward declarations and temp declarations for function pointers
- Add funcPtrTypes table in C backend so &Fn temps get proper type
- Add _test_funcptr integration test
This commit is contained in:
2026-06-08 22:31:32 +03:00
parent 3f0a3901ca
commit 2e536488e6
9 changed files with 99 additions and 9 deletions
+2
View File
@@ -0,0 +1,2 @@
name = "test_funcptr"
version = "0.1.0"
+17
View File
@@ -0,0 +1,17 @@
module Main {
func Apply(x: int, op: func(int) -> int) -> int {
return op(x);
}
func Double(n: int) -> int {
return n * 2;
}
func main() -> int {
let result: int = Apply(5, &Double);
if result != 10 { return 1; }
return 0;
}
}
+4
View File
@@ -33,6 +33,7 @@ type
tekDynRef ## &dyn Trait — trait object (fat pointer)
tekTuple
tekSelf
tekFunc
TypeExpr* = ref object
loc*: SourceLocation
@@ -54,6 +55,9 @@ type
tupleElements*: seq[TypeExpr]
of tekSelf:
discard
of tekFunc:
funcParams*: seq[TypeExpr]
funcRet*: TypeExpr
# ---------------------------------------------------------------------------
# Patterns
+7 -1
View File
@@ -264,6 +264,12 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
of tekDynRef: return makeDynRef(te.dynInterface)
of tekPointer: return makePointer(ctx.resolveTypeExpr(te.pointerPointee))
of tekSlice: return makeSlice(ctx.resolveTypeExpr(te.sliceElement))
of tekFunc:
var params: seq[Type] = @[]
for p in te.funcParams:
params.add(ctx.resolveTypeExpr(p))
let ret = if te.funcRet != nil: ctx.resolveTypeExpr(te.funcRet) else: makeVoid()
return makeFunc(params, ret)
else: return makeUnknown()
# Forward declarations
@@ -320,7 +326,7 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
of ekUnary:
case expr.exprUnaryOp
of tkBang: return makeBool()
of tkAmp: return makePointer(ctx.resolveExprType(expr.exprUnaryOperand))
of tkAmp: return makeMutRef(ctx.resolveExprType(expr.exprUnaryOperand))
of tkStar:
let inner = ctx.resolveExprType(expr.exprUnaryOperand)
if inner.isPointer: return inner.inner[0]
+36 -8
View File
@@ -241,11 +241,18 @@ proc emitInstr(be: var LirCBackend, instr: LirInstr) =
# ── Function emission ──
proc emitFunc(be: var LirCBackend, f: LirFunc, funcRetTypes: Table[string, string]) =
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:
if i > 0: paramsStr.add(", ")
paramsStr.add(&"{p.cType} {p.name}")
paramsStr.add(cParamDecl(p.cType, p.name))
if f.params.len == 0:
paramsStr = "void"
@@ -318,7 +325,12 @@ proc emitFunc(be: var LirCBackend, f: LirFunc, funcRetTypes: Table[string, strin
newType = be.tempTypes[instr.extra[0].strVal]
elif instr.src2.kind == lvkVar and varTypes.hasKey(instr.src2.strVal):
newType = varTypes[instr.src2.strVal]
of lirAddrOf, lirFieldPtr, lirArrowFieldPtr, lirIndexPtr, lirPtrAdd:
of lirAddrOf:
if funcPtrTypes.hasKey(instr.src.strVal):
newType = funcPtrTypes[instr.src.strVal]
else:
newType = "void*";
of lirFieldPtr, lirArrowFieldPtr, lirIndexPtr, lirPtrAdd:
newType = "void*"
of lirAdd, lirSub, lirMul, lirDiv, lirMod, lirNeg,
lirCmpEq, lirCmpNe, lirCmpLt, lirCmpLe, lirCmpGt, lirCmpGe,
@@ -342,7 +354,7 @@ proc emitFunc(be: var LirCBackend, f: LirFunc, funcRetTypes: Table[string, strin
let ct = be.tempTypes[instr.dst.strVal]
if ct != "":
declared.add(instr.dst.strVal)
be.emitLine(&"{ct} {instr.dst.strVal};")
be.emitLine(cParamDecl(ct, instr.dst.strVal) & ";")
# ── Pass 4: emit instructions ──
for instr in f.instrs:
@@ -402,6 +414,11 @@ proc typeToCStr(typ: Type): string =
of "float64": return "double"
of "bool": return "bool"
else: return typ.name
of tkFunc:
if typ.inner.len == 0: return "void (*)(void)"
let params = typ.inner[0..^2].mapIt(typeToCStr(it)).join(", ")
let ret = typeToCStr(typ.inner[^1])
return ret & " (*)(" & params & ")"
else: return "int"
proc emitStructDef(be: var LirCBackend, name: string, fields: seq[tuple[name: string, typ: Type]]) =
@@ -483,6 +500,17 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
for f in module.externFuncs:
funcRetTypes[f.name] = typeToCStr(f.retType)
# Build function-pointer type lookup table (for address-of)
var funcPtrTypes = initTable[string, string]()
for f in module.funcs:
let params = f.params.mapIt(typeToCStr(it.typ)).join(", ")
let ret = typeToCStr(f.retType)
funcPtrTypes[f.name] = ret & " (*)(" & params & ")"
for f in module.externFuncs:
let params = f.params.mapIt(typeToCStr(it.typ)).join(", ")
let ret = typeToCStr(f.retType)
funcPtrTypes[f.name] = ret & " (*)(" & params & ")"
# Header
be.emitLine("/* Generated by Bux Compiler (LIR backend) */")
be.emitLine("#include <stdio.h>")
@@ -512,7 +540,7 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
let rt = typeToCStr(ef.retType)
var params: seq[string] = @[]
for p in ef.params:
params.add(&"{typeToCStr(p.typ)} {p.name}")
params.add(cParamDecl(typeToCStr(p.typ), p.name))
if params.len == 0: params.add("void")
be.emitLine(&"extern {rt} {ef.name}({params.join(\", \")});")
be.emitLine("")
@@ -559,7 +587,7 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
let rt = typeToCStr(f.retType)
var params: seq[string] = @[]
for p in f.params:
params.add(&"{typeToCStr(p.typ)} {p.name}")
params.add(cParamDecl(typeToCStr(p.typ), p.name))
if params.len == 0: params.add("void")
be.emitLine(&"{rt} {f.name}({params.join(\", \")});")
be.emitLine("")
@@ -573,7 +601,7 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
for m in iface.methods:
var paramCTypes: seq[string] = @["void* self"]
for i in 1 ..< m.params.len:
paramCTypes.add(typeToCStr(m.params[i]) & " param")
paramCTypes.add(cParamDecl(typeToCStr(m.params[i]), "param"))
let rt = typeToCStr(m.ret)
be.emitLine(&"{rt} (*{m.name})({paramCTypes.join(\", \")});")
be.indent -= 1
@@ -600,7 +628,7 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
# Emit all LIR functions
for f in builder.funcs:
be.emitFunc(f, funcRetTypes)
be.emitFunc(f, funcRetTypes, funcPtrTypes)
# C main wrapper
var hasMain = false
+5
View File
@@ -105,6 +105,11 @@ proc typeToCStr(typ: Type): string =
of "float64": return "double"
of "bool": return "bool"
else: return typ.name
of tkFunc:
if typ.inner.len == 0: return "void (*)(void)"
let params = typ.inner[0..^2].mapIt(typeToCStr(it)).join(", ")
let ret = typeToCStr(typ.inner[^1])
return ret & " (*)(" & params & ")"
else: return "int"
proc hirTypeToC(ctx: var LowerToLirCtx, node: HirNode): string =
+14
View File
@@ -248,6 +248,20 @@ proc parseBaseType(p: var Parser): TypeExpr =
discard p.advance()
discard p.expect(tkRParen, "expected ')' to close tuple type")
return TypeExpr(kind: tekTuple, loc: loc, tupleElements: elems)
of tkFunc:
discard p.advance() # func
discard p.expect(tkLParen, "expected '(' after 'func'")
var params: seq[TypeExpr] = @[]
while not p.check(tkRParen) and not p.isAtEnd:
params.add(p.parseType())
if p.check(tkComma):
discard p.advance()
discard p.expect(tkRParen, "expected ')' to close func param types")
var ret: TypeExpr = nil
if p.check(tkArrow):
discard p.advance()
ret = p.parseType()
return TypeExpr(kind: tekFunc, loc: loc, funcParams: params, funcRet: ret)
else:
p.emitError(loc, "expected type expression")
return TypeExpr(kind: tekNamed, loc: loc, typeName: "")
+10
View File
@@ -126,6 +126,10 @@ proc typeExprReferencesTypeParam(te: TypeExpr, name: string): bool =
of tekTuple:
for elem in te.tupleElements:
if typeExprReferencesTypeParam(elem, name): return true
of tekFunc:
for p in te.funcParams:
if typeExprReferencesTypeParam(p, name): return true
return typeExprReferencesTypeParam(te.funcRet, name)
of tekSelf:
return false
@@ -278,6 +282,12 @@ proc resolveType(sema: var Sema, te: TypeExpr): Type =
return makeTuple(elems)
of tekSelf:
return makeNamed("self")
of tekFunc:
var params: seq[Type] = @[]
for p in te.funcParams:
params.add(sema.resolveType(p))
let ret = if te.funcRet != nil: sema.resolveType(te.funcRet) else: makeVoid()
return makeFunc(params, ret)
# ---------------------------------------------------------------------------
# First pass: collect global symbols
+4
View File
@@ -183,6 +183,10 @@ proc isAssignableTo*(a, b: Type): bool =
return true
if b.isDynRef and a.isMutRef:
return true
# &mut func(...) -> func(...) (function pointer decay)
if a.isMutRef and b.kind == tkFunc:
if a.inner.len > 0 and a.inner[0].kind == tkFunc:
return true
return false
# String representation