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:
@@ -3,9 +3,9 @@ SRC := bootstrap/main.nim
|
|||||||
OUT := buxc
|
OUT := buxc
|
||||||
BUILD_DIR := build
|
BUILD_DIR := build
|
||||||
|
|
||||||
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt
|
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure
|
||||||
|
|
||||||
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden selfhost-loop lsp
|
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden test-errors selfhost-loop lsp
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ dev:
|
|||||||
debug: dev
|
debug: dev
|
||||||
@echo "Debug binary: buxc_debug"
|
@echo "Debug binary: buxc_debug"
|
||||||
|
|
||||||
test: build test-examples
|
test: build test-examples test-errors
|
||||||
@echo "Running lexer tests..."
|
@echo "Running lexer tests..."
|
||||||
$(NIM) c -r tests/lexer_test.nim
|
$(NIM) c -r tests/lexer_test.nim
|
||||||
@echo "Running parser tests..."
|
@echo "Running parser tests..."
|
||||||
@@ -100,6 +100,11 @@ test-golden: build
|
|||||||
echo "Golden tests: $$passed passed, $$failed failed"; \
|
echo "Golden tests: $$passed passed, $$failed failed"; \
|
||||||
if [ $$failed -gt 0 ]; then exit 1; fi
|
if [ $$failed -gt 0 ]; then exit 1; fi
|
||||||
|
|
||||||
|
test-errors: build
|
||||||
|
@echo "=== Error diagnostic golden tests ==="
|
||||||
|
@chmod +x tests/error_golden/run.sh
|
||||||
|
@tests/error_golden/run.sh ./$(OUT)
|
||||||
|
|
||||||
selfhost-loop: build
|
selfhost-loop: build
|
||||||
@echo "=== Selfhost loop: bootstrap determinism check ==="
|
@echo "=== Selfhost loop: bootstrap determinism check ==="
|
||||||
@echo "Build A..."
|
@echo "Build A..."
|
||||||
|
|||||||
+30
-6
@@ -128,8 +128,31 @@ proc typeToC*(be: var CBackend, typ: Type): string =
|
|||||||
of "float64": return "double"
|
of "float64": return "double"
|
||||||
of "bool": return "bool"
|
of "bool": return "bool"
|
||||||
else: return typ.name
|
else: return typ.name
|
||||||
of tkTuple: return "void*" # TODO: proper tuple struct
|
of tkTuple:
|
||||||
of tkFunc: return "void*" # TODO: function pointer
|
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:
|
else:
|
||||||
when defined(release):
|
when defined(release):
|
||||||
return "void*"
|
return "void*"
|
||||||
@@ -311,10 +334,11 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
|
|||||||
return &"({base}).data[{idx}]"
|
return &"({base}).data[{idx}]"
|
||||||
|
|
||||||
of hTupleInit:
|
of hTupleInit:
|
||||||
var elems: seq[string] = @[]
|
let typeName = typeToC(be, node.typ)
|
||||||
for e in node.tupleInitElements:
|
var fields: seq[string] = @[]
|
||||||
elems.add(be.emitExpr(e))
|
for i, e in node.tupleInitElements:
|
||||||
return &"{{{elems.join(\", \")}}}"
|
fields.add(&"._{i} = {be.emitExpr(e)}")
|
||||||
|
return &"(({typeName}){{{fields.join(\", \")}}})"
|
||||||
|
|
||||||
of hCast:
|
of hCast:
|
||||||
let operand = be.emitExpr(node.castOperand)
|
let operand = be.emitExpr(node.castOperand)
|
||||||
|
|||||||
+202
-10
@@ -1,5 +1,6 @@
|
|||||||
import std/[os, strutils, terminal, strformat, osproc, sets]
|
import std/[os, strutils, terminal, strformat, osproc, sets]
|
||||||
import lexer, parser, ast, sema, manifest, hir_lower, lir_lower, lir_c_backend
|
import lexer, parser, ast, sema, manifest, hir_lower, lir_lower, lir_c_backend
|
||||||
|
import source_location
|
||||||
|
|
||||||
type
|
type
|
||||||
ColorMode* = enum
|
ColorMode* = enum
|
||||||
@@ -89,6 +90,203 @@ proc printInfo(msg: string, useColor: bool) =
|
|||||||
else:
|
else:
|
||||||
echo("info: " & msg)
|
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
|
# Commands
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -304,14 +502,12 @@ proc prepareProject(root: string, useColor: bool, opts: GlobalOptions): (Project
|
|||||||
let lexRes = tokenize(source, path)
|
let lexRes = tokenize(source, path)
|
||||||
if lexRes.hasErrors:
|
if lexRes.hasErrors:
|
||||||
printError(&"lex errors in {path}", useColor)
|
printError(&"lex errors in {path}", useColor)
|
||||||
for d in lexRes.diagnostics:
|
printLexerDiags(lexRes.diagnostics, useColor, path)
|
||||||
echo $d
|
|
||||||
return (pctx, 1)
|
return (pctx, 1)
|
||||||
let parseRes = parse(lexRes.tokens, path)
|
let parseRes = parse(lexRes.tokens, path)
|
||||||
if parseRes.diagnostics.len > 0:
|
if parseRes.diagnostics.len > 0:
|
||||||
printError(&"parse errors in {path}", useColor)
|
printError(&"parse errors in {path}", useColor)
|
||||||
for d in parseRes.diagnostics:
|
printParserDiags(parseRes.diagnostics, useColor, path)
|
||||||
echo &"error: {d.message} at {d.loc}"
|
|
||||||
return (pctx, 1)
|
return (pctx, 1)
|
||||||
for decl in parseRes.module.items:
|
for decl in parseRes.module.items:
|
||||||
if decl.kind == dkModule:
|
if decl.kind == dkModule:
|
||||||
@@ -345,9 +541,7 @@ proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
|
|||||||
let semaRes = analyze(unifiedModule)
|
let semaRes = analyze(unifiedModule)
|
||||||
if semaRes.hasErrors:
|
if semaRes.hasErrors:
|
||||||
printError("type errors in project", useColor)
|
printError("type errors in project", useColor)
|
||||||
for d in semaRes.diagnostics:
|
printSemaDiags(semaRes.diagnostics, useColor)
|
||||||
let sev = if d.severity == sdsError: "error" else: "warning"
|
|
||||||
echo &"{sev}: {d.message} at {d.loc}"
|
|
||||||
return 1
|
return 1
|
||||||
if not opts.quiet:
|
if not opts.quiet:
|
||||||
printInfo("check passed", useColor)
|
printInfo("check passed", useColor)
|
||||||
@@ -448,9 +642,7 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
|||||||
let (semaRes, semaCtx) = analyzeFull(unifiedModule)
|
let (semaRes, semaCtx) = analyzeFull(unifiedModule)
|
||||||
if semaRes.hasErrors:
|
if semaRes.hasErrors:
|
||||||
printError("type errors in project", useColor)
|
printError("type errors in project", useColor)
|
||||||
for d in semaRes.diagnostics:
|
printSemaDiags(semaRes.diagnostics, useColor)
|
||||||
let sev = if d.severity == sdsError: "error" else: "warning"
|
|
||||||
echo &"{sev}: {d.message} at {d.loc}"
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
let hirMod = lowerModule(unifiedModule, semaCtx)
|
let hirMod = lowerModule(unifiedModule, semaCtx)
|
||||||
|
|||||||
@@ -189,6 +189,10 @@ type
|
|||||||
consts*: seq[tuple[name: string, typ: Type, value: HirNode]]
|
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]]]]
|
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]]
|
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
|
# Constructor helpers
|
||||||
proc hirLit*(tok: Token, typ: Type, loc: SourceLocation): HirNode =
|
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
|
import ast, types, token, source_location, hir, sema, scope
|
||||||
|
|
||||||
type
|
type
|
||||||
@@ -25,6 +25,11 @@ type
|
|||||||
closureDepth*: int
|
closureDepth*: int
|
||||||
currentClosureExpr*: Expr
|
currentClosureExpr*: Expr
|
||||||
envInstanceName*: string
|
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 =
|
proc freshName(ctx: var LowerCtx): string =
|
||||||
inc ctx.varCounter
|
inc ctx.varCounter
|
||||||
@@ -159,6 +164,50 @@ proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
|
|||||||
result.generatedFuncInsts = initTable[string, bool]()
|
result.generatedFuncInsts = initTable[string, bool]()
|
||||||
result.extraFuncs = @[]
|
result.extraFuncs = @[]
|
||||||
result.varTypeExprs = initTable[string, TypeExpr]()
|
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
|
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 tekOwn: return ctx.resolveTypeExpr(te.pointerPointee)
|
||||||
of tekDynRef: return makeDynRef(te.dynInterface)
|
of tekDynRef: return makeDynRef(te.dynInterface)
|
||||||
of tekPointer: return makePointer(ctx.resolveTypeExpr(te.pointerPointee))
|
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 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:
|
of tekFunc:
|
||||||
var params: seq[Type] = @[]
|
var params: seq[Type] = @[]
|
||||||
for p in te.funcParams:
|
for p in te.funcParams:
|
||||||
@@ -523,6 +579,18 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
|||||||
return makeVoid()
|
return makeVoid()
|
||||||
of ekBorrow:
|
of ekBorrow:
|
||||||
return ctx.resolveExprType(expr.exprBorrowOperand)
|
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()
|
else: return makeUnknown()
|
||||||
|
|
||||||
proc extractGenericStructInfo(ctx: LowerCtx, te: TypeExpr): tuple[baseName: string, typeArgs: seq[TypeExpr]] =
|
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 capType = if idx < ctx.currentClosureExpr.captureTypeKinds.len: Type(kind: TypeKind(ctx.currentClosureExpr.captureTypeKinds[idx])) else: makeInt()
|
||||||
let base = hirVar(ctx.envInstanceName, makeNamed(""), loc)
|
let base = hirVar(ctx.envInstanceName, makeNamed(""), loc)
|
||||||
return HirNode(kind: hFieldAccess, fieldAccessName: name, fieldAccessBase: base, typ: capType, loc: loc)
|
return HirNode(kind: hFieldAccess, fieldAccessName: name, fieldAccessBase: base, typ: capType, loc: loc)
|
||||||
|
var resolvedName = name
|
||||||
if ctx.importTable.hasKey(name):
|
if ctx.importTable.hasKey(name):
|
||||||
return hirVar(ctx.importTable[name], typ, loc)
|
resolvedName = ctx.importTable[name]
|
||||||
return hirVar(name, typ, loc)
|
# 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:
|
of ekPath:
|
||||||
# Handle enum variants: Color::Red → Color_Red
|
# Handle enum variants: Color::Red → Color_Red
|
||||||
@@ -756,6 +841,32 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
return hirSelf(typ, loc)
|
return hirSelf(typ, loc)
|
||||||
|
|
||||||
of ekUnary:
|
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)
|
let operand = ctx.lowerExpr(expr.exprUnaryOperand)
|
||||||
return hirUnary(expr.exprUnaryOp, operand, typ, loc)
|
return hirUnary(expr.exprUnaryOp, operand, typ, loc)
|
||||||
|
|
||||||
@@ -883,6 +994,16 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
calleeName = expr.exprCallCallee.exprPath.join("_")
|
calleeName = expr.exprCallCallee.exprPath.join("_")
|
||||||
let args = ctx.lowerCallArgs(expr.exprCallCallee, expr.exprCallArgs)
|
let args = ctx.lowerCallArgs(expr.exprCallCallee, expr.exprCallArgs)
|
||||||
if calleeName != "":
|
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)
|
return hirCall(calleeName, args, typ, loc)
|
||||||
else:
|
else:
|
||||||
let callee = ctx.lowerExpr(expr.exprCallCallee)
|
let callee = ctx.lowerExpr(expr.exprCallCallee)
|
||||||
@@ -1236,7 +1357,32 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
|
|
||||||
of ekClosure:
|
of ekClosure:
|
||||||
let f = ctx.lowerClosureFunc(expr)
|
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:
|
else:
|
||||||
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
|
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:
|
if stmt.stmtLetInit != nil:
|
||||||
initHir = ctx.lowerExpr(stmt.stmtLetInit)
|
initHir = ctx.lowerExpr(stmt.stmtLetInit)
|
||||||
let allocaType = if stmt.stmtLetType != nil:
|
let allocaType = if stmt.stmtLetType != nil:
|
||||||
case stmt.stmtLetType.kind
|
# Full resolve covers named, pointer, slice, tuple, func, refs, etc.
|
||||||
of tekNamed:
|
ctx.resolveTypeExpr(stmt.stmtLetType)
|
||||||
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()
|
|
||||||
elif stmt.stmtLetInit != nil:
|
elif stmt.stmtLetInit != nil:
|
||||||
ctx.resolveExprType(stmt.stmtLetInit)
|
ctx.resolveExprType(stmt.stmtLetInit)
|
||||||
else:
|
else:
|
||||||
makeUnknown()
|
makeUnknown()
|
||||||
|
if allocaType != nil and allocaType.kind == tkFunc:
|
||||||
|
ctx.seenFatTypes.add(allocaType)
|
||||||
|
|
||||||
let alloca = hirAlloca(stmt.stmtLetName, allocaType, loc)
|
let alloca = hirAlloca(stmt.stmtLetName, allocaType, loc)
|
||||||
let varNode = hirVar(stmt.stmtLetName, makePointer(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:
|
if initHir != nil:
|
||||||
let store = hirStore(varNode, initHir, loc)
|
let store = hirStore(varNode, initHir, loc)
|
||||||
stmts.add(store)
|
stmts.add(store)
|
||||||
# If init is a closure with captures, emit capture assignments
|
# Capture filling for closures is done at the ekClosure site (heap env).
|
||||||
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)
|
|
||||||
return hirBlock(stmts, nil, makeVoid(), loc)
|
return hirBlock(stmts, nil, makeVoid(), loc)
|
||||||
|
|
||||||
of skReturn:
|
of skReturn:
|
||||||
@@ -1723,6 +1840,8 @@ proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc =
|
|||||||
let name = "__closure_" & $ctx.varCounter
|
let name = "__closure_" & $ctx.varCounter
|
||||||
inc ctx.varCounter
|
inc ctx.varCounter
|
||||||
var f = HirFunc(name: name, isPublic: false)
|
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
|
# Copy capture metadata
|
||||||
if expr.captureCount > 0:
|
if expr.captureCount > 0:
|
||||||
f.captureNames = expr.captureNames
|
f.captureNames = expr.captureNames
|
||||||
@@ -1730,7 +1849,7 @@ proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc =
|
|||||||
f.captureTypes.add(Type(kind: TypeKind(tk)))
|
f.captureTypes.add(Type(kind: TypeKind(tk)))
|
||||||
f.envStructName = "__closure_env_" & $(ctx.varCounter - 1)
|
f.envStructName = "__closure_env_" & $(ctx.varCounter - 1)
|
||||||
f.envInstanceName = "__closure_env_instance_" & $(ctx.varCounter - 1)
|
f.envInstanceName = "__closure_env_instance_" & $(ctx.varCounter - 1)
|
||||||
# Params
|
# User params
|
||||||
for p in expr.exprClosureParams:
|
for p in expr.exprClosureParams:
|
||||||
f.params.add((name: p.name, typ: if p.ptype != nil: ctx.resolveTypeExpr(p.ptype) else: makeUnknown()))
|
f.params.add((name: p.name, typ: if p.ptype != nil: ctx.resolveTypeExpr(p.ptype) else: makeUnknown()))
|
||||||
# Return type
|
# Return type
|
||||||
@@ -1935,4 +2054,8 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
|||||||
if allFound:
|
if allFound:
|
||||||
vtableInfos.add((ifaceName, typeName, methodNames, hasAssoc))
|
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
|
return true
|
||||||
|
|
||||||
proc currentLocation(lex: Lexer): SourceLocation =
|
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) =
|
proc emitError(lex: var Lexer, loc: SourceLocation, message: string) =
|
||||||
lex.diagnostics.add(LexerDiagnostic(severity: ldsError, loc: loc, message: message))
|
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});")
|
be.emitLine(&"{v(instr.src)}({argsStr});")
|
||||||
|
|
||||||
of lirCallIndirect:
|
of lirCallIndirect:
|
||||||
|
## Fat function pointer call: f.code(f.env, args...)
|
||||||
var argsStr = ""
|
var argsStr = ""
|
||||||
for i, arg in instr.extra:
|
for i, arg in instr.extra:
|
||||||
if i > 0: argsStr.add(", ")
|
if i > 0: argsStr.add(", ")
|
||||||
argsStr.add(v(arg))
|
argsStr.add(v(arg))
|
||||||
|
let callee = v(instr.src)
|
||||||
if instr.dst.kind != lvkVoid:
|
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:
|
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 ──
|
# ── Return ──
|
||||||
of lirRet:
|
of lirRet:
|
||||||
@@ -348,6 +356,21 @@ proc emitFunc(be: var LirCBackend, f: LirFunc, funcRetTypes: Table[string, strin
|
|||||||
|
|
||||||
# ── Struct/Enum emission (from HIR module) ──
|
# ── 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 =
|
proc typeToCStr(typ: Type): string =
|
||||||
## Duplicate from lir_lower for self-containedness
|
## Duplicate from lir_lower for self-containedness
|
||||||
if typ == nil: return "int"
|
if typ == nil: return "int"
|
||||||
@@ -396,13 +419,39 @@ proc typeToCStr(typ: Type): string =
|
|||||||
of "float64": return "double"
|
of "float64": return "double"
|
||||||
of "bool": return "bool"
|
of "bool": return "bool"
|
||||||
else: return typ.name
|
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:
|
of tkFunc:
|
||||||
if typ.inner.len == 0: return "void (*)(void)"
|
return funcFatTypeName(typ)
|
||||||
let params = typ.inner[0..^2].mapIt(typeToCStr(it)).join(", ")
|
|
||||||
let ret = typeToCStr(typ.inner[^1])
|
|
||||||
return ret & " (*)(" & params & ")"
|
|
||||||
else: return "int"
|
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]]) =
|
proc emitStructDef(be: var LirCBackend, name: string, fields: seq[tuple[name: string, typ: Type]]) =
|
||||||
be.emitLine(&"typedef struct {name} {{")
|
be.emitLine(&"typedef struct {name} {{")
|
||||||
be.indent += 1
|
be.indent += 1
|
||||||
@@ -481,11 +530,36 @@ proc collectValueDeps(typ: Type): seq[string] =
|
|||||||
return @[typ.name]
|
return @[typ.name]
|
||||||
of tkSlice:
|
of tkSlice:
|
||||||
return @[typeToCStr(typ)]
|
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 @[]
|
return @[]
|
||||||
else:
|
else:
|
||||||
return @[]
|
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) =
|
proc emitSliceTypeDef(be: var LirCBackend, name: string, elem: string) =
|
||||||
be.emitLine(&"typedef struct {{ {elem}* data; size_t len; }} {name};")
|
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):
|
elif sliceMap.hasKey(name):
|
||||||
be.emitSliceTypeDef(name, sliceMap[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
|
# Forward function declarations
|
||||||
for f in module.funcs:
|
for f in module.funcs:
|
||||||
let rt = typeToCStr(f.retType)
|
let rt = typeToCStr(f.retType)
|
||||||
@@ -707,18 +879,28 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
|
|||||||
be.emitLine("};")
|
be.emitLine("};")
|
||||||
be.emitLine("")
|
be.emitLine("")
|
||||||
|
|
||||||
# Emit env structs for closures with captures
|
# Adapters for named functions used as fat-func values (after forward decls)
|
||||||
for f in module.funcs:
|
if module.funcAdapters.len > 0:
|
||||||
if f.captureNames.len > 0 and f.envStructName != "":
|
be.emitLine("/* Fat-func adapters for named functions */")
|
||||||
be.emitLine(&"struct {f.envStructName} {{")
|
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
|
be.indent += 1
|
||||||
for i in 0 ..< f.captureNames.len:
|
be.emitLine("(void)env;")
|
||||||
let capName = f.captureNames[i]
|
if ret == "void":
|
||||||
let capType = if i < f.captureTypes.len: typeToCStr(f.captureTypes[i]) else: "int"
|
be.emitLine(&"{a.name}({argsStr});")
|
||||||
be.emitLine(&"{capType} {capName};")
|
else:
|
||||||
|
be.emitLine(&"return {a.name}({argsStr});")
|
||||||
be.indent -= 1
|
be.indent -= 1
|
||||||
be.emitLine("};")
|
be.emitLine("}")
|
||||||
be.emitLine(&"static struct {f.envStructName} {f.envInstanceName};")
|
|
||||||
be.emitLine("")
|
be.emitLine("")
|
||||||
|
|
||||||
# Emit all LIR functions
|
# Emit all LIR functions
|
||||||
|
|||||||
+64
-5
@@ -57,6 +57,22 @@ proc cEscape(s: string): string =
|
|||||||
of '\0': result.add("\\0")
|
of '\0': result.add("\\0")
|
||||||
else: result.add(c)
|
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 =
|
proc typeToCStr(typ: Type): string =
|
||||||
## Convert a Bux Type to a C type string.
|
## Convert a Bux Type to a C type string.
|
||||||
if typ == nil: return "int"
|
if typ == nil: return "int"
|
||||||
@@ -105,13 +121,44 @@ proc typeToCStr(typ: Type): string =
|
|||||||
of "float64": return "double"
|
of "float64": return "double"
|
||||||
of "bool": return "bool"
|
of "bool": return "bool"
|
||||||
else: return typ.name
|
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:
|
of tkFunc:
|
||||||
if typ.inner.len == 0: return "void (*)(void)"
|
## Fat function pointer: { code(env, args...), env }
|
||||||
let params = typ.inner[0..^2].mapIt(typeToCStr(it)).join(", ")
|
## Enables multi-instance closures with captures.
|
||||||
let ret = typeToCStr(typ.inner[^1])
|
return funcFatTypeName(typ)
|
||||||
return ret & " (*)(" & params & ")"
|
|
||||||
else: return "int"
|
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 =
|
proc hirTypeToC(ctx: var LowerToLirCtx, node: HirNode): string =
|
||||||
if node == nil: return "int"
|
if node == nil: return "int"
|
||||||
result = typeToCStr(node.typ)
|
result = typeToCStr(node.typ)
|
||||||
@@ -498,7 +545,12 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
|||||||
for e in node.tupleInitElements:
|
for e in node.tupleInitElements:
|
||||||
elems.add(lowerExpr(ctx, e))
|
elems.add(lowerExpr(ctx, e))
|
||||||
let t = b.freshTemp()
|
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
|
return t
|
||||||
|
|
||||||
# ── If expression (ternary) ──
|
# ── If expression (ternary) ──
|
||||||
@@ -780,6 +832,13 @@ proc lowerModuleToLir*(hirMod: HirModule): LirBuilder =
|
|||||||
ctx.funcRetType = retCT
|
ctx.funcRetType = retCT
|
||||||
ctx.builder.beginFunc(f.name, params, retCT, f.isPublic)
|
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 != nil:
|
||||||
if f.body.kind == hBlock:
|
if f.body.kind == hBlock:
|
||||||
for stmt in f.body.blockStmts:
|
for stmt in f.body.blockStmts:
|
||||||
|
|||||||
@@ -660,11 +660,15 @@ proc parsePostfix(p: var Parser): Expr =
|
|||||||
discard p.expect(tkRBracket, "expected ']' to close index")
|
discard p.expect(tkRBracket, "expected ']' to close index")
|
||||||
left = Expr(kind: ekIndex, loc: loc, exprIndexObj: left, exprIndexIdx: idx, exprIndexBoundsCheck: false)
|
left = Expr(kind: ekIndex, loc: loc, exprIndexObj: left, exprIndexIdx: idx, exprIndexBoundsCheck: false)
|
||||||
of tkDot:
|
of tkDot:
|
||||||
# Field expression or .await
|
# Field expression, tuple index (.0, .1), or .await
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
if p.check(tkAwait):
|
if p.check(tkAwait):
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
left = Expr(kind: ekAwait, loc: loc, exprAwaitOperand: left)
|
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:
|
else:
|
||||||
let fieldName = p.expectIdentOrKeyword("expected field name after '.'").text
|
let fieldName = p.expectIdentOrKeyword("expected field name after '.'").text
|
||||||
left = Expr(kind: ekField, loc: loc, exprFieldObj: left, exprFieldName: fieldName)
|
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
|
# Auto-dereference pointer/reference types for field access
|
||||||
if objType.kind in {tkPointer, tkRef, tkMutRef} and objType.inner.len > 0:
|
if objType.kind in {tkPointer, tkRef, tkMutRef} and objType.inner.len > 0:
|
||||||
objType = objType.inner[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:
|
if objType.kind == tkNamed:
|
||||||
# Check if this is a _Data union field access
|
# Check if this is a _Data union field access
|
||||||
if objType.name.endsWith("_Data"):
|
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)
|
initType = sema.checkExpr(stmt.stmtLetInit, scope)
|
||||||
let declaredType = if stmt.stmtLetType != nil: sema.resolveType(stmt.stmtLetType) else: initType
|
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}):
|
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:
|
if stmt.stmtLetInit == nil and stmt.stmtLetType == nil:
|
||||||
sema.emitError(stmt.loc, "variable must have either type annotation or initializer")
|
sema.emitError(stmt.loc, "variable must have either type annotation or initializer")
|
||||||
let isOwnVar = stmt.stmtLetType != nil and stmt.stmtLetType.kind == tekOwn
|
let isOwnVar = stmt.stmtLetType != nil and stmt.stmtLetType.kind == tekOwn
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ type
|
|||||||
line*: uint32 ## 1-based
|
line*: uint32 ## 1-based
|
||||||
column*: uint32 ## 1-based (UTF-8 byte offset in line)
|
column*: uint32 ## 1-based (UTF-8 byte offset in line)
|
||||||
offset*: uint32 ## byte offset from start of file
|
offset*: uint32 ## byte offset from start of file
|
||||||
|
file*: string ## source file path (empty if unknown)
|
||||||
|
|
||||||
proc `$`*(loc: SourceLocation): string =
|
proc `$`*(loc: SourceLocation): string =
|
||||||
$loc.line & ":" & $loc.column
|
if loc.file.len > 0:
|
||||||
|
loc.file & ":" & $loc.line & ":" & $loc.column
|
||||||
|
else:
|
||||||
|
$loc.line & ":" & $loc.column
|
||||||
|
|||||||
+42
-2
@@ -105,10 +105,50 @@ f"Hello, {name}" // Interpolated string — expressions inside {}
|
|||||||
own T // Owned value (move semantics)
|
own T // Owned value (move semantics)
|
||||||
T[] // Slice (unsized)
|
T[] // Slice (unsized)
|
||||||
T[N] // Fixed-size array
|
T[N] // Fixed-size array
|
||||||
(T1, T2, T3) // Tuple
|
(T1, T2, T3) // Tuple — access fields with .0, .1, .2
|
||||||
func(T1) -> T2 // Function type
|
func(T1) -> T2 // Function pointer type
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Tuples
|
||||||
|
```bux
|
||||||
|
func Pair(a: int, b: int) -> (int, int) {
|
||||||
|
return (a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let t: (int, int) = Pair(10, 20);
|
||||||
|
PrintInt(t.0); // 10
|
||||||
|
PrintInt(t.1); // 20
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Function pointers and closures
|
||||||
|
```bux
|
||||||
|
func Apply(f: func(int) -> int, x: int) -> int {
|
||||||
|
return f(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Double(n: int) -> int { return n * 2; }
|
||||||
|
|
||||||
|
func MakeAdder(base: int) -> func(int) -> int {
|
||||||
|
// Each call allocates its own capture environment
|
||||||
|
return |a: int| -> int { return a + base; };
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let g: func(int) -> int = Double; // named func → fat pointer
|
||||||
|
let a10 = MakeAdder(10);
|
||||||
|
let a20 = MakeAdder(20);
|
||||||
|
// a10 and a20 are independent instances
|
||||||
|
return Apply(g, 21) + a10(1) + a20(1); // 42 + 11 + 21
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`func(T) -> R` values are **fat pointers** `{ code, env }`:
|
||||||
|
- capturing closures store captures in a heap env
|
||||||
|
- capture-less closures and named functions use `env = null`
|
||||||
|
|
||||||
### Structs
|
### Structs
|
||||||
```bux
|
```bux
|
||||||
struct Point {
|
struct Point {
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
# Bux — План към „добър“ език (v0.5 → v1.0)
|
||||||
|
|
||||||
|
> **Дата:** 2026-07-15
|
||||||
|
> **Текущо:** v0.5.0 — selfhost loop, gradual ownership, green threads, 26+ examples ✅
|
||||||
|
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Диагноза (къде сме)
|
||||||
|
|
||||||
|
| Слой | Състояние | Оценка |
|
||||||
|
|------|-----------|--------|
|
||||||
|
| Frontend (lex/parse) | Пълен Pratt parser, recovery | ★★★★☆ |
|
||||||
|
| Sema / generics | Monomorphization, trait bounds basic | ★★★★☆ |
|
||||||
|
| HIR → C | Работи; tuples/func-ptr half-baked в bootstrap | ★★★☆☆ |
|
||||||
|
| Selfhost (`src/`) | ~12k LOC, binary-identical loop | ★★★★★ |
|
||||||
|
| Gradual ownership | `@[Checked]`, `&`/`&mut`, move, Drop | ★★★☆☆ (basic) |
|
||||||
|
| Concurrency | M:N tasks + channels + async | ★★★★☆ |
|
||||||
|
| Stdlib | 25+ модула, но колекциите са минимални | ★★★☆☆ |
|
||||||
|
| Tooling | `new/build/run/test/fmt`, LSP prototype, VSCode | ★★☆☆☆ |
|
||||||
|
| Ecosystem / registry | path+git deps; няма централен registry | ★☆☆☆☆ |
|
||||||
|
| Документация | Има, но drift (PLAN vs README версии) | ★★★☆☆ |
|
||||||
|
|
||||||
|
**Силна ниша:** gradual ownership (C-скорост на писане + opt-in Rust-safety).
|
||||||
|
**Слабо място:** ergonomics на stdlib + maturity на tooling + пълнота на borrow checker.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Какво значи „добър“ за Bux
|
||||||
|
|
||||||
|
1. **Ежедневен DX** — колекции, string, assert, грешки, които разбираш за секунди.
|
||||||
|
2. **Предвидима безопасност** — `@[Checked]` да хваща 80% от UAF/double-borrow без lifetime hell.
|
||||||
|
3. **Selfhost като dogfood** — компилаторът и apps (`nexus`, `boko`) са proof.
|
||||||
|
4. **Инструменти** — fmt, test, LSP, package install без ръчна магия.
|
||||||
|
5. **Стабилна спецификация** — LanguageRef = реалното поведение.
|
||||||
|
|
||||||
|
Не целим „по-добър Rust“. Целим **единствения език с gradual safety + Go-стил concurrency без GC**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Фази
|
||||||
|
|
||||||
|
### A — Ergonomics & Stdlib (P0, сега) 🔄
|
||||||
|
|
||||||
|
| # | Задача | Защо | Статус |
|
||||||
|
|---|--------|------|--------|
|
||||||
|
| A.1 | Array: Pop, Clear, IsEmpty, First, Last, Cap, Reserve | Без това колекциите са неудобни | ✅ (тази сесия) |
|
||||||
|
| A.2 | String: IsEmpty, ReplaceAll | Чести операции; само first-replace досега | ✅ (тази сесия) |
|
||||||
|
| A.3 | Os_Exit + Test_AssertEqString / richer asserts | Тестове и CLI без raw `bux_exit` | ✅ (тази сесия) |
|
||||||
|
| A.4 | Map_Remove / Set polish | Completeness на колекциите | ✅ (тази сесия) |
|
||||||
|
| A.5 | Iter: map/filter/fold върху closures | Higher-order без boilerplate | ⏳ |
|
||||||
|
| A.6 | Result helpers: Expect, UnwrapErr, Or | По-малко match boilerplate | ✅ (тази сесия) |
|
||||||
|
|
||||||
|
### B — Compiler Correctness (P0)
|
||||||
|
|
||||||
|
| # | Задача | Защо | Статус |
|
||||||
|
|---|--------|------|--------|
|
||||||
|
| B.1 | Proper tuple types в C backend | `(T,U)` → `Tuple_T_U` struct + `.0`/`.1` | ✅ |
|
||||||
|
| B.2 | Function pointer types | `func(T)->U` вече работи в LIR backend | ✅ |
|
||||||
|
| B.3 | Match expression до край в C (не `return "0"`) | Expression-context match |
|
||||||
|
| B.4 | Closures: multi-instance + loop/return в body | Реални higher-order callbacks |
|
||||||
|
| B.5 | По-добри diagnostics (snippet + hint) | DX #1 за нови потребители | ✅ |
|
||||||
|
| B.6 | Bootstrap ↔ selfhost feature parity | Operator overloading, string interp и в selfhost |
|
||||||
|
|
||||||
|
### C — Gradual Ownership 2.0 (P1)
|
||||||
|
|
||||||
|
| # | Задача | Защо |
|
||||||
|
|---|--------|------|
|
||||||
|
| C.1 | Lifetime elision за common cases | Без `'a` в 90% от API-тата |
|
||||||
|
| C.2 | Exclusive `&mut` vs shared `&` data-flow | По-малко false negatives |
|
||||||
|
| C.3 | Auto-drop edge cases (early return, branches) | RAII да е надежден |
|
||||||
|
| C.4 | `@[Release]` zero-cost path документация + golden tests | Killer story: safe default, free hot path |
|
||||||
|
|
||||||
|
### D — Tooling (P1)
|
||||||
|
|
||||||
|
| # | Задача | Защо |
|
||||||
|
|---|--------|------|
|
||||||
|
| D.1 | LSP: hover, go-to-def, diagnostics (wire към sema) | IDE = adoption |
|
||||||
|
| D.2 | `bux fmt` стабилен + CI check | Единен style |
|
||||||
|
| D.3 | `bux test` с `--filter`, exit codes, summary table | CI-friendly |
|
||||||
|
| D.4 | `bux doc` от `///` comments | Самодокументиращ се stdlib |
|
||||||
|
| D.5 | Golden tests за stdlib modules | Регресии без изненади |
|
||||||
|
|
||||||
|
### E — Ecosystem & v1.0 (P2)
|
||||||
|
|
||||||
|
| # | Задача | Защо |
|
||||||
|
|---|--------|------|
|
||||||
|
| E.1 | Package registry protocol (git/HTTP) | `bux add foo` без path hacks |
|
||||||
|
| E.2 | 3–5 production-quality apps в `apps/` | Showcase |
|
||||||
|
| E.3 | Language freeze + semver policy | Trust |
|
||||||
|
| E.4 | Debugger/DWARF basics | Systems audience |
|
||||||
|
| E.5 | Benchmarks vs C/Zig/Nim (micro + nexus) | Marketing + regression |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Препоръчан ред на работа
|
||||||
|
|
||||||
|
```
|
||||||
|
A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
|
||||||
|
↓ ↓
|
||||||
|
D (tooling) ←────────── dogfood apps
|
||||||
|
↓
|
||||||
|
E (v1.0 ecosystem)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Правило:** всяка сесия ship-ва нещо runnable (stdlib API, fix, example), не само docs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acceptance criteria за „добър v1.0“
|
||||||
|
|
||||||
|
- [ ] Всички examples + selfhost-loop + 3 apps минават на CI
|
||||||
|
- [ ] Array/Map/String/Test API покрива 90% от ежедневните нужди
|
||||||
|
- [ ] `@[Checked]` хваща use-after-move + double `&mut` в documented subset
|
||||||
|
- [ ] `bux test` + `bux fmt` + `bux check` са default developer loop
|
||||||
|
- [ ] LanguageRef синхронизиран с компилатора
|
||||||
|
- [ ] Поне един външен проект (не в monorepo) build-ва с git dep
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Сесия 1 (stdlib ergonomics)
|
||||||
|
|
||||||
|
1. `Array_Pop`, `Array_Clear`, `Array_IsEmpty`, `Array_First`, `Array_Last`, `Array_Cap`, `Array_Reserve`
|
||||||
|
2. `String_IsEmpty`, `String_ReplaceAll`
|
||||||
|
3. `Os_Exit`
|
||||||
|
4. `Test_AssertEqString`, `Test_AssertNeqInt`, `Test_AssertEqBool`
|
||||||
|
5. Example + docs update
|
||||||
|
|
||||||
|
## Сесия 2 (collections + tuples)
|
||||||
|
|
||||||
|
1. `Map_Remove` / `Map_Clear` / `Map_IsEmpty` (+ StringMap)
|
||||||
|
2. `Set_Remove` / `Set_Clear` / `Set_IsEmpty`
|
||||||
|
3. `Result_Expect` / `Result_UnwrapErr` / `Result_Or`
|
||||||
|
4. `Option_Expect` / `Option_Or`
|
||||||
|
5. **Tuples:** `(T, U)` → `typedef struct { T _0; U _1; } Tuple_T_U` + field access `.0`/`.1`
|
||||||
|
6. Examples: `tuples`, `func_ptr`, `map_remove`
|
||||||
|
|
||||||
|
## Сесия 3 (diagnostics + collections)
|
||||||
|
|
||||||
|
1. **Rust-style errors** in bootstrap CLI: `--> file:line:col`, source snippet, `^` caret, `= help:` hints
|
||||||
|
2. `SourceLocation.file` propagated from lexer
|
||||||
|
3. Better caret for type-mismatch on `let` (points at initializer)
|
||||||
|
4. `Array_Contains` / `Array_IndexOf` / `Array_Extend`
|
||||||
|
5. `Iter_AnyEq` / `Iter_AllEq` / `Iter_Collect`
|
||||||
|
6. Selfhost `Diagnostic_Hint` for common messages
|
||||||
|
|
||||||
|
## Сесия 4 (diagnostics depth + LSP + strings)
|
||||||
|
|
||||||
|
1. **Multi-char underlines** (`^^^^^^^` under tokens/strings/idents)
|
||||||
|
2. Quoted-name highlighting for `undeclared identifier 'x'`
|
||||||
|
3. **Golden error tests** (`tests/error_golden/`, `make test-errors`)
|
||||||
|
4. **LSP** runs `buxc check` and publishes real diagnostics
|
||||||
|
5. `String_IsBlank` / `String_Repeat`
|
||||||
|
|
||||||
|
## Сесия 5 (multi-instance closures)
|
||||||
|
|
||||||
|
1. **Fat function pointers** for all `func(...)` types: `BuxFn { code(env, args...), env }`
|
||||||
|
2. Capturing closures: heap-allocate env per creation site (independent instances)
|
||||||
|
3. Capture-less closures + named funcs: adapters with `env = NULL`
|
||||||
|
4. Calls through func values: `f.code(f.env, args...)`
|
||||||
|
5. Example `multi_closure.bux` — MakeAdder(10)/MakeAdder(20) yield 11 and 21
|
||||||
|
6. **Selfhost parity:** same fat ABI in `src/hir_lower.bux` + `src/c_backend.bux` (makers, adapters)
|
||||||
|
```
|
||||||
+3
-1
@@ -153,7 +153,9 @@ Array_Filter(nums, |x| { return x > 10; });
|
|||||||
4. In thunk body: rewrite captured identifiers to `env_instance.x` via `hFieldAccess`.
|
4. In thunk body: rewrite captured identifiers to `env_instance.x` via `hFieldAccess`.
|
||||||
5. C backend: emit env struct definition + global instance before thunk function.
|
5. C backend: emit env struct definition + global instance before thunk function.
|
||||||
|
|
||||||
**Limitations:** One global instance per closure AST node (no multiple instances). No loop/return support in closures yet.
|
**Status:** Multi-instance capturing closures work in **both** bootstrap and selfhost via fat
|
||||||
|
function pointers (`BuxFn { code, env }` + heap-allocated env per creation). Capture-less
|
||||||
|
closures and named functions use the same ABI (`env = NULL`, adapters for named funcs).
|
||||||
|
|
||||||
**Complexity:** High — touches parser, sema, type system, HIR/LIR backend.
|
**Complexity:** High — touches parser, sema, type system, HIR/LIR backend.
|
||||||
|
|
||||||
|
|||||||
+50
-1
@@ -81,8 +81,19 @@ struct Array<T> {
|
|||||||
|----------|-----------|-------------|
|
|----------|-----------|-------------|
|
||||||
| `Array_New<T>` | `func Array_New<T>(cap: uint) -> Array<T>` | Create new array |
|
| `Array_New<T>` | `func Array_New<T>(cap: uint) -> Array<T>` | Create new array |
|
||||||
| `Array_Push<T>` | `func Array_Push<T>(arr: *Array<T>, value: T)` | Append element |
|
| `Array_Push<T>` | `func Array_Push<T>(arr: *Array<T>, value: T)` | Append element |
|
||||||
|
| `Array_Pop<T>` | `func Array_Pop<T>(arr: *Array<T>) -> T` | Remove and return last element |
|
||||||
|
| `Array_Contains<T>` | `func Array_Contains<T>(arr: *Array<T>, value: T) -> bool` | Linear search for value |
|
||||||
|
| `Array_IndexOf<T>` | `func Array_IndexOf<T>(arr: *Array<T>, value: T) -> int` | First index or -1 |
|
||||||
|
| `Array_Extend<T>` | `func Array_Extend<T>(arr: *Array<T>, other: *Array<T>)` | Append all from other |
|
||||||
| `Array_Get<T>` | `func Array_Get<T>(arr: *Array<T>, index: uint) -> T` | Get element at index |
|
| `Array_Get<T>` | `func Array_Get<T>(arr: *Array<T>, index: uint) -> T` | Get element at index |
|
||||||
|
| `Array_Set<T>` | `func Array_Set<T>(arr: *Array<T>, index: uint, value: T)` | Set element at index |
|
||||||
|
| `Array_First<T>` | `func Array_First<T>(arr: *Array<T>) -> T` | First element (bounds-checked) |
|
||||||
|
| `Array_Last<T>` | `func Array_Last<T>(arr: *Array<T>) -> T` | Last element (bounds-checked) |
|
||||||
| `Array_Len<T>` | `func Array_Len<T>(arr: *Array<T>) -> uint` | Get length |
|
| `Array_Len<T>` | `func Array_Len<T>(arr: *Array<T>) -> uint` | Get length |
|
||||||
|
| `Array_Cap<T>` | `func Array_Cap<T>(arr: *Array<T>) -> uint` | Get capacity |
|
||||||
|
| `Array_IsEmpty<T>` | `func Array_IsEmpty<T>(arr: *Array<T>) -> bool` | True if length is 0 |
|
||||||
|
| `Array_Clear<T>` | `func Array_Clear<T>(arr: *Array<T>)` | Set length to 0 (keeps capacity) |
|
||||||
|
| `Array_Reserve<T>` | `func Array_Reserve<T>(arr: *Array<T>, minCap: uint)` | Grow capacity if needed |
|
||||||
| `Array_Free<T>` | `func Array_Free<T>(arr: *Array<T>)` | Free memory |
|
| `Array_Free<T>` | `func Array_Free<T>(arr: *Array<T>)` | Free memory |
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
@@ -128,6 +139,9 @@ struct Iter<T> {
|
|||||||
| `Iter_Count<T>` | `func Iter_Count<T>(it: *Iter<T>) -> uint` | Count remaining elements |
|
| `Iter_Count<T>` | `func Iter_Count<T>(it: *Iter<T>) -> uint` | Count remaining elements |
|
||||||
| `Iter_Skip<T>` | `func Iter_Skip<T>(it: *Iter<T>, n: uint)` | Skip N elements |
|
| `Iter_Skip<T>` | `func Iter_Skip<T>(it: *Iter<T>, n: uint)` | Skip N elements |
|
||||||
| `Iter_Take<T>` | `func Iter_Take<T>(it: *Iter<T>, n: uint) -> Iter<T>` | Take first N elements as new iterator |
|
| `Iter_Take<T>` | `func Iter_Take<T>(it: *Iter<T>, n: uint) -> Iter<T>` | Take first N elements as new iterator |
|
||||||
|
| `Iter_AnyEq<T>` | `func Iter_AnyEq<T>(it: *Iter<T>, value: T) -> bool` | True if any remaining element equals value |
|
||||||
|
| `Iter_AllEq<T>` | `func Iter_AllEq<T>(it: *Iter<T>, value: T) -> bool` | True if all remaining equal value |
|
||||||
|
| `Iter_Collect<T>` | `func Iter_Collect<T>(it: *Iter<T>) -> Array<T>` | Collect remaining into a new Array |
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
```bux
|
```bux
|
||||||
@@ -200,8 +214,12 @@ String manipulation utilities.
|
|||||||
|
|
||||||
| Function | Signature | Description |
|
| Function | Signature | Description |
|
||||||
|----------|-----------|-------------|
|
|----------|-----------|-------------|
|
||||||
|
| `String_IsEmpty` | `func String_IsEmpty(s: String) -> bool` | True if length is 0 |
|
||||||
|
| `String_IsBlank` | `func String_IsBlank(s: String) -> bool` | True if empty or only whitespace |
|
||||||
|
| `String_Repeat` | `func String_Repeat(s: String, count: uint) -> String` | Repeat string N times |
|
||||||
| `String_Find` | `func String_Find(haystack: String, needle: String) -> String` | Find substring (returns pointer; 0 = not found) |
|
| `String_Find` | `func String_Find(haystack: String, needle: String) -> String` | Find substring (returns pointer; 0 = not found) |
|
||||||
| `String_Replace` | `func String_Replace(s: String, old: String, new: String) -> String` | Replace first occurrence |
|
| `String_Replace` | `func String_Replace(s: String, old: String, new: String) -> String` | Replace first occurrence |
|
||||||
|
| `String_ReplaceAll` | `func String_ReplaceAll(s: String, old: String, new: String) -> String` | Replace all non-overlapping occurrences |
|
||||||
| `String_Format1` | `func String_Format1(pattern: String, a0: String) -> String` | Format with 1 arg (`{0}`) |
|
| `String_Format1` | `func String_Format1(pattern: String, a0: String) -> String` | Format with 1 arg (`{0}`) |
|
||||||
| `String_Format2` | `func String_Format2(pattern: String, a0: String, a1: String) -> String` | Format with 2 args |
|
| `String_Format2` | `func String_Format2(pattern: String, a0: String, a1: String) -> String` | Format with 2 args |
|
||||||
| `String_Format3` | `func String_Format3(pattern: String, a0: String, a1: String, a2: String) -> String` | Format with 3 args |
|
| `String_Format3` | `func String_Format3(pattern: String, a0: String, a1: String, a2: String) -> String` | Format with 3 args |
|
||||||
@@ -322,6 +340,9 @@ struct Set<T> {
|
|||||||
| `Set_New<T>` | `func Set_New<T>(cap: uint) -> Set<T>` | Create set |
|
| `Set_New<T>` | `func Set_New<T>(cap: uint) -> Set<T>` | Create set |
|
||||||
| `Set_Add<T>` | `func Set_Add<T>(s: *Set<T>, value: T)` | Insert element (ignores duplicates) |
|
| `Set_Add<T>` | `func Set_Add<T>(s: *Set<T>, value: T)` | Insert element (ignores duplicates) |
|
||||||
| `Set_Has<T>` | `func Set_Has<T>(s: *Set<T>, value: T) -> bool` | Check membership |
|
| `Set_Has<T>` | `func Set_Has<T>(s: *Set<T>, value: T) -> bool` | Check membership |
|
||||||
|
| `Set_Remove<T>` | `func Set_Remove<T>(s: *Set<T>, value: T) -> bool` | Remove value |
|
||||||
|
| `Set_Clear<T>` | `func Set_Clear<T>(s: *Set<T>)` | Clear all elements |
|
||||||
|
| `Set_IsEmpty<T>` | `func Set_IsEmpty<T>(s: *Set<T>) -> bool` | True if empty |
|
||||||
| `Set_Len<T>` | `func Set_Len<T>(s: *Set<T>) -> uint` | Element count |
|
| `Set_Len<T>` | `func Set_Len<T>(s: *Set<T>) -> uint` | Element count |
|
||||||
| `Set_Free<T>` | `func Set_Free<T>(s: *Set<T>)` | Free memory |
|
| `Set_Free<T>` | `func Set_Free<T>(s: *Set<T>)` | Free memory |
|
||||||
|
|
||||||
@@ -371,6 +392,9 @@ struct Map<K, V> {
|
|||||||
| `Map_Set<K,V>` | `func Map_Set<K,V>(m: *Map<K,V>, key: K, value: V)` | Insert/update |
|
| `Map_Set<K,V>` | `func Map_Set<K,V>(m: *Map<K,V>, key: K, value: V)` | Insert/update |
|
||||||
| `Map_Get<K,V>` | `func Map_Get<K,V>(m: *Map<K,V>, key: K) -> V` | Get value (zero if missing) |
|
| `Map_Get<K,V>` | `func Map_Get<K,V>(m: *Map<K,V>, key: K) -> V` | Get value (zero if missing) |
|
||||||
| `Map_Has<K,V>` | `func Map_Has<K,V>(m: *Map<K,V>, key: K) -> bool` | Check key exists |
|
| `Map_Has<K,V>` | `func Map_Has<K,V>(m: *Map<K,V>, key: K) -> bool` | Check key exists |
|
||||||
|
| `Map_Remove<K,V>` | `func Map_Remove<K,V>(m: *Map<K,V>, key: K) -> bool` | Remove key (true if present) |
|
||||||
|
| `Map_Clear<K,V>` | `func Map_Clear<K,V>(m: *Map<K,V>)` | Remove all entries (keeps capacity) |
|
||||||
|
| `Map_IsEmpty<K,V>` | `func Map_IsEmpty<K,V>(m: *Map<K,V>) -> bool` | True if no entries |
|
||||||
| `Map_Len<K,V>` | `func Map_Len<K,V>(m: *Map<K,V>) -> uint` | Entry count |
|
| `Map_Len<K,V>` | `func Map_Len<K,V>(m: *Map<K,V>) -> uint` | Entry count |
|
||||||
| `Map_Free<K,V>` | `func Map_Free<K,V>(m: *Map<K,V>)` | Free memory |
|
| `Map_Free<K,V>` | `func Map_Free<K,V>(m: *Map<K,V>)` | Free memory |
|
||||||
|
|
||||||
@@ -419,6 +443,9 @@ struct StringMap<V> {
|
|||||||
| `StringMap_Set<V>` | `func StringMap_Set<V>(m: *StringMap<V>, key: String, value: V)` | Insert/update |
|
| `StringMap_Set<V>` | `func StringMap_Set<V>(m: *StringMap<V>, key: String, value: V)` | Insert/update |
|
||||||
| `StringMap_Get<V>` | `func StringMap_Get<V>(m: *StringMap<V>, key: String) -> V` | Get value |
|
| `StringMap_Get<V>` | `func StringMap_Get<V>(m: *StringMap<V>, key: String) -> V` | Get value |
|
||||||
| `StringMap_Has<V>` | `func StringMap_Has<V>(m: *StringMap<V>, key: String) -> bool` | Check key exists |
|
| `StringMap_Has<V>` | `func StringMap_Has<V>(m: *StringMap<V>, key: String) -> bool` | Check key exists |
|
||||||
|
| `StringMap_Remove<V>` | `func StringMap_Remove<V>(m: *StringMap<V>, key: String) -> bool` | Remove key |
|
||||||
|
| `StringMap_Clear<V>` | `func StringMap_Clear<V>(m: *StringMap<V>)` | Clear all entries |
|
||||||
|
| `StringMap_IsEmpty<V>` | `func StringMap_IsEmpty<V>(m: *StringMap<V>) -> bool` | True if empty |
|
||||||
| `StringMap_Len<V>` | `func StringMap_Len<V>(m: *StringMap<V>) -> uint` | Entry count |
|
| `StringMap_Len<V>` | `func StringMap_Len<V>(m: *StringMap<V>) -> uint` | Entry count |
|
||||||
| `StringMap_Free<V>` | `func StringMap_Free<V>(m: *StringMap<V>)` | Free memory |
|
| `StringMap_Free<V>` | `func StringMap_Free<V>(m: *StringMap<V>)` | Free memory |
|
||||||
|
|
||||||
@@ -914,7 +941,7 @@ func Main() -> int {
|
|||||||
Operating system interface.
|
Operating system interface.
|
||||||
|
|
||||||
```bux
|
```bux
|
||||||
import Std::Os::{Os_ArgsCount, Os_Args, Os_GetEnv, Os_SetEnv, Os_GetCwd, Os_Chdir};
|
import Std::Os::{Os_ArgsCount, Os_Args, Os_GetEnv, Os_SetEnv, Os_GetCwd, Os_Chdir, Os_Exit};
|
||||||
```
|
```
|
||||||
|
|
||||||
| Function | Signature | Description |
|
| Function | Signature | Description |
|
||||||
@@ -925,6 +952,28 @@ import Std::Os::{Os_ArgsCount, Os_Args, Os_GetEnv, Os_SetEnv, Os_GetCwd, Os_Chdi
|
|||||||
| `Os_SetEnv` | `func Os_SetEnv(name: String, value: String) -> bool` | Set environment variable |
|
| `Os_SetEnv` | `func Os_SetEnv(name: String, value: String) -> bool` | Set environment variable |
|
||||||
| `Os_GetCwd` | `func Os_GetCwd() -> String` | Get current working directory |
|
| `Os_GetCwd` | `func Os_GetCwd() -> String` | Get current working directory |
|
||||||
| `Os_Chdir` | `func Os_Chdir(path: String) -> bool` | Change directory |
|
| `Os_Chdir` | `func Os_Chdir(path: String) -> bool` | Change directory |
|
||||||
|
| `Os_Exit` | `func Os_Exit(code: int)` | Terminate process with exit code |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Std::Test
|
||||||
|
|
||||||
|
Lightweight assertions for `bux test` and example programs.
|
||||||
|
|
||||||
|
```bux
|
||||||
|
import Std::Test::*;
|
||||||
|
```
|
||||||
|
|
||||||
|
| Function | Signature | Description |
|
||||||
|
|----------|-----------|-------------|
|
||||||
|
| `Test_Assert` | `func Test_Assert(cond: bool)` | Panic if false |
|
||||||
|
| `Test_AssertTrue` / `Test_AssertFalse` | `func ...(cond: bool)` | Boolean asserts |
|
||||||
|
| `Test_AssertEqInt` | `func Test_AssertEqInt(a: int, b: int)` | Integer equality |
|
||||||
|
| `Test_AssertNeqInt` | `func Test_AssertNeqInt(a: int, b: int)` | Integer inequality |
|
||||||
|
| `Test_AssertEqString` | `func Test_AssertEqString(a: String, b: String)` | String equality |
|
||||||
|
| `Test_AssertEqBool` | `func Test_AssertEqBool(a: bool, b: bool)` | Boolean equality |
|
||||||
|
| `Test_Fail` / `Test_Pass` | `func ...(msg: String)` | Explicit fail / log pass |
|
||||||
|
| `Test_Exit` | `func Test_Exit(code: int)` | Exit with code |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Array_Contains / IndexOf / Extend + Iter_Collect / AnyEq
|
||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Array::{
|
||||||
|
Array, Array_New, Array_Push, Array_Contains, Array_IndexOf,
|
||||||
|
Array_Extend, Array_Len, Array_Get, Array_Free
|
||||||
|
};
|
||||||
|
import Std::Iter::{Array_Iter, Iter, Iter_Collect, Iter_AnyEq, Iter_AllEq, Iter_HasNext, Iter_Next};
|
||||||
|
import Std::Test::{Test_AssertTrue, Test_AssertFalse, Test_AssertEqInt, Test_Pass};
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
var a: Array<int> = Array_New<int>(4);
|
||||||
|
Array_Push<int>(&a, 10);
|
||||||
|
Array_Push<int>(&a, 20);
|
||||||
|
Array_Push<int>(&a, 30);
|
||||||
|
|
||||||
|
Test_AssertTrue(Array_Contains<int>(&a, 20));
|
||||||
|
Test_AssertFalse(Array_Contains<int>(&a, 99));
|
||||||
|
Test_AssertEqInt(Array_IndexOf<int>(&a, 30), 2);
|
||||||
|
Test_AssertEqInt(Array_IndexOf<int>(&a, 99), -1);
|
||||||
|
|
||||||
|
var b: Array<int> = Array_New<int>(2);
|
||||||
|
Array_Push<int>(&b, 40);
|
||||||
|
Array_Push<int>(&b, 50);
|
||||||
|
Array_Extend<int>(&a, &b);
|
||||||
|
Test_AssertEqInt(Array_Len<int>(&a) as int, 5);
|
||||||
|
Test_AssertEqInt(Array_Get<int>(&a, 4), 50);
|
||||||
|
|
||||||
|
var it: Iter<int> = Array_Iter<int>(&a);
|
||||||
|
Test_AssertTrue(Iter_AnyEq<int>(&it, 10));
|
||||||
|
Test_AssertFalse(Iter_AllEq<int>(&it, 10));
|
||||||
|
|
||||||
|
// Skip first two via advancing, collect rest
|
||||||
|
discard Iter_Next<int>(&it);
|
||||||
|
discard Iter_Next<int>(&it);
|
||||||
|
var rest: Array<int> = Iter_Collect<int>(&it);
|
||||||
|
Test_AssertEqInt(Array_Len<int>(&rest) as int, 3);
|
||||||
|
Test_AssertEqInt(Array_Get<int>(&rest, 0), 30);
|
||||||
|
|
||||||
|
Array_Free<int>(&a);
|
||||||
|
Array_Free<int>(&b);
|
||||||
|
Array_Free<int>(&rest);
|
||||||
|
|
||||||
|
PrintLine("array_iter_extra: ok");
|
||||||
|
Test_Pass("array + iter extras");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Function pointer types: func(T) -> U
|
||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Test::{Test_AssertEqInt, Test_Pass};
|
||||||
|
|
||||||
|
func Apply(f: func(int) -> int, x: int) -> int {
|
||||||
|
return f(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Double(n: int) -> int {
|
||||||
|
return n * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Inc(n: int) -> int {
|
||||||
|
return n + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let g: func(int) -> int = Double;
|
||||||
|
Test_AssertEqInt(Apply(g, 21), 42);
|
||||||
|
Test_AssertEqInt(Apply(Double, 7), 14);
|
||||||
|
Test_AssertEqInt(Apply(Inc, 99), 100);
|
||||||
|
|
||||||
|
PrintInt(Apply(Double, 5));
|
||||||
|
PrintLine("");
|
||||||
|
Test_Pass("func_ptr");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// Map_Remove / Map_Clear / Set_Remove
|
||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Map::{Map, Map_New, Map_Set, Map_Get, Map_Has, Map_Remove, Map_Clear, Map_Len, Map_IsEmpty, Map_Free};
|
||||||
|
import Std::Set::{Set, Set_New, Set_Add, Set_Has, Set_Remove, Set_Len, Set_IsEmpty, Set_Free};
|
||||||
|
import Std::Test::{Test_AssertTrue, Test_AssertFalse, Test_AssertEqInt, Test_Pass};
|
||||||
|
import Std::Result::{Result, Result_NewOk, Result_NewErr, Result_IsOk, Result_IsErr, Result_UnwrapOr, Result_Or, Result_UnwrapErr};
|
||||||
|
import Std::Option::{Option, Option_NewSome, Option_NewNone, Option_IsSome, Option_Or, Option_UnwrapOr};
|
||||||
|
import Std::String::{String_Eq};
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
// --- Map ---
|
||||||
|
var m: Map<int, int> = Map_New<int, int>(16);
|
||||||
|
Map_Set<int, int>(&m, 1, 100);
|
||||||
|
Map_Set<int, int>(&m, 2, 200);
|
||||||
|
Map_Set<int, int>(&m, 3, 300);
|
||||||
|
Test_AssertEqInt(Map_Len<int, int>(&m) as int, 3);
|
||||||
|
Test_AssertTrue(Map_Has<int, int>(&m, 2));
|
||||||
|
Test_AssertTrue(Map_Remove<int, int>(&m, 2));
|
||||||
|
Test_AssertFalse(Map_Has<int, int>(&m, 2));
|
||||||
|
Test_AssertEqInt(Map_Len<int, int>(&m) as int, 2);
|
||||||
|
Test_AssertEqInt(Map_Get<int, int>(&m, 1), 100);
|
||||||
|
Test_AssertEqInt(Map_Get<int, int>(&m, 3), 300);
|
||||||
|
Test_AssertFalse(Map_Remove<int, int>(&m, 99));
|
||||||
|
Map_Clear<int, int>(&m);
|
||||||
|
Test_AssertTrue(Map_IsEmpty<int, int>(&m));
|
||||||
|
Map_Free<int, int>(&m);
|
||||||
|
|
||||||
|
// --- Set ---
|
||||||
|
var s: Set<int> = Set_New<int>(16);
|
||||||
|
Set_Add<int>(&s, 10);
|
||||||
|
Set_Add<int>(&s, 20);
|
||||||
|
Set_Add<int>(&s, 30);
|
||||||
|
Test_AssertTrue(Set_Remove<int>(&s, 20));
|
||||||
|
Test_AssertFalse(Set_Has<int>(&s, 20));
|
||||||
|
Test_AssertTrue(Set_Has<int>(&s, 10));
|
||||||
|
Test_AssertEqInt(Set_Len<int>(&s) as int, 2);
|
||||||
|
Test_AssertFalse(Set_IsEmpty<int>(&s));
|
||||||
|
Set_Free<int>(&s);
|
||||||
|
|
||||||
|
// --- Result helpers ---
|
||||||
|
let ok: Result = Result_NewOk(42);
|
||||||
|
let err: Result = Result_NewErr("boom");
|
||||||
|
Test_AssertTrue(Result_IsOk(ok));
|
||||||
|
Test_AssertTrue(Result_IsErr(err));
|
||||||
|
Test_AssertEqInt(Result_UnwrapOr(err, -1), -1);
|
||||||
|
let recovered: Result = Result_Or(err, Result_NewOk(7));
|
||||||
|
Test_AssertEqInt(Result_UnwrapOr(recovered, 0), 7);
|
||||||
|
Test_AssertTrue(String_Eq(Result_UnwrapErr(err), "boom"));
|
||||||
|
|
||||||
|
// --- Option helpers ---
|
||||||
|
let some: Option = Option_NewSome(5);
|
||||||
|
let none: Option = Option_NewNone();
|
||||||
|
Test_AssertTrue(Option_IsSome(some));
|
||||||
|
let o2: Option = Option_Or(none, some);
|
||||||
|
Test_AssertEqInt(Option_UnwrapOr(o2, 0), 5);
|
||||||
|
|
||||||
|
PrintLine("map_remove: ok");
|
||||||
|
Test_Pass("map_remove + result helpers");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Multi-instance closures: each capturing closure gets its own heap env
|
||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Test::{Test_AssertEqInt, Test_Pass};
|
||||||
|
|
||||||
|
func MakeAdder(base: int) -> func(int) -> int {
|
||||||
|
return |a: int| -> int {
|
||||||
|
return a + base;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
func Apply(f: func(int) -> int, x: int) -> int {
|
||||||
|
return f(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let a10: func(int) -> int = MakeAdder(10);
|
||||||
|
let a20: func(int) -> int = MakeAdder(20);
|
||||||
|
|
||||||
|
// Independent instances — not a single global env
|
||||||
|
Test_AssertEqInt(a10(1), 11);
|
||||||
|
Test_AssertEqInt(a20(1), 21);
|
||||||
|
Test_AssertEqInt(a10(5), 15);
|
||||||
|
Test_AssertEqInt(Apply(a20, 3), 23);
|
||||||
|
|
||||||
|
// Capture-less still works
|
||||||
|
let add: func(int, int) -> int = |x: int, y: int| -> int {
|
||||||
|
return x + y;
|
||||||
|
};
|
||||||
|
Test_AssertEqInt(add(2, 3), 5);
|
||||||
|
|
||||||
|
PrintInt(a10(1));
|
||||||
|
PrintLine("");
|
||||||
|
PrintInt(a20(1));
|
||||||
|
PrintLine("");
|
||||||
|
Test_Pass("multi_closure");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Stdlib ergonomics demo: Array helpers, String_ReplaceAll, Test asserts
|
||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Array::{
|
||||||
|
Array, Array_New, Array_Push, Array_Pop, Array_Clear, Array_IsEmpty,
|
||||||
|
Array_First, Array_Last, Array_Cap, Array_Reserve, Array_Len, Array_Get, Array_Free
|
||||||
|
};
|
||||||
|
import Std::String::{String_IsEmpty, String_ReplaceAll, String_Eq, String_Len};
|
||||||
|
import Std::Test::{
|
||||||
|
Test_AssertTrue, Test_AssertFalse, Test_AssertEqInt, Test_AssertEqString,
|
||||||
|
Test_AssertNeqInt, Test_AssertEqBool, Test_Pass
|
||||||
|
};
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
// --- Array: Reserve / Push / First / Last / Pop / Clear ---
|
||||||
|
var arr: Array<int> = Array_New<int>(2);
|
||||||
|
Array_Reserve<int>(&arr, 8);
|
||||||
|
Test_AssertTrue(Array_Cap<int>(&arr) >= 8);
|
||||||
|
Test_AssertTrue(Array_IsEmpty<int>(&arr));
|
||||||
|
|
||||||
|
Array_Push<int>(&arr, 10);
|
||||||
|
Array_Push<int>(&arr, 20);
|
||||||
|
Array_Push<int>(&arr, 30);
|
||||||
|
|
||||||
|
Test_AssertFalse(Array_IsEmpty<int>(&arr));
|
||||||
|
Test_AssertEqInt(Array_Len<int>(&arr) as int, 3);
|
||||||
|
Test_AssertEqInt(Array_First<int>(&arr), 10);
|
||||||
|
Test_AssertEqInt(Array_Last<int>(&arr), 30);
|
||||||
|
|
||||||
|
let popped: int = Array_Pop<int>(&arr);
|
||||||
|
Test_AssertEqInt(popped, 30);
|
||||||
|
Test_AssertEqInt(Array_Len<int>(&arr) as int, 2);
|
||||||
|
Test_AssertEqInt(Array_Get<int>(&arr, 1), 20);
|
||||||
|
|
||||||
|
Array_Clear<int>(&arr);
|
||||||
|
Test_AssertTrue(Array_IsEmpty<int>(&arr));
|
||||||
|
Test_AssertTrue(Array_Cap<int>(&arr) >= 8); // capacity retained
|
||||||
|
Array_Free<int>(&arr);
|
||||||
|
|
||||||
|
// --- String: IsEmpty / ReplaceAll ---
|
||||||
|
Test_AssertTrue(String_IsEmpty(""));
|
||||||
|
Test_AssertFalse(String_IsEmpty("x"));
|
||||||
|
|
||||||
|
let multi: String = String_ReplaceAll("a-b-a-b-a", "a", "X");
|
||||||
|
Test_AssertEqString(multi, "X-b-X-b-X");
|
||||||
|
Test_AssertNeqInt(String_Len(multi) as int, 0);
|
||||||
|
|
||||||
|
// Safe when replacement contains the needle (no infinite loop)
|
||||||
|
let safe: String = String_ReplaceAll("..", ".", "x.");
|
||||||
|
Test_AssertEqString(safe, "x.x.");
|
||||||
|
|
||||||
|
Test_AssertEqBool(true, true);
|
||||||
|
Test_Pass("stdlib ergonomics");
|
||||||
|
PrintLine("stdlib_ergonomics: all checks passed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// String_IsBlank / String_Repeat
|
||||||
|
import Std::Io::{PrintLine};
|
||||||
|
import Std::String::{String_IsBlank, String_IsEmpty, String_Repeat, String_Eq, String_Len};
|
||||||
|
import Std::Test::{Test_AssertTrue, Test_AssertFalse, Test_AssertEqInt, Test_AssertEqString, Test_Pass};
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
Test_AssertTrue(String_IsEmpty(""));
|
||||||
|
Test_AssertTrue(String_IsBlank(""));
|
||||||
|
Test_AssertTrue(String_IsBlank(" \t\n"));
|
||||||
|
Test_AssertFalse(String_IsBlank(" x "));
|
||||||
|
|
||||||
|
let dots: String = String_Repeat(".", 5);
|
||||||
|
Test_AssertEqString(dots, ".....");
|
||||||
|
Test_AssertEqInt(String_Len(String_Repeat("ab", 3)) as int, 6);
|
||||||
|
Test_AssertEqString(String_Repeat("x", 0), "");
|
||||||
|
Test_AssertEqString(String_Repeat("ok", 1), "ok");
|
||||||
|
|
||||||
|
PrintLine(String_Repeat("Bux ", 2));
|
||||||
|
Test_Pass("string_extra");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Tuple types: (T, U), return, field access via .0 / .1
|
||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Test::{Test_AssertEqInt, Test_Pass};
|
||||||
|
|
||||||
|
func MakePair(a: int, b: int) -> (int, int) {
|
||||||
|
return (a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Swap(t: (int, int)) -> (int, int) {
|
||||||
|
return (t.1, t.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let t: (int, int) = MakePair(10, 20);
|
||||||
|
Test_AssertEqInt(t.0, 10);
|
||||||
|
Test_AssertEqInt(t.1, 20);
|
||||||
|
|
||||||
|
let s: (int, int) = Swap(t);
|
||||||
|
Test_AssertEqInt(s.0, 20);
|
||||||
|
Test_AssertEqInt(s.1, 10);
|
||||||
|
|
||||||
|
let lit: (int, int) = (7, 8);
|
||||||
|
Test_AssertEqInt(lit.0, 7);
|
||||||
|
Test_AssertEqInt(lit.1, 8);
|
||||||
|
|
||||||
|
PrintInt(t.0);
|
||||||
|
PrintLine("");
|
||||||
|
PrintInt(t.1);
|
||||||
|
PrintLine("");
|
||||||
|
Test_Pass("tuples");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -58,4 +58,78 @@ func Array_operator_index_set<T>(self: *Array<T>, idx: uint, value: T) {
|
|||||||
Array_Set<T>(self, idx, value);
|
Array_Set<T>(self, idx, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* True if the array has no elements */
|
||||||
|
func Array_IsEmpty<T>(self: *Array<T>) -> bool {
|
||||||
|
return self.len == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Current capacity (not length) */
|
||||||
|
func Array_Cap<T>(self: *Array<T>) -> uint {
|
||||||
|
return self.cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drop length to zero; keeps allocated capacity */
|
||||||
|
func Array_Clear<T>(self: *Array<T>) {
|
||||||
|
self.len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure capacity is at least minCap (does not shrink) */
|
||||||
|
func Array_Reserve<T>(self: *Array<T>, minCap: uint) {
|
||||||
|
if minCap <= self.cap {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.cap = minCap;
|
||||||
|
self.data = bux_realloc(self.data as *void, self.cap * sizeof(T)) as *T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* First element (panics if empty via bounds check) */
|
||||||
|
func Array_First<T>(self: *Array<T>) -> T {
|
||||||
|
return Array_Get<T>(self, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Last element (panics if empty via bounds check) */
|
||||||
|
func Array_Last<T>(self: *Array<T>) -> T {
|
||||||
|
return Array_Get<T>(self, self.len - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove and return the last element (panics if empty) */
|
||||||
|
func Array_Pop<T>(self: *Array<T>) -> T {
|
||||||
|
bux_bounds_check(0, self.len);
|
||||||
|
self.len = self.len - 1;
|
||||||
|
return self.data[self.len];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Linear search: true if value is present (uses ==) */
|
||||||
|
func Array_Contains<T>(self: *Array<T>, value: T) -> bool {
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < self.len {
|
||||||
|
if self.data[i] == value {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Index of first equal element, or -1 if not found */
|
||||||
|
func Array_IndexOf<T>(self: *Array<T>, value: T) -> int {
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < self.len {
|
||||||
|
if self.data[i] == value {
|
||||||
|
return i as int;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Append all elements of other onto self */
|
||||||
|
func Array_Extend<T>(self: *Array<T>, other: *Array<T>) {
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < other.len {
|
||||||
|
Array_Push<T>(self, other.data[i]);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,4 +67,44 @@ func Iter_Take<T>(it: *Iter<T>, n: uint) -> Iter<T> {
|
|||||||
return Iter<T> { data: it.data, len: endPos, pos: it.pos };
|
return Iter<T> { data: it.data, len: endPos, pos: it.pos };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* True if any remaining element equals value */
|
||||||
|
func Iter_AnyEq<T>(it: *Iter<T>, value: T) -> bool {
|
||||||
|
var i: uint = it.pos;
|
||||||
|
while i < it.len {
|
||||||
|
if it.data[i] == value {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* True if every remaining element equals value (true if empty) */
|
||||||
|
func Iter_AllEq<T>(it: *Iter<T>, value: T) -> bool {
|
||||||
|
var i: uint = it.pos;
|
||||||
|
while i < it.len {
|
||||||
|
if it.data[i] != value {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Collect remaining elements into a new Array */
|
||||||
|
func Iter_Collect<T>(it: *Iter<T>) -> Array<T> {
|
||||||
|
let remaining: uint = it.len - it.pos;
|
||||||
|
var cap: uint = remaining;
|
||||||
|
if cap == 0 {
|
||||||
|
cap = 1;
|
||||||
|
}
|
||||||
|
var arr: Array<T> = Array_New<T>(cap);
|
||||||
|
var i: uint = it.pos;
|
||||||
|
while i < it.len {
|
||||||
|
Array_Push<T>(&arr, it.data[i]);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+69
@@ -80,6 +80,41 @@ func Map_Len<K, V>(m: *Map<K, V>) -> uint {
|
|||||||
return m.len;
|
return m.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Map_IsEmpty<K, V>(m: *Map<K, V>) -> bool {
|
||||||
|
return m.len == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove key if present. Rebuilds the table to keep open-addressing correct. */
|
||||||
|
func Map_Remove<K, V>(m: *Map<K, V>, key: K) -> bool {
|
||||||
|
if !Map_Has<K, V>(m, key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var fresh: Map<K, V> = Map_New<K, V>(m.cap);
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < m.cap {
|
||||||
|
if m.entries[i].occupied {
|
||||||
|
if m.entries[i].key != key {
|
||||||
|
Map_Set<K, V>(&fresh, m.entries[i].key, m.entries[i].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
bux_free(m.entries as *void);
|
||||||
|
m.entries = fresh.entries;
|
||||||
|
m.cap = fresh.cap;
|
||||||
|
m.len = fresh.len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map_Clear<K, V>(m: *Map<K, V>) {
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < m.cap {
|
||||||
|
m.entries[i].occupied = false;
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
m.len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
func Map_Free<K, V>(m: *Map<K, V>) {
|
func Map_Free<K, V>(m: *Map<K, V>) {
|
||||||
bux_free(m.entries as *void);
|
bux_free(m.entries as *void);
|
||||||
m.entries = null as *MapEntry<K, V>;
|
m.entries = null as *MapEntry<K, V>;
|
||||||
@@ -163,6 +198,40 @@ func StringMap_Len<V>(m: *StringMap<V>) -> uint {
|
|||||||
return m.len;
|
return m.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StringMap_IsEmpty<V>(m: *StringMap<V>) -> bool {
|
||||||
|
return m.len == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringMap_Remove<V>(m: *StringMap<V>, key: String) -> bool {
|
||||||
|
if !StringMap_Has<V>(m, key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var fresh: StringMap<V> = StringMap_New<V>(m.cap);
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < m.cap {
|
||||||
|
if m.entries[i].occupied {
|
||||||
|
if !String_Eq(m.entries[i].key, key) {
|
||||||
|
StringMap_Set<V>(&fresh, m.entries[i].key, m.entries[i].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
bux_free(m.entries as *void);
|
||||||
|
m.entries = fresh.entries;
|
||||||
|
m.cap = fresh.cap;
|
||||||
|
m.len = fresh.len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringMap_Clear<V>(m: *StringMap<V>) {
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < m.cap {
|
||||||
|
m.entries[i].occupied = false;
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
m.len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
func StringMap_Free<V>(m: *StringMap<V>) {
|
func StringMap_Free<V>(m: *StringMap<V>) {
|
||||||
bux_free(m.entries as *void);
|
bux_free(m.entries as *void);
|
||||||
m.entries = null as *StringMapEntry<V>;
|
m.entries = null as *StringMapEntry<V>;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
module Std::Option {
|
module Std::Option {
|
||||||
import Std::Io::{PrintLine};
|
import Std::Io::{PrintLine};
|
||||||
|
|
||||||
|
extern func bux_exit(code: int);
|
||||||
|
|
||||||
enum Option {
|
enum Option {
|
||||||
Some(int),
|
Some(int),
|
||||||
None,
|
None,
|
||||||
@@ -39,4 +41,21 @@ func Option_UnwrapOr(o: Option, fallback: int) -> int {
|
|||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Unwrap Some or panic with a custom message */
|
||||||
|
func Option_Expect(o: Option, msg: String) -> int {
|
||||||
|
if o.tag != Option_Some {
|
||||||
|
PrintLine(msg);
|
||||||
|
bux_exit(1);
|
||||||
|
}
|
||||||
|
return o.data.Some_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If o is Some return it, otherwise return other */
|
||||||
|
func Option_Or(o: Option, other: Option) -> Option {
|
||||||
|
if o.tag == Option_Some {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ extern func bux_getenv(name: String) -> String;
|
|||||||
extern func bux_setenv(name: String, value: String) -> int;
|
extern func bux_setenv(name: String, value: String) -> int;
|
||||||
extern func bux_getcwd() -> String;
|
extern func bux_getcwd() -> String;
|
||||||
extern func bux_chdir(path: String) -> int;
|
extern func bux_chdir(path: String) -> int;
|
||||||
|
extern func bux_exit(code: int);
|
||||||
|
|
||||||
func Os_ArgsCount() -> int {
|
func Os_ArgsCount() -> int {
|
||||||
return bux_argc();
|
return bux_argc();
|
||||||
@@ -31,4 +32,9 @@ func Os_Chdir(path: String) -> bool {
|
|||||||
return bux_chdir(path) == 0;
|
return bux_chdir(path) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Terminate the process with the given exit code */
|
||||||
|
func Os_Exit(code: int) {
|
||||||
|
bux_exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
module Std::Result {
|
module Std::Result {
|
||||||
import Std::Io::{PrintLine};
|
import Std::Io::{PrintLine};
|
||||||
|
|
||||||
|
extern func bux_exit(code: int);
|
||||||
|
|
||||||
enum Result {
|
enum Result {
|
||||||
Ok(int),
|
Ok(int),
|
||||||
Err(String),
|
Err(String),
|
||||||
@@ -41,4 +43,30 @@ func Result_UnwrapOr(r: Result, fallback: int) -> int {
|
|||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Unwrap Ok or panic with a custom message */
|
||||||
|
func Result_Expect(r: Result, msg: String) -> int {
|
||||||
|
if r.tag != Result_Ok {
|
||||||
|
PrintLine(msg);
|
||||||
|
bux_exit(1);
|
||||||
|
}
|
||||||
|
return r.data.Ok_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extract Err payload (panics if Ok) */
|
||||||
|
func Result_UnwrapErr(r: Result) -> String {
|
||||||
|
if r.tag != Result_Err {
|
||||||
|
PrintLine("panic: unwrap_err on Ok");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return r.data.Err_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If r is Ok return it, otherwise return other */
|
||||||
|
func Result_Or(r: Result, other: Result) -> Result {
|
||||||
|
if r.tag == Result_Ok {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+37
@@ -61,6 +61,43 @@ func Set_Len<T>(s: *Set<T>) -> uint {
|
|||||||
return s.len;
|
return s.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Set_IsEmpty<T>(s: *Set<T>) -> bool {
|
||||||
|
return s.len == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove value if present. Rebuilds the table to keep open-addressing correct. */
|
||||||
|
func Set_Remove<T>(s: *Set<T>, value: T) -> bool {
|
||||||
|
if !Set_Has<T>(s, value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var fresh: Set<T> = Set_New<T>(s.cap);
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < s.cap {
|
||||||
|
if s.entries[i].occupied {
|
||||||
|
var entryPtr: *T = &s.entries[i].value;
|
||||||
|
var valuePtr: *T = &value;
|
||||||
|
if bux_mem_eq(entryPtr as *void, valuePtr as *void, sizeof(T)) == 0 {
|
||||||
|
Set_Add<T>(&fresh, s.entries[i].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
bux_free(s.entries as *void);
|
||||||
|
s.entries = fresh.entries;
|
||||||
|
s.cap = fresh.cap;
|
||||||
|
s.len = fresh.len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Set_Clear<T>(s: *Set<T>) {
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < s.cap {
|
||||||
|
s.entries[i].occupied = false;
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
s.len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
func Set_Free<T>(s: *Set<T>) {
|
func Set_Free<T>(s: *Set<T>) {
|
||||||
bux_free(s.entries as *void);
|
bux_free(s.entries as *void);
|
||||||
s.entries = null as *SetEntry<T>;
|
s.entries = null as *SetEntry<T>;
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ func String_Len(s: String) -> uint {
|
|||||||
return bux_strlen(s);
|
return bux_strlen(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func String_IsEmpty(s: String) -> bool {
|
||||||
|
return bux_strlen(s) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
func String_IsNull(s: String) -> bool {
|
func String_IsNull(s: String) -> bool {
|
||||||
return bux_str_is_null(s) != 0;
|
return bux_str_is_null(s) != 0;
|
||||||
}
|
}
|
||||||
@@ -147,6 +151,39 @@ func StringBuilder_Free(sb: *StringBuilder) {
|
|||||||
bux_sb_free(sb.handle);
|
bux_sb_free(sb.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* True if empty or only whitespace (space, tab, CR, LF) */
|
||||||
|
func String_IsBlank(s: String) -> bool {
|
||||||
|
let n: uint = bux_strlen(s);
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < n {
|
||||||
|
let ch: String = bux_str_slice(s, i, 1);
|
||||||
|
if !(String_Eq(ch, " ") || String_Eq(ch, "\t") || String_Eq(ch, "\n") || String_Eq(ch, "\r")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Repeat s, count times (count==0 → empty string) */
|
||||||
|
func String_Repeat(s: String, count: uint) -> String {
|
||||||
|
if count == 0 {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if count == 1 {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
var sb: StringBuilder = StringBuilder_New();
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < count {
|
||||||
|
StringBuilder_Append(&sb, s);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
let result: String = StringBuilder_Build(&sb);
|
||||||
|
StringBuilder_Free(&sb);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// String split/join
|
// String split/join
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -194,6 +231,33 @@ func String_Replace(s: String, old: String, new: String) -> String {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Replace every non-overlapping occurrence of old with new.
|
||||||
|
Empty old is a no-op (returns s unchanged). Safe if new contains old. */
|
||||||
|
func String_ReplaceAll(s: String, old: String, new: String) -> String {
|
||||||
|
let oldLen: uint = bux_strlen(old);
|
||||||
|
if oldLen == 0 {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
var sb: StringBuilder = StringBuilder_New();
|
||||||
|
var remaining: String = s;
|
||||||
|
while true {
|
||||||
|
let pos: String = bux_strstr(remaining, old);
|
||||||
|
if String_IsNull(pos) {
|
||||||
|
StringBuilder_Append(&sb, remaining);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let prefixLen: uint = String_Offset(pos, remaining);
|
||||||
|
let prefix: String = bux_str_slice(remaining, 0, prefixLen);
|
||||||
|
StringBuilder_Append(&sb, prefix);
|
||||||
|
StringBuilder_Append(&sb, new);
|
||||||
|
let remLen: uint = bux_strlen(remaining);
|
||||||
|
remaining = bux_str_slice(remaining, prefixLen + oldLen, remLen - prefixLen - oldLen);
|
||||||
|
}
|
||||||
|
let result: String = StringBuilder_Build(&sb);
|
||||||
|
StringBuilder_Free(&sb);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
extern func bux_str_to_float(s: String) -> float64;
|
extern func bux_str_to_float(s: String) -> float64;
|
||||||
|
|
||||||
func String_ToFloat(s: String) -> float64 {
|
func String_ToFloat(s: String) -> float64 {
|
||||||
|
|||||||
+32
-1
@@ -1,5 +1,6 @@
|
|||||||
module Std::Test {
|
module Std::Test {
|
||||||
import Std::Io::{PrintLine, PrintInt};
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::String::{String_Eq};
|
||||||
|
|
||||||
extern func bux_exit(code: int);
|
extern func bux_exit(code: int);
|
||||||
extern func bux_assert(cond: int, file: String, line: int, expr: String);
|
extern func bux_assert(cond: int, file: String, line: int, expr: String);
|
||||||
@@ -14,7 +15,7 @@ func Test_Assert(cond: bool) {
|
|||||||
|
|
||||||
func Test_AssertEqInt(a: int, b: int) {
|
func Test_AssertEqInt(a: int, b: int) {
|
||||||
if a != b {
|
if a != b {
|
||||||
PrintLine("ASSERT_EQ FAILED:");
|
PrintLine("ASSERT_EQ_INT FAILED:");
|
||||||
PrintInt(a);
|
PrintInt(a);
|
||||||
PrintLine(" != ");
|
PrintLine(" != ");
|
||||||
PrintInt(b);
|
PrintInt(b);
|
||||||
@@ -22,6 +23,31 @@ func Test_AssertEqInt(a: int, b: int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_AssertNeqInt(a: int, b: int) {
|
||||||
|
if a == b {
|
||||||
|
PrintLine("ASSERT_NEQ_INT FAILED: both are");
|
||||||
|
PrintInt(a);
|
||||||
|
bux_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AssertEqString(a: String, b: String) {
|
||||||
|
if !String_Eq(a, b) {
|
||||||
|
PrintLine("ASSERT_EQ_STRING FAILED:");
|
||||||
|
PrintLine(a);
|
||||||
|
PrintLine(" != ");
|
||||||
|
PrintLine(b);
|
||||||
|
bux_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AssertEqBool(a: bool, b: bool) {
|
||||||
|
if a != b {
|
||||||
|
PrintLine("ASSERT_EQ_BOOL FAILED");
|
||||||
|
bux_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_AssertTrue(cond: bool) {
|
func Test_AssertTrue(cond: bool) {
|
||||||
if !cond {
|
if !cond {
|
||||||
PrintLine("ASSERT_TRUE FAILED");
|
PrintLine("ASSERT_TRUE FAILED");
|
||||||
@@ -42,4 +68,9 @@ func Test_Fail(msg: String) {
|
|||||||
bux_exit(1);
|
bux_exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_Pass(msg: String) {
|
||||||
|
PrintLine("PASS:");
|
||||||
|
PrintLine(msg);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,64 @@ int64_t bux_mod_i64(int64_t a, int64_t b) {
|
|||||||
return a % b;
|
return a % b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Integer overflow checks (debug mode) */
|
||||||
|
int64_t bux_add_i64_checked(int64_t a, int64_t b) {
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
int64_t result;
|
||||||
|
if (__builtin_add_overflow(a, b, &result)) {
|
||||||
|
bux_panic("integer overflow in addition");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
#else
|
||||||
|
if ((b > 0 && a > INT64_MAX - b) || (b < 0 && a < INT64_MIN - b)) {
|
||||||
|
bux_panic("integer overflow in addition");
|
||||||
|
}
|
||||||
|
return a + b;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t bux_sub_i64_checked(int64_t a, int64_t b) {
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
int64_t result;
|
||||||
|
if (__builtin_sub_overflow(a, b, &result)) {
|
||||||
|
bux_panic("integer overflow in subtraction");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
#else
|
||||||
|
if ((b > 0 && a < INT64_MIN + b) || (b < 0 && a > INT64_MAX + b)) {
|
||||||
|
bux_panic("integer overflow in subtraction");
|
||||||
|
}
|
||||||
|
return a - b;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t bux_mul_i64_checked(int64_t a, int64_t b) {
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
int64_t result;
|
||||||
|
if (__builtin_mul_overflow(a, b, &result)) {
|
||||||
|
bux_panic("integer overflow in multiplication");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
#else
|
||||||
|
if (a != 0 && b != 0) {
|
||||||
|
if ((a > 0 && b > 0 && a > INT64_MAX / b) ||
|
||||||
|
(a > 0 && b < 0 && b < INT64_MIN / a) ||
|
||||||
|
(a < 0 && b > 0 && a < INT64_MIN / b) ||
|
||||||
|
(a < 0 && b < 0 && a < INT64_MAX / b)) {
|
||||||
|
bux_panic("integer overflow in multiplication");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a * b;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t bux_neg_i64_checked(int64_t a) {
|
||||||
|
if (a == INT64_MIN) {
|
||||||
|
bux_panic("integer overflow in negation");
|
||||||
|
}
|
||||||
|
return -a;
|
||||||
|
}
|
||||||
|
|
||||||
/* String operations */
|
/* String operations */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char* data;
|
const char* data;
|
||||||
@@ -189,6 +247,15 @@ void bux_bounds_check(size_t index, size_t len) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Bounds check that returns the index (usable in expressions) */
|
||||||
|
size_t bux_index_check(size_t index, size_t len) {
|
||||||
|
if (index >= len) {
|
||||||
|
fprintf(stderr, "bux panic: index out of bounds (index %zu, len %zu)\n", index, len);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
/* String wrappers with Bux-compatible signatures */
|
/* String wrappers with Bux-compatible signatures */
|
||||||
unsigned int bux_strlen(const char* s) {
|
unsigned int bux_strlen(const char* s) {
|
||||||
return (unsigned int)strlen(s);
|
return (unsigned int)strlen(s);
|
||||||
|
|||||||
+425
-88
@@ -8,30 +8,9 @@ module CBackend {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func CBackend_TypeToC(kind: int) -> String {
|
func CBackend_TypeToC(kind: int) -> String {
|
||||||
if kind == tyVoid { return "void"; }
|
let cName: String = Type_ToCName(kind);
|
||||||
if kind == tyBool { return "bool"; }
|
if !String_Eq(cName, "") { return cName; }
|
||||||
if kind == tyBool8 { return "bool"; }
|
|
||||||
if kind == tyBool16 { return "bool"; }
|
|
||||||
if kind == tyBool32 { return "bool"; }
|
|
||||||
if kind == tyChar8 { return "char"; }
|
|
||||||
if kind == tyChar16 { return "uint16"; }
|
|
||||||
if kind == tyChar32 { return "uint32"; }
|
|
||||||
if kind == tyStr { return "String"; }
|
|
||||||
if kind == tyInt8 { return "int8"; }
|
|
||||||
if kind == tyInt16 { return "int16"; }
|
|
||||||
if kind == tyInt32 { return "int32"; }
|
|
||||||
if kind == tyInt64 { return "int64"; }
|
|
||||||
if kind == tyInt{ return "int"; }
|
|
||||||
if kind == tyUInt8 { return "uint8"; }
|
|
||||||
if kind == tyUInt16 { return "uint16"; }
|
|
||||||
if kind == tyUInt32 { return "uint32"; }
|
|
||||||
if kind == tyUInt64 { return "uint64"; }
|
|
||||||
if kind == tyUInt { return "uint"; }
|
|
||||||
if kind == tyFloat32 { return "float32"; }
|
|
||||||
if kind == tyFloat64 { return "float64"; }
|
|
||||||
if kind == tyPointer { return "void*"; }
|
|
||||||
if kind == tyNamed { return "int"; }
|
if kind == tyNamed { return "int"; }
|
||||||
if kind == tyFunc { return "void (*)(void)"; }
|
|
||||||
return "int";
|
return "int";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,31 +317,27 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indirect call through function pointer
|
// Indirect call through fat function pointer: f.code(f.env, args...)
|
||||||
if kind == hCallIndirect {
|
if kind == hCallIndirect {
|
||||||
CBE_EmitExpr(cbe, node.child1);
|
|
||||||
StringBuilder_Append(&cbe.sb, "(");
|
StringBuilder_Append(&cbe.sb, "(");
|
||||||
var needsComma: bool = false;
|
CBE_EmitExpr(cbe, node.child1);
|
||||||
|
StringBuilder_Append(&cbe.sb, ".code)(");
|
||||||
|
CBE_EmitExpr(cbe, node.child1);
|
||||||
|
StringBuilder_Append(&cbe.sb, ".env");
|
||||||
if node.child2 != null as *HirNode {
|
if node.child2 != null as *HirNode {
|
||||||
|
StringBuilder_Append(&cbe.sb, ", ");
|
||||||
CBE_EmitExpr(cbe, node.child2);
|
CBE_EmitExpr(cbe, node.child2);
|
||||||
needsComma = true;
|
|
||||||
}
|
}
|
||||||
if node.child3 != null as *HirNode {
|
if node.child3 != null as *HirNode {
|
||||||
if needsComma {
|
StringBuilder_Append(&cbe.sb, ", ");
|
||||||
StringBuilder_Append(&cbe.sb, ", ");
|
|
||||||
}
|
|
||||||
CBE_EmitExpr(cbe, node.child3);
|
CBE_EmitExpr(cbe, node.child3);
|
||||||
needsComma = true;
|
|
||||||
}
|
}
|
||||||
// Emit extra args from linked list
|
// Emit extra args from linked list
|
||||||
var ai: int = 0;
|
var ai: int = 0;
|
||||||
var curExtra: *HirArgList = node.extraData as *HirArgList;
|
var curExtra: *HirArgList = node.extraData as *HirArgList;
|
||||||
while ai < node.extraCount {
|
while ai < node.extraCount {
|
||||||
if needsComma {
|
StringBuilder_Append(&cbe.sb, ", ");
|
||||||
StringBuilder_Append(&cbe.sb, ", ");
|
|
||||||
}
|
|
||||||
CBE_EmitExpr(cbe, curExtra.node);
|
CBE_EmitExpr(cbe, curExtra.node);
|
||||||
needsComma = true;
|
|
||||||
curExtra = curExtra.next;
|
curExtra = curExtra.next;
|
||||||
ai = ai + 1;
|
ai = ai + 1;
|
||||||
}
|
}
|
||||||
@@ -590,11 +565,27 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Index: arr[idx] — emit as arr[idx]
|
// Index: arr[idx] — emit as arr[idx]
|
||||||
|
// For Array<T> desugar pattern (fieldPtr "data"), emit bounds-checked access
|
||||||
if kind == hIndexPtr {
|
if kind == hIndexPtr {
|
||||||
CBE_EmitExpr(cbe, node.child1);
|
var isArrayAccess: bool = false;
|
||||||
StringBuilder_Append(&cbe.sb, "[");
|
if node.child1 != null as *HirNode {
|
||||||
CBE_EmitExpr(cbe, node.child2);
|
if node.child1.kind == hFieldPtr && String_Eq(node.child1.strValue, "data") {
|
||||||
StringBuilder_Append(&cbe.sb, "]");
|
isArrayAccess = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isArrayAccess {
|
||||||
|
CBE_EmitExpr(cbe, node.child1.child1);
|
||||||
|
StringBuilder_Append(&cbe.sb, ".data[bux_index_check(");
|
||||||
|
CBE_EmitExpr(cbe, node.child2);
|
||||||
|
StringBuilder_Append(&cbe.sb, ", ");
|
||||||
|
CBE_EmitExpr(cbe, node.child1.child1);
|
||||||
|
StringBuilder_Append(&cbe.sb, ".len)]");
|
||||||
|
} else {
|
||||||
|
CBE_EmitExpr(cbe, node.child1);
|
||||||
|
StringBuilder_Append(&cbe.sb, "[");
|
||||||
|
CBE_EmitExpr(cbe, node.child2);
|
||||||
|
StringBuilder_Append(&cbe.sb, "]");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,19 +620,28 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
|||||||
StringBuilder_Append(&cbe.sb, ptrNode.strValue);
|
StringBuilder_Append(&cbe.sb, ptrNode.strValue);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// arrow field: load(arrow_field(base, field)) → base->field
|
|
||||||
if ptrKind == hArrowField {
|
|
||||||
CBE_EmitExpr(cbe, ptrNode.child1);
|
|
||||||
StringBuilder_Append(&cbe.sb, "->");
|
|
||||||
StringBuilder_Append(&cbe.sb, ptrNode.strValue);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// index: load(index_ptr(base, idx)) → base[idx]
|
// index: load(index_ptr(base, idx)) → base[idx]
|
||||||
|
// For Array<T> desugar pattern, emit bounds-checked access
|
||||||
if ptrKind == hIndexPtr {
|
if ptrKind == hIndexPtr {
|
||||||
CBE_EmitExpr(cbe, ptrNode.child1);
|
var isArrayAccess: bool = false;
|
||||||
StringBuilder_Append(&cbe.sb, "[");
|
if ptrNode.child1 != null as *HirNode {
|
||||||
CBE_EmitExpr(cbe, ptrNode.child2);
|
if ptrNode.child1.kind == hFieldPtr && String_Eq(ptrNode.child1.strValue, "data") {
|
||||||
StringBuilder_Append(&cbe.sb, "]");
|
isArrayAccess = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isArrayAccess {
|
||||||
|
CBE_EmitExpr(cbe, ptrNode.child1.child1);
|
||||||
|
StringBuilder_Append(&cbe.sb, ".data[bux_index_check(");
|
||||||
|
CBE_EmitExpr(cbe, ptrNode.child2);
|
||||||
|
StringBuilder_Append(&cbe.sb, ", ");
|
||||||
|
CBE_EmitExpr(cbe, ptrNode.child1.child1);
|
||||||
|
StringBuilder_Append(&cbe.sb, ".len)]");
|
||||||
|
} else {
|
||||||
|
CBE_EmitExpr(cbe, ptrNode.child1);
|
||||||
|
StringBuilder_Append(&cbe.sb, "[");
|
||||||
|
CBE_EmitExpr(cbe, ptrNode.child2);
|
||||||
|
StringBuilder_Append(&cbe.sb, "]");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -727,6 +727,312 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
|||||||
// Emit function declaration
|
// Emit function declaration
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Infer C type for a BuxFn mangled part (int, cstr, uint, void, ...)
|
||||||
|
func CBE_FatPartToC(part: String) -> String {
|
||||||
|
if String_Eq(part, "cstr") { return "const char*"; }
|
||||||
|
if String_Eq(part, "void") { return "void"; }
|
||||||
|
if String_Eq(part, "bool") { return "bool"; }
|
||||||
|
if String_Eq(part, "uint") { return "unsigned int"; }
|
||||||
|
if String_Eq(part, "float") { return "float"; }
|
||||||
|
if String_Eq(part, "double") || String_Eq(part, "float64") { return "double"; }
|
||||||
|
if String_EndsWith(part, "Ptr") {
|
||||||
|
let base: String = String_Slice(part, 0, String_Len(part) - 3);
|
||||||
|
return String_Concat(CBE_FatPartToC(base), "*");
|
||||||
|
}
|
||||||
|
return part; // int, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit typedefs for common BuxFn_* shapes (fat function pointers)
|
||||||
|
func CBE_EmitFatFuncTypedefs(cbe: *CEmitter, mod: *HirModule) {
|
||||||
|
StringBuilder_Append(&cbe.sb, "/* Fat function pointer types (code + env) */\n");
|
||||||
|
// (int)->int
|
||||||
|
StringBuilder_Append(&cbe.sb, "typedef struct BuxFn_int_int {\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, " int (*code)(void* env, int a0);\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, " void* env;\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, "} BuxFn_int_int;\n");
|
||||||
|
// (int,int)->int
|
||||||
|
StringBuilder_Append(&cbe.sb, "typedef struct BuxFn_int_int_int {\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, " int (*code)(void* env, int a0, int a1);\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, " void* env;\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, "} BuxFn_int_int_int;\n");
|
||||||
|
// ()->void
|
||||||
|
StringBuilder_Append(&cbe.sb, "typedef struct BuxFn_void_void {\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, " void (*code)(void* env);\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, " void* env;\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, "} BuxFn_void_void;\n");
|
||||||
|
// ()->int
|
||||||
|
StringBuilder_Append(&cbe.sb, "typedef struct BuxFn_int_void {\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, " int (*code)(void* env);\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, " void* env;\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, "} BuxFn_int_void;\n");
|
||||||
|
// Scan module for any other BuxFn_* names
|
||||||
|
var i: int = 0;
|
||||||
|
while i < mod.funcCount {
|
||||||
|
CBE_MaybeEmitExtraFat(cbe, mod.funcs[i].retTypeName);
|
||||||
|
var p: int = 0;
|
||||||
|
while p < mod.funcs[i].paramCount {
|
||||||
|
var ptype: String = "";
|
||||||
|
if p == 0 { ptype = mod.funcs[i].param0.typeName; }
|
||||||
|
else if p == 1 { ptype = mod.funcs[i].param1.typeName; }
|
||||||
|
else if p == 2 { ptype = mod.funcs[i].param2.typeName; }
|
||||||
|
else if p == 3 { ptype = mod.funcs[i].param3.typeName; }
|
||||||
|
else if p == 4 { ptype = mod.funcs[i].param4.typeName; }
|
||||||
|
else if p == 5 { ptype = mod.funcs[i].param5.typeName; }
|
||||||
|
else if p == 6 { ptype = mod.funcs[i].param6.typeName; }
|
||||||
|
else if p == 7 { ptype = mod.funcs[i].param7.typeName; }
|
||||||
|
else if p == 8 { ptype = mod.funcs[i].param8.typeName; }
|
||||||
|
CBE_MaybeEmitExtraFat(cbe, ptype);
|
||||||
|
p = p + 1;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
StringBuilder_Append(&cbe.sb, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
func CBE_MaybeEmitExtraFat(cbe: *CEmitter, name: String) {
|
||||||
|
if String_Eq(name, "") { return; }
|
||||||
|
if !String_StartsWith(name, "BuxFn_") { return; }
|
||||||
|
// Skip ones we already emit as built-ins
|
||||||
|
if String_Eq(name, "BuxFn_int_int") { return; }
|
||||||
|
if String_Eq(name, "BuxFn_int_int_int") { return; }
|
||||||
|
if String_Eq(name, "BuxFn_void_void") { return; }
|
||||||
|
if String_Eq(name, "BuxFn_int_void") { return; }
|
||||||
|
CBE_EmitOneFatTypedef(cbe, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
func CBE_CollectBuxFn(names: *String, count: *int, name: String) {
|
||||||
|
if String_Eq(name, "") { return; }
|
||||||
|
if !String_StartsWith(name, "BuxFn_") { return; }
|
||||||
|
if *count >= 64 { return; }
|
||||||
|
var i: int = 0;
|
||||||
|
while i < *count {
|
||||||
|
if String_Eq(names[i], name) { return; }
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
names[*count] = name;
|
||||||
|
*count = *count + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuxFn_ret_p0_p1 → typedef with code pointer
|
||||||
|
func CBE_EmitOneFatTypedef(cbe: *CEmitter, fatName: String) {
|
||||||
|
// Split fatName after "BuxFn_" into parts by '_'
|
||||||
|
let prefixLen: uint = 6; // "BuxFn_"
|
||||||
|
let rest: String = String_Slice(fatName, prefixLen, String_Len(fatName) - prefixLen);
|
||||||
|
// Parse parts: first = ret, rest = params (use simple split)
|
||||||
|
let partCount: uint = String_SplitCount(rest, "_");
|
||||||
|
if partCount == 0 { return; }
|
||||||
|
let retPart: String = String_SplitPart(rest, "_", 0);
|
||||||
|
let retC: String = CBE_FatPartToC(retPart);
|
||||||
|
|
||||||
|
StringBuilder_Append(&cbe.sb, "typedef struct ");
|
||||||
|
StringBuilder_Append(&cbe.sb, fatName);
|
||||||
|
StringBuilder_Append(&cbe.sb, " {\n ");
|
||||||
|
// code field: ret (*code)(void* env, params...)
|
||||||
|
StringBuilder_Append(&cbe.sb, retC);
|
||||||
|
StringBuilder_Append(&cbe.sb, " (*code)(void* env");
|
||||||
|
var pi: uint = 1;
|
||||||
|
while pi < partCount {
|
||||||
|
let pPart: String = String_SplitPart(rest, "_", pi);
|
||||||
|
if !(pi == 1 && String_Eq(pPart, "void") && partCount == 2) {
|
||||||
|
StringBuilder_Append(&cbe.sb, ", ");
|
||||||
|
StringBuilder_Append(&cbe.sb, CBE_FatPartToC(pPart));
|
||||||
|
}
|
||||||
|
pi = pi + 1;
|
||||||
|
}
|
||||||
|
StringBuilder_Append(&cbe.sb, ");\n void* env;\n} ");
|
||||||
|
StringBuilder_Append(&cbe.sb, fatName);
|
||||||
|
StringBuilder_Append(&cbe.sb, ";\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
func CBE_EmitMakerDecl(cbe: *CEmitter, f: *HirFunc) {
|
||||||
|
// Infer fat type from thunk: skip __env, use user params + ret
|
||||||
|
var fatName: String = "BuxFn_";
|
||||||
|
var retC: String = f.retTypeName;
|
||||||
|
if String_Eq(retC, "") { retC = "int"; }
|
||||||
|
fatName = String_Concat(fatName, Lcx_SanitizeFatPart(retC));
|
||||||
|
var pi: int = 1; // skip __env
|
||||||
|
while pi < f.paramCount {
|
||||||
|
var ptype: String = "int";
|
||||||
|
if pi == 1 { ptype = f.param1.typeName; }
|
||||||
|
else if pi == 2 { ptype = f.param2.typeName; }
|
||||||
|
else if pi == 3 { ptype = f.param3.typeName; }
|
||||||
|
else if pi == 4 { ptype = f.param4.typeName; }
|
||||||
|
else if pi == 5 { ptype = f.param5.typeName; }
|
||||||
|
else if pi == 6 { ptype = f.param6.typeName; }
|
||||||
|
else if pi == 7 { ptype = f.param7.typeName; }
|
||||||
|
else if pi == 8 { ptype = f.param8.typeName; }
|
||||||
|
if String_Eq(ptype, "") { ptype = "int"; }
|
||||||
|
fatName = String_Concat(fatName, "_");
|
||||||
|
fatName = String_Concat(fatName, Lcx_SanitizeFatPart(ptype));
|
||||||
|
pi = pi + 1;
|
||||||
|
}
|
||||||
|
if f.paramCount <= 1 {
|
||||||
|
fatName = String_Concat(fatName, "_void");
|
||||||
|
}
|
||||||
|
StringBuilder_Append(&cbe.sb, fatName);
|
||||||
|
StringBuilder_Append(&cbe.sb, " __make_");
|
||||||
|
StringBuilder_Append(&cbe.sb, f.name);
|
||||||
|
StringBuilder_Append(&cbe.sb, "(");
|
||||||
|
var ci: int = 0;
|
||||||
|
while ci < f.captureCount {
|
||||||
|
if ci > 0 { StringBuilder_Append(&cbe.sb, ", "); }
|
||||||
|
var capType: String = "int";
|
||||||
|
var capName: String = "c";
|
||||||
|
if ci == 0 { capName = f.captureName0; capType = CBackend_TypeToC(f.captureType0); }
|
||||||
|
else if ci == 1 { capName = f.captureName1; capType = CBackend_TypeToC(f.captureType1); }
|
||||||
|
else if ci == 2 { capName = f.captureName2; capType = CBackend_TypeToC(f.captureType2); }
|
||||||
|
else if ci == 3 { capName = f.captureName3; capType = CBackend_TypeToC(f.captureType3); }
|
||||||
|
else if ci == 4 { capName = f.captureName4; capType = CBackend_TypeToC(f.captureType4); }
|
||||||
|
else if ci == 5 { capName = f.captureName5; capType = CBackend_TypeToC(f.captureType5); }
|
||||||
|
else if ci == 6 { capName = f.captureName6; capType = CBackend_TypeToC(f.captureType6); }
|
||||||
|
else if ci == 7 { capName = f.captureName7; capType = CBackend_TypeToC(f.captureType7); }
|
||||||
|
StringBuilder_Append(&cbe.sb, capType);
|
||||||
|
StringBuilder_Append(&cbe.sb, " ");
|
||||||
|
StringBuilder_Append(&cbe.sb, capName);
|
||||||
|
ci = ci + 1;
|
||||||
|
}
|
||||||
|
StringBuilder_Append(&cbe.sb, ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
func CBE_EmitMakerFunc(cbe: *CEmitter, f: *HirFunc) {
|
||||||
|
CBE_EmitMakerDecl(cbe, f);
|
||||||
|
StringBuilder_Append(&cbe.sb, " {\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, " ");
|
||||||
|
StringBuilder_Append(&cbe.sb, f.envStructName);
|
||||||
|
StringBuilder_Append(&cbe.sb, "* __e = (");
|
||||||
|
StringBuilder_Append(&cbe.sb, f.envStructName);
|
||||||
|
StringBuilder_Append(&cbe.sb, "*)bux_alloc(sizeof(");
|
||||||
|
StringBuilder_Append(&cbe.sb, f.envStructName);
|
||||||
|
StringBuilder_Append(&cbe.sb, "));\n");
|
||||||
|
var ci: int = 0;
|
||||||
|
while ci < f.captureCount {
|
||||||
|
var capName: String = "";
|
||||||
|
if ci == 0 { capName = f.captureName0; }
|
||||||
|
else if ci == 1 { capName = f.captureName1; }
|
||||||
|
else if ci == 2 { capName = f.captureName2; }
|
||||||
|
else if ci == 3 { capName = f.captureName3; }
|
||||||
|
else if ci == 4 { capName = f.captureName4; }
|
||||||
|
else if ci == 5 { capName = f.captureName5; }
|
||||||
|
else if ci == 6 { capName = f.captureName6; }
|
||||||
|
else if ci == 7 { capName = f.captureName7; }
|
||||||
|
StringBuilder_Append(&cbe.sb, " __e->");
|
||||||
|
StringBuilder_Append(&cbe.sb, capName);
|
||||||
|
StringBuilder_Append(&cbe.sb, " = ");
|
||||||
|
StringBuilder_Append(&cbe.sb, capName);
|
||||||
|
StringBuilder_Append(&cbe.sb, ";\n");
|
||||||
|
ci = ci + 1;
|
||||||
|
}
|
||||||
|
// Build fat return type name same as decl
|
||||||
|
var fatName: String = "BuxFn_";
|
||||||
|
var retC: String = f.retTypeName;
|
||||||
|
if String_Eq(retC, "") { retC = "int"; }
|
||||||
|
fatName = String_Concat(fatName, Lcx_SanitizeFatPart(retC));
|
||||||
|
var pi: int = 1;
|
||||||
|
while pi < f.paramCount {
|
||||||
|
var ptype: String = "int";
|
||||||
|
if pi == 1 { ptype = f.param1.typeName; }
|
||||||
|
else if pi == 2 { ptype = f.param2.typeName; }
|
||||||
|
else if pi == 3 { ptype = f.param3.typeName; }
|
||||||
|
else if pi == 4 { ptype = f.param4.typeName; }
|
||||||
|
else if pi == 5 { ptype = f.param5.typeName; }
|
||||||
|
else if pi == 6 { ptype = f.param6.typeName; }
|
||||||
|
else if pi == 7 { ptype = f.param7.typeName; }
|
||||||
|
else if pi == 8 { ptype = f.param8.typeName; }
|
||||||
|
if String_Eq(ptype, "") { ptype = "int"; }
|
||||||
|
fatName = String_Concat(fatName, "_");
|
||||||
|
fatName = String_Concat(fatName, Lcx_SanitizeFatPart(ptype));
|
||||||
|
pi = pi + 1;
|
||||||
|
}
|
||||||
|
if f.paramCount <= 1 {
|
||||||
|
fatName = String_Concat(fatName, "_void");
|
||||||
|
}
|
||||||
|
StringBuilder_Append(&cbe.sb, " return (");
|
||||||
|
StringBuilder_Append(&cbe.sb, fatName);
|
||||||
|
StringBuilder_Append(&cbe.sb, "){ .code = ");
|
||||||
|
StringBuilder_Append(&cbe.sb, f.name);
|
||||||
|
StringBuilder_Append(&cbe.sb, ", .env = __e };\n}\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit adapters for any non-closure function (used when taken as value)
|
||||||
|
func CBE_EmitAllAdapters(cbe: *CEmitter, mod: *HirModule) {
|
||||||
|
StringBuilder_Append(&cbe.sb, "/* Fat-func adapters for named functions */\n");
|
||||||
|
var i: int = 0;
|
||||||
|
while i < mod.funcCount {
|
||||||
|
let fname: String = mod.funcs[i].name;
|
||||||
|
// Skip closures, makers, adapters themselves
|
||||||
|
if String_StartsWith(fname, "__closure_") || String_StartsWith(fname, "__make_") || String_StartsWith(fname, "__adapt_") {
|
||||||
|
i = i + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if CBE_FuncHasGeneric(&mod.funcs[i]) {
|
||||||
|
i = i + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Only emit adapter if function has body
|
||||||
|
if mod.funcs[i].body == null as *HirNode {
|
||||||
|
i = i + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Adapter signature: ret __adapt_F(void* env, params...) { return F(params); }
|
||||||
|
var retC: String = mod.funcs[i].retTypeName;
|
||||||
|
if String_Eq(retC, "") { retC = "void"; }
|
||||||
|
StringBuilder_Append(&cbe.sb, "static ");
|
||||||
|
StringBuilder_Append(&cbe.sb, retC);
|
||||||
|
StringBuilder_Append(&cbe.sb, " __adapt_");
|
||||||
|
StringBuilder_Append(&cbe.sb, fname);
|
||||||
|
StringBuilder_Append(&cbe.sb, "(void* env");
|
||||||
|
var p: int = 0;
|
||||||
|
while p < mod.funcs[i].paramCount {
|
||||||
|
// Skip if first param is already __env (shouldn't for named funcs)
|
||||||
|
var pname: String = "";
|
||||||
|
var ptype: String = "int";
|
||||||
|
if p == 0 { pname = mod.funcs[i].param0.name; ptype = mod.funcs[i].param0.typeName; }
|
||||||
|
else if p == 1 { pname = mod.funcs[i].param1.name; ptype = mod.funcs[i].param1.typeName; }
|
||||||
|
else if p == 2 { pname = mod.funcs[i].param2.name; ptype = mod.funcs[i].param2.typeName; }
|
||||||
|
else if p == 3 { pname = mod.funcs[i].param3.name; ptype = mod.funcs[i].param3.typeName; }
|
||||||
|
else if p == 4 { pname = mod.funcs[i].param4.name; ptype = mod.funcs[i].param4.typeName; }
|
||||||
|
else if p == 5 { pname = mod.funcs[i].param5.name; ptype = mod.funcs[i].param5.typeName; }
|
||||||
|
else if p == 6 { pname = mod.funcs[i].param6.name; ptype = mod.funcs[i].param6.typeName; }
|
||||||
|
else if p == 7 { pname = mod.funcs[i].param7.name; ptype = mod.funcs[i].param7.typeName; }
|
||||||
|
else if p == 8 { pname = mod.funcs[i].param8.name; ptype = mod.funcs[i].param8.typeName; }
|
||||||
|
if String_Eq(ptype, "") { ptype = "int"; }
|
||||||
|
StringBuilder_Append(&cbe.sb, ", ");
|
||||||
|
StringBuilder_Append(&cbe.sb, ptype);
|
||||||
|
StringBuilder_Append(&cbe.sb, " ");
|
||||||
|
StringBuilder_Append(&cbe.sb, pname);
|
||||||
|
p = p + 1;
|
||||||
|
}
|
||||||
|
StringBuilder_Append(&cbe.sb, ") {\n (void)env;\n");
|
||||||
|
if String_Eq(retC, "void") {
|
||||||
|
StringBuilder_Append(&cbe.sb, " ");
|
||||||
|
StringBuilder_Append(&cbe.sb, fname);
|
||||||
|
StringBuilder_Append(&cbe.sb, "(");
|
||||||
|
} else {
|
||||||
|
StringBuilder_Append(&cbe.sb, " return ");
|
||||||
|
StringBuilder_Append(&cbe.sb, fname);
|
||||||
|
StringBuilder_Append(&cbe.sb, "(");
|
||||||
|
}
|
||||||
|
p = 0;
|
||||||
|
while p < mod.funcs[i].paramCount {
|
||||||
|
if p > 0 { StringBuilder_Append(&cbe.sb, ", "); }
|
||||||
|
var pname: String = "";
|
||||||
|
if p == 0 { pname = mod.funcs[i].param0.name; }
|
||||||
|
else if p == 1 { pname = mod.funcs[i].param1.name; }
|
||||||
|
else if p == 2 { pname = mod.funcs[i].param2.name; }
|
||||||
|
else if p == 3 { pname = mod.funcs[i].param3.name; }
|
||||||
|
else if p == 4 { pname = mod.funcs[i].param4.name; }
|
||||||
|
else if p == 5 { pname = mod.funcs[i].param5.name; }
|
||||||
|
else if p == 6 { pname = mod.funcs[i].param6.name; }
|
||||||
|
else if p == 7 { pname = mod.funcs[i].param7.name; }
|
||||||
|
else if p == 8 { pname = mod.funcs[i].param8.name; }
|
||||||
|
StringBuilder_Append(&cbe.sb, pname);
|
||||||
|
p = p + 1;
|
||||||
|
}
|
||||||
|
StringBuilder_Append(&cbe.sb, ");\n}\n\n");
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
|
func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
|
||||||
// Return type
|
// Return type
|
||||||
if String_Eq(f.retTypeName, "") || String_Eq(f.retTypeName, "void") {
|
if String_Eq(f.retTypeName, "") || String_Eq(f.retTypeName, "void") {
|
||||||
@@ -961,7 +1267,12 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
|||||||
StringBuilder_Append(&cbe.sb, "typedef char char8;\n\n");
|
StringBuilder_Append(&cbe.sb, "typedef char char8;\n\n");
|
||||||
// Runtime declarations
|
// Runtime declarations
|
||||||
StringBuilder_Append(&cbe.sb, "void* bux_alloc(unsigned int size);\n");
|
StringBuilder_Append(&cbe.sb, "void* bux_alloc(unsigned int size);\n");
|
||||||
StringBuilder_Append(&cbe.sb, "void bux_free(void* ptr);\n\n");
|
StringBuilder_Append(&cbe.sb, "void bux_free(void* ptr);\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, "unsigned long long bux_index_check(unsigned long long index, unsigned long long len);\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, "int64 bux_add_i64_checked(int64 a, int64 b);\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, "int64 bux_sub_i64_checked(int64 a, int64 b);\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, "int64 bux_mul_i64_checked(int64 a, int64 b);\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, "int64 bux_neg_i64_checked(int64 a);\n\n");
|
||||||
|
|
||||||
// Forward declare all struct types (skip empty names)
|
// Forward declare all struct types (skip empty names)
|
||||||
var si: int = 0;
|
var si: int = 0;
|
||||||
@@ -1121,12 +1432,53 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
|||||||
si = si + 1;
|
si = si + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fat function-pointer typedefs (BuxFn_*) — before forward decls
|
||||||
|
CBE_EmitFatFuncTypedefs(cbe, mod);
|
||||||
|
|
||||||
|
// Env structs for capturing closures (no static instance — heap per value)
|
||||||
|
var ei2: int = 0;
|
||||||
|
while ei2 < mod.funcCount {
|
||||||
|
if mod.funcs[ei2].captureCount > 0 && !String_Eq(mod.funcs[ei2].envStructName, "") {
|
||||||
|
StringBuilder_Append(&cbe.sb, "typedef struct ");
|
||||||
|
StringBuilder_Append(&cbe.sb, mod.funcs[ei2].envStructName);
|
||||||
|
StringBuilder_Append(&cbe.sb, " {\n");
|
||||||
|
var ci2: int = 0;
|
||||||
|
while ci2 < mod.funcs[ei2].captureCount {
|
||||||
|
var capName: String = "";
|
||||||
|
var capType: String = "int";
|
||||||
|
if ci2 == 0 { capName = mod.funcs[ei2].captureName0; capType = CBackend_TypeToC(mod.funcs[ei2].captureType0); }
|
||||||
|
else if ci2 == 1 { capName = mod.funcs[ei2].captureName1; capType = CBackend_TypeToC(mod.funcs[ei2].captureType1); }
|
||||||
|
else if ci2 == 2 { capName = mod.funcs[ei2].captureName2; capType = CBackend_TypeToC(mod.funcs[ei2].captureType2); }
|
||||||
|
else if ci2 == 3 { capName = mod.funcs[ei2].captureName3; capType = CBackend_TypeToC(mod.funcs[ei2].captureType3); }
|
||||||
|
else if ci2 == 4 { capName = mod.funcs[ei2].captureName4; capType = CBackend_TypeToC(mod.funcs[ei2].captureType4); }
|
||||||
|
else if ci2 == 5 { capName = mod.funcs[ei2].captureName5; capType = CBackend_TypeToC(mod.funcs[ei2].captureType5); }
|
||||||
|
else if ci2 == 6 { capName = mod.funcs[ei2].captureName6; capType = CBackend_TypeToC(mod.funcs[ei2].captureType6); }
|
||||||
|
else if ci2 == 7 { capName = mod.funcs[ei2].captureName7; capType = CBackend_TypeToC(mod.funcs[ei2].captureType7); }
|
||||||
|
StringBuilder_Append(&cbe.sb, " ");
|
||||||
|
StringBuilder_Append(&cbe.sb, capType);
|
||||||
|
StringBuilder_Append(&cbe.sb, " ");
|
||||||
|
StringBuilder_Append(&cbe.sb, capName);
|
||||||
|
StringBuilder_Append(&cbe.sb, ";\n");
|
||||||
|
ci2 = ci2 + 1;
|
||||||
|
}
|
||||||
|
StringBuilder_Append(&cbe.sb, "} ");
|
||||||
|
StringBuilder_Append(&cbe.sb, mod.funcs[ei2].envStructName);
|
||||||
|
StringBuilder_Append(&cbe.sb, ";\n\n");
|
||||||
|
}
|
||||||
|
ei2 = ei2 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Forward declarations for all functions (skip generics)
|
// Forward declarations for all functions (skip generics)
|
||||||
var i: int = 0;
|
var i: int = 0;
|
||||||
while i < mod.funcCount {
|
while i < mod.funcCount {
|
||||||
if !CBE_FuncHasGeneric(&mod.funcs[i]) {
|
if !CBE_FuncHasGeneric(&mod.funcs[i]) {
|
||||||
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
|
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
|
||||||
StringBuilder_Append(&cbe.sb, ";\n");
|
StringBuilder_Append(&cbe.sb, ";\n");
|
||||||
|
// Maker for capturing closures
|
||||||
|
if mod.funcs[i].captureCount > 0 {
|
||||||
|
CBE_EmitMakerDecl(cbe, &mod.funcs[i]);
|
||||||
|
StringBuilder_Append(&cbe.sb, ";\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
@@ -1141,6 +1493,9 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
|||||||
}
|
}
|
||||||
StringBuilder_Append(&cbe.sb, "\n");
|
StringBuilder_Append(&cbe.sb, "\n");
|
||||||
|
|
||||||
|
// Adapters before function bodies (bodies may take funcs as values)
|
||||||
|
CBE_EmitAllAdapters(cbe, mod);
|
||||||
|
|
||||||
// Function definitions (skip generics)
|
// Function definitions (skip generics)
|
||||||
var hasMain: bool = false;
|
var hasMain: bool = false;
|
||||||
i = 0;
|
i = 0;
|
||||||
@@ -1157,45 +1512,23 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
|||||||
i = i + 1;
|
i = i + 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Emit env struct and global instance for closures with captures
|
|
||||||
if mod.funcs[i].captureCount > 0 {
|
|
||||||
let envName: String = mod.funcs[i].envStructName;
|
|
||||||
let instName: String = mod.funcs[i].envInstanceName;
|
|
||||||
if !String_Eq(envName, "") {
|
|
||||||
// Emit struct definition
|
|
||||||
StringBuilder_Append(&cbe.sb, "struct ");
|
|
||||||
StringBuilder_Append(&cbe.sb, envName);
|
|
||||||
StringBuilder_Append(&cbe.sb, " {\n");
|
|
||||||
var ci: int = 0;
|
|
||||||
while ci < mod.funcs[i].captureCount {
|
|
||||||
var capName: String = "";
|
|
||||||
var capType: String = "int";
|
|
||||||
if ci == 0 { capName = mod.funcs[i].captureName0; capType = CBackend_TypeToC(mod.funcs[i].captureType0); }
|
|
||||||
else if ci == 1 { capName = mod.funcs[i].captureName1; capType = CBackend_TypeToC(mod.funcs[i].captureType1); }
|
|
||||||
else if ci == 2 { capName = mod.funcs[i].captureName2; capType = CBackend_TypeToC(mod.funcs[i].captureType2); }
|
|
||||||
else if ci == 3 { capName = mod.funcs[i].captureName3; capType = CBackend_TypeToC(mod.funcs[i].captureType3); }
|
|
||||||
else if ci == 4 { capName = mod.funcs[i].captureName4; capType = CBackend_TypeToC(mod.funcs[i].captureType4); }
|
|
||||||
else if ci == 5 { capName = mod.funcs[i].captureName5; capType = CBackend_TypeToC(mod.funcs[i].captureType5); }
|
|
||||||
else if ci == 6 { capName = mod.funcs[i].captureName6; capType = CBackend_TypeToC(mod.funcs[i].captureType6); }
|
|
||||||
else if ci == 7 { capName = mod.funcs[i].captureName7; capType = CBackend_TypeToC(mod.funcs[i].captureType7); }
|
|
||||||
StringBuilder_Append(&cbe.sb, " ");
|
|
||||||
StringBuilder_Append(&cbe.sb, capType);
|
|
||||||
StringBuilder_Append(&cbe.sb, " ");
|
|
||||||
StringBuilder_Append(&cbe.sb, capName);
|
|
||||||
StringBuilder_Append(&cbe.sb, ";\n");
|
|
||||||
ci = ci + 1;
|
|
||||||
}
|
|
||||||
StringBuilder_Append(&cbe.sb, "};\n");
|
|
||||||
// Emit global instance
|
|
||||||
StringBuilder_Append(&cbe.sb, "static struct ");
|
|
||||||
StringBuilder_Append(&cbe.sb, envName);
|
|
||||||
StringBuilder_Append(&cbe.sb, " ");
|
|
||||||
StringBuilder_Append(&cbe.sb, instName);
|
|
||||||
StringBuilder_Append(&cbe.sb, ";\n\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
|
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
|
||||||
StringBuilder_Append(&cbe.sb, " {\n");
|
StringBuilder_Append(&cbe.sb, " {\n");
|
||||||
|
// Capturing closure thunk: materialize env from fat-func env pointer
|
||||||
|
if mod.funcs[i].captureCount > 0 && !String_Eq(mod.funcs[i].envStructName, "") && !String_Eq(mod.funcs[i].envInstanceName, "") {
|
||||||
|
StringBuilder_Append(&cbe.sb, " ");
|
||||||
|
StringBuilder_Append(&cbe.sb, mod.funcs[i].envStructName);
|
||||||
|
StringBuilder_Append(&cbe.sb, " ");
|
||||||
|
StringBuilder_Append(&cbe.sb, mod.funcs[i].envInstanceName);
|
||||||
|
StringBuilder_Append(&cbe.sb, " = *((");
|
||||||
|
StringBuilder_Append(&cbe.sb, mod.funcs[i].envStructName);
|
||||||
|
StringBuilder_Append(&cbe.sb, "*)__env);\n");
|
||||||
|
} else if mod.funcs[i].paramCount > 0 {
|
||||||
|
// Capture-less closure still has __env
|
||||||
|
if String_Eq(mod.funcs[i].param0.name, "__env") {
|
||||||
|
StringBuilder_Append(&cbe.sb, " (void)__env;\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
// Body
|
// Body
|
||||||
cbe.checkedFunc = mod.funcs[i].checkedFunc;
|
cbe.checkedFunc = mod.funcs[i].checkedFunc;
|
||||||
cbe.deferCount = 0;
|
cbe.deferCount = 0;
|
||||||
@@ -1216,6 +1549,10 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
|||||||
StringBuilder_Append(&cbe.sb, " return 0;\n");
|
StringBuilder_Append(&cbe.sb, " return 0;\n");
|
||||||
}
|
}
|
||||||
StringBuilder_Append(&cbe.sb, "\n}\n\n");
|
StringBuilder_Append(&cbe.sb, "\n}\n\n");
|
||||||
|
// After capturing closure thunk, emit heap-env maker
|
||||||
|
if mod.funcs[i].captureCount > 0 {
|
||||||
|
CBE_EmitMakerFunc(cbe, &mod.funcs[i]);
|
||||||
|
}
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+77
-2
@@ -55,12 +55,44 @@ func Diagnostic_GetLine(path: String, lineNum: uint32) -> String {
|
|||||||
return bux_str_split_part(content, "\n", lineNum - 1);
|
return bux_str_split_part(content, "\n", lineNum - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Simple substring check for help hints */
|
||||||
|
func Diagnostic_MsgContains(msg: String, needle: String) -> bool {
|
||||||
|
return bux_str_contains(msg, needle) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actionable help for common error messages */
|
||||||
|
func Diagnostic_Hint(msg: String) -> String {
|
||||||
|
if Diagnostic_MsgContains(msg, "cannot assign") {
|
||||||
|
return "ensure the right-hand side type matches the left-hand side";
|
||||||
|
}
|
||||||
|
if Diagnostic_MsgContains(msg, "undeclared identifier") {
|
||||||
|
return "check the spelling, or import the symbol from the right module";
|
||||||
|
}
|
||||||
|
if Diagnostic_MsgContains(msg, "too few arguments") {
|
||||||
|
return "compare the call with the function's parameter list";
|
||||||
|
}
|
||||||
|
if Diagnostic_MsgContains(msg, "too many arguments") {
|
||||||
|
return "compare the call with the function's parameter list";
|
||||||
|
}
|
||||||
|
if Diagnostic_MsgContains(msg, "use of moved value") {
|
||||||
|
return "the value was moved; clone it or restructure ownership";
|
||||||
|
}
|
||||||
|
if Diagnostic_MsgContains(msg, "expected expression") {
|
||||||
|
return "the previous statement may be incomplete (missing value or ';')";
|
||||||
|
}
|
||||||
|
if Diagnostic_MsgContains(msg, "duplicate symbol") {
|
||||||
|
return "rename one of the definitions or remove the duplicate";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
/* Print a diagnostic in Rust-style format:
|
/* Print a diagnostic in Rust-style format:
|
||||||
* error: <message>
|
* error: <message>
|
||||||
* --> <path>:<line>:<col>
|
* --> <path>:<line>:<col>
|
||||||
* |
|
* |
|
||||||
* 42 | <source_line>
|
* 42 | <source_line>
|
||||||
* | <spaces>^
|
* | <spaces>^
|
||||||
|
* = help: <hint>
|
||||||
*/
|
*/
|
||||||
func Diagnostic_Print(diag: *Diagnostic, sourcePath: String) {
|
func Diagnostic_Print(diag: *Diagnostic, sourcePath: String) {
|
||||||
/* Severity prefix */
|
/* Severity prefix */
|
||||||
@@ -94,14 +126,57 @@ func Diagnostic_Print(diag: *Diagnostic, sourcePath: String) {
|
|||||||
Print(" | ");
|
Print(" | ");
|
||||||
PrintLine(lineText);
|
PrintLine(lineText);
|
||||||
|
|
||||||
/* Underline */
|
/* Underline (multi-char for identifiers/string tokens) */
|
||||||
Print(" | ");
|
Print(" | ");
|
||||||
var i: uint32 = 0;
|
var i: uint32 = 0;
|
||||||
while i < diag.column - 1 && i < 120 {
|
while i < diag.column - 1 && i < 120 {
|
||||||
Print(" ");
|
Print(" ");
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
PrintLine("^");
|
/* Estimate token length from the source line */
|
||||||
|
var ulen: uint = 1;
|
||||||
|
let col0: uint = diag.column - 1;
|
||||||
|
let lineLen: uint = String_Len(lineText);
|
||||||
|
if col0 < lineLen {
|
||||||
|
let first: String = String_Chars(lineText, col0);
|
||||||
|
if String_Eq(first, "\"") || String_Eq(first, "`") || String_Eq(first, "'") {
|
||||||
|
var j: uint = col0 + 1;
|
||||||
|
while j < lineLen {
|
||||||
|
let cj: String = String_Chars(lineText, j);
|
||||||
|
if String_Eq(cj, first) {
|
||||||
|
ulen = j - col0 + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
j = j + 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var j: uint = col0;
|
||||||
|
while j < lineLen {
|
||||||
|
let cj: String = String_Chars(lineText, j);
|
||||||
|
if String_Contains("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", cj) {
|
||||||
|
j = j + 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if j > col0 {
|
||||||
|
ulen = j - col0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var k: uint = 0;
|
||||||
|
while k < ulen {
|
||||||
|
Print("^");
|
||||||
|
k = k + 1;
|
||||||
|
}
|
||||||
|
PrintLine("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Helpful hint when we recognize the error */
|
||||||
|
let hint: String = Diagnostic_Hint(diag.message);
|
||||||
|
if !String_Eq(hint, "") {
|
||||||
|
Print(" = help: ");
|
||||||
|
PrintLine(hint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+256
-146
@@ -43,30 +43,7 @@ struct LowerCtx {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func Lcx_ResolveTypeKindFromName(name: String) -> int {
|
func Lcx_ResolveTypeKindFromName(name: String) -> int {
|
||||||
if String_Eq(name, "void") { return tyVoid; }
|
return Type_FromName(name);
|
||||||
if String_Eq(name, "bool") { return tyBool; }
|
|
||||||
if String_Eq(name, "bool8") { return tyBool8; }
|
|
||||||
if String_Eq(name, "bool16") { return tyBool16; }
|
|
||||||
if String_Eq(name, "bool32") { return tyBool32; }
|
|
||||||
if String_Eq(name, "char8") { return tyChar8; }
|
|
||||||
if String_Eq(name, "char16") { return tyChar16; }
|
|
||||||
if String_Eq(name, "char32") { return tyChar32; }
|
|
||||||
if String_Eq(name, "String") { return tyStr; }
|
|
||||||
if String_Eq(name, "str") { return tyStr; }
|
|
||||||
if String_Eq(name, "int8") { return tyInt8; }
|
|
||||||
if String_Eq(name, "int16") { return tyInt16; }
|
|
||||||
if String_Eq(name, "int32") { return tyInt32; }
|
|
||||||
if String_Eq(name, "int64") { return tyInt64; }
|
|
||||||
if String_Eq(name, "int") { return tyInt; }
|
|
||||||
if String_Eq(name, "uint8") { return tyUInt8; }
|
|
||||||
if String_Eq(name, "uint16") { return tyUInt16; }
|
|
||||||
if String_Eq(name, "uint32") { return tyUInt32; }
|
|
||||||
if String_Eq(name, "uint64") { return tyUInt64; }
|
|
||||||
if String_Eq(name, "uint") { return tyUInt; }
|
|
||||||
if String_Eq(name, "float32") { return tyFloat32; }
|
|
||||||
if String_Eq(name, "float64") { return tyFloat64; }
|
|
||||||
if String_Eq(name, "float") { return tyFloat64; }
|
|
||||||
return tyNamed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Lcx_TypeKindToName(kind: int) -> String {
|
func Lcx_TypeKindToName(kind: int) -> String {
|
||||||
@@ -178,38 +155,58 @@ func Lcx_SubstituteType(ctx: *LowerCtx, te: *TypeExpr) -> *TypeExpr {
|
|||||||
return te;
|
return te;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build C function-pointer type string from a tekFunc TypeExpr, e.g. "int (*)(int)"
|
// Sanitize a C type fragment for use inside BuxFn_* mangled names
|
||||||
func Lcx_BuildFuncTypeName(te: *TypeExpr) -> String {
|
func Lcx_SanitizeFatPart(s: String) -> String {
|
||||||
if te == null as *TypeExpr || te.kind != tekFunc { return "void (*)(void)"; }
|
var r: String = s;
|
||||||
var retName: String = "void";
|
if String_Eq(r, "String") || String_Eq(r, "str") || String_Eq(r, "const char*") {
|
||||||
if te.funcRet != null as *TypeExpr {
|
return "cstr";
|
||||||
if te.funcRet.kind == tekPointer && te.funcRet.pointerPointee != null as *TypeExpr {
|
|
||||||
retName = String_Concat(te.funcRet.pointerPointee.typeName, "*");
|
|
||||||
} else {
|
|
||||||
retName = te.funcRet.typeName;
|
|
||||||
}
|
|
||||||
if String_Eq(retName, "") { retName = "int"; }
|
|
||||||
}
|
}
|
||||||
var result: String = retName;
|
if String_Eq(r, "unsigned int") { return "uint"; }
|
||||||
result = String_Concat(result, " (*)(");
|
// crude replacements for * and spaces
|
||||||
|
r = String_ReplaceAll(r, "*", "Ptr");
|
||||||
|
r = String_ReplaceAll(r, " ", "_");
|
||||||
|
r = String_ReplaceAll(r, "(", "");
|
||||||
|
r = String_ReplaceAll(r, ")", "");
|
||||||
|
r = String_ReplaceAll(r, ",", "_");
|
||||||
|
if String_Eq(r, "") { return "int"; }
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lcx_TypeExprFatPart(te: *TypeExpr) -> String {
|
||||||
|
if te == null as *TypeExpr { return "void"; }
|
||||||
|
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
|
||||||
|
return Lcx_SanitizeFatPart(String_Concat(te.pointerPointee.typeName, "Ptr"));
|
||||||
|
}
|
||||||
|
if te.kind == tekFunc {
|
||||||
|
return Lcx_SanitizeFatPart(Lcx_BuildFuncTypeName(te));
|
||||||
|
}
|
||||||
|
var n: String = te.typeName;
|
||||||
|
if String_Eq(n, "") { n = "int"; }
|
||||||
|
return Lcx_SanitizeFatPart(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fat function-pointer type name: BuxFn_<ret>_<p0>_<p1>...
|
||||||
|
// Enables multi-instance closures (code + env).
|
||||||
|
func Lcx_BuildFuncTypeName(te: *TypeExpr) -> String {
|
||||||
|
if te == null as *TypeExpr || te.kind != tekFunc {
|
||||||
|
return "BuxFn_void_void";
|
||||||
|
}
|
||||||
|
var retPart: String = "void";
|
||||||
|
if te.funcRet != null as *TypeExpr {
|
||||||
|
retPart = Lcx_TypeExprFatPart(te.funcRet);
|
||||||
|
}
|
||||||
|
var result: String = String_Concat("BuxFn_", retPart);
|
||||||
var cur: *TypeExprList = te.funcParams;
|
var cur: *TypeExprList = te.funcParams;
|
||||||
var first: bool = true;
|
var anyParam: bool = false;
|
||||||
while cur != null as *TypeExprList {
|
while cur != null as *TypeExprList {
|
||||||
if !first {
|
result = String_Concat(result, "_");
|
||||||
result = String_Concat(result, ", ");
|
result = String_Concat(result, Lcx_TypeExprFatPart(cur.te));
|
||||||
}
|
anyParam = true;
|
||||||
var pName: String = "int";
|
|
||||||
if cur.te.kind == tekPointer && cur.te.pointerPointee != null as *TypeExpr {
|
|
||||||
pName = String_Concat(cur.te.pointerPointee.typeName, "*");
|
|
||||||
} else {
|
|
||||||
pName = cur.te.typeName;
|
|
||||||
}
|
|
||||||
if String_Eq(pName, "") { pName = "int"; }
|
|
||||||
result = String_Concat(result, pName);
|
|
||||||
first = false;
|
|
||||||
cur = cur.next;
|
cur = cur.next;
|
||||||
}
|
}
|
||||||
result = String_Concat(result, ")");
|
if !anyParam {
|
||||||
|
result = String_Concat(result, "_void");
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,9 +466,44 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let sym: Symbol = Scope_Lookup(ctx.scope, expr.strValue);
|
||||||
|
// Named function used as a value → fat pointer via __adapt_ wrapper
|
||||||
|
if sym.kind == skFunc {
|
||||||
|
var fatName: String = "BuxFn_int_int";
|
||||||
|
if sym.refType != null as *TypeExpr && sym.refType.kind == tekFunc {
|
||||||
|
fatName = Lcx_BuildFuncTypeName(sym.refType);
|
||||||
|
} else if !String_Eq(sym.typeName, "") && String_StartsWith(sym.typeName, "BuxFn_") {
|
||||||
|
fatName = sym.typeName;
|
||||||
|
}
|
||||||
|
n.kind = hStructInit;
|
||||||
|
n.strValue = fatName;
|
||||||
|
n.typeKind = tyFunc;
|
||||||
|
n.typeName = fatName;
|
||||||
|
let codeField: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
codeField.kind = hBlock;
|
||||||
|
codeField.strValue = "code";
|
||||||
|
let codeVal: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
codeVal.kind = hVar;
|
||||||
|
codeVal.strValue = String_Concat("__adapt_", expr.strValue);
|
||||||
|
codeField.child1 = codeVal;
|
||||||
|
let envField: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
envField.kind = hBlock;
|
||||||
|
envField.strValue = "env";
|
||||||
|
let nullEnv: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
nullEnv.kind = hCast;
|
||||||
|
nullEnv.typeName = "void*";
|
||||||
|
let zero: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
zero.kind = hLit;
|
||||||
|
zero.intValue = tkIntLiteral;
|
||||||
|
zero.strValue = "0";
|
||||||
|
nullEnv.child1 = zero;
|
||||||
|
envField.child1 = nullEnv;
|
||||||
|
codeField.child3 = envField;
|
||||||
|
n.child1 = codeField;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
n.kind = hVar;
|
n.kind = hVar;
|
||||||
n.strValue = expr.strValue;
|
n.strValue = expr.strValue;
|
||||||
let sym: Symbol = Scope_Lookup(ctx.scope, expr.strValue);
|
|
||||||
n.typeKind = sym.typeKind;
|
n.typeKind = sym.typeKind;
|
||||||
|
|
||||||
if expr.refType != null as *TypeExpr {
|
if expr.refType != null as *TypeExpr {
|
||||||
@@ -566,6 +598,30 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Overflow checking: in @[Checked] mode, lower +, -, * on signed integers to checked calls
|
||||||
|
if ctx.checkedFunc && !ctx.releaseFunc {
|
||||||
|
var opKind: int = expr.intValue;
|
||||||
|
var isArithOp: bool = opKind == tkPlus || opKind == tkMinus || opKind == tkStar;
|
||||||
|
var isSignedInt: bool = false;
|
||||||
|
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
|
||||||
|
let lhsKind: int = Lcx_ResolveTypeKind(expr.child1.refType);
|
||||||
|
isSignedInt = Type_IsSigned(lhsKind);
|
||||||
|
}
|
||||||
|
if isArithOp && isSignedInt {
|
||||||
|
var checkedFunc: String = "";
|
||||||
|
if opKind == tkPlus { checkedFunc = "bux_add_i64_checked"; }
|
||||||
|
else if opKind == tkMinus { checkedFunc = "bux_sub_i64_checked"; }
|
||||||
|
else if opKind == tkStar { checkedFunc = "bux_mul_i64_checked"; }
|
||||||
|
if !String_Eq(checkedFunc, "") {
|
||||||
|
n.kind = hCall;
|
||||||
|
n.strValue = checkedFunc;
|
||||||
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
||||||
|
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
n.kind = hBinary;
|
n.kind = hBinary;
|
||||||
n.intValue = expr.intValue; // operator
|
n.intValue = expr.intValue; // operator
|
||||||
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
||||||
@@ -575,6 +631,20 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
|
|
||||||
// Unary
|
// Unary
|
||||||
if kind == ekUnary {
|
if kind == ekUnary {
|
||||||
|
// Overflow checking: in @[Checked] mode, lower negation on signed integers to checked call
|
||||||
|
if ctx.checkedFunc && !ctx.releaseFunc && expr.intValue == tkMinus {
|
||||||
|
var isSignedInt: bool = false;
|
||||||
|
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
|
||||||
|
let operandKind: int = Lcx_ResolveTypeKind(expr.child1.refType);
|
||||||
|
isSignedInt = Type_IsSigned(operandKind);
|
||||||
|
}
|
||||||
|
if isSignedInt {
|
||||||
|
n.kind = hCall;
|
||||||
|
n.strValue = "bux_neg_i64_checked";
|
||||||
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
n.kind = hUnary;
|
n.kind = hUnary;
|
||||||
n.intValue = expr.intValue;
|
n.intValue = expr.intValue;
|
||||||
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
||||||
@@ -1132,20 +1202,85 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Closure: generate function and return address-of
|
// Closure: fat function pointer (multi-instance via heap env + maker)
|
||||||
if kind == ekClosure {
|
if kind == ekClosure {
|
||||||
let f: *HirFunc = Lcx_LowerClosureFunc(ctx, expr);
|
let f: *HirFunc = Lcx_LowerClosureFunc(ctx, expr);
|
||||||
n.kind = hUnary;
|
var fatName: String = "BuxFn_int_int";
|
||||||
n.intValue = tkAmp;
|
if expr.refType != null as *TypeExpr && expr.refType.kind == tekFunc {
|
||||||
let varNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
fatName = Lcx_BuildFuncTypeName(expr.refType);
|
||||||
varNode.kind = hVar;
|
} else if f.paramCount >= 2 {
|
||||||
varNode.strValue = f.name;
|
// thunk has __env + user params; approximate from ret + user arity
|
||||||
varNode.typeKind = tyFunc;
|
fatName = "BuxFn_int_int";
|
||||||
n.child1 = varNode;
|
if f.paramCount == 3 { fatName = "BuxFn_int_int_int"; }
|
||||||
n.typeKind = tyFunc;
|
if f.paramCount == 1 { fatName = "BuxFn_int_void"; }
|
||||||
if expr.refType != null as *TypeExpr {
|
|
||||||
n.typeName = Lcx_BuildFuncTypeName(expr.refType);
|
|
||||||
}
|
}
|
||||||
|
if f.captureCount > 0 {
|
||||||
|
// Call __make_<closure>(captures...) which heap-allocs env
|
||||||
|
n.kind = hCall;
|
||||||
|
n.strValue = String_Concat("__make_", f.name);
|
||||||
|
n.typeKind = tyFunc;
|
||||||
|
n.typeName = fatName;
|
||||||
|
var ci: int = 0;
|
||||||
|
while ci < f.captureCount {
|
||||||
|
var capName: String = "";
|
||||||
|
if ci == 0 { capName = f.captureName0; }
|
||||||
|
else if ci == 1 { capName = f.captureName1; }
|
||||||
|
else if ci == 2 { capName = f.captureName2; }
|
||||||
|
else if ci == 3 { capName = f.captureName3; }
|
||||||
|
else if ci == 4 { capName = f.captureName4; }
|
||||||
|
else if ci == 5 { capName = f.captureName5; }
|
||||||
|
else if ci == 6 { capName = f.captureName6; }
|
||||||
|
else if ci == 7 { capName = f.captureName7; }
|
||||||
|
let capVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
capVar.kind = hVar;
|
||||||
|
capVar.strValue = capName;
|
||||||
|
if ci == 0 { n.child1 = capVar; }
|
||||||
|
else if ci == 1 { n.child2 = capVar; }
|
||||||
|
else if ci == 2 {
|
||||||
|
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
|
||||||
|
firstExtra.node = capVar;
|
||||||
|
firstExtra.next = null as *HirArgList;
|
||||||
|
n.extraData = firstExtra as *void;
|
||||||
|
n.extraCount = 1;
|
||||||
|
} else {
|
||||||
|
var cur: *HirArgList = n.extraData as *HirArgList;
|
||||||
|
while cur.next != null as *HirArgList { cur = cur.next; }
|
||||||
|
let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
|
||||||
|
newNode.node = capVar;
|
||||||
|
newNode.next = null as *HirArgList;
|
||||||
|
cur.next = newNode;
|
||||||
|
n.extraCount = n.extraCount + 1;
|
||||||
|
}
|
||||||
|
ci = ci + 1;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
// Capture-less: compound literal fat pointer with NULL env
|
||||||
|
n.kind = hStructInit;
|
||||||
|
n.strValue = fatName;
|
||||||
|
n.typeKind = tyFunc;
|
||||||
|
n.typeName = fatName;
|
||||||
|
let codeField: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
codeField.kind = hBlock;
|
||||||
|
codeField.strValue = "code";
|
||||||
|
let codeVal: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
codeVal.kind = hVar;
|
||||||
|
codeVal.strValue = f.name;
|
||||||
|
codeField.child1 = codeVal;
|
||||||
|
let envField: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
envField.kind = hBlock;
|
||||||
|
envField.strValue = "env";
|
||||||
|
let nullEnv: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
nullEnv.kind = hCast;
|
||||||
|
nullEnv.typeName = "void*";
|
||||||
|
let zero: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
zero.kind = hLit;
|
||||||
|
zero.intValue = tkIntLiteral;
|
||||||
|
zero.strValue = "0";
|
||||||
|
nullEnv.child1 = zero;
|
||||||
|
envField.child1 = nullEnv;
|
||||||
|
codeField.child3 = envField;
|
||||||
|
n.child1 = codeField;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1227,6 +1362,25 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Block expression (boolValue = true means unsafe block)
|
||||||
|
if kind == ekBlock {
|
||||||
|
if expr.refBlock != null as *Block {
|
||||||
|
if expr.boolValue {
|
||||||
|
let oldChecked: bool = ctx.checkedFunc;
|
||||||
|
let oldRelease: bool = ctx.releaseFunc;
|
||||||
|
ctx.checkedFunc = false;
|
||||||
|
ctx.releaseFunc = false;
|
||||||
|
let blockNode: *HirNode = Lcx_LowerBlock(ctx, expr.refBlock, -1);
|
||||||
|
ctx.checkedFunc = oldChecked;
|
||||||
|
ctx.releaseFunc = oldRelease;
|
||||||
|
return blockNode;
|
||||||
|
} else {
|
||||||
|
return Lcx_LowerBlock(ctx, expr.refBlock, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1461,71 +1615,8 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If init is a closure with captures, emit capture assignments before the let
|
// Capturing closures allocate env via __make_* at ekClosure site.
|
||||||
if stmt.child1 != null as *Expr && stmt.child1.kind == ekClosure && stmt.child1.captureCount > 0 {
|
// Wrap with defer if present
|
||||||
let closureIdx: int = ctx.funcCount - 1;
|
|
||||||
let envInst: String = String_Concat("__closure_env_instance_", String_FromInt(closureIdx));
|
|
||||||
// Build a block: capture assignments + let store
|
|
||||||
let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
||||||
blockNode.kind = hBlock;
|
|
||||||
blockNode.line = line;
|
|
||||||
blockNode.column = col;
|
|
||||||
var firstStmt: *HirNode = null as *HirNode;
|
|
||||||
var lastStmt: *HirNode = null as *HirNode;
|
|
||||||
var ci: int = 0;
|
|
||||||
while ci < stmt.child1.captureCount {
|
|
||||||
var capName: String = "";
|
|
||||||
if ci == 0 { capName = stmt.child1.captureName0; }
|
|
||||||
else if ci == 1 { capName = stmt.child1.captureName1; }
|
|
||||||
else if ci == 2 { capName = stmt.child1.captureName2; }
|
|
||||||
else if ci == 3 { capName = stmt.child1.captureName3; }
|
|
||||||
else if ci == 4 { capName = stmt.child1.captureName4; }
|
|
||||||
else if ci == 5 { capName = stmt.child1.captureName5; }
|
|
||||||
else if ci == 6 { capName = stmt.child1.captureName6; }
|
|
||||||
else if ci == 7 { capName = stmt.child1.captureName7; }
|
|
||||||
// Build: envInst.capName = capName;
|
|
||||||
let assignNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
||||||
assignNode.kind = hAssign;
|
|
||||||
assignNode.line = line;
|
|
||||||
assignNode.column = col;
|
|
||||||
let fieldNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
||||||
fieldNode.kind = hFieldAccess;
|
|
||||||
fieldNode.strValue = capName;
|
|
||||||
let baseNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
||||||
baseNode.kind = hVar;
|
|
||||||
baseNode.strValue = envInst;
|
|
||||||
fieldNode.child1 = baseNode;
|
|
||||||
let valNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
||||||
valNode.kind = hVar;
|
|
||||||
valNode.strValue = capName;
|
|
||||||
assignNode.child1 = fieldNode;
|
|
||||||
assignNode.child2 = valNode;
|
|
||||||
if firstStmt == null as *HirNode {
|
|
||||||
firstStmt = assignNode;
|
|
||||||
lastStmt = assignNode;
|
|
||||||
} else {
|
|
||||||
lastStmt.child3 = assignNode;
|
|
||||||
lastStmt = assignNode;
|
|
||||||
}
|
|
||||||
ci = ci + 1;
|
|
||||||
}
|
|
||||||
// Append the let store
|
|
||||||
if firstStmt == null as *HirNode {
|
|
||||||
firstStmt = storeNode;
|
|
||||||
lastStmt = storeNode;
|
|
||||||
} else {
|
|
||||||
lastStmt.child3 = storeNode;
|
|
||||||
lastStmt = storeNode;
|
|
||||||
}
|
|
||||||
// Append auto-Drop defer if present
|
|
||||||
if deferNode != null as *HirNode {
|
|
||||||
lastStmt.child3 = deferNode;
|
|
||||||
lastStmt = deferNode;
|
|
||||||
}
|
|
||||||
blockNode.child1 = firstStmt;
|
|
||||||
return blockNode;
|
|
||||||
}
|
|
||||||
// Non-closure: wrap with defer if present
|
|
||||||
if deferNode != null as *HirNode {
|
if deferNode != null as *HirNode {
|
||||||
storeNode.child3 = deferNode;
|
storeNode.child3 = deferNode;
|
||||||
let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
@@ -2244,8 +2335,16 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
|
|||||||
|
|
||||||
let retTe: *TypeExpr = Lcx_SubstituteType(ctx, decl.retType);
|
let retTe: *TypeExpr = Lcx_SubstituteType(ctx, decl.retType);
|
||||||
if retTe != null as *TypeExpr {
|
if retTe != null as *TypeExpr {
|
||||||
f.retTypeName = retTe.typeName;
|
|
||||||
f.retTypeKind = Lcx_ResolveTypeKind(retTe);
|
f.retTypeKind = Lcx_ResolveTypeKind(retTe);
|
||||||
|
if retTe.kind == tekFunc {
|
||||||
|
f.retTypeName = Lcx_BuildFuncTypeName(retTe);
|
||||||
|
} else if !String_Eq(retTe.typeName, "") {
|
||||||
|
f.retTypeName = retTe.typeName;
|
||||||
|
} else if retTe.kind == tekPointer && retTe.pointerPointee != null as *TypeExpr {
|
||||||
|
f.retTypeName = String_Concat(retTe.pointerPointee.typeName, "*");
|
||||||
|
} else {
|
||||||
|
f.retTypeName = "";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
f.retTypeName = "";
|
f.retTypeName = "";
|
||||||
f.retTypeKind = 0;
|
f.retTypeKind = 0;
|
||||||
@@ -2320,25 +2419,36 @@ func Lcx_LowerClosureFunc(ctx: *LowerCtx, expr: *Expr) -> *HirFunc {
|
|||||||
f.isPublic = false;
|
f.isPublic = false;
|
||||||
|
|
||||||
let params: *Decl = expr.closureParams;
|
let params: *Decl = expr.closureParams;
|
||||||
if params != null as *Decl {
|
// Fat-func ABI: leading void* __env, then user params
|
||||||
f.paramCount = params.paramCount;
|
var userCount: int = 0;
|
||||||
if params.paramCount > 0 { f.param0 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param0, ¶ms.param0, ctx); }
|
if params != null as *Decl { userCount = params.paramCount; }
|
||||||
if params.paramCount > 1 { f.param1 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param1, ¶ms.param1, ctx); }
|
f.paramCount = userCount + 1;
|
||||||
if params.paramCount > 2 { f.param2 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param2, ¶ms.param2, ctx); }
|
f.param0 = bux_alloc(sizeof(HirParam)) as *HirParam;
|
||||||
if params.paramCount > 3 { f.param3 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param3, ¶ms.param3, ctx); }
|
f.param0.name = "__env";
|
||||||
if params.paramCount > 4 { f.param4 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param4, ¶ms.param4, ctx); }
|
f.param0.typeKind = tyPointer;
|
||||||
if params.paramCount > 5 { f.param5 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param5, ¶ms.param5, ctx); }
|
f.param0.typeName = "void*";
|
||||||
if params.paramCount > 6 { f.param6 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param6, ¶ms.param6, ctx); }
|
if userCount > 0 { f.param1 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param1, ¶ms.param0, ctx); }
|
||||||
if params.paramCount > 7 { f.param7 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param7, ¶ms.param7, ctx); }
|
if userCount > 1 { f.param2 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param2, ¶ms.param1, ctx); }
|
||||||
if params.paramCount > 8 { f.param8 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param8, ¶ms.param8, ctx); }
|
if userCount > 2 { f.param3 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param3, ¶ms.param2, ctx); }
|
||||||
}
|
if userCount > 3 { f.param4 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param4, ¶ms.param3, ctx); }
|
||||||
|
if userCount > 4 { f.param5 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param5, ¶ms.param4, ctx); }
|
||||||
|
if userCount > 5 { f.param6 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param6, ¶ms.param5, ctx); }
|
||||||
|
if userCount > 6 { f.param7 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param7, ¶ms.param6, ctx); }
|
||||||
|
if userCount > 7 { f.param8 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param8, ¶ms.param7, ctx); }
|
||||||
|
|
||||||
if expr.refType != null as *TypeExpr && expr.refType.kind == tekFunc && expr.refType.funcRet != null as *TypeExpr {
|
if expr.refType != null as *TypeExpr && expr.refType.kind == tekFunc {
|
||||||
f.retTypeName = expr.refType.funcRet.typeName;
|
// Return type of the *thunk* is the closure's return type (not the fat type)
|
||||||
f.retTypeKind = Lcx_ResolveTypeKind(expr.refType.funcRet);
|
if expr.refType.funcRet != null as *TypeExpr {
|
||||||
|
f.retTypeName = expr.refType.funcRet.typeName;
|
||||||
|
if String_Eq(f.retTypeName, "") { f.retTypeName = "int"; }
|
||||||
|
f.retTypeKind = Lcx_ResolveTypeKind(expr.refType.funcRet);
|
||||||
|
} else {
|
||||||
|
f.retTypeName = "void";
|
||||||
|
f.retTypeKind = tyVoid;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
f.retTypeName = "";
|
f.retTypeName = "int";
|
||||||
f.retTypeKind = 0;
|
f.retTypeKind = tyInt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy capture metadata from AST
|
// Copy capture metadata from AST
|
||||||
|
|||||||
@@ -277,6 +277,7 @@ func lexKeywordKind(text: String) -> int {
|
|||||||
if String_Eq(text, "switch") { return tkSwitch; }
|
if String_Eq(text, "switch") { return tkSwitch; }
|
||||||
if String_Eq(text, "case") { return tkCase; }
|
if String_Eq(text, "case") { return tkCase; }
|
||||||
if String_Eq(text, "default") { return tkDefault; }
|
if String_Eq(text, "default") { return tkDefault; }
|
||||||
|
if String_Eq(text, "unsafe") { return tkUnsafe; }
|
||||||
if String_Eq(text, "async") { return tkAsync; }
|
if String_Eq(text, "async") { return tkAsync; }
|
||||||
if String_Eq(text, "await") { return tkAwait; }
|
if String_Eq(text, "await") { return tkAwait; }
|
||||||
if String_Eq(text, "spawn") { return tkSpawn; }
|
if String_Eq(text, "spawn") { return tkSpawn; }
|
||||||
|
|||||||
@@ -399,6 +399,15 @@ func parserParsePrimary(p: *Parser) -> *Expr {
|
|||||||
return parserParseClosure(p);
|
return parserParseClosure(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unsafe { ... } — unsafe block expression
|
||||||
|
if kind == tkUnsafe {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
let e: *Expr = parserMakeExpr(ekBlock, line, col);
|
||||||
|
e.boolValue = true; // marks this block as unsafe
|
||||||
|
e.refBlock = parserParseBlock(p);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
parserEmitDiag(p, line, col, "expected expression");
|
parserEmitDiag(p, line, col, "expected expression");
|
||||||
return parserMakeExpr(ekLiteral, line, col);
|
return parserMakeExpr(ekLiteral, line, col);
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-30
@@ -136,7 +136,6 @@ func Sema_BuildFuncTypeExprFromDecl(decl: *Decl) -> *TypeExpr {
|
|||||||
|
|
||||||
func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
|
func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
|
||||||
if te == null as *TypeExpr { return tyUnknown; }
|
if te == null as *TypeExpr { return tyUnknown; }
|
||||||
let name: String = te.typeName;
|
|
||||||
|
|
||||||
if te.kind == tekPointer {
|
if te.kind == tekPointer {
|
||||||
return tyPointer;
|
return tyPointer;
|
||||||
@@ -146,33 +145,7 @@ func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
|
|||||||
return tyFunc;
|
return tyFunc;
|
||||||
}
|
}
|
||||||
|
|
||||||
if String_Eq(name, "void") { return tyVoid; }
|
return Type_FromName(te.typeName);
|
||||||
if String_Eq(name, "bool") { return tyBool; }
|
|
||||||
if String_Eq(name, "bool8") { return tyBool8; }
|
|
||||||
if String_Eq(name, "bool16") { return tyBool16; }
|
|
||||||
if String_Eq(name, "bool32") { return tyBool32; }
|
|
||||||
if String_Eq(name, "char8") { return tyChar8; }
|
|
||||||
if String_Eq(name, "char16") { return tyChar16; }
|
|
||||||
if String_Eq(name, "char32") { return tyChar32; }
|
|
||||||
if String_Eq(name, "String") { return tyStr; }
|
|
||||||
if String_Eq(name, "str") { return tyStr; }
|
|
||||||
if String_Eq(name, "int8") { return tyInt8; }
|
|
||||||
if String_Eq(name, "int16") { return tyInt16; }
|
|
||||||
if String_Eq(name, "int32") { return tyInt32; }
|
|
||||||
if String_Eq(name, "int64") { return tyInt64; }
|
|
||||||
if String_Eq(name, "int") { return tyInt; }
|
|
||||||
if String_Eq(name, "uint8") { return tyUInt8; }
|
|
||||||
if String_Eq(name, "uint16") { return tyUInt16; }
|
|
||||||
if String_Eq(name, "uint32") { return tyUInt32; }
|
|
||||||
if String_Eq(name, "uint64") { return tyUInt64; }
|
|
||||||
if String_Eq(name, "uint") { return tyUInt; }
|
|
||||||
if String_Eq(name, "float32") { return tyFloat32; }
|
|
||||||
if String_Eq(name, "float64") { return tyFloat64; }
|
|
||||||
if String_Eq(name, "float") { return tyFloat64; }
|
|
||||||
|
|
||||||
// Type resolution uses scope-based lookup via Sema_CollectGlobals
|
|
||||||
// TODO: add StringMap-based type table for faster named-type validation
|
|
||||||
return tyNamed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -758,10 +731,17 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
|||||||
return tyUnknown;
|
return tyUnknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block expression
|
// Block expression (boolValue = true means unsafe block)
|
||||||
if kind == ekBlock {
|
if kind == ekBlock {
|
||||||
if expr.refBlock != null as *Block {
|
if expr.refBlock != null as *Block {
|
||||||
Sema_CheckBlock(sema, expr.refBlock);
|
if expr.boolValue {
|
||||||
|
let prevChecked: bool = sema.checkedFunc;
|
||||||
|
sema.checkedFunc = false;
|
||||||
|
Sema_CheckBlock(sema, expr.refBlock);
|
||||||
|
sema.checkedFunc = prevChecked;
|
||||||
|
} else {
|
||||||
|
Sema_CheckBlock(sema, expr.refBlock);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return tyVoid;
|
return tyVoid;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ const tkDefer: int = 106;
|
|||||||
const tkSwitch: int = 107;
|
const tkSwitch: int = 107;
|
||||||
const tkCase: int = 108;
|
const tkCase: int = 108;
|
||||||
const tkDefault: int = 109;
|
const tkDefault: int = 109;
|
||||||
|
const tkUnsafe: int = 110;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Token struct
|
// Token struct
|
||||||
@@ -165,6 +166,7 @@ func Token_IsKeyword(kind: int) -> bool {
|
|||||||
if kind >= tkFunc && kind <= tkExtern { return true; }
|
if kind >= tkFunc && kind <= tkExtern { return true; }
|
||||||
if kind >= tkAs && kind <= tkSuper { return true; }
|
if kind >= tkAs && kind <= tkSuper { return true; }
|
||||||
if kind == tkSizeOf { return true; }
|
if kind == tkSizeOf { return true; }
|
||||||
|
if kind >= tkDefer && kind <= tkUnsafe { return true; }
|
||||||
if kind >= tkAsync && kind <= tkSpawn { return true; }
|
if kind >= tkAsync && kind <= tkSpawn { return true; }
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -215,6 +217,15 @@ func Token_KeywordKind(text: String) -> int {
|
|||||||
if String_Eq(text, "self") { return tkSelf; }
|
if String_Eq(text, "self") { return tkSelf; }
|
||||||
if String_Eq(text, "super") { return tkSuper; }
|
if String_Eq(text, "super") { return tkSuper; }
|
||||||
if String_Eq(text, "sizeof") { return tkSizeOf; }
|
if String_Eq(text, "sizeof") { return tkSizeOf; }
|
||||||
|
if String_Eq(text, "defer") { return tkDefer; }
|
||||||
|
if String_Eq(text, "switch") { return tkSwitch; }
|
||||||
|
if String_Eq(text, "case") { return tkCase; }
|
||||||
|
if String_Eq(text, "default") { return tkDefault; }
|
||||||
|
if String_Eq(text, "unsafe") { return tkUnsafe; }
|
||||||
|
if String_Eq(text, "discard") { return tkDiscard; }
|
||||||
|
if String_Eq(text, "async") { return tkAsync; }
|
||||||
|
if String_Eq(text, "await") { return tkAwait; }
|
||||||
|
if String_Eq(text, "spawn") { return tkSpawn; }
|
||||||
if String_Eq(text, "true") { return tkBoolLiteral; }
|
if String_Eq(text, "true") { return tkBoolLiteral; }
|
||||||
if String_Eq(text, "false") { return tkBoolLiteral; }
|
if String_Eq(text, "false") { return tkBoolLiteral; }
|
||||||
return tkIdent;
|
return tkIdent;
|
||||||
@@ -259,6 +270,15 @@ func Token_KindName(kind: int) -> String {
|
|||||||
if kind == tkNull { return "null"; }
|
if kind == tkNull { return "null"; }
|
||||||
if kind == tkSelf { return "self"; }
|
if kind == tkSelf { return "self"; }
|
||||||
if kind == tkSuper { return "super"; }
|
if kind == tkSuper { return "super"; }
|
||||||
|
if kind == tkUnsafe { return "unsafe"; }
|
||||||
|
if kind == tkDefer { return "defer"; }
|
||||||
|
if kind == tkSwitch { return "switch"; }
|
||||||
|
if kind == tkCase { return "case"; }
|
||||||
|
if kind == tkDefault { return "default"; }
|
||||||
|
if kind == tkAsync { return "async"; }
|
||||||
|
if kind == tkAwait { return "await"; }
|
||||||
|
if kind == tkSpawn { return "spawn"; }
|
||||||
|
if kind == tkDiscard { return "discard"; }
|
||||||
if kind == tkLParen { return "("; }
|
if kind == tkLParen { return "("; }
|
||||||
if kind == tkRParen { return ")"; }
|
if kind == tkRParen { return ")"; }
|
||||||
if kind == tkLBrace { return "{"; }
|
if kind == tkLBrace { return "{"; }
|
||||||
|
|||||||
@@ -186,4 +186,94 @@ func Type_ToString(t: Type) -> String {
|
|||||||
if t.kind == tyFunc { return t.name; }
|
if t.kind == tyFunc { return t.name; }
|
||||||
return "?";
|
return "?";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Type_FromName — central type-name → kind mapping (used by sema, hir_lower)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func Type_FromName(name: String) -> int {
|
||||||
|
if String_Eq(name, "void") { return tyVoid; }
|
||||||
|
if String_Eq(name, "bool") { return tyBool; }
|
||||||
|
if String_Eq(name, "bool8") { return tyBool8; }
|
||||||
|
if String_Eq(name, "bool16") { return tyBool16; }
|
||||||
|
if String_Eq(name, "bool32") { return tyBool32; }
|
||||||
|
if String_Eq(name, "char8") { return tyChar8; }
|
||||||
|
if String_Eq(name, "char16") { return tyChar16; }
|
||||||
|
if String_Eq(name, "char32") { return tyChar32; }
|
||||||
|
if String_Eq(name, "String") { return tyStr; }
|
||||||
|
if String_Eq(name, "str") { return tyStr; }
|
||||||
|
if String_Eq(name, "int8") { return tyInt8; }
|
||||||
|
if String_Eq(name, "int16") { return tyInt16; }
|
||||||
|
if String_Eq(name, "int32") { return tyInt32; }
|
||||||
|
if String_Eq(name, "int64") { return tyInt64; }
|
||||||
|
if String_Eq(name, "int") { return tyInt; }
|
||||||
|
if String_Eq(name, "uint8") { return tyUInt8; }
|
||||||
|
if String_Eq(name, "uint16") { return tyUInt16; }
|
||||||
|
if String_Eq(name, "uint32") { return tyUInt32; }
|
||||||
|
if String_Eq(name, "uint64") { return tyUInt64; }
|
||||||
|
if String_Eq(name, "uint") { return tyUInt; }
|
||||||
|
if String_Eq(name, "float32") { return tyFloat32; }
|
||||||
|
if String_Eq(name, "float64") { return tyFloat64; }
|
||||||
|
if String_Eq(name, "float") { return tyFloat64; }
|
||||||
|
return tyNamed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Type_ToCName — type kind → C type name (used by C backend)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func Type_ToCName(kind: int) -> String {
|
||||||
|
if kind == tyVoid { return "void"; }
|
||||||
|
if kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32 { return "bool"; }
|
||||||
|
if kind == tyChar8 { return "char"; }
|
||||||
|
if kind == tyChar16 { return "uint16"; }
|
||||||
|
if kind == tyChar32 { return "uint32"; }
|
||||||
|
if kind == tyStr { return "String"; }
|
||||||
|
if kind == tyInt8 { return "int8"; }
|
||||||
|
if kind == tyInt16 { return "int16"; }
|
||||||
|
if kind == tyInt32 { return "int32"; }
|
||||||
|
if kind == tyInt64 { return "int64"; }
|
||||||
|
if kind == tyInt { return "int"; }
|
||||||
|
if kind == tyUInt8 { return "uint8"; }
|
||||||
|
if kind == tyUInt16 { return "uint16"; }
|
||||||
|
if kind == tyUInt32 { return "uint32"; }
|
||||||
|
if kind == tyUInt64 { return "uint64"; }
|
||||||
|
if kind == tyUInt { return "uint"; }
|
||||||
|
if kind == tyFloat32 { return "float32"; }
|
||||||
|
if kind == tyFloat64 { return "float64"; }
|
||||||
|
if kind == tyPointer { return "void*"; }
|
||||||
|
// Fat function pointer — concrete BuxFn_* name comes from typeName field
|
||||||
|
if kind == tyFunc { return "BuxFn"; }
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Signed / Unsigned / Float predicates
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func Type_IsSigned(kind: int) -> bool {
|
||||||
|
return kind == tyInt8 || kind == tyInt16 || kind == tyInt32 || kind == tyInt64 || kind == tyInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Type_IsUnsigned(kind: int) -> bool {
|
||||||
|
return kind == tyUInt8 || kind == tyUInt16 || kind == tyUInt32 || kind == tyUInt64 || kind == tyUInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Type_IsFloat(kind: int) -> bool {
|
||||||
|
return kind == tyFloat32 || kind == tyFloat64;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Type_SizeOf — byte size of a primitive type (0 for non-primitive)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func Type_SizeOf(kind: int) -> int {
|
||||||
|
if kind == tyBool || kind == tyBool8 || kind == tyChar8 || kind == tyInt8 || kind == tyUInt8 { return 1; }
|
||||||
|
if kind == tyBool16 || kind == tyChar16 || kind == tyInt16 || kind == tyUInt16 { return 2; }
|
||||||
|
if kind == tyBool32 || kind == tyChar32 || kind == tyInt32 || kind == tyUInt32 || kind == tyFloat32 { return 4; }
|
||||||
|
if kind == tyInt64 || kind == tyUInt64 || kind == tyFloat64 { return 8; }
|
||||||
|
if kind == tyInt || kind == tyUInt { return 8; }
|
||||||
|
if kind == tyPointer { return 8; }
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+54
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Golden tests for Rust-style compiler diagnostics.
|
||||||
|
# Usage: from repo root: tests/error_golden/run.sh [path/to/buxc]
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||||
|
BUXC="${1:-$ROOT/buxc}"
|
||||||
|
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
if [[ ! -x "$BUXC" ]]; then
|
||||||
|
echo "error: buxc not found at $BUXC (run make build first)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
passed=0
|
||||||
|
failed=0
|
||||||
|
|
||||||
|
normalize() {
|
||||||
|
# Replace absolute path prefix with FILE, drop trailing blank lines
|
||||||
|
sed -E \
|
||||||
|
-e "s|$DIR/[^:]+:|FILE:|g" \
|
||||||
|
-e "s|$ROOT/[^:]+:|FILE:|g" \
|
||||||
|
-e "s|//+|/|g" \
|
||||||
|
| sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'
|
||||||
|
}
|
||||||
|
|
||||||
|
for case_dir in "$DIR"/*/; do
|
||||||
|
name="$(basename "$case_dir")"
|
||||||
|
[[ -f "$case_dir/expected.err" ]] || continue
|
||||||
|
[[ -f "$case_dir/bux.toml" ]] || continue
|
||||||
|
|
||||||
|
out="$("$BUXC" build "$case_dir" --color off 2>&1 || true)"
|
||||||
|
got="$(printf '%s\n' "$out" | normalize)"
|
||||||
|
exp="$(cat "$case_dir/expected.err")"
|
||||||
|
|
||||||
|
# Compare ignoring full absolute path differences already normalized
|
||||||
|
if [[ "$got" == "$exp" ]]; then
|
||||||
|
echo " PASS $name"
|
||||||
|
passed=$((passed + 1))
|
||||||
|
else
|
||||||
|
echo " FAIL $name"
|
||||||
|
echo "---- expected ----"
|
||||||
|
printf '%s\n' "$exp"
|
||||||
|
echo "---- got ----"
|
||||||
|
printf '%s\n' "$got"
|
||||||
|
echo "--------------"
|
||||||
|
failed=$((failed + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Error golden tests: $passed passed, $failed failed"
|
||||||
|
if [[ $failed -gt 0 ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[Package]
|
||||||
|
Name = "type_mismatch"
|
||||||
|
Version = "0.1.0"
|
||||||
|
Type = "bin"
|
||||||
|
|
||||||
|
[Build]
|
||||||
|
Output = "Bin"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
error: type errors in project
|
||||||
|
error: cannot assign String to int
|
||||||
|
--> FILE:4:18
|
||||||
|
|
|
||||||
|
4 | let x: int = "hello";
|
||||||
|
| ^^^^^^^
|
||||||
|
= help: ensure the right-hand side type matches the left-hand side
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import Std::Io::PrintLine;
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let x: int = "hello";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[Package]
|
||||||
|
Name = "undeclared"
|
||||||
|
Version = "0.1.0"
|
||||||
|
Type = "bin"
|
||||||
|
|
||||||
|
[Build]
|
||||||
|
Output = "Bin"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
error: type errors in project
|
||||||
|
error: undeclared identifier 'noSuchVar'
|
||||||
|
--> FILE:4:15
|
||||||
|
|
|
||||||
|
4 | PrintLine(noSuchVar);
|
||||||
|
| ^^^^^^^^^
|
||||||
|
= help: check the spelling, or import the symbol from the right module
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import Std::Io::PrintLine;
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
PrintLine(noSuchVar);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+137
-6
@@ -4,7 +4,7 @@
|
|||||||
# Usage: bux-lsp
|
# Usage: bux-lsp
|
||||||
# The editor spawns this binary and communicates via stdin/stdout.
|
# The editor spawns this binary and communicates via stdin/stdout.
|
||||||
|
|
||||||
import std/[json, os, strutils, streams, tables]
|
import std/[json, os, strutils, streams, tables, osproc]
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# JSON-RPC Transport
|
# JSON-RPC Transport
|
||||||
@@ -175,19 +175,150 @@ proc analyzeFile(path: string, content: string): DocumentState =
|
|||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Diagnostics (placeholder — emits empty diagnostics)
|
# Diagnostics — run `buxc check` when available and parse Rust-style errors
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
proc publishDiagnostics(stream: FileStream, uri: string) =
|
type
|
||||||
|
LspDiag = object
|
||||||
|
line: int ## 0-based for LSP
|
||||||
|
col: int ## 0-based
|
||||||
|
endCol: int ## 0-based exclusive
|
||||||
|
severity: int ## 1=error, 2=warning
|
||||||
|
message: string
|
||||||
|
|
||||||
|
proc findBuxc(): string =
|
||||||
|
## Prefer buxc next to the LSP binary, then PATH.
|
||||||
|
let beside = getAppDir() / "buxc"
|
||||||
|
if fileExists(beside): return beside
|
||||||
|
let beside2 = getCurrentDir() / "buxc"
|
||||||
|
if fileExists(beside2): return beside2
|
||||||
|
result = findExe("buxc")
|
||||||
|
|
||||||
|
proc parseBuxcDiagnostics(output, sourcePath: string): seq[LspDiag] =
|
||||||
|
## Parse lines like:
|
||||||
|
## error: cannot assign String to int
|
||||||
|
## --> /path/Main.bux:4:18
|
||||||
|
result = @[]
|
||||||
|
let lines = output.splitLines()
|
||||||
|
var i = 0
|
||||||
|
while i < lines.len:
|
||||||
|
let line = lines[i]
|
||||||
|
var sev = 0
|
||||||
|
var msg = ""
|
||||||
|
if line.startsWith("error: "):
|
||||||
|
sev = 1
|
||||||
|
msg = line[7..^1]
|
||||||
|
elif line.startsWith("warning: "):
|
||||||
|
sev = 2
|
||||||
|
msg = line[9..^1]
|
||||||
|
else:
|
||||||
|
inc i
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skip aggregate headers like "type errors in project"
|
||||||
|
if msg.startsWith("type errors") or msg.startsWith("parse errors") or
|
||||||
|
msg.startsWith("lex errors"):
|
||||||
|
inc i
|
||||||
|
continue
|
||||||
|
|
||||||
|
var fileLine = 1
|
||||||
|
var fileCol = 1
|
||||||
|
if i + 1 < lines.len and lines[i + 1].strip().startsWith("-->"):
|
||||||
|
let locPart = lines[i + 1].strip()[3..^1].strip()
|
||||||
|
# path:line:col
|
||||||
|
let parts = locPart.rsplit(':', maxsplit = 2)
|
||||||
|
if parts.len >= 3:
|
||||||
|
try:
|
||||||
|
fileLine = parseInt(parts[^2])
|
||||||
|
fileCol = parseInt(parts[^1])
|
||||||
|
except: discard
|
||||||
|
# Optionally filter to the open document
|
||||||
|
let pathPart = if parts.len >= 3: parts[0] else: ""
|
||||||
|
if sourcePath.len > 0 and pathPart.len > 0:
|
||||||
|
if not pathPart.endsWith(sourcePath.extractFilename) and
|
||||||
|
pathPart != sourcePath:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
# Estimate end column from message quote or single caret width
|
||||||
|
var endCol = fileCol
|
||||||
|
let q = msg.find('\'')
|
||||||
|
if q >= 0:
|
||||||
|
let q2 = msg.find('\'', q + 1)
|
||||||
|
if q2 > q + 1:
|
||||||
|
endCol = fileCol + (q2 - q - 1)
|
||||||
|
if endCol <= fileCol:
|
||||||
|
endCol = fileCol + 1
|
||||||
|
|
||||||
|
result.add(LspDiag(
|
||||||
|
line: max(0, fileLine - 1),
|
||||||
|
col: max(0, fileCol - 1),
|
||||||
|
endCol: max(0, endCol - 1),
|
||||||
|
severity: sev,
|
||||||
|
message: msg
|
||||||
|
))
|
||||||
|
inc i
|
||||||
|
|
||||||
|
proc runBuxcDiagnostics(sourcePath, content: string): seq[LspDiag] =
|
||||||
|
result = @[]
|
||||||
|
let buxc = findBuxc()
|
||||||
|
if buxc.len == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Prefer package root if this file lives under src/
|
||||||
|
var projectDir = sourcePath.parentDir
|
||||||
|
if projectDir.endsWith("src"):
|
||||||
|
projectDir = projectDir.parentDir
|
||||||
|
let toml = projectDir / "bux.toml"
|
||||||
|
|
||||||
|
var cmd: string
|
||||||
|
var workDir: string
|
||||||
|
if fileExists(toml):
|
||||||
|
workDir = projectDir
|
||||||
|
cmd = buxc & " check --color off"
|
||||||
|
else:
|
||||||
|
# Temp package for free-standing buffers
|
||||||
|
let tmp = getTempDir() / "bux-lsp-" & $getCurrentProcessId()
|
||||||
|
createDir(tmp / "src")
|
||||||
|
writeFile(tmp / "bux.toml", """[Package]
|
||||||
|
Name = "lsp_tmp"
|
||||||
|
Version = "0.0.0"
|
||||||
|
Type = "bin"
|
||||||
|
[Build]
|
||||||
|
Output = "Bin"
|
||||||
|
""")
|
||||||
|
writeFile(tmp / "src" / "Main.bux", content)
|
||||||
|
workDir = tmp
|
||||||
|
cmd = buxc & " check --color off"
|
||||||
|
|
||||||
|
try:
|
||||||
|
let (output, _) = execCmdEx(cmd, workingDir = workDir)
|
||||||
|
result = parseBuxcDiagnostics(output, sourcePath)
|
||||||
|
except CatchableError:
|
||||||
|
discard
|
||||||
|
|
||||||
|
proc publishDiagnostics(stream: FileStream, uri: string, diags: seq[LspDiag] = @[]) =
|
||||||
|
var arr = newJArray()
|
||||||
|
for d in diags:
|
||||||
|
arr.add(%*{
|
||||||
|
"range": {
|
||||||
|
"start": {"line": d.line, "character": d.col},
|
||||||
|
"end": {"line": d.line, "character": d.endCol}
|
||||||
|
},
|
||||||
|
"severity": d.severity,
|
||||||
|
"source": "buxc",
|
||||||
|
"message": d.message
|
||||||
|
})
|
||||||
sendNotification(stream, "textDocument/publishDiagnostics", %*{
|
sendNotification(stream, "textDocument/publishDiagnostics", %*{
|
||||||
"uri": uri,
|
"uri": uri,
|
||||||
"diagnostics": []
|
"diagnostics": arr
|
||||||
})
|
})
|
||||||
|
|
||||||
proc analyzeAndPublishDiagnostics(stream: FileStream, doc: DocumentState) =
|
proc analyzeAndPublishDiagnostics(stream: FileStream, doc: DocumentState) =
|
||||||
let updated = analyzeFile(uriToPath(doc.uri), doc.content)
|
let path = uriToPath(doc.uri)
|
||||||
|
let updated = analyzeFile(path, doc.content)
|
||||||
doc.symbols = updated.symbols
|
doc.symbols = updated.symbols
|
||||||
publishDiagnostics(stream, doc.uri)
|
let diags = runBuxcDiagnostics(path, doc.content)
|
||||||
|
publishDiagnostics(stream, doc.uri, diags)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Completion
|
# Completion
|
||||||
|
|||||||
Reference in New Issue
Block a user