diff --git a/_selfhost/src/c_backend.bux b/_selfhost/src/c_backend.bux index 502d9b5..c576288 100644 --- a/_selfhost/src/c_backend.bux +++ b/_selfhost/src/c_backend.bux @@ -440,6 +440,47 @@ func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) { StringBuilder_Append(&cbe.sb, ")"); } +// --------------------------------------------------------------------------- +// Helpers for detecting generic declarations (not yet monomorphized) +// --------------------------------------------------------------------------- + +func CBE_IsGenericTypeName(name: String) -> bool { + if String_Eq(name, "T") || String_Eq(name, "K") || String_Eq(name, "V") { return true; } + if String_Eq(name, "T*") || String_Eq(name, "K*") || String_Eq(name, "V*") { return true; } + // Generic container structs and their pointer variants + if String_Eq(name, "Array") || String_Eq(name, "Array*") { return true; } + if String_Eq(name, "SetEntry") || String_Eq(name, "SetEntry*") { return true; } + if String_Eq(name, "StringMapEntry") || String_Eq(name, "StringMapEntry*") { return true; } + if String_Eq(name, "MapEntry") || String_Eq(name, "MapEntry*") { return true; } + return false; +} + +func CBE_StructHasGeneric(st: *HirStruct) -> bool { + var fi: int = 0; + while fi < st.fieldCount { + if CBE_IsGenericTypeName(st.fields[fi].typeName) { return true; } + fi = fi + 1; + } + return false; +} + +func CBE_FuncHasGeneric(f: *HirFunc) -> bool { + var pi: int = 0; + while pi < f.paramCount { + var ptype: String = ""; + if pi == 0 { ptype = f.param0.typeName; } + if pi == 1 { ptype = f.param1.typeName; } + if pi == 2 { ptype = f.param2.typeName; } + if pi == 3 { ptype = f.param3.typeName; } + if pi == 4 { ptype = f.param4.typeName; } + if pi == 5 { ptype = f.param5.typeName; } + if CBE_IsGenericTypeName(ptype) { return true; } + pi = pi + 1; + } + if CBE_IsGenericTypeName(f.retTypeName) { return true; } + return false; +} + // --------------------------------------------------------------------------- // Generate complete C module // --------------------------------------------------------------------------- @@ -466,27 +507,62 @@ func CBackend_Generate(mod: *HirModule) -> String { StringBuilder_Append(&cbe.sb, "typedef short int16;\n"); StringBuilder_Append(&cbe.sb, "typedef long long int64;\n"); StringBuilder_Append(&cbe.sb, "typedef float float32;\n"); - StringBuilder_Append(&cbe.sb, "typedef double float64;\n\n"); + StringBuilder_Append(&cbe.sb, "typedef double float64;\n"); + 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"); - // Forward declare all struct types + // Forward declare all struct types (skip generics) var si: int = 0; while si < mod.structCount { - StringBuilder_Append(&cbe.sb, "typedef struct "); - StringBuilder_Append(&cbe.sb, mod.structs[si].name); - StringBuilder_Append(&cbe.sb, " "); - StringBuilder_Append(&cbe.sb, mod.structs[si].name); - StringBuilder_Append(&cbe.sb, ";\n"); + if !CBE_StructHasGeneric(&mod.structs[si]) { + StringBuilder_Append(&cbe.sb, "typedef struct "); + StringBuilder_Append(&cbe.sb, mod.structs[si].name); + StringBuilder_Append(&cbe.sb, " "); + StringBuilder_Append(&cbe.sb, mod.structs[si].name); + StringBuilder_Append(&cbe.sb, ";\n"); + } si = si + 1; } StringBuilder_Append(&cbe.sb, "\n"); - // Struct definitions + // Enum definitions (typedef to int) + var ei: int = 0; + while ei < mod.enumCount { + StringBuilder_Append(&cbe.sb, "typedef int "); + StringBuilder_Append(&cbe.sb, mod.enums[ei].name); + StringBuilder_Append(&cbe.sb, ";\n"); + ei = ei + 1; + } + if mod.enumCount > 0 { + StringBuilder_Append(&cbe.sb, "\n"); + } + + // Constant definitions + var ci: int = 0; + while ci < mod.constCount { + StringBuilder_Append(&cbe.sb, "#define "); + StringBuilder_Append(&cbe.sb, mod.consts[ci].name); + StringBuilder_Append(&cbe.sb, " "); + StringBuilder_Append(&cbe.sb, String_FromInt(mod.consts[ci].value as int64)); + StringBuilder_Append(&cbe.sb, "\n"); + ci = ci + 1; + } + if mod.constCount > 0 { + StringBuilder_Append(&cbe.sb, "\n"); + } + + // Struct definitions (skip generics) si = 0; while si < mod.structCount { - StringBuilder_Append(&cbe.sb, "typedef struct {\n"); + if CBE_StructHasGeneric(&mod.structs[si]) { + si = si + 1; + continue; + } + StringBuilder_Append(&cbe.sb, "struct "); + StringBuilder_Append(&cbe.sb, mod.structs[si].name); + StringBuilder_Append(&cbe.sb, " {\n"); var fi: int = 0; while fi < mod.structs[si].fieldCount { StringBuilder_Append(&cbe.sb, " "); @@ -508,17 +584,17 @@ func CBackend_Generate(mod: *HirModule) -> String { StringBuilder_Append(&cbe.sb, ";\n"); fi = fi + 1; } - StringBuilder_Append(&cbe.sb, "} "); - StringBuilder_Append(&cbe.sb, mod.structs[si].name); - StringBuilder_Append(&cbe.sb, ";\n\n"); + StringBuilder_Append(&cbe.sb, "};\n\n"); si = si + 1; } - // Forward declarations for all functions + // Forward declarations for all functions (skip generics) var i: int = 0; while i < mod.funcCount { - CBE_EmitFuncDecl(cbe, &mod.funcs[i]); - StringBuilder_Append(&cbe.sb, ";\n"); + if !CBE_FuncHasGeneric(&mod.funcs[i]) { + CBE_EmitFuncDecl(cbe, &mod.funcs[i]); + StringBuilder_Append(&cbe.sb, ";\n"); + } i = i + 1; } StringBuilder_Append(&cbe.sb, "\n"); @@ -532,11 +608,16 @@ func CBackend_Generate(mod: *HirModule) -> String { } StringBuilder_Append(&cbe.sb, "\n"); - // Function definitions + // Function definitions (skip generics) var hasMain: bool = false; i = 0; while i < mod.funcCount { if String_Eq(mod.funcs[i].name, "Main") { hasMain = true; } + // Skip generic functions + if CBE_FuncHasGeneric(&mod.funcs[i]) { + i = i + 1; + continue; + } // Skip forward declarations (functions without body) let body: *HirNode = mod.funcs[i].body; if body == null as *HirNode { diff --git a/_selfhost/src/cli.bux b/_selfhost/src/cli.bux index 2302d75..5b422f0 100644 --- a/_selfhost/src/cli.bux +++ b/_selfhost/src/cli.bux @@ -1,5 +1,5 @@ // cli.bux — CLI driver for the Bux self-hosting compiler -// Wires together: Lexer → Parser → Sema → HirLower → NimBackend +// Wires together: Lexer → Parser → Sema → HirLower → CBackend module Cli { extern func PrintLine(s: String); @@ -97,14 +97,14 @@ func Cli_Compile(source: String, sourceName: String) -> String { Print("HIR funcCount="); PrintInt(hirMod.funcCount); - // Phase 5: QBE SSA code generation - let qbeCode: String = QbeBackend_Generate(hirMod); + // Phase 5: C code generation + let cCode: String = CBackend_Generate(hirMod); // Cleanup Lexer_Free(lex); Sema_Free(sema); - return qbeCode; + return cCode; } // --------------------------------------------------------------------------- @@ -122,23 +122,64 @@ func Cli_Build(srcPath: String, outPath: String) -> int { Print("Compiling "); PrintLine(srcPath); - let ssaCode: String = Cli_Compile(source, srcPath); + let cCode: String = Cli_Compile(source, srcPath); - if String_Eq(ssaCode, "") { + if String_Eq(cCode, "") { PrintLine("Compilation failed"); return 1; } - // Write SSA output - let ok: bool = WriteFile(outPath, ssaCode); - if ok { - Print(" → SSA written to "); - PrintLine(outPath); - return 0; - } else { + // Write C output + let cFile: String = String_Concat(outPath, ".c"); + let ok: bool = WriteFile(cFile, cCode); + if !ok { PrintLine("Error: cannot write output file"); return 1; } + Print(" → C written to "); + PrintLine(cFile); + + // Find runtime.c and io.c for linking + var rtPath: String = "stdlib/runtime.c"; + var ioPath: String = "stdlib/io.c"; + if !FileExists(rtPath) { + rtPath = "../stdlib/runtime.c"; + } + if !FileExists(ioPath) { + ioPath = "../stdlib/io.c"; + } + if !FileExists(rtPath) { + rtPath = "/home/ziko/z-git/bux/bux/stdlib/runtime.c"; + } + if !FileExists(ioPath) { + ioPath = "/home/ziko/z-git/bux/bux/stdlib/io.c"; + } + + // Compile with cc + PrintLine("Compiling C..."); + var cmdBuf: StringBuilder = StringBuilder_NewCap(512); + StringBuilder_Append(&cmdBuf, "cc -O2 -pthread -o "); + StringBuilder_Append(&cmdBuf, outPath); + StringBuilder_Append(&cmdBuf, " "); + StringBuilder_Append(&cmdBuf, cFile); + StringBuilder_Append(&cmdBuf, " "); + if FileExists(rtPath) { + StringBuilder_Append(&cmdBuf, rtPath); + StringBuilder_Append(&cmdBuf, " "); + } + if FileExists(ioPath) { + StringBuilder_Append(&cmdBuf, ioPath); + StringBuilder_Append(&cmdBuf, " "); + } + StringBuilder_Append(&cmdBuf, "-lm"); + let ccRc: int = bux_system(StringBuilder_Build(&cmdBuf)); + if ccRc != 0 { + PrintLine("Error: C compilation failed"); + return 1; + } + Print(" → Binary: "); + PrintLine(outPath); + return 0; } // --------------------------------------------------------------------------- @@ -307,6 +348,55 @@ func Cli_CollectNames(mod: *Module, names: *String, maxCount: int) -> int { return count; } +// Helper: convert single char int to String +func String_FromChar(c: int) -> String { + var buf: *char8 = bux_alloc(2) as *char8; + buf[0] = c as char8; + buf[1] = 0 as char8; + return buf as String; +} + +// Collect stdlib module paths imported by user code. +// Returns number of unique stdlib files to merge (paths written into outPaths). +func Cli_CollectStdlibImports(mod: *Module, outPaths: *String, maxCount: int, stdlibDir: String) -> int { + if mod == null as *Module { return 0; } + var count: int = 0; + var decl: *Decl = mod.firstItem; + while decl != null as *Decl && count < maxCount { + if decl.kind == dkUse { + let path: String = decl.usePath; + // Only handle Std::* imports + if String_StartsWith(path, "Std::") { + // Extract module name after Std:: (e.g., "Std::Io" -> "Io") + var j: int = 5; // skip "Std::" + var modName: String = ""; + while j < 100 { + let c: int = path[j] as int; + if c == 0 || c == 58 { break; } // null or ':' + modName = String_Concat(modName, String_FromChar(c)); + j = j + 1; + } + if !String_Eq(modName, "") { + let filePath: String = bux_path_join(bux_path_join(stdlibDir, "Std"), String_Concat(modName, ".bux")); + // Check for duplicates + var dup: bool = false; + var di: int = 0; + while di < count { + if String_Eq(outPaths[di], filePath) { dup = true; } + di = di + 1; + } + if !dup { + outPaths[count] = filePath; + count = count + 1; + } + } + } + } + decl = decl.childDecl2; + } + return count; +} + func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCount: int) -> int { Print("DEBUG: MergeFileInto "); PrintLine(path); @@ -473,39 +563,30 @@ func Cli_BuildProject(projectDir: String) -> int { merged.itemCount = 0; merged.firstItem = null as *Decl; - // Find and merge stdlib declarations + // Find and merge stdlib declarations (only imported modules) let stdlibDir: String = Cli_FindStdlibDir(projectDir); if !String_Eq(stdlibDir, "") { Print("Stdlib found: "); PrintLine(stdlibDir); - let stdSubdir: String = bux_path_join(stdlibDir, "Std"); - if DirExists(stdSubdir) { - var stdFileCount: int = 0; - let stdFiles: *String = bux_list_dir(stdSubdir, ".bux", &stdFileCount); - Print("DEBUG: stdFileCount="); - PrintInt(stdFileCount); - if stdFileCount > 0 { - Print("Merging "); - PrintInt(stdFileCount); - PrintLine(" stdlib files"); - var si: int = 0; - var stdAdded: int = 0; - while si < stdFileCount { - Print("DEBUG: merging stdlib file "); - PrintInt(si); - // Skip Channel.bux for debugging - if String_Eq(stdFiles[si], "./../stdlib/Std/Channel.bux") { - PrintLine("DEBUG: skipping Channel.bux"); - si = si + 1; - continue; - } - let added: int = Cli_MergeFileInto(merged, stdFiles[si], userNames, userNameCount); - stdAdded = stdAdded + added; - si = si + 1; - } - Print("Stdlib declarations added: "); - PrintInt(stdAdded); + // Collect imported stdlib modules from user code + let maxImports: int = 64; + let importPaths: *String = bux_alloc(maxImports * 8) as *String; + let importCount: int = Cli_CollectStdlibImports(userMerged, importPaths, maxImports, stdlibDir); + Print("Imported stdlib modules: "); + PrintInt(importCount); + PrintLine(""); + if importCount > 0 { + var si: int = 0; + var stdAdded: int = 0; + while si < importCount { + Print(" Merging "); + PrintLine(importPaths[si]); + let added: int = Cli_MergeFileInto(merged, importPaths[si], userNames, userNameCount); + stdAdded = stdAdded + added; + si = si + 1; } + Print("Stdlib declarations added: "); + PrintInt(stdAdded); } } @@ -554,6 +635,14 @@ func Cli_BuildProject(projectDir: String) -> int { let sema: *Sema = Sema_Analyze(merged); if Sema_HasError(sema) { PrintLine("Sema errors found"); + var i: int = 0; + while i < Sema_DiagCount(sema) { + Print(" line "); + PrintInt(sema.diags[i].line as int64); + Print(": "); + PrintLine(sema.diags[i].message); + i = i + 1; + } return 1; } @@ -569,85 +658,60 @@ func Cli_BuildProject(projectDir: String) -> int { } let hirMod: *HirModule = HirLower_LowerModule(merged, sema); - // QBE SSA code generation - PrintLine("Generating QBE SSA..."); - var fi: int = 0; - while fi < hirMod.funcCount { - Print("func "); - PrintLine(hirMod.funcs[fi].name); - fi = fi + 1; - } - let qbeCode: String = QbeBackend_Generate(hirMod); + // C code generation + PrintLine("Generating C code..."); + let cCode: String = CBackend_Generate(hirMod); // Create build directory let buildDir: String = bux_path_join(projectDir, "build"); discard bux_mkdir_if_needed(buildDir); - // Write SSA output - let ssaFile: String = bux_path_join(buildDir, "main.ssa"); - if !WriteFile(ssaFile, qbeCode) { - PrintLine("Error: cannot write main.ssa"); + // Write C output + let cFile: String = bux_path_join(buildDir, "main.c"); + if !WriteFile(cFile, cCode) { + PrintLine("Error: cannot write main.c"); return 1; } + Print("C code written to "); + PrintLine(cFile); - Print("SSA written to "); - PrintLine(ssaFile); - - // Invoke QBE to generate assembly - PrintLine("Invoking QBE..."); - let asmFile: String = bux_path_join(buildDir, "main.s"); - var qbeCmd: String = bux_path_join(projectDir, "vendor/qbe/qbe"); - // Try relative to project first, then absolute - if !FileExists(qbeCmd) { - // Try from compiler location - qbeCmd = "../vendor/qbe/qbe"; + // Find runtime.c and io.c (use stdlibDir if available) + var rtPath: String = bux_path_join(stdlibDir, "runtime.c"); + var ioPath: String = bux_path_join(stdlibDir, "io.c"); + if !FileExists(rtPath) { + rtPath = bux_path_join(projectDir, "stdlib/runtime.c"); } - if !FileExists(qbeCmd) { - qbeCmd = "vendor/qbe/qbe"; + if !FileExists(ioPath) { + ioPath = bux_path_join(projectDir, "stdlib/io.c"); } - // Actually let's use bux_system with a constructed command - var cmdBuf: StringBuilder = StringBuilder_NewCap(256); - StringBuilder_Append(&cmdBuf, qbeCmd); - StringBuilder_Append(&cmdBuf, " -o "); - StringBuilder_Append(&cmdBuf, asmFile); - StringBuilder_Append(&cmdBuf, " "); - StringBuilder_Append(&cmdBuf, ssaFile); - let qbeRc: int = bux_system(StringBuilder_Build(&cmdBuf)); - if qbeRc != 0 { - PrintLine("Error: QBE compilation failed"); - return 1; - } - - // Link with cc - PrintLine("Linking..."); - var linkBuf: StringBuilder = StringBuilder_NewCap(512); - StringBuilder_Append(&linkBuf, "cc -o "); - let outBin: String = bux_path_join(buildDir, outName); - StringBuilder_Append(&linkBuf, outBin); - StringBuilder_Append(&linkBuf, " "); - StringBuilder_Append(&linkBuf, asmFile); - StringBuilder_Append(&linkBuf, " "); - // Find runtime.c and io.c - var rtPath: String = bux_path_join(projectDir, "stdlib/runtime.c"); - var ioPath: String = bux_path_join(projectDir, "stdlib/io.c"); if !FileExists(rtPath) { rtPath = bux_path_join(projectDir, "../stdlib/runtime.c"); } if !FileExists(ioPath) { ioPath = bux_path_join(projectDir, "../stdlib/io.c"); } + + // Compile with cc + PrintLine("Compiling C..."); + let outBin: String = bux_path_join(buildDir, outName); + var ccBuf: StringBuilder = StringBuilder_NewCap(512); + StringBuilder_Append(&ccBuf, "cc -O2 -pthread -o "); + StringBuilder_Append(&ccBuf, outBin); + StringBuilder_Append(&ccBuf, " "); + StringBuilder_Append(&ccBuf, cFile); + StringBuilder_Append(&ccBuf, " "); if FileExists(rtPath) { - StringBuilder_Append(&linkBuf, rtPath); - StringBuilder_Append(&linkBuf, " "); + StringBuilder_Append(&ccBuf, rtPath); + StringBuilder_Append(&ccBuf, " "); } if FileExists(ioPath) { - StringBuilder_Append(&linkBuf, ioPath); - StringBuilder_Append(&linkBuf, " "); + StringBuilder_Append(&ccBuf, ioPath); + StringBuilder_Append(&ccBuf, " "); } - StringBuilder_Append(&linkBuf, "-lm"); - let linkRc: int = bux_system(StringBuilder_Build(&linkBuf)); - if linkRc != 0 { - PrintLine("Error: Linking failed"); + StringBuilder_Append(&ccBuf, "-lm"); + let ccRc: int = bux_system(StringBuilder_Build(&ccBuf)); + if ccRc != 0 { + PrintLine("Error: C compilation failed"); return 1; } @@ -693,7 +757,7 @@ func Cli_Run(args: *String, argCount: int) -> int { PrintLine(" Parser ✅ 1004 lines"); PrintLine(" Sema ✅ 393 lines"); PrintLine(" HirLower ✅ 307 lines"); - PrintLine(" CBackend ✅ 264 lines"); + PrintLine(" CBackend ✅ 585 lines"); PrintLine(" Total: 3830 lines of Bux"); return 0; } @@ -716,7 +780,7 @@ func Cli_Run(args: *String, argCount: int) -> int { if String_Eq(cmd, "build") { let src: String = "src/Main.bux"; - let out: String = "build/main.nim"; + let out: String = "build/main"; if argCount >= 3 { src = args[2]; } if argCount >= 4 { out = args[3]; } return Cli_Build(src, out); diff --git a/_selfhost/src/hir.bux b/_selfhost/src/hir.bux index ef21728..3b3667d 100644 --- a/_selfhost/src/hir.bux +++ b/_selfhost/src/hir.bux @@ -115,6 +115,10 @@ struct HirConst { value: int; } +struct HirEnum { + name: String; +} + struct HirModule { funcCount: int; funcs: *HirFunc; @@ -123,6 +127,7 @@ struct HirModule { structCount: int; structs: *HirStruct; enumCount: int; + enums: *HirEnum; constCount: int; consts: *HirConst; } diff --git a/_selfhost/src/hir_lower.bux b/_selfhost/src/hir_lower.bux index 97417df..811403a 100644 --- a/_selfhost/src/hir_lower.bux +++ b/_selfhost/src/hir_lower.bux @@ -213,6 +213,16 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode { // Field access if kind == ekField { + // Check if this is enum variant access: Color::Green + if expr.child1 != null as *Expr && expr.child1.kind == ekIdent { + let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.strValue); + if sym.decl != null as *Decl && sym.decl.kind == dkEnum { + // Emit as variable reference: Color_Green + n.kind = hVar; + n.strValue = String_Concat(String_Concat(expr.child1.strValue, "_"), expr.strValue); + return n; + } + } n.kind = hFieldPtr; n.child1 = Lcx_LowerExpr(ctx, expr.child1); n.strValue = expr.strValue; @@ -543,6 +553,8 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule { hm.funcs = ctx.funcs; hm.structCount = 0; hm.structs = bux_alloc(64 as uint * sizeof(HirStruct)) as *HirStruct; + hm.enumCount = 0; + hm.enums = bux_alloc(64 as uint * sizeof(HirEnum)) as *HirEnum; hm.constCount = 0; hm.consts = bux_alloc(512 as uint * sizeof(HirConst)) as *HirConst; @@ -628,7 +640,25 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule { hm.constCount = hm.constCount + 1; } if decl.kind == dkEnum { + let ei: int = hm.enumCount; + hm.enums[ei].name = decl.strValue; hm.enumCount = hm.enumCount + 1; + // Emit enum variants as constants: EnumName_VariantName = value + var vi: int = 0; + while vi < decl.variantCount && hm.constCount < 512 { + var vname: String = ""; + if vi == 0 { vname = decl.variant0.name; } + if vi == 1 { vname = decl.variant1.name; } + if vi == 2 { vname = decl.variant2.name; } + if vi == 3 { vname = decl.variant3.name; } + if !String_Eq(vname, "") { + let ci: int = hm.constCount; + hm.consts[ci].name = String_Concat(String_Concat(decl.strValue, "_"), vname); + hm.consts[ci].value = vi; + hm.constCount = hm.constCount + 1; + } + vi = vi + 1; + } } decl = decl.childDecl2; } diff --git a/_selfhost/src/sema.bux b/_selfhost/src/sema.bux index be8dd6e..c63835f 100644 --- a/_selfhost/src/sema.bux +++ b/_selfhost/src/sema.bux @@ -96,7 +96,10 @@ func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int { func Sema_IsNumeric(kind: int) -> bool { if kind == tyUnknown || kind == tyNamed || kind == tyTypeParam { return true; } - return kind >= tyInt8 && kind <= tyUInt64; + if kind == tyInt8 || kind == tyInt16 || kind == tyInt32 || kind == tyInt64 || kind == tyInt { return true; } + if kind == tyUInt8 || kind == tyUInt16 || kind == tyUInt32 || kind == tyUInt64 || kind == tyUInt { return true; } + if kind == tyFloat32 || kind == tyFloat64 { return true; } + return false; } func Sema_IsBool(kind: int) -> bool { @@ -328,9 +331,12 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) { let retType: int = Sema_CheckExpr(sema, stmt.child1); if sema.currentRetType != tyUnknown && retType != tyUnknown { if retType != sema.currentRetType { - // Be permissive: allow numeric widening, pointer/named mismatch + // Be permissive: allow numeric widening, pointer compatibility if !Sema_IsNumeric(retType) || !Sema_IsNumeric(sema.currentRetType) { - if retType != sema.currentRetType { + // Allow pointer-type compatibility (String <-> *T, *T <-> *U) + let retIsPtr: bool = retType == tyPointer || retType == tyStr; + let expectedIsPtr: bool = sema.currentRetType == tyPointer || sema.currentRetType == tyStr; + if !retIsPtr || !expectedIsPtr { Sema_EmitError(sema, stmt.line, stmt.column, "return type mismatch"); } } diff --git a/src_bux/c_backend.bux b/src_bux/c_backend.bux index 502d9b5..c576288 100644 --- a/src_bux/c_backend.bux +++ b/src_bux/c_backend.bux @@ -440,6 +440,47 @@ func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) { StringBuilder_Append(&cbe.sb, ")"); } +// --------------------------------------------------------------------------- +// Helpers for detecting generic declarations (not yet monomorphized) +// --------------------------------------------------------------------------- + +func CBE_IsGenericTypeName(name: String) -> bool { + if String_Eq(name, "T") || String_Eq(name, "K") || String_Eq(name, "V") { return true; } + if String_Eq(name, "T*") || String_Eq(name, "K*") || String_Eq(name, "V*") { return true; } + // Generic container structs and their pointer variants + if String_Eq(name, "Array") || String_Eq(name, "Array*") { return true; } + if String_Eq(name, "SetEntry") || String_Eq(name, "SetEntry*") { return true; } + if String_Eq(name, "StringMapEntry") || String_Eq(name, "StringMapEntry*") { return true; } + if String_Eq(name, "MapEntry") || String_Eq(name, "MapEntry*") { return true; } + return false; +} + +func CBE_StructHasGeneric(st: *HirStruct) -> bool { + var fi: int = 0; + while fi < st.fieldCount { + if CBE_IsGenericTypeName(st.fields[fi].typeName) { return true; } + fi = fi + 1; + } + return false; +} + +func CBE_FuncHasGeneric(f: *HirFunc) -> bool { + var pi: int = 0; + while pi < f.paramCount { + var ptype: String = ""; + if pi == 0 { ptype = f.param0.typeName; } + if pi == 1 { ptype = f.param1.typeName; } + if pi == 2 { ptype = f.param2.typeName; } + if pi == 3 { ptype = f.param3.typeName; } + if pi == 4 { ptype = f.param4.typeName; } + if pi == 5 { ptype = f.param5.typeName; } + if CBE_IsGenericTypeName(ptype) { return true; } + pi = pi + 1; + } + if CBE_IsGenericTypeName(f.retTypeName) { return true; } + return false; +} + // --------------------------------------------------------------------------- // Generate complete C module // --------------------------------------------------------------------------- @@ -466,27 +507,62 @@ func CBackend_Generate(mod: *HirModule) -> String { StringBuilder_Append(&cbe.sb, "typedef short int16;\n"); StringBuilder_Append(&cbe.sb, "typedef long long int64;\n"); StringBuilder_Append(&cbe.sb, "typedef float float32;\n"); - StringBuilder_Append(&cbe.sb, "typedef double float64;\n\n"); + StringBuilder_Append(&cbe.sb, "typedef double float64;\n"); + 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"); - // Forward declare all struct types + // Forward declare all struct types (skip generics) var si: int = 0; while si < mod.structCount { - StringBuilder_Append(&cbe.sb, "typedef struct "); - StringBuilder_Append(&cbe.sb, mod.structs[si].name); - StringBuilder_Append(&cbe.sb, " "); - StringBuilder_Append(&cbe.sb, mod.structs[si].name); - StringBuilder_Append(&cbe.sb, ";\n"); + if !CBE_StructHasGeneric(&mod.structs[si]) { + StringBuilder_Append(&cbe.sb, "typedef struct "); + StringBuilder_Append(&cbe.sb, mod.structs[si].name); + StringBuilder_Append(&cbe.sb, " "); + StringBuilder_Append(&cbe.sb, mod.structs[si].name); + StringBuilder_Append(&cbe.sb, ";\n"); + } si = si + 1; } StringBuilder_Append(&cbe.sb, "\n"); - // Struct definitions + // Enum definitions (typedef to int) + var ei: int = 0; + while ei < mod.enumCount { + StringBuilder_Append(&cbe.sb, "typedef int "); + StringBuilder_Append(&cbe.sb, mod.enums[ei].name); + StringBuilder_Append(&cbe.sb, ";\n"); + ei = ei + 1; + } + if mod.enumCount > 0 { + StringBuilder_Append(&cbe.sb, "\n"); + } + + // Constant definitions + var ci: int = 0; + while ci < mod.constCount { + StringBuilder_Append(&cbe.sb, "#define "); + StringBuilder_Append(&cbe.sb, mod.consts[ci].name); + StringBuilder_Append(&cbe.sb, " "); + StringBuilder_Append(&cbe.sb, String_FromInt(mod.consts[ci].value as int64)); + StringBuilder_Append(&cbe.sb, "\n"); + ci = ci + 1; + } + if mod.constCount > 0 { + StringBuilder_Append(&cbe.sb, "\n"); + } + + // Struct definitions (skip generics) si = 0; while si < mod.structCount { - StringBuilder_Append(&cbe.sb, "typedef struct {\n"); + if CBE_StructHasGeneric(&mod.structs[si]) { + si = si + 1; + continue; + } + StringBuilder_Append(&cbe.sb, "struct "); + StringBuilder_Append(&cbe.sb, mod.structs[si].name); + StringBuilder_Append(&cbe.sb, " {\n"); var fi: int = 0; while fi < mod.structs[si].fieldCount { StringBuilder_Append(&cbe.sb, " "); @@ -508,17 +584,17 @@ func CBackend_Generate(mod: *HirModule) -> String { StringBuilder_Append(&cbe.sb, ";\n"); fi = fi + 1; } - StringBuilder_Append(&cbe.sb, "} "); - StringBuilder_Append(&cbe.sb, mod.structs[si].name); - StringBuilder_Append(&cbe.sb, ";\n\n"); + StringBuilder_Append(&cbe.sb, "};\n\n"); si = si + 1; } - // Forward declarations for all functions + // Forward declarations for all functions (skip generics) var i: int = 0; while i < mod.funcCount { - CBE_EmitFuncDecl(cbe, &mod.funcs[i]); - StringBuilder_Append(&cbe.sb, ";\n"); + if !CBE_FuncHasGeneric(&mod.funcs[i]) { + CBE_EmitFuncDecl(cbe, &mod.funcs[i]); + StringBuilder_Append(&cbe.sb, ";\n"); + } i = i + 1; } StringBuilder_Append(&cbe.sb, "\n"); @@ -532,11 +608,16 @@ func CBackend_Generate(mod: *HirModule) -> String { } StringBuilder_Append(&cbe.sb, "\n"); - // Function definitions + // Function definitions (skip generics) var hasMain: bool = false; i = 0; while i < mod.funcCount { if String_Eq(mod.funcs[i].name, "Main") { hasMain = true; } + // Skip generic functions + if CBE_FuncHasGeneric(&mod.funcs[i]) { + i = i + 1; + continue; + } // Skip forward declarations (functions without body) let body: *HirNode = mod.funcs[i].body; if body == null as *HirNode { diff --git a/src_bux/cli.bux b/src_bux/cli.bux index 2302d75..5b422f0 100644 --- a/src_bux/cli.bux +++ b/src_bux/cli.bux @@ -1,5 +1,5 @@ // cli.bux — CLI driver for the Bux self-hosting compiler -// Wires together: Lexer → Parser → Sema → HirLower → NimBackend +// Wires together: Lexer → Parser → Sema → HirLower → CBackend module Cli { extern func PrintLine(s: String); @@ -97,14 +97,14 @@ func Cli_Compile(source: String, sourceName: String) -> String { Print("HIR funcCount="); PrintInt(hirMod.funcCount); - // Phase 5: QBE SSA code generation - let qbeCode: String = QbeBackend_Generate(hirMod); + // Phase 5: C code generation + let cCode: String = CBackend_Generate(hirMod); // Cleanup Lexer_Free(lex); Sema_Free(sema); - return qbeCode; + return cCode; } // --------------------------------------------------------------------------- @@ -122,23 +122,64 @@ func Cli_Build(srcPath: String, outPath: String) -> int { Print("Compiling "); PrintLine(srcPath); - let ssaCode: String = Cli_Compile(source, srcPath); + let cCode: String = Cli_Compile(source, srcPath); - if String_Eq(ssaCode, "") { + if String_Eq(cCode, "") { PrintLine("Compilation failed"); return 1; } - // Write SSA output - let ok: bool = WriteFile(outPath, ssaCode); - if ok { - Print(" → SSA written to "); - PrintLine(outPath); - return 0; - } else { + // Write C output + let cFile: String = String_Concat(outPath, ".c"); + let ok: bool = WriteFile(cFile, cCode); + if !ok { PrintLine("Error: cannot write output file"); return 1; } + Print(" → C written to "); + PrintLine(cFile); + + // Find runtime.c and io.c for linking + var rtPath: String = "stdlib/runtime.c"; + var ioPath: String = "stdlib/io.c"; + if !FileExists(rtPath) { + rtPath = "../stdlib/runtime.c"; + } + if !FileExists(ioPath) { + ioPath = "../stdlib/io.c"; + } + if !FileExists(rtPath) { + rtPath = "/home/ziko/z-git/bux/bux/stdlib/runtime.c"; + } + if !FileExists(ioPath) { + ioPath = "/home/ziko/z-git/bux/bux/stdlib/io.c"; + } + + // Compile with cc + PrintLine("Compiling C..."); + var cmdBuf: StringBuilder = StringBuilder_NewCap(512); + StringBuilder_Append(&cmdBuf, "cc -O2 -pthread -o "); + StringBuilder_Append(&cmdBuf, outPath); + StringBuilder_Append(&cmdBuf, " "); + StringBuilder_Append(&cmdBuf, cFile); + StringBuilder_Append(&cmdBuf, " "); + if FileExists(rtPath) { + StringBuilder_Append(&cmdBuf, rtPath); + StringBuilder_Append(&cmdBuf, " "); + } + if FileExists(ioPath) { + StringBuilder_Append(&cmdBuf, ioPath); + StringBuilder_Append(&cmdBuf, " "); + } + StringBuilder_Append(&cmdBuf, "-lm"); + let ccRc: int = bux_system(StringBuilder_Build(&cmdBuf)); + if ccRc != 0 { + PrintLine("Error: C compilation failed"); + return 1; + } + Print(" → Binary: "); + PrintLine(outPath); + return 0; } // --------------------------------------------------------------------------- @@ -307,6 +348,55 @@ func Cli_CollectNames(mod: *Module, names: *String, maxCount: int) -> int { return count; } +// Helper: convert single char int to String +func String_FromChar(c: int) -> String { + var buf: *char8 = bux_alloc(2) as *char8; + buf[0] = c as char8; + buf[1] = 0 as char8; + return buf as String; +} + +// Collect stdlib module paths imported by user code. +// Returns number of unique stdlib files to merge (paths written into outPaths). +func Cli_CollectStdlibImports(mod: *Module, outPaths: *String, maxCount: int, stdlibDir: String) -> int { + if mod == null as *Module { return 0; } + var count: int = 0; + var decl: *Decl = mod.firstItem; + while decl != null as *Decl && count < maxCount { + if decl.kind == dkUse { + let path: String = decl.usePath; + // Only handle Std::* imports + if String_StartsWith(path, "Std::") { + // Extract module name after Std:: (e.g., "Std::Io" -> "Io") + var j: int = 5; // skip "Std::" + var modName: String = ""; + while j < 100 { + let c: int = path[j] as int; + if c == 0 || c == 58 { break; } // null or ':' + modName = String_Concat(modName, String_FromChar(c)); + j = j + 1; + } + if !String_Eq(modName, "") { + let filePath: String = bux_path_join(bux_path_join(stdlibDir, "Std"), String_Concat(modName, ".bux")); + // Check for duplicates + var dup: bool = false; + var di: int = 0; + while di < count { + if String_Eq(outPaths[di], filePath) { dup = true; } + di = di + 1; + } + if !dup { + outPaths[count] = filePath; + count = count + 1; + } + } + } + } + decl = decl.childDecl2; + } + return count; +} + func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCount: int) -> int { Print("DEBUG: MergeFileInto "); PrintLine(path); @@ -473,39 +563,30 @@ func Cli_BuildProject(projectDir: String) -> int { merged.itemCount = 0; merged.firstItem = null as *Decl; - // Find and merge stdlib declarations + // Find and merge stdlib declarations (only imported modules) let stdlibDir: String = Cli_FindStdlibDir(projectDir); if !String_Eq(stdlibDir, "") { Print("Stdlib found: "); PrintLine(stdlibDir); - let stdSubdir: String = bux_path_join(stdlibDir, "Std"); - if DirExists(stdSubdir) { - var stdFileCount: int = 0; - let stdFiles: *String = bux_list_dir(stdSubdir, ".bux", &stdFileCount); - Print("DEBUG: stdFileCount="); - PrintInt(stdFileCount); - if stdFileCount > 0 { - Print("Merging "); - PrintInt(stdFileCount); - PrintLine(" stdlib files"); - var si: int = 0; - var stdAdded: int = 0; - while si < stdFileCount { - Print("DEBUG: merging stdlib file "); - PrintInt(si); - // Skip Channel.bux for debugging - if String_Eq(stdFiles[si], "./../stdlib/Std/Channel.bux") { - PrintLine("DEBUG: skipping Channel.bux"); - si = si + 1; - continue; - } - let added: int = Cli_MergeFileInto(merged, stdFiles[si], userNames, userNameCount); - stdAdded = stdAdded + added; - si = si + 1; - } - Print("Stdlib declarations added: "); - PrintInt(stdAdded); + // Collect imported stdlib modules from user code + let maxImports: int = 64; + let importPaths: *String = bux_alloc(maxImports * 8) as *String; + let importCount: int = Cli_CollectStdlibImports(userMerged, importPaths, maxImports, stdlibDir); + Print("Imported stdlib modules: "); + PrintInt(importCount); + PrintLine(""); + if importCount > 0 { + var si: int = 0; + var stdAdded: int = 0; + while si < importCount { + Print(" Merging "); + PrintLine(importPaths[si]); + let added: int = Cli_MergeFileInto(merged, importPaths[si], userNames, userNameCount); + stdAdded = stdAdded + added; + si = si + 1; } + Print("Stdlib declarations added: "); + PrintInt(stdAdded); } } @@ -554,6 +635,14 @@ func Cli_BuildProject(projectDir: String) -> int { let sema: *Sema = Sema_Analyze(merged); if Sema_HasError(sema) { PrintLine("Sema errors found"); + var i: int = 0; + while i < Sema_DiagCount(sema) { + Print(" line "); + PrintInt(sema.diags[i].line as int64); + Print(": "); + PrintLine(sema.diags[i].message); + i = i + 1; + } return 1; } @@ -569,85 +658,60 @@ func Cli_BuildProject(projectDir: String) -> int { } let hirMod: *HirModule = HirLower_LowerModule(merged, sema); - // QBE SSA code generation - PrintLine("Generating QBE SSA..."); - var fi: int = 0; - while fi < hirMod.funcCount { - Print("func "); - PrintLine(hirMod.funcs[fi].name); - fi = fi + 1; - } - let qbeCode: String = QbeBackend_Generate(hirMod); + // C code generation + PrintLine("Generating C code..."); + let cCode: String = CBackend_Generate(hirMod); // Create build directory let buildDir: String = bux_path_join(projectDir, "build"); discard bux_mkdir_if_needed(buildDir); - // Write SSA output - let ssaFile: String = bux_path_join(buildDir, "main.ssa"); - if !WriteFile(ssaFile, qbeCode) { - PrintLine("Error: cannot write main.ssa"); + // Write C output + let cFile: String = bux_path_join(buildDir, "main.c"); + if !WriteFile(cFile, cCode) { + PrintLine("Error: cannot write main.c"); return 1; } + Print("C code written to "); + PrintLine(cFile); - Print("SSA written to "); - PrintLine(ssaFile); - - // Invoke QBE to generate assembly - PrintLine("Invoking QBE..."); - let asmFile: String = bux_path_join(buildDir, "main.s"); - var qbeCmd: String = bux_path_join(projectDir, "vendor/qbe/qbe"); - // Try relative to project first, then absolute - if !FileExists(qbeCmd) { - // Try from compiler location - qbeCmd = "../vendor/qbe/qbe"; + // Find runtime.c and io.c (use stdlibDir if available) + var rtPath: String = bux_path_join(stdlibDir, "runtime.c"); + var ioPath: String = bux_path_join(stdlibDir, "io.c"); + if !FileExists(rtPath) { + rtPath = bux_path_join(projectDir, "stdlib/runtime.c"); } - if !FileExists(qbeCmd) { - qbeCmd = "vendor/qbe/qbe"; + if !FileExists(ioPath) { + ioPath = bux_path_join(projectDir, "stdlib/io.c"); } - // Actually let's use bux_system with a constructed command - var cmdBuf: StringBuilder = StringBuilder_NewCap(256); - StringBuilder_Append(&cmdBuf, qbeCmd); - StringBuilder_Append(&cmdBuf, " -o "); - StringBuilder_Append(&cmdBuf, asmFile); - StringBuilder_Append(&cmdBuf, " "); - StringBuilder_Append(&cmdBuf, ssaFile); - let qbeRc: int = bux_system(StringBuilder_Build(&cmdBuf)); - if qbeRc != 0 { - PrintLine("Error: QBE compilation failed"); - return 1; - } - - // Link with cc - PrintLine("Linking..."); - var linkBuf: StringBuilder = StringBuilder_NewCap(512); - StringBuilder_Append(&linkBuf, "cc -o "); - let outBin: String = bux_path_join(buildDir, outName); - StringBuilder_Append(&linkBuf, outBin); - StringBuilder_Append(&linkBuf, " "); - StringBuilder_Append(&linkBuf, asmFile); - StringBuilder_Append(&linkBuf, " "); - // Find runtime.c and io.c - var rtPath: String = bux_path_join(projectDir, "stdlib/runtime.c"); - var ioPath: String = bux_path_join(projectDir, "stdlib/io.c"); if !FileExists(rtPath) { rtPath = bux_path_join(projectDir, "../stdlib/runtime.c"); } if !FileExists(ioPath) { ioPath = bux_path_join(projectDir, "../stdlib/io.c"); } + + // Compile with cc + PrintLine("Compiling C..."); + let outBin: String = bux_path_join(buildDir, outName); + var ccBuf: StringBuilder = StringBuilder_NewCap(512); + StringBuilder_Append(&ccBuf, "cc -O2 -pthread -o "); + StringBuilder_Append(&ccBuf, outBin); + StringBuilder_Append(&ccBuf, " "); + StringBuilder_Append(&ccBuf, cFile); + StringBuilder_Append(&ccBuf, " "); if FileExists(rtPath) { - StringBuilder_Append(&linkBuf, rtPath); - StringBuilder_Append(&linkBuf, " "); + StringBuilder_Append(&ccBuf, rtPath); + StringBuilder_Append(&ccBuf, " "); } if FileExists(ioPath) { - StringBuilder_Append(&linkBuf, ioPath); - StringBuilder_Append(&linkBuf, " "); + StringBuilder_Append(&ccBuf, ioPath); + StringBuilder_Append(&ccBuf, " "); } - StringBuilder_Append(&linkBuf, "-lm"); - let linkRc: int = bux_system(StringBuilder_Build(&linkBuf)); - if linkRc != 0 { - PrintLine("Error: Linking failed"); + StringBuilder_Append(&ccBuf, "-lm"); + let ccRc: int = bux_system(StringBuilder_Build(&ccBuf)); + if ccRc != 0 { + PrintLine("Error: C compilation failed"); return 1; } @@ -693,7 +757,7 @@ func Cli_Run(args: *String, argCount: int) -> int { PrintLine(" Parser ✅ 1004 lines"); PrintLine(" Sema ✅ 393 lines"); PrintLine(" HirLower ✅ 307 lines"); - PrintLine(" CBackend ✅ 264 lines"); + PrintLine(" CBackend ✅ 585 lines"); PrintLine(" Total: 3830 lines of Bux"); return 0; } @@ -716,7 +780,7 @@ func Cli_Run(args: *String, argCount: int) -> int { if String_Eq(cmd, "build") { let src: String = "src/Main.bux"; - let out: String = "build/main.nim"; + let out: String = "build/main"; if argCount >= 3 { src = args[2]; } if argCount >= 4 { out = args[3]; } return Cli_Build(src, out); diff --git a/src_bux/hir.bux b/src_bux/hir.bux index ef21728..3b3667d 100644 --- a/src_bux/hir.bux +++ b/src_bux/hir.bux @@ -115,6 +115,10 @@ struct HirConst { value: int; } +struct HirEnum { + name: String; +} + struct HirModule { funcCount: int; funcs: *HirFunc; @@ -123,6 +127,7 @@ struct HirModule { structCount: int; structs: *HirStruct; enumCount: int; + enums: *HirEnum; constCount: int; consts: *HirConst; } diff --git a/src_bux/hir_lower.bux b/src_bux/hir_lower.bux index 97417df..811403a 100644 --- a/src_bux/hir_lower.bux +++ b/src_bux/hir_lower.bux @@ -213,6 +213,16 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode { // Field access if kind == ekField { + // Check if this is enum variant access: Color::Green + if expr.child1 != null as *Expr && expr.child1.kind == ekIdent { + let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.strValue); + if sym.decl != null as *Decl && sym.decl.kind == dkEnum { + // Emit as variable reference: Color_Green + n.kind = hVar; + n.strValue = String_Concat(String_Concat(expr.child1.strValue, "_"), expr.strValue); + return n; + } + } n.kind = hFieldPtr; n.child1 = Lcx_LowerExpr(ctx, expr.child1); n.strValue = expr.strValue; @@ -543,6 +553,8 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule { hm.funcs = ctx.funcs; hm.structCount = 0; hm.structs = bux_alloc(64 as uint * sizeof(HirStruct)) as *HirStruct; + hm.enumCount = 0; + hm.enums = bux_alloc(64 as uint * sizeof(HirEnum)) as *HirEnum; hm.constCount = 0; hm.consts = bux_alloc(512 as uint * sizeof(HirConst)) as *HirConst; @@ -628,7 +640,25 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule { hm.constCount = hm.constCount + 1; } if decl.kind == dkEnum { + let ei: int = hm.enumCount; + hm.enums[ei].name = decl.strValue; hm.enumCount = hm.enumCount + 1; + // Emit enum variants as constants: EnumName_VariantName = value + var vi: int = 0; + while vi < decl.variantCount && hm.constCount < 512 { + var vname: String = ""; + if vi == 0 { vname = decl.variant0.name; } + if vi == 1 { vname = decl.variant1.name; } + if vi == 2 { vname = decl.variant2.name; } + if vi == 3 { vname = decl.variant3.name; } + if !String_Eq(vname, "") { + let ci: int = hm.constCount; + hm.consts[ci].name = String_Concat(String_Concat(decl.strValue, "_"), vname); + hm.consts[ci].value = vi; + hm.constCount = hm.constCount + 1; + } + vi = vi + 1; + } } decl = decl.childDecl2; } diff --git a/src_bux/sema.bux b/src_bux/sema.bux index be8dd6e..c63835f 100644 --- a/src_bux/sema.bux +++ b/src_bux/sema.bux @@ -96,7 +96,10 @@ func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int { func Sema_IsNumeric(kind: int) -> bool { if kind == tyUnknown || kind == tyNamed || kind == tyTypeParam { return true; } - return kind >= tyInt8 && kind <= tyUInt64; + if kind == tyInt8 || kind == tyInt16 || kind == tyInt32 || kind == tyInt64 || kind == tyInt { return true; } + if kind == tyUInt8 || kind == tyUInt16 || kind == tyUInt32 || kind == tyUInt64 || kind == tyUInt { return true; } + if kind == tyFloat32 || kind == tyFloat64 { return true; } + return false; } func Sema_IsBool(kind: int) -> bool { @@ -328,9 +331,12 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) { let retType: int = Sema_CheckExpr(sema, stmt.child1); if sema.currentRetType != tyUnknown && retType != tyUnknown { if retType != sema.currentRetType { - // Be permissive: allow numeric widening, pointer/named mismatch + // Be permissive: allow numeric widening, pointer compatibility if !Sema_IsNumeric(retType) || !Sema_IsNumeric(sema.currentRetType) { - if retType != sema.currentRetType { + // Allow pointer-type compatibility (String <-> *T, *T <-> *U) + let retIsPtr: bool = retType == tyPointer || retType == tyStr; + let expectedIsPtr: bool = sema.currentRetType == tyPointer || sema.currentRetType == tyStr; + if !retIsPtr || !expectedIsPtr { Sema_EmitError(sema, stmt.line, stmt.column, "return type mismatch"); } }