feat: multi-instance closures, richer stdlib, and Rust-style diagnostics
Introduce fat function pointers (BuxFn {code, env}) so capturing closures
are heap-allocated per value in both bootstrap and selfhost. Expand
Array/Map/Set/String/Test/Result APIs, add proper tuple codegen and
error snippets with multi-char underlines, golden diagnostic tests, and
LSP diagnostics via buxc check.
This commit is contained in:
+30
-6
@@ -128,8 +128,31 @@ proc typeToC*(be: var CBackend, typ: Type): string =
|
||||
of "float64": return "double"
|
||||
of "bool": return "bool"
|
||||
else: return typ.name
|
||||
of tkTuple: return "void*" # TODO: proper tuple struct
|
||||
of tkFunc: return "void*" # TODO: function pointer
|
||||
of tkTuple:
|
||||
if typ.inner.len == 0:
|
||||
return "Tuple_Empty"
|
||||
var parts: seq[string] = @[]
|
||||
for e in typ.inner:
|
||||
var p = typeToC(be, e)
|
||||
p = p.replace("const char*", "cstr").replace("unsigned int", "uint")
|
||||
p = p.replace(" ", "_").replace("*", "Ptr").replace("(", "").replace(")", "").replace(",", "_")
|
||||
parts.add(p)
|
||||
let tname = "Tuple_" & parts.join("_")
|
||||
# Ensure typedef is collected alongside slices
|
||||
var already = false
|
||||
for d in be.sliceTypeDefs:
|
||||
if d.name == tname:
|
||||
already = true
|
||||
break
|
||||
if not already:
|
||||
# Reuse sliceTypeDefs as a generic "extra typedef" bag: elem holds field list markup
|
||||
be.sliceTypeDefs.add((name: tname, elem: "/*tuple*/"))
|
||||
return tname
|
||||
of tkFunc:
|
||||
if typ.inner.len == 0: return "void (*)(void)"
|
||||
let params = typ.inner[0..^2].mapIt(typeToC(be, it)).join(", ")
|
||||
let ret = typeToC(be, typ.inner[^1])
|
||||
return ret & " (*)(" & params & ")"
|
||||
else:
|
||||
when defined(release):
|
||||
return "void*"
|
||||
@@ -311,10 +334,11 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
|
||||
return &"({base}).data[{idx}]"
|
||||
|
||||
of hTupleInit:
|
||||
var elems: seq[string] = @[]
|
||||
for e in node.tupleInitElements:
|
||||
elems.add(be.emitExpr(e))
|
||||
return &"{{{elems.join(\", \")}}}"
|
||||
let typeName = typeToC(be, node.typ)
|
||||
var fields: seq[string] = @[]
|
||||
for i, e in node.tupleInitElements:
|
||||
fields.add(&"._{i} = {be.emitExpr(e)}")
|
||||
return &"(({typeName}){{{fields.join(\", \")}}})"
|
||||
|
||||
of hCast:
|
||||
let operand = be.emitExpr(node.castOperand)
|
||||
|
||||
+202
-10
@@ -1,5 +1,6 @@
|
||||
import std/[os, strutils, terminal, strformat, osproc, sets]
|
||||
import lexer, parser, ast, sema, manifest, hir_lower, lir_lower, lir_c_backend
|
||||
import source_location
|
||||
|
||||
type
|
||||
ColorMode* = enum
|
||||
@@ -89,6 +90,203 @@ proc printInfo(msg: string, useColor: bool) =
|
||||
else:
|
||||
echo("info: " & msg)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Rust-style diagnostics (snippet + optional help hint)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc getSourceLine(path: string, lineNum: uint32): string =
|
||||
## Read a single 1-based line from path. Empty if unavailable.
|
||||
if path.len == 0 or lineNum == 0 or not fileExists(path):
|
||||
return ""
|
||||
try:
|
||||
let content = readFile(path)
|
||||
var n: uint32 = 1
|
||||
for line in content.splitLines():
|
||||
if n == lineNum:
|
||||
return line
|
||||
inc n
|
||||
except CatchableError:
|
||||
discard
|
||||
return ""
|
||||
|
||||
proc extractQuotedName(msg: string): string =
|
||||
## Pull the first 'name' from messages like: undeclared identifier 'foo'
|
||||
let a = msg.find('\'')
|
||||
if a < 0: return ""
|
||||
let b = msg.find('\'', a + 1)
|
||||
if b <= a + 1: return ""
|
||||
return msg[a + 1 .. b - 1]
|
||||
|
||||
proc underlineLength(lineText: string, col: uint32, message: string): int =
|
||||
## Multi-character underline under the token at `col` (1-based).
|
||||
## Falls back to scanning a source token, or matching a quoted name in the message.
|
||||
if lineText.len == 0:
|
||||
return 1
|
||||
let start = if col > 0: int(col) - 1 else: 0
|
||||
if start < 0 or start >= lineText.len:
|
||||
return 1
|
||||
|
||||
# Prefer highlighting the quoted identifier/token from the message when it
|
||||
# appears on this line (e.g. undeclared identifier 'foo').
|
||||
let quoted = extractQuotedName(message)
|
||||
if quoted.len > 0:
|
||||
let idx = lineText.find(quoted)
|
||||
if idx >= 0:
|
||||
# If caret is on/near that token, use its full length
|
||||
if abs(idx - start) <= quoted.len:
|
||||
return quoted.len
|
||||
|
||||
let c0 = lineText[start]
|
||||
# String / char / backtick literals
|
||||
if c0 == '"' or c0 == '\'' or c0 == '`':
|
||||
let quote = c0
|
||||
var i = start + 1
|
||||
while i < lineText.len:
|
||||
if lineText[i] == '\\' and i + 1 < lineText.len:
|
||||
i += 2
|
||||
continue
|
||||
if lineText[i] == quote:
|
||||
return i - start + 1
|
||||
inc i
|
||||
return max(1, lineText.len - start)
|
||||
|
||||
# Identifier or keyword
|
||||
if c0.isAlphaAscii or c0 == '_':
|
||||
var i = start
|
||||
while i < lineText.len and (lineText[i].isAlphaNumeric or lineText[i] == '_'):
|
||||
inc i
|
||||
return max(1, i - start)
|
||||
|
||||
# Number literal
|
||||
if c0.isDigit:
|
||||
var i = start
|
||||
while i < lineText.len and (lineText[i].isDigit or lineText[i] in {'.', 'x', 'X', 'b', 'B', 'o', 'O', 'a'..'f', 'A'..'F', '_'}):
|
||||
inc i
|
||||
# optional type suffix: 42i64, 1.0f
|
||||
while i < lineText.len and lineText[i] in {'i', 'u', 'f', 'I', 'U', 'F', '0'..'9'}:
|
||||
inc i
|
||||
return max(1, i - start)
|
||||
|
||||
# Multi-char operators starting at caret
|
||||
const multiOps = ["<<=", ">>=", "**", "++", "--", "==", "!=", "<=", ">=",
|
||||
"&&", "||", "<<", ">>", "+=", "-=", "*=", "/=", "%=",
|
||||
"&=", "|=", "^=", "=>", "..", "->"]
|
||||
for op in multiOps:
|
||||
if start + op.len <= lineText.len and lineText[start .. start + op.len - 1] == op:
|
||||
return op.len
|
||||
|
||||
return 1
|
||||
|
||||
proc hintForMessage(msg: string): string =
|
||||
## Actionable help text for common compiler errors.
|
||||
let m = msg.toLowerAscii()
|
||||
if "cannot assign" in m:
|
||||
return "ensure the right-hand side type matches the left-hand side"
|
||||
if "undeclared identifier" in m:
|
||||
return "check the spelling, or import the symbol from the right module"
|
||||
if "too few arguments" in m or "too many arguments" in m:
|
||||
return "compare the call with the function's parameter list"
|
||||
if "missing argument for parameter" in m:
|
||||
return "provide the missing argument (positional or named)"
|
||||
if "use of moved value" in m:
|
||||
return "the value was moved; clone it or restructure ownership"
|
||||
if "shared reference" in m or "checked function" in m:
|
||||
return "use '&mut T' for mutation, or drop @[Checked] for unchecked code"
|
||||
if "double mutable borrow" in m or "already mutably borrowed" in m:
|
||||
return "only one active '&mut' borrow is allowed at a time"
|
||||
if "expected expression" in m:
|
||||
return "the previous statement may be incomplete (missing value or ';')"
|
||||
if "expected type" in m:
|
||||
return "write a type name such as 'int', 'String', or 'Array<int>'"
|
||||
if "expected field name" in m:
|
||||
return "after '.' use an identifier or a tuple index (.0, .1, ...)"
|
||||
if "does not implement trait" in m:
|
||||
return "add an 'extend Type for Trait { ... }' block, or pick another type"
|
||||
if "duplicate symbol" in m:
|
||||
return "rename one of the definitions or remove the duplicate"
|
||||
if "unterminated" in m:
|
||||
return "check for a missing closing quote, backtick, or comment delimiter"
|
||||
return ""
|
||||
|
||||
proc printDiagnostic*(severity: string, message: string, loc: SourceLocation,
|
||||
useColor: bool, fallbackFile: string = "") =
|
||||
## Print a Rust-style diagnostic:
|
||||
## error: message
|
||||
## --> file:line:col
|
||||
## |
|
||||
## 42 | source line
|
||||
## | ^
|
||||
## = help: hint
|
||||
let isError = severity == "error"
|
||||
if useColor:
|
||||
stdout.setForegroundColor(if isError: fgRed else: fgYellow)
|
||||
stdout.write(severity & ": ")
|
||||
stdout.resetAttributes()
|
||||
stdout.writeLine(message)
|
||||
else:
|
||||
let stream = if isError: stderr else: stdout
|
||||
stream.writeLine(severity & ": " & message)
|
||||
|
||||
let file = if loc.file.len > 0: loc.file else: fallbackFile
|
||||
if loc.line > 0:
|
||||
let locStr = if file.len > 0:
|
||||
&"{file}:{loc.line}:{loc.column}"
|
||||
else:
|
||||
&"{loc.line}:{loc.column}"
|
||||
if useColor:
|
||||
stdout.setForegroundColor(fgCyan)
|
||||
stdout.write(" --> ")
|
||||
stdout.resetAttributes()
|
||||
stdout.writeLine(locStr)
|
||||
else:
|
||||
stderr.writeLine(" --> " & locStr)
|
||||
|
||||
let lineText = getSourceLine(file, loc.line)
|
||||
if lineText.len > 0:
|
||||
let gutter = $loc.line
|
||||
let pad = " ".repeat(max(gutter.len, 3))
|
||||
# If message names a quoted token, prefer caret at that token's start
|
||||
var col = loc.column
|
||||
let quoted = extractQuotedName(message)
|
||||
if quoted.len > 0:
|
||||
let idx = lineText.find(quoted)
|
||||
if idx >= 0:
|
||||
col = uint32(idx + 1)
|
||||
let ulen = underlineLength(lineText, col, message)
|
||||
stdout.writeLine(pad & " |")
|
||||
stdout.writeLine(" " & gutter & " | " & lineText)
|
||||
# Multi-char underline under the token (1-based column)
|
||||
var caretPad = ""
|
||||
if col > 0:
|
||||
caretPad = " ".repeat(int(col) - 1)
|
||||
let marks = "^".repeat(max(1, ulen))
|
||||
stdout.writeLine(pad & " | " & caretPad & marks)
|
||||
|
||||
let hint = hintForMessage(message)
|
||||
if hint.len > 0:
|
||||
if useColor:
|
||||
stdout.setForegroundColor(fgGreen)
|
||||
stdout.write(" = help: ")
|
||||
stdout.resetAttributes()
|
||||
stdout.writeLine(hint)
|
||||
else:
|
||||
stdout.writeLine(" = help: " & hint)
|
||||
|
||||
proc printLexerDiags(diags: seq[LexerDiagnostic], useColor: bool, fallbackFile = "") =
|
||||
for d in diags:
|
||||
let sev = if d.severity == ldsError: "error" else: "warning"
|
||||
printDiagnostic(sev, d.message, d.loc, useColor, fallbackFile)
|
||||
|
||||
proc printParserDiags(diags: seq[ParserDiagnostic], useColor: bool, fallbackFile = "") =
|
||||
for d in diags:
|
||||
let sev = if d.severity == pdsError: "error" else: "warning"
|
||||
printDiagnostic(sev, d.message, d.loc, useColor, fallbackFile)
|
||||
|
||||
proc printSemaDiags(diags: seq[SemaDiagnostic], useColor: bool, fallbackFile = "") =
|
||||
for d in diags:
|
||||
let sev = if d.severity == sdsError: "error" else: "warning"
|
||||
printDiagnostic(sev, d.message, d.loc, useColor, fallbackFile)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Commands
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -304,14 +502,12 @@ proc prepareProject(root: string, useColor: bool, opts: GlobalOptions): (Project
|
||||
let lexRes = tokenize(source, path)
|
||||
if lexRes.hasErrors:
|
||||
printError(&"lex errors in {path}", useColor)
|
||||
for d in lexRes.diagnostics:
|
||||
echo $d
|
||||
printLexerDiags(lexRes.diagnostics, useColor, path)
|
||||
return (pctx, 1)
|
||||
let parseRes = parse(lexRes.tokens, path)
|
||||
if parseRes.diagnostics.len > 0:
|
||||
printError(&"parse errors in {path}", useColor)
|
||||
for d in parseRes.diagnostics:
|
||||
echo &"error: {d.message} at {d.loc}"
|
||||
printParserDiags(parseRes.diagnostics, useColor, path)
|
||||
return (pctx, 1)
|
||||
for decl in parseRes.module.items:
|
||||
if decl.kind == dkModule:
|
||||
@@ -345,9 +541,7 @@ proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
|
||||
let semaRes = analyze(unifiedModule)
|
||||
if semaRes.hasErrors:
|
||||
printError("type errors in project", useColor)
|
||||
for d in semaRes.diagnostics:
|
||||
let sev = if d.severity == sdsError: "error" else: "warning"
|
||||
echo &"{sev}: {d.message} at {d.loc}"
|
||||
printSemaDiags(semaRes.diagnostics, useColor)
|
||||
return 1
|
||||
if not opts.quiet:
|
||||
printInfo("check passed", useColor)
|
||||
@@ -448,9 +642,7 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
||||
let (semaRes, semaCtx) = analyzeFull(unifiedModule)
|
||||
if semaRes.hasErrors:
|
||||
printError("type errors in project", useColor)
|
||||
for d in semaRes.diagnostics:
|
||||
let sev = if d.severity == sdsError: "error" else: "warning"
|
||||
echo &"{sev}: {d.message} at {d.loc}"
|
||||
printSemaDiags(semaRes.diagnostics, useColor)
|
||||
return 1
|
||||
|
||||
let hirMod = lowerModule(unifiedModule, semaCtx)
|
||||
|
||||
@@ -189,6 +189,10 @@ type
|
||||
consts*: seq[tuple[name: string, typ: Type, value: HirNode]]
|
||||
interfaces*: seq[tuple[name: string, hasAssocTypes: bool, methods: seq[tuple[name: string, params: seq[Type], ret: Type]]]]
|
||||
vtables*: seq[tuple[interfaceName: string, concreteType: string, methodNames: seq[string], hasAssocTypes: bool]]
|
||||
## Named functions used as values → need __adapt_ wrappers for fat-func ABI
|
||||
funcAdapters*: seq[tuple[name: string, typ: Type]]
|
||||
## Extra func types seen in locals/closures that need BuxFn_* typedefs
|
||||
seenFatTypes*: seq[Type]
|
||||
|
||||
# Constructor helpers
|
||||
proc hirLit*(tok: Token, typ: Type, loc: SourceLocation): HirNode =
|
||||
|
||||
+163
-40
@@ -1,4 +1,4 @@
|
||||
import std/[tables, sets, strutils]
|
||||
import std/[tables, sets, strutils, strformat]
|
||||
import ast, types, token, source_location, hir, sema, scope
|
||||
|
||||
type
|
||||
@@ -25,6 +25,11 @@ type
|
||||
closureDepth*: int
|
||||
currentClosureExpr*: Expr
|
||||
envInstanceName*: string
|
||||
## Named functions that must be wrapped as fat func values (multi-instance ABI)
|
||||
funcAdapters*: HashSet[string]
|
||||
funcAdapterSigs*: Table[string, Type]
|
||||
## All func types that need BuxFn_* typedefs (including locals)
|
||||
seenFatTypes*: seq[Type]
|
||||
|
||||
proc freshName(ctx: var LowerCtx): string =
|
||||
inc ctx.varCounter
|
||||
@@ -159,6 +164,50 @@ proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
|
||||
result.generatedFuncInsts = initTable[string, bool]()
|
||||
result.extraFuncs = @[]
|
||||
result.varTypeExprs = initTable[string, TypeExpr]()
|
||||
result.funcAdapters = initHashSet[string]()
|
||||
result.funcAdapterSigs = initTable[string, Type]()
|
||||
result.seenFatTypes = @[]
|
||||
|
||||
proc sanitizeFatPart(s: string): string =
|
||||
result = s.replace("const char*", "cstr").replace("unsigned int", "uint")
|
||||
result = result.replace(" ", "_").replace("*", "Ptr").replace("(", "").replace(")", "").replace(",", "_").replace(".", "_")
|
||||
|
||||
proc typeNameForFat(typ: Type): string
|
||||
proc hirFuncFatTypeName*(typ: Type): string
|
||||
|
||||
proc typeNameForFat(typ: Type): string =
|
||||
## Lightweight C-ish name for fat-func mangling (mirrors lir typeToCStr subset).
|
||||
if typ == nil: return "void"
|
||||
case typ.kind
|
||||
of tkVoid: return "void"
|
||||
of tkBool, tkBool8, tkBool16, tkBool32: return "bool"
|
||||
of tkStr: return "cstr"
|
||||
of tkInt, tkInt8, tkInt16, tkInt32, tkInt64: return "int"
|
||||
of tkUInt, tkUInt8, tkUInt16, tkUInt32, tkUInt64: return "uint"
|
||||
of tkFloat32: return "float"
|
||||
of tkFloat64: return "double"
|
||||
of tkPointer, tkRef, tkMutRef:
|
||||
if typ.inner.len > 0: return sanitizeFatPart(typeNameForFat(typ.inner[0]) & "Ptr")
|
||||
return "voidPtr"
|
||||
of tkNamed:
|
||||
case typ.name
|
||||
of "String", "str": return "cstr"
|
||||
else: return sanitizeFatPart(typ.name)
|
||||
of tkFunc:
|
||||
return hirFuncFatTypeName(typ)
|
||||
else:
|
||||
return "int"
|
||||
|
||||
proc hirFuncFatTypeName*(typ: Type): string =
|
||||
if typ == nil or typ.kind != tkFunc: return "BuxFn_void"
|
||||
let ret = if typ.inner.len > 0: typeNameForFat(typ.inner[^1]) else: "void"
|
||||
var parts: seq[string] = @[sanitizeFatPart(ret)]
|
||||
if typ.inner.len > 1:
|
||||
for p in typ.inner[0 ..^ 2]:
|
||||
parts.add(sanitizeFatPart(typeNameForFat(p)))
|
||||
else:
|
||||
parts.add("void")
|
||||
return "BuxFn_" & parts.join("_")
|
||||
|
||||
proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type
|
||||
|
||||
@@ -291,7 +340,14 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
|
||||
of tekOwn: return ctx.resolveTypeExpr(te.pointerPointee)
|
||||
of tekDynRef: return makeDynRef(te.dynInterface)
|
||||
of tekPointer: return makePointer(ctx.resolveTypeExpr(te.pointerPointee))
|
||||
of tekRef: return makeRef(ctx.resolveTypeExpr(te.pointerPointee))
|
||||
of tekMutRef: return makeMutRef(ctx.resolveTypeExpr(te.pointerPointee))
|
||||
of tekSlice: return makeSlice(ctx.resolveTypeExpr(te.sliceElement))
|
||||
of tekTuple:
|
||||
var elems: seq[Type] = @[]
|
||||
for e in te.tupleElements:
|
||||
elems.add(ctx.resolveTypeExpr(e))
|
||||
return makeTuple(elems)
|
||||
of tekFunc:
|
||||
var params: seq[Type] = @[]
|
||||
for p in te.funcParams:
|
||||
@@ -523,6 +579,18 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
return makeVoid()
|
||||
of ekBorrow:
|
||||
return ctx.resolveExprType(expr.exprBorrowOperand)
|
||||
of ekClosure:
|
||||
var params: seq[Type] = @[]
|
||||
for p in expr.exprClosureParams:
|
||||
if p.ptype != nil:
|
||||
params.add(ctx.resolveTypeExpr(p.ptype))
|
||||
else:
|
||||
params.add(makeUnknown())
|
||||
let ret = if expr.exprClosureReturnType != nil:
|
||||
ctx.resolveTypeExpr(expr.exprClosureReturnType)
|
||||
else:
|
||||
makeVoid()
|
||||
return makeFunc(params, ret)
|
||||
else: return makeUnknown()
|
||||
|
||||
proc extractGenericStructInfo(ctx: LowerCtx, te: TypeExpr): tuple[baseName: string, typeArgs: seq[TypeExpr]] =
|
||||
@@ -742,9 +810,26 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
let capType = if idx < ctx.currentClosureExpr.captureTypeKinds.len: Type(kind: TypeKind(ctx.currentClosureExpr.captureTypeKinds[idx])) else: makeInt()
|
||||
let base = hirVar(ctx.envInstanceName, makeNamed(""), loc)
|
||||
return HirNode(kind: hFieldAccess, fieldAccessName: name, fieldAccessBase: base, typ: capType, loc: loc)
|
||||
var resolvedName = name
|
||||
if ctx.importTable.hasKey(name):
|
||||
return hirVar(ctx.importTable[name], typ, loc)
|
||||
return hirVar(name, typ, loc)
|
||||
resolvedName = ctx.importTable[name]
|
||||
# Named function used as a value → fat function pointer via adapter
|
||||
if typ != nil and typ.kind == tkFunc:
|
||||
let sym = ctx.globalScope.lookup(name)
|
||||
let sym2 = if sym == nil: ctx.globalScope.lookup(resolvedName) else: sym
|
||||
if sym2 != nil and sym2.kind == skFunc:
|
||||
let adaptName = "__adapt_" & resolvedName
|
||||
ctx.funcAdapters.incl(resolvedName)
|
||||
ctx.funcAdapterSigs[resolvedName] = typ
|
||||
let fatName = hirFuncFatTypeName(typ)
|
||||
let nullEnv = HirNode(kind: hCast,
|
||||
castOperand: hirLit(Token(kind: tkIntLiteral, text: "0", loc: loc), makeInt(), loc),
|
||||
castType: makePointer(makeVoid()), typ: makePointer(makeVoid()), loc: loc)
|
||||
return HirNode(kind: hStructInit, structInitName: fatName, structInitFields: @[
|
||||
(name: "code", value: hirVar(adaptName, makePointer(makeVoid()), loc)),
|
||||
(name: "env", value: nullEnv)
|
||||
], typ: typ, loc: loc)
|
||||
return hirVar(resolvedName, typ, loc)
|
||||
|
||||
of ekPath:
|
||||
# Handle enum variants: Color::Red → Color_Red
|
||||
@@ -756,6 +841,32 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
return hirSelf(typ, loc)
|
||||
|
||||
of ekUnary:
|
||||
# &NamedFunc used as func value → fat adapter (not a raw C function pointer)
|
||||
if expr.exprUnaryOp == tkAmp and expr.exprUnaryOperand != nil and
|
||||
expr.exprUnaryOperand.kind == ekIdent:
|
||||
let fname = expr.exprUnaryOperand.exprIdent
|
||||
var resolved = fname
|
||||
if ctx.importTable.hasKey(fname):
|
||||
resolved = ctx.importTable[fname]
|
||||
let sym = ctx.globalScope.lookup(resolved)
|
||||
if sym != nil and sym.kind == skFunc:
|
||||
# Prefer declared func type on the symbol; fall back to expression type
|
||||
var ftyp = if sym.typ != nil and sym.typ.kind == tkFunc: sym.typ else: typ
|
||||
if ftyp == nil or ftyp.kind != tkFunc:
|
||||
ftyp = ctx.resolveExprType(expr.exprUnaryOperand)
|
||||
if ftyp != nil and ftyp.kind == tkFunc:
|
||||
let adaptName = "__adapt_" & resolved
|
||||
ctx.funcAdapters.incl(resolved)
|
||||
ctx.funcAdapterSigs[resolved] = ftyp
|
||||
ctx.seenFatTypes.add(ftyp)
|
||||
let fatName = hirFuncFatTypeName(ftyp)
|
||||
let nullEnv = HirNode(kind: hCast,
|
||||
castOperand: hirLit(Token(kind: tkIntLiteral, text: "0", loc: loc), makeInt(), loc),
|
||||
castType: makePointer(makeVoid()), typ: makePointer(makeVoid()), loc: loc)
|
||||
return HirNode(kind: hStructInit, structInitName: fatName, structInitFields: @[
|
||||
(name: "code", value: hirVar(adaptName, makePointer(makeVoid()), loc)),
|
||||
(name: "env", value: nullEnv)
|
||||
], typ: ftyp, loc: loc)
|
||||
let operand = ctx.lowerExpr(expr.exprUnaryOperand)
|
||||
return hirUnary(expr.exprUnaryOp, operand, typ, loc)
|
||||
|
||||
@@ -883,6 +994,16 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
calleeName = expr.exprCallCallee.exprPath.join("_")
|
||||
let args = ctx.lowerCallArgs(expr.exprCallCallee, expr.exprCallArgs)
|
||||
if calleeName != "":
|
||||
# Named global function → direct call
|
||||
let sym = ctx.globalScope.lookup(calleeName)
|
||||
if sym != nil and sym.kind == skFunc:
|
||||
return hirCall(calleeName, args, typ, loc)
|
||||
# Variable holding a fat function pointer → indirect call
|
||||
let ct = ctx.resolveExprType(expr.exprCallCallee)
|
||||
if ct != nil and ct.kind == tkFunc:
|
||||
let callee = hirVar(calleeName, ct, loc)
|
||||
return HirNode(kind: hCallIndirect, callIndirectCallee: callee,
|
||||
callIndirectArgs: args, typ: typ, loc: loc)
|
||||
return hirCall(calleeName, args, typ, loc)
|
||||
else:
|
||||
let callee = ctx.lowerExpr(expr.exprCallCallee)
|
||||
@@ -1236,7 +1357,32 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
|
||||
of ekClosure:
|
||||
let f = ctx.lowerClosureFunc(expr)
|
||||
return hirUnary(tkAmp, hirVar(f.name, makeFunc(@[], makeVoid()), loc), typ, loc)
|
||||
if typ != nil and typ.kind == tkFunc:
|
||||
ctx.seenFatTypes.add(typ)
|
||||
let fatName = hirFuncFatTypeName(typ)
|
||||
if expr.captureCount > 0 and f.envStructName.len > 0:
|
||||
# Heap-allocate a fresh env so each closure value is independent
|
||||
let envTmp = "__envp_" & $ctx.varCounter
|
||||
inc ctx.varCounter
|
||||
let fatTmp = "__fat_" & $ctx.varCounter
|
||||
inc ctx.varCounter
|
||||
var code = ""
|
||||
code.add(&"{f.envStructName}* {envTmp} = ({f.envStructName}*)bux_alloc(sizeof({f.envStructName}));\n")
|
||||
for i in 0 ..< expr.captureCount:
|
||||
let capName = expr.captureNames[i]
|
||||
code.add(&"{envTmp}->{capName} = {capName};\n")
|
||||
code.add(&"{fatName} {fatTmp} = {{ .code = {f.name}, .env = {envTmp} }};")
|
||||
ctx.pendingStmts.add(HirNode(kind: hEmit, emitCode: code, typ: makeVoid(), loc: loc))
|
||||
return hirVar(fatTmp, typ, loc)
|
||||
else:
|
||||
# Capture-less: fat pointer with NULL env
|
||||
let nullEnv = HirNode(kind: hCast,
|
||||
castOperand: hirLit(Token(kind: tkIntLiteral, text: "0", loc: loc), makeInt(), loc),
|
||||
castType: makePointer(makeVoid()), typ: makePointer(makeVoid()), loc: loc)
|
||||
return HirNode(kind: hStructInit, structInitName: fatName, structInitFields: @[
|
||||
(name: "code", value: hirVar(f.name, makePointer(makeVoid()), loc)),
|
||||
(name: "env", value: nullEnv)
|
||||
], typ: typ, loc: loc)
|
||||
|
||||
else:
|
||||
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
|
||||
@@ -1255,28 +1401,14 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
if stmt.stmtLetInit != nil:
|
||||
initHir = ctx.lowerExpr(stmt.stmtLetInit)
|
||||
let allocaType = if stmt.stmtLetType != nil:
|
||||
case stmt.stmtLetType.kind
|
||||
of tekNamed:
|
||||
ctx.resolveTypeExpr(stmt.stmtLetType)
|
||||
of tekOwn:
|
||||
ctx.resolveTypeExpr(stmt.stmtLetType.pointerPointee)
|
||||
of tekPointer:
|
||||
let pointeeType = ctx.resolveTypeExpr(stmt.stmtLetType.pointerPointee)
|
||||
makePointer(pointeeType)
|
||||
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()
|
||||
# Full resolve covers named, pointer, slice, tuple, func, refs, etc.
|
||||
ctx.resolveTypeExpr(stmt.stmtLetType)
|
||||
elif stmt.stmtLetInit != nil:
|
||||
ctx.resolveExprType(stmt.stmtLetInit)
|
||||
else:
|
||||
makeUnknown()
|
||||
if allocaType != nil and allocaType.kind == tkFunc:
|
||||
ctx.seenFatTypes.add(allocaType)
|
||||
|
||||
let alloca = hirAlloca(stmt.stmtLetName, allocaType, loc)
|
||||
let varNode = hirVar(stmt.stmtLetName, makePointer(allocaType), loc)
|
||||
@@ -1296,22 +1428,7 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
if initHir != nil:
|
||||
let store = hirStore(varNode, initHir, loc)
|
||||
stmts.add(store)
|
||||
# If init is a closure with captures, emit capture assignments
|
||||
if stmt.stmtLetInit != nil and stmt.stmtLetInit.kind == ekClosure and stmt.stmtLetInit.captureCount > 0:
|
||||
let closureIdx = ctx.varCounter - 1
|
||||
let envInst = "__closure_env_instance_" & $closureIdx
|
||||
var capStmts: seq[HirNode] = @[]
|
||||
for i in 0 ..< stmt.stmtLetInit.captureCount:
|
||||
let capName = stmt.stmtLetInit.captureNames[i]
|
||||
let capType = if i < stmt.stmtLetInit.captureTypeKinds.len: Type(kind: TypeKind(stmt.stmtLetInit.captureTypeKinds[i])) else: makeInt()
|
||||
let base = hirVar(envInst, makeNamed(""), loc)
|
||||
let field = HirNode(kind: hFieldAccess, fieldAccessName: capName, fieldAccessBase: base, typ: capType, loc: loc)
|
||||
let val = hirVar(capName, capType, loc)
|
||||
capStmts.add(hirAssign(field, val, loc))
|
||||
# Prepend capture assignments before the let
|
||||
var allStmts = capStmts
|
||||
allStmts.add(stmts)
|
||||
return hirBlock(allStmts, nil, makeVoid(), loc)
|
||||
# Capture filling for closures is done at the ekClosure site (heap env).
|
||||
return hirBlock(stmts, nil, makeVoid(), loc)
|
||||
|
||||
of skReturn:
|
||||
@@ -1723,6 +1840,8 @@ proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc =
|
||||
let name = "__closure_" & $ctx.varCounter
|
||||
inc ctx.varCounter
|
||||
var f = HirFunc(name: name, isPublic: false)
|
||||
# Always take a leading env pointer (fat-func ABI); may be unused.
|
||||
f.params.add((name: "__env", typ: makePointer(makeVoid())))
|
||||
# Copy capture metadata
|
||||
if expr.captureCount > 0:
|
||||
f.captureNames = expr.captureNames
|
||||
@@ -1730,7 +1849,7 @@ proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc =
|
||||
f.captureTypes.add(Type(kind: TypeKind(tk)))
|
||||
f.envStructName = "__closure_env_" & $(ctx.varCounter - 1)
|
||||
f.envInstanceName = "__closure_env_instance_" & $(ctx.varCounter - 1)
|
||||
# Params
|
||||
# User 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
|
||||
@@ -1935,4 +2054,8 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
if allFound:
|
||||
vtableInfos.add((ifaceName, typeName, methodNames, hasAssoc))
|
||||
|
||||
result = HirModule(funcs: funcs, externFuncs: externFuncs, structs: structs, enums: enums, consts: consts, interfaces: ifaceInfos, vtables: vtableInfos)
|
||||
var adapters: seq[tuple[name: string, typ: Type]] = @[]
|
||||
for name in ctx.funcAdapters:
|
||||
let t = if ctx.funcAdapterSigs.hasKey(name): ctx.funcAdapterSigs[name] else: makeFunc(@[makeInt()], makeInt())
|
||||
adapters.add((name, t))
|
||||
result = HirModule(funcs: funcs, externFuncs: externFuncs, structs: structs, enums: enums, consts: consts, interfaces: ifaceInfos, vtables: vtableInfos, funcAdapters: adapters, seenFatTypes: ctx.seenFatTypes)
|
||||
|
||||
+2
-1
@@ -70,7 +70,8 @@ proc matchStr(lex: var Lexer, s: string): bool =
|
||||
return true
|
||||
|
||||
proc currentLocation(lex: Lexer): SourceLocation =
|
||||
result = SourceLocation(line: lex.line, column: lex.col, offset: uint32(lex.pos))
|
||||
result = SourceLocation(line: lex.line, column: lex.col, offset: uint32(lex.pos),
|
||||
file: lex.sourceName)
|
||||
|
||||
proc emitError(lex: var Lexer, loc: SourceLocation, message: string) =
|
||||
lex.diagnostics.add(LexerDiagnostic(severity: ldsError, loc: loc, message: message))
|
||||
|
||||
+199
-17
@@ -149,14 +149,22 @@ proc emitInstr(be: var LirCBackend, instr: LirInstr) =
|
||||
be.emitLine(&"{v(instr.src)}({argsStr});")
|
||||
|
||||
of lirCallIndirect:
|
||||
## Fat function pointer call: f.code(f.env, args...)
|
||||
var argsStr = ""
|
||||
for i, arg in instr.extra:
|
||||
if i > 0: argsStr.add(", ")
|
||||
argsStr.add(v(arg))
|
||||
let callee = v(instr.src)
|
||||
if instr.dst.kind != lvkVoid:
|
||||
be.emitLine(&"{v(instr.dst)} = ({v(instr.src)})({argsStr});")
|
||||
if argsStr.len > 0:
|
||||
be.emitLine(&"{v(instr.dst)} = ({callee}.code)({callee}.env, {argsStr});")
|
||||
else:
|
||||
be.emitLine(&"{v(instr.dst)} = ({callee}.code)({callee}.env);")
|
||||
else:
|
||||
be.emitLine(&"({v(instr.src)})({argsStr});")
|
||||
if argsStr.len > 0:
|
||||
be.emitLine(&"({callee}.code)({callee}.env, {argsStr});")
|
||||
else:
|
||||
be.emitLine(&"({callee}.code)({callee}.env);")
|
||||
|
||||
# ── Return ──
|
||||
of lirRet:
|
||||
@@ -348,6 +356,21 @@ proc emitFunc(be: var LirCBackend, f: LirFunc, funcRetTypes: Table[string, strin
|
||||
|
||||
# ── Struct/Enum emission (from HIR module) ──
|
||||
|
||||
proc sanitizeCTypeNamePart(s: string): string =
|
||||
result = s
|
||||
result = result.replace("const char*", "cstr")
|
||||
result = result.replace("unsigned int", "uint")
|
||||
result = result.replace(" ", "_")
|
||||
result = result.replace("*", "Ptr")
|
||||
result = result.replace("(", "")
|
||||
result = result.replace(")", "")
|
||||
result = result.replace(",", "_")
|
||||
result = result.replace(".", "_")
|
||||
|
||||
proc typeToCStr(typ: Type): string
|
||||
proc funcFatTypeName(typ: Type): string
|
||||
proc funcCodePtrType(typ: Type): string
|
||||
|
||||
proc typeToCStr(typ: Type): string =
|
||||
## Duplicate from lir_lower for self-containedness
|
||||
if typ == nil: return "int"
|
||||
@@ -396,13 +419,39 @@ proc typeToCStr(typ: Type): string =
|
||||
of "float64": return "double"
|
||||
of "bool": return "bool"
|
||||
else: return typ.name
|
||||
of tkTuple:
|
||||
if typ.inner.len == 0:
|
||||
return "Tuple_Empty"
|
||||
var parts: seq[string] = @[]
|
||||
for e in typ.inner:
|
||||
parts.add(sanitizeCTypeNamePart(typeToCStr(e)))
|
||||
return "Tuple_" & parts.join("_")
|
||||
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 & ")"
|
||||
return funcFatTypeName(typ)
|
||||
else: return "int"
|
||||
|
||||
proc funcFatTypeName(typ: Type): string =
|
||||
if typ == nil or typ.kind != tkFunc:
|
||||
return "BuxFn_void"
|
||||
let ret = if typ.inner.len > 0: typeToCStr(typ.inner[^1]) else: "void"
|
||||
var parts: seq[string] = @[sanitizeCTypeNamePart(ret)]
|
||||
if typ.inner.len > 1:
|
||||
for p in typ.inner[0 ..^ 2]:
|
||||
parts.add(sanitizeCTypeNamePart(typeToCStr(p)))
|
||||
else:
|
||||
parts.add("void")
|
||||
return "BuxFn_" & parts.join("_")
|
||||
|
||||
proc funcCodePtrType(typ: Type): string =
|
||||
if typ == nil or typ.kind != tkFunc:
|
||||
return "void (*)(void*)"
|
||||
let ret = if typ.inner.len > 0: typeToCStr(typ.inner[^1]) else: "void"
|
||||
var params: seq[string] = @["void* env"]
|
||||
if typ.inner.len > 1:
|
||||
for p in typ.inner[0 ..^ 2]:
|
||||
params.add(typeToCStr(p))
|
||||
return ret & " (*)(" & params.join(", ") & ")"
|
||||
|
||||
proc emitStructDef(be: var LirCBackend, name: string, fields: seq[tuple[name: string, typ: Type]]) =
|
||||
be.emitLine(&"typedef struct {name} {{")
|
||||
be.indent += 1
|
||||
@@ -481,11 +530,36 @@ proc collectValueDeps(typ: Type): seq[string] =
|
||||
return @[typ.name]
|
||||
of tkSlice:
|
||||
return @[typeToCStr(typ)]
|
||||
of tkPointer, tkRef, tkMutRef, tkTuple, tkFunc:
|
||||
of tkTuple:
|
||||
var deps: seq[string] = @[]
|
||||
for e in typ.inner:
|
||||
for d in collectValueDeps(e):
|
||||
if d notin deps:
|
||||
deps.add(d)
|
||||
if e != nil and e.kind == tkTuple:
|
||||
let tn = typeToCStr(e)
|
||||
if tn notin deps:
|
||||
deps.add(tn)
|
||||
return deps
|
||||
of tkPointer, tkRef, tkMutRef, tkFunc:
|
||||
return @[]
|
||||
else:
|
||||
return @[]
|
||||
|
||||
proc emitTupleDef(be: var LirCBackend, typ: Type) =
|
||||
## typedef struct { T0 _0; T1 _1; ... } Tuple_...;
|
||||
let name = typeToCStr(typ)
|
||||
be.emitLine(&"typedef struct {name} {{")
|
||||
be.indent += 1
|
||||
if typ.inner.len == 0:
|
||||
be.emitLine("char _pad;")
|
||||
else:
|
||||
for i, e in typ.inner:
|
||||
be.emitLine(&"{typeToCStr(e)} _{i};")
|
||||
be.indent -= 1
|
||||
be.emitLine(&"}} {name};")
|
||||
be.emitLine("")
|
||||
|
||||
proc emitSliceTypeDef(be: var LirCBackend, name: string, elem: string) =
|
||||
be.emitLine(&"typedef struct {{ {elem}* data; size_t len; }} {name};")
|
||||
|
||||
@@ -663,6 +737,104 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
|
||||
elif sliceMap.hasKey(name):
|
||||
be.emitSliceTypeDef(name, sliceMap[name])
|
||||
|
||||
# Collect and emit tuple typedefs used in the module (and nested tuples first).
|
||||
var tupleTypes: seq[Type] = @[]
|
||||
var tupleNames: HashSet[string]
|
||||
proc registerTuple(t: Type) =
|
||||
if t == nil: return
|
||||
case t.kind
|
||||
of tkTuple:
|
||||
for e in t.inner:
|
||||
registerTuple(e)
|
||||
let name = typeToCStr(t)
|
||||
if not tupleNames.contains(name):
|
||||
tupleNames.incl(name)
|
||||
tupleTypes.add(t)
|
||||
of tkPointer, tkRef, tkMutRef, tkSlice:
|
||||
if t.inner.len > 0:
|
||||
registerTuple(t.inner[0])
|
||||
of tkFunc:
|
||||
for e in t.inner:
|
||||
registerTuple(e)
|
||||
else:
|
||||
discard
|
||||
|
||||
for f in module.funcs:
|
||||
registerTuple(f.retType)
|
||||
for p in f.params:
|
||||
registerTuple(p.typ)
|
||||
for ef in module.externFuncs:
|
||||
registerTuple(ef.retType)
|
||||
for p in ef.params:
|
||||
registerTuple(p.typ)
|
||||
for s in module.structs:
|
||||
for f in s.fields:
|
||||
registerTuple(f.typ)
|
||||
for e in module.enums:
|
||||
for v in e.variants:
|
||||
for ft in v.fields:
|
||||
registerTuple(ft)
|
||||
for nf in v.namedFields:
|
||||
registerTuple(nf.typ)
|
||||
|
||||
if tupleTypes.len > 0:
|
||||
be.emitLine("/* Tuple types */")
|
||||
for tt in tupleTypes:
|
||||
be.emitTupleDef(tt)
|
||||
|
||||
# Fat function-pointer typedefs (BuxFn_*) — before forward decls that use them
|
||||
var fatTypes: seq[Type] = @[]
|
||||
var fatNames: HashSet[string]
|
||||
proc registerFat(t: Type) =
|
||||
if t == nil: return
|
||||
case t.kind
|
||||
of tkFunc:
|
||||
for e in t.inner: registerFat(e)
|
||||
let n = funcFatTypeName(t)
|
||||
if not fatNames.contains(n):
|
||||
fatNames.incl(n)
|
||||
fatTypes.add(t)
|
||||
of tkPointer, tkRef, tkMutRef, tkSlice:
|
||||
if t.inner.len > 0: registerFat(t.inner[0])
|
||||
of tkTuple:
|
||||
for e in t.inner: registerFat(e)
|
||||
else: discard
|
||||
for f in module.funcs:
|
||||
registerFat(f.retType)
|
||||
for p in f.params: registerFat(p.typ)
|
||||
for ef in module.externFuncs:
|
||||
registerFat(ef.retType)
|
||||
for p in ef.params: registerFat(p.typ)
|
||||
for a in module.funcAdapters:
|
||||
registerFat(a.typ)
|
||||
for t in module.seenFatTypes:
|
||||
registerFat(t)
|
||||
if fatTypes.len > 0:
|
||||
be.emitLine("/* Fat function pointer types (code + env) */")
|
||||
for ft in fatTypes:
|
||||
let n = funcFatTypeName(ft)
|
||||
let codeT = funcCodePtrType(ft)
|
||||
be.emitLine(&"typedef struct {n} {{")
|
||||
be.indent += 1
|
||||
be.emitLine(cParamDecl(codeT, "code") & ";")
|
||||
be.emitLine("void* env;")
|
||||
be.indent -= 1
|
||||
be.emitLine(&"}} {n};")
|
||||
be.emitLine("")
|
||||
|
||||
# Env structs for closures with captures (heap-allocated per value)
|
||||
for f in module.funcs:
|
||||
if f.captureNames.len > 0 and f.envStructName != "":
|
||||
be.emitLine(&"typedef struct {f.envStructName} {{")
|
||||
be.indent += 1
|
||||
for i in 0 ..< f.captureNames.len:
|
||||
let capName = f.captureNames[i]
|
||||
let capType = if i < f.captureTypes.len: typeToCStr(f.captureTypes[i]) else: "int"
|
||||
be.emitLine(&"{capType} {capName};")
|
||||
be.indent -= 1
|
||||
be.emitLine(&"}} {f.envStructName};")
|
||||
be.emitLine("")
|
||||
|
||||
# Forward function declarations
|
||||
for f in module.funcs:
|
||||
let rt = typeToCStr(f.retType)
|
||||
@@ -707,18 +879,28 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
|
||||
be.emitLine("};")
|
||||
be.emitLine("")
|
||||
|
||||
# Emit env structs for closures with captures
|
||||
for f in module.funcs:
|
||||
if f.captureNames.len > 0 and f.envStructName != "":
|
||||
be.emitLine(&"struct {f.envStructName} {{")
|
||||
# Adapters for named functions used as fat-func values (after forward decls)
|
||||
if module.funcAdapters.len > 0:
|
||||
be.emitLine("/* Fat-func adapters for named functions */")
|
||||
for a in module.funcAdapters:
|
||||
let ret = if a.typ.inner.len > 0: typeToCStr(a.typ.inner[^1]) else: "void"
|
||||
var params: seq[string] = @["void* env"]
|
||||
var argNames: seq[string] = @[]
|
||||
if a.typ.inner.len > 1:
|
||||
for i, p in a.typ.inner[0 ..^ 2]:
|
||||
let pn = "a" & $i
|
||||
params.add(typeToCStr(p) & " " & pn)
|
||||
argNames.add(pn)
|
||||
let argsStr = argNames.join(", ")
|
||||
be.emitLine(&"static {ret} __adapt_{a.name}({params.join(\", \")}) {{")
|
||||
be.indent += 1
|
||||
for i in 0 ..< f.captureNames.len:
|
||||
let capName = f.captureNames[i]
|
||||
let capType = if i < f.captureTypes.len: typeToCStr(f.captureTypes[i]) else: "int"
|
||||
be.emitLine(&"{capType} {capName};")
|
||||
be.emitLine("(void)env;")
|
||||
if ret == "void":
|
||||
be.emitLine(&"{a.name}({argsStr});")
|
||||
else:
|
||||
be.emitLine(&"return {a.name}({argsStr});")
|
||||
be.indent -= 1
|
||||
be.emitLine("};")
|
||||
be.emitLine(&"static struct {f.envStructName} {f.envInstanceName};")
|
||||
be.emitLine("}")
|
||||
be.emitLine("")
|
||||
|
||||
# Emit all LIR functions
|
||||
|
||||
+64
-5
@@ -57,6 +57,22 @@ proc cEscape(s: string): string =
|
||||
of '\0': result.add("\\0")
|
||||
else: result.add(c)
|
||||
|
||||
proc sanitizeCTypeNamePart(s: string): string =
|
||||
## Make a C type string safe for use inside a typedef name.
|
||||
result = s
|
||||
result = result.replace("const char*", "cstr")
|
||||
result = result.replace("unsigned int", "uint")
|
||||
result = result.replace(" ", "_")
|
||||
result = result.replace("*", "Ptr")
|
||||
result = result.replace("(", "")
|
||||
result = result.replace(")", "")
|
||||
result = result.replace(",", "_")
|
||||
result = result.replace(".", "_")
|
||||
|
||||
proc typeToCStr(typ: Type): string
|
||||
proc funcFatTypeName*(typ: Type): string
|
||||
proc funcCodePtrType*(typ: Type): string
|
||||
|
||||
proc typeToCStr(typ: Type): string =
|
||||
## Convert a Bux Type to a C type string.
|
||||
if typ == nil: return "int"
|
||||
@@ -105,13 +121,44 @@ proc typeToCStr(typ: Type): string =
|
||||
of "float64": return "double"
|
||||
of "bool": return "bool"
|
||||
else: return typ.name
|
||||
of tkTuple:
|
||||
## (T, U) → typedef struct { T _0; U _1; } Tuple_T_U;
|
||||
if typ.inner.len == 0:
|
||||
return "Tuple_Empty"
|
||||
var parts: seq[string] = @[]
|
||||
for e in typ.inner:
|
||||
parts.add(sanitizeCTypeNamePart(typeToCStr(e)))
|
||||
return "Tuple_" & parts.join("_")
|
||||
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 & ")"
|
||||
## Fat function pointer: { code(env, args...), env }
|
||||
## Enables multi-instance closures with captures.
|
||||
return funcFatTypeName(typ)
|
||||
else: return "int"
|
||||
|
||||
proc funcFatTypeName*(typ: Type): string =
|
||||
## BuxFn_<ret>_<params...> (sanitized)
|
||||
if typ == nil or typ.kind != tkFunc:
|
||||
return "BuxFn_void"
|
||||
let ret = if typ.inner.len > 0: typeToCStr(typ.inner[^1]) else: "void"
|
||||
var parts: seq[string] = @[sanitizeCTypeNamePart(ret)]
|
||||
if typ.inner.len > 1:
|
||||
for p in typ.inner[0 ..^ 2]:
|
||||
parts.add(sanitizeCTypeNamePart(typeToCStr(p)))
|
||||
else:
|
||||
parts.add("void")
|
||||
return "BuxFn_" & parts.join("_")
|
||||
|
||||
proc funcCodePtrType*(typ: Type): string =
|
||||
## C type of the .code field: ret (*)(void* env, params...)
|
||||
if typ == nil or typ.kind != tkFunc:
|
||||
return "void (*)(void*)"
|
||||
let ret = if typ.inner.len > 0: typeToCStr(typ.inner[^1]) else: "void"
|
||||
var params: seq[string] = @["void* env"]
|
||||
if typ.inner.len > 1:
|
||||
for i, p in typ.inner[0 ..^ 2]:
|
||||
params.add(typeToCStr(p))
|
||||
return ret & " (*)(" & params.join(", ") & ")"
|
||||
|
||||
proc hirTypeToC(ctx: var LowerToLirCtx, node: HirNode): string =
|
||||
if node == nil: return "int"
|
||||
result = typeToCStr(node.typ)
|
||||
@@ -498,7 +545,12 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
for e in node.tupleInitElements:
|
||||
elems.add(lowerExpr(ctx, e))
|
||||
let t = b.freshTemp()
|
||||
b.emitRawC(&"/* tuple */ {t.strVal} = {{{elems.mapIt($it).join(\", \")}}};")
|
||||
let typeName = typeToCStr(node.typ)
|
||||
b.emitAlloca(t.strVal, typeName)
|
||||
var fields: seq[string] = @[]
|
||||
for i, e in elems:
|
||||
fields.add(&"._{i} = {lirValToC(e)}")
|
||||
b.emitRawC(&"{t.strVal} = ({typeName}){{{fields.join(\", \")}}};")
|
||||
return t
|
||||
|
||||
# ── If expression (ternary) ──
|
||||
@@ -780,6 +832,13 @@ proc lowerModuleToLir*(hirMod: HirModule): LirBuilder =
|
||||
ctx.funcRetType = retCT
|
||||
ctx.builder.beginFunc(f.name, params, retCT, f.isPublic)
|
||||
|
||||
# Closure thunks: materialize env from fat-func env pointer (by-value copy)
|
||||
if f.captureNames.len > 0 and f.envStructName.len > 0 and f.envInstanceName.len > 0:
|
||||
ctx.builder.emitRawC(&"struct {f.envStructName} {f.envInstanceName} = *((struct {f.envStructName}*)__env);")
|
||||
elif f.params.len > 0 and f.params[0].name == "__env":
|
||||
# Capture-less closure thunk still receives env
|
||||
ctx.builder.emitRawC("(void)__env;")
|
||||
|
||||
if f.body != nil:
|
||||
if f.body.kind == hBlock:
|
||||
for stmt in f.body.blockStmts:
|
||||
|
||||
@@ -660,11 +660,15 @@ proc parsePostfix(p: var Parser): Expr =
|
||||
discard p.expect(tkRBracket, "expected ']' to close index")
|
||||
left = Expr(kind: ekIndex, loc: loc, exprIndexObj: left, exprIndexIdx: idx, exprIndexBoundsCheck: false)
|
||||
of tkDot:
|
||||
# Field expression or .await
|
||||
# Field expression, tuple index (.0, .1), or .await
|
||||
discard p.advance()
|
||||
if p.check(tkAwait):
|
||||
discard p.advance()
|
||||
left = Expr(kind: ekAwait, loc: loc, exprAwaitOperand: left)
|
||||
elif p.check(tkIntLiteral):
|
||||
# Tuple element access: t.0 → field "_0"
|
||||
let idxText = p.advance().text
|
||||
left = Expr(kind: ekField, loc: loc, exprFieldObj: left, exprFieldName: "_" & idxText)
|
||||
else:
|
||||
let fieldName = p.expectIdentOrKeyword("expected field name after '.'").text
|
||||
left = Expr(kind: ekField, loc: loc, exprFieldObj: left, exprFieldName: fieldName)
|
||||
|
||||
+16
-1
@@ -1249,6 +1249,20 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
# Auto-dereference pointer/reference types for field access
|
||||
if objType.kind in {tkPointer, tkRef, tkMutRef} and objType.inner.len > 0:
|
||||
objType = objType.inner[0]
|
||||
if objType.kind == tkTuple:
|
||||
# Tuple fields: .0 / .1 → stored as "_0" / "_1"
|
||||
var idx = -1
|
||||
let fname = expr.exprFieldName
|
||||
if fname.len > 0 and fname[0] == '_':
|
||||
try: idx = parseInt(fname[1..^1])
|
||||
except ValueError: idx = -1
|
||||
else:
|
||||
try: idx = parseInt(fname)
|
||||
except ValueError: idx = -1
|
||||
if idx >= 0 and idx < objType.inner.len:
|
||||
return objType.inner[idx]
|
||||
sema.emitError(expr.loc, &"tuple has no element '{fname}' (tuple arity {objType.inner.len})")
|
||||
return makeUnknown()
|
||||
if objType.kind == tkNamed:
|
||||
# Check if this is a _Data union field access
|
||||
if objType.name.endsWith("_Data"):
|
||||
@@ -1468,7 +1482,8 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
|
||||
initType = sema.checkExpr(stmt.stmtLetInit, scope)
|
||||
let declaredType = if stmt.stmtLetType != nil: sema.resolveType(stmt.stmtLetType) else: initType
|
||||
if stmt.stmtLetInit != nil and stmt.stmtLetType != nil and not initType.isAssignableTo(declaredType) and not (initType.kind in {TypeKind.tkUnknown, TypeKind.tkNamed, TypeKind.tkTypeParam}):
|
||||
sema.emitError(stmt.loc, &"cannot assign {initType.toString} to {declaredType.toString}")
|
||||
# Point at the initializer expression for a clearer caret
|
||||
sema.emitError(stmt.stmtLetInit.loc, &"cannot assign {initType.toString} to {declaredType.toString}")
|
||||
if stmt.stmtLetInit == nil and stmt.stmtLetType == nil:
|
||||
sema.emitError(stmt.loc, "variable must have either type annotation or initializer")
|
||||
let isOwnVar = stmt.stmtLetType != nil and stmt.stmtLetType.kind == tekOwn
|
||||
|
||||
@@ -3,6 +3,10 @@ type
|
||||
line*: uint32 ## 1-based
|
||||
column*: uint32 ## 1-based (UTF-8 byte offset in line)
|
||||
offset*: uint32 ## byte offset from start of file
|
||||
file*: string ## source file path (empty if unknown)
|
||||
|
||||
proc `$`*(loc: SourceLocation): string =
|
||||
$loc.line & ":" & $loc.column
|
||||
if loc.file.len > 0:
|
||||
loc.file & ":" & $loc.line & ":" & $loc.column
|
||||
else:
|
||||
$loc.line & ":" & $loc.column
|
||||
|
||||
Reference in New Issue
Block a user