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:
2026-07-15 16:00:21 +03:00
parent 94e6806dda
commit 61ac06ab5f
48 changed files with 2789 additions and 362 deletions
+425 -88
View File
@@ -8,30 +8,9 @@ module CBackend {
// ---------------------------------------------------------------------------
func CBackend_TypeToC(kind: int) -> String {
if kind == tyVoid { return "void"; }
if kind == tyBool { return "bool"; }
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*"; }
let cName: String = Type_ToCName(kind);
if !String_Eq(cName, "") { return cName; }
if kind == tyNamed { return "int"; }
if kind == tyFunc { return "void (*)(void)"; }
return "int";
}
@@ -338,31 +317,27 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
return;
}
// Indirect call through function pointer
// Indirect call through fat function pointer: f.code(f.env, args...)
if kind == hCallIndirect {
CBE_EmitExpr(cbe, node.child1);
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 {
StringBuilder_Append(&cbe.sb, ", ");
CBE_EmitExpr(cbe, node.child2);
needsComma = true;
}
if node.child3 != null as *HirNode {
if needsComma {
StringBuilder_Append(&cbe.sb, ", ");
}
StringBuilder_Append(&cbe.sb, ", ");
CBE_EmitExpr(cbe, node.child3);
needsComma = true;
}
// Emit extra args from linked list
var ai: int = 0;
var curExtra: *HirArgList = node.extraData as *HirArgList;
while ai < node.extraCount {
if needsComma {
StringBuilder_Append(&cbe.sb, ", ");
}
StringBuilder_Append(&cbe.sb, ", ");
CBE_EmitExpr(cbe, curExtra.node);
needsComma = true;
curExtra = curExtra.next;
ai = ai + 1;
}
@@ -590,11 +565,27 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
}
// Index: arr[idx] — emit as arr[idx]
// For Array<T> desugar pattern (fieldPtr "data"), emit bounds-checked access
if kind == hIndexPtr {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, "[");
CBE_EmitExpr(cbe, node.child2);
StringBuilder_Append(&cbe.sb, "]");
var isArrayAccess: bool = false;
if node.child1 != null as *HirNode {
if node.child1.kind == hFieldPtr && String_Eq(node.child1.strValue, "data") {
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;
}
@@ -629,19 +620,28 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
StringBuilder_Append(&cbe.sb, ptrNode.strValue);
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]
// For Array<T> desugar pattern, emit bounds-checked access
if ptrKind == hIndexPtr {
CBE_EmitExpr(cbe, ptrNode.child1);
StringBuilder_Append(&cbe.sb, "[");
CBE_EmitExpr(cbe, ptrNode.child2);
StringBuilder_Append(&cbe.sb, "]");
var isArrayAccess: bool = false;
if ptrNode.child1 != null as *HirNode {
if ptrNode.child1.kind == hFieldPtr && String_Eq(ptrNode.child1.strValue, "data") {
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;
}
}
@@ -727,6 +727,312 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
// 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) {
// Return type
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");
// Runtime declarations
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)
var si: int = 0;
@@ -1121,12 +1432,53 @@ func CBackend_Generate(mod: *HirModule) -> String {
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)
var i: int = 0;
while i < mod.funcCount {
if !CBE_FuncHasGeneric(&mod.funcs[i]) {
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
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;
}
@@ -1141,6 +1493,9 @@ func CBackend_Generate(mod: *HirModule) -> String {
}
StringBuilder_Append(&cbe.sb, "\n");
// Adapters before function bodies (bodies may take funcs as values)
CBE_EmitAllAdapters(cbe, mod);
// Function definitions (skip generics)
var hasMain: bool = false;
i = 0;
@@ -1157,45 +1512,23 @@ func CBackend_Generate(mod: *HirModule) -> String {
i = i + 1;
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]);
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
cbe.checkedFunc = mod.funcs[i].checkedFunc;
cbe.deferCount = 0;
@@ -1216,6 +1549,10 @@ func CBackend_Generate(mod: *HirModule) -> String {
StringBuilder_Append(&cbe.sb, " return 0;\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;
}
+77 -2
View File
@@ -55,12 +55,44 @@ func Diagnostic_GetLine(path: String, lineNum: uint32) -> String {
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:
* error: <message>
* --> <path>:<line>:<col>
* |
* 42 | <source_line>
* | <spaces>^
* = help: <hint>
*/
func Diagnostic_Print(diag: *Diagnostic, sourcePath: String) {
/* Severity prefix */
@@ -94,14 +126,57 @@ func Diagnostic_Print(diag: *Diagnostic, sourcePath: String) {
Print(" | ");
PrintLine(lineText);
/* Underline */
/* Underline (multi-char for identifiers/string tokens) */
Print(" | ");
var i: uint32 = 0;
while i < diag.column - 1 && i < 120 {
Print(" ");
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
View File
@@ -43,30 +43,7 @@ struct LowerCtx {
// ---------------------------------------------------------------------------
func Lcx_ResolveTypeKindFromName(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;
return Type_FromName(name);
}
func Lcx_TypeKindToName(kind: int) -> String {
@@ -178,38 +155,58 @@ func Lcx_SubstituteType(ctx: *LowerCtx, te: *TypeExpr) -> *TypeExpr {
return te;
}
// Build C function-pointer type string from a tekFunc TypeExpr, e.g. "int (*)(int)"
func Lcx_BuildFuncTypeName(te: *TypeExpr) -> String {
if te == null as *TypeExpr || te.kind != tekFunc { return "void (*)(void)"; }
var retName: String = "void";
if te.funcRet != null as *TypeExpr {
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"; }
// Sanitize a C type fragment for use inside BuxFn_* mangled names
func Lcx_SanitizeFatPart(s: String) -> String {
var r: String = s;
if String_Eq(r, "String") || String_Eq(r, "str") || String_Eq(r, "const char*") {
return "cstr";
}
var result: String = retName;
result = String_Concat(result, " (*)(");
if String_Eq(r, "unsigned int") { return "uint"; }
// 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 first: bool = true;
var anyParam: bool = false;
while cur != null as *TypeExprList {
if !first {
result = String_Concat(result, ", ");
}
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;
result = String_Concat(result, "_");
result = String_Concat(result, Lcx_TypeExprFatPart(cur.te));
anyParam = true;
cur = cur.next;
}
result = String_Concat(result, ")");
if !anyParam {
result = String_Concat(result, "_void");
}
return result;
}
@@ -469,9 +466,44 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
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.strValue = expr.strValue;
let sym: Symbol = Scope_Lookup(ctx.scope, expr.strValue);
n.typeKind = sym.typeKind;
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.intValue = expr.intValue; // operator
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
@@ -575,6 +631,20 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
// Unary
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.intValue = expr.intValue;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
@@ -1132,20 +1202,85 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
return n;
}
// Closure: generate function and return address-of
// Closure: fat function pointer (multi-instance via heap env + maker)
if kind == ekClosure {
let f: *HirFunc = Lcx_LowerClosureFunc(ctx, expr);
n.kind = hUnary;
n.intValue = tkAmp;
let varNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
varNode.kind = hVar;
varNode.strValue = f.name;
varNode.typeKind = tyFunc;
n.child1 = varNode;
n.typeKind = tyFunc;
if expr.refType != null as *TypeExpr {
n.typeName = Lcx_BuildFuncTypeName(expr.refType);
var fatName: String = "BuxFn_int_int";
if expr.refType != null as *TypeExpr && expr.refType.kind == tekFunc {
fatName = Lcx_BuildFuncTypeName(expr.refType);
} else if f.paramCount >= 2 {
// thunk has __env + user params; approximate from ret + user arity
fatName = "BuxFn_int_int";
if f.paramCount == 3 { fatName = "BuxFn_int_int_int"; }
if f.paramCount == 1 { fatName = "BuxFn_int_void"; }
}
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;
}
@@ -1227,6 +1362,25 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
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;
}
@@ -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
if stmt.child1 != null as *Expr && stmt.child1.kind == ekClosure && stmt.child1.captureCount > 0 {
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
// Capturing closures allocate env via __make_* at ekClosure site.
// Wrap with defer if present
if deferNode != null as *HirNode {
storeNode.child3 = deferNode;
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);
if retTe != null as *TypeExpr {
f.retTypeName = retTe.typeName;
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 {
f.retTypeName = "";
f.retTypeKind = 0;
@@ -2320,25 +2419,36 @@ func Lcx_LowerClosureFunc(ctx: *LowerCtx, expr: *Expr) -> *HirFunc {
f.isPublic = false;
let params: *Decl = expr.closureParams;
if params != null as *Decl {
f.paramCount = params.paramCount;
if params.paramCount > 0 { f.param0 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param0, &params.param0, ctx); }
if params.paramCount > 1 { f.param1 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param1, &params.param1, ctx); }
if params.paramCount > 2 { f.param2 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param2, &params.param2, ctx); }
if params.paramCount > 3 { f.param3 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param3, &params.param3, ctx); }
if params.paramCount > 4 { f.param4 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param4, &params.param4, ctx); }
if params.paramCount > 5 { f.param5 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param5, &params.param5, ctx); }
if params.paramCount > 6 { f.param6 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param6, &params.param6, ctx); }
if params.paramCount > 7 { f.param7 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param7, &params.param7, ctx); }
if params.paramCount > 8 { f.param8 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param8, &params.param8, ctx); }
}
// Fat-func ABI: leading void* __env, then user params
var userCount: int = 0;
if params != null as *Decl { userCount = params.paramCount; }
f.paramCount = userCount + 1;
f.param0 = bux_alloc(sizeof(HirParam)) as *HirParam;
f.param0.name = "__env";
f.param0.typeKind = tyPointer;
f.param0.typeName = "void*";
if userCount > 0 { f.param1 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param1, &params.param0, ctx); }
if userCount > 1 { f.param2 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param2, &params.param1, ctx); }
if userCount > 2 { f.param3 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param3, &params.param2, ctx); }
if userCount > 3 { f.param4 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param4, &params.param3, ctx); }
if userCount > 4 { f.param5 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param5, &params.param4, ctx); }
if userCount > 5 { f.param6 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param6, &params.param5, ctx); }
if userCount > 6 { f.param7 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param7, &params.param6, ctx); }
if userCount > 7 { f.param8 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param8, &params.param7, ctx); }
if expr.refType != null as *TypeExpr && expr.refType.kind == tekFunc && expr.refType.funcRet != null as *TypeExpr {
f.retTypeName = expr.refType.funcRet.typeName;
f.retTypeKind = Lcx_ResolveTypeKind(expr.refType.funcRet);
if expr.refType != null as *TypeExpr && expr.refType.kind == tekFunc {
// Return type of the *thunk* is the closure's return type (not the fat type)
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 {
f.retTypeName = "";
f.retTypeKind = 0;
f.retTypeName = "int";
f.retTypeKind = tyInt;
}
// Copy capture metadata from AST
+1
View File
@@ -277,6 +277,7 @@ func lexKeywordKind(text: String) -> int {
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, "async") { return tkAsync; }
if String_Eq(text, "await") { return tkAwait; }
if String_Eq(text, "spawn") { return tkSpawn; }
+9
View File
@@ -399,6 +399,15 @@ func parserParsePrimary(p: *Parser) -> *Expr {
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");
return parserMakeExpr(ekLiteral, line, col);
}
+10 -30
View File
@@ -136,7 +136,6 @@ func Sema_BuildFuncTypeExprFromDecl(decl: *Decl) -> *TypeExpr {
func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
if te == null as *TypeExpr { return tyUnknown; }
let name: String = te.typeName;
if te.kind == tekPointer {
return tyPointer;
@@ -146,33 +145,7 @@ func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
return tyFunc;
}
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; }
// Type resolution uses scope-based lookup via Sema_CollectGlobals
// TODO: add StringMap-based type table for faster named-type validation
return tyNamed;
return Type_FromName(te.typeName);
}
// ---------------------------------------------------------------------------
@@ -758,10 +731,17 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
return tyUnknown;
}
// Block expression
// Block expression (boolValue = true means unsafe block)
if kind == ekBlock {
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;
}
+20
View File
@@ -142,6 +142,7 @@ const tkDefer: int = 106;
const tkSwitch: int = 107;
const tkCase: int = 108;
const tkDefault: int = 109;
const tkUnsafe: int = 110;
// ---------------------------------------------------------------------------
// Token struct
@@ -165,6 +166,7 @@ func Token_IsKeyword(kind: int) -> bool {
if kind >= tkFunc && kind <= tkExtern { return true; }
if kind >= tkAs && kind <= tkSuper { return true; }
if kind == tkSizeOf { return true; }
if kind >= tkDefer && kind <= tkUnsafe { return true; }
if kind >= tkAsync && kind <= tkSpawn { return true; }
return false;
}
@@ -215,6 +217,15 @@ func Token_KeywordKind(text: String) -> int {
if String_Eq(text, "self") { return tkSelf; }
if String_Eq(text, "super") { return tkSuper; }
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, "false") { return tkBoolLiteral; }
return tkIdent;
@@ -259,6 +270,15 @@ func Token_KindName(kind: int) -> String {
if kind == tkNull { return "null"; }
if kind == tkSelf { return "self"; }
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 == tkRParen { return ")"; }
if kind == tkLBrace { return "{"; }
+90
View File
@@ -186,4 +186,94 @@ func Type_ToString(t: Type) -> String {
if t.kind == tyFunc { return t.name; }
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;
}
}