Phase 7.9: Self-hosted compiler buxc2 builds and runs! 🎉

buxc (Nim bootstrap) successfully compiles buxc2 (Bux self-hosted compiler)
into a working 88KB ELF x86-64 binary.

Compiler fixes (Nim):
- duplicate symbol: user funcs shadow stdlib funcs via mergeDecls()
- forward declarations: func without body + definition (both orderings)
- extern func dedup: same extern in multiple files
- discard keyword: new language keyword, lowered to expr stmt or no-op
- parser: keywords as field names + advance-on-error safeguard
- parser: var without initializer (zero-init)
- parser: multi-line || && continuation expressions
- parser: else-if chain newline handling
- C backend: const declarations emitted as #define
- C backend: load(field_ptr) → base.field optimization (fixes lvalue errors)

Source fixes (src_bux/*.bux):
- types.bux: tk* → ty* prefix for type kind constants (avoid token conflict)
- sema.bux: remaining tk* → ty* references, StringMap → *void workaround
- hir_lower.bux: ekReturn removed, Lcx_LowerParam helper, *f dereference
- c_backend.bux: &mod.funcs[i] for pointer passing
- cli.bux: ReadFile/WriteFile → bux_read_file/bux_write_file wrappers
- parser.bux: pathStr.len → String_Len(pathStr)
- types.bux: "*" + x → String_Concat("*", x)

Build system:
- Makefile: added 4 missing examples + selfhost target
- PLAN.md: updated with actual project state, Phase 7.9 marked complete

All 18 examples pass, all unit tests pass.
This commit is contained in:
2026-05-31 16:34:36 +03:00
parent 166954204c
commit cb256397bd
31 changed files with 4603 additions and 284 deletions
+75 -78
View File
@@ -8,8 +8,8 @@ module Sema {
struct Sema {
module: *Module,
scope: *Scope,
typeTable: *StringMap,
methodTable: *StringMap,
typeTable: *void,
methodTable: *void,
diagCount: int,
diags: *SemaDiag,
hasError: bool,
@@ -38,44 +38,41 @@ func Sema_EmitError(sema: *Sema, line: uint32, col: uint32, msg: String) {
// ---------------------------------------------------------------------------
func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
if te == null as *TypeExpr { return tkUnknown; }
if te == null as *TypeExpr { return tyUnknown; }
let name: String = te.typeName;
if te.kind == tekPointer {
return tkPointer;
return tyPointer;
}
if String_Eq(name, "void") { return tkVoid; }
if String_Eq(name, "bool") { return tkBool; }
if String_Eq(name, "bool8") { return tkBool8; }
if String_Eq(name, "bool16") { return tkBool16; }
if String_Eq(name, "bool32") { return tkBool32; }
if String_Eq(name, "char8") { return tkChar8; }
if String_Eq(name, "char16") { return tkChar16; }
if String_Eq(name, "char32") { return tkChar32; }
if String_Eq(name, "String") { return tkStr; }
if String_Eq(name, "str") { return tkStr; }
if String_Eq(name, "int8") { return tkInt8; }
if String_Eq(name, "int16") { return tkInt16; }
if String_Eq(name, "int32") { return tkInt32; }
if String_Eq(name, "int64") { return tkInt64; }
if String_Eq(name, "int") { return tkInt; }
if String_Eq(name, "uint8") { return tkUInt8; }
if String_Eq(name, "uint16") { return tkUInt16; }
if String_Eq(name, "uint32") { return tkUInt32; }
if String_Eq(name, "uint64") { return tkUInt64; }
if String_Eq(name, "uint") { return tkUInt; }
if String_Eq(name, "float32") { return tkFloat32; }
if String_Eq(name, "float64") { return tkFloat64; }
if String_Eq(name, "float") { return tkFloat64; }
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; }
// Check type table for user-defined types
if sema.typeTable != null as *StringMap {
if StringMap_Has(sema.typeTable, name) {
return tkNamed;
}
}
return tkNamed; // assume named type
// Check type table for user-defined types (StringMap not yet supported)
// TODO: re-enable when StringMap generic is available
// if sema.typeTable != null as *void { ... }
return tyNamed; // assume named type
}
// ---------------------------------------------------------------------------
@@ -83,26 +80,26 @@ func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
// ---------------------------------------------------------------------------
func Sema_IsNumeric(kind: int) -> bool {
if kind == tkUnknown || kind == tkNamed || kind == tkTypeParam { return true; }
return kind >= tkInt8 && kind <= tkUInt64;
if kind == tyUnknown || kind == tyNamed || kind == tyTypeParam { return true; }
return kind >= tyInt8 && kind <= tyUInt64;
}
func Sema_IsBool(kind: int) -> bool {
return kind == tkBool || kind == tkBool8 || kind == tkBool16 || kind == tkBool32;
return kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32;
}
func Sema_TypeName(kind: int) -> String {
if kind == tkUnknown { return "?"; }
if kind == tkVoid { return "void"; }
if kind == tkBool { return "bool"; }
if kind == tkInt { return "int"; }
if kind == tkInt64 { return "int64"; }
if kind == tkUInt { return "uint"; }
if kind == tkFloat64 { return "float64"; }
if kind == tkStr { return "String"; }
if kind == tkPointer { return "*ptr"; }
if kind == tkNamed { return "user-type"; }
if kind == tkTypeParam { return "type-param"; }
if kind == tyUnknown { return "?"; }
if kind == tyVoid { return "void"; }
if kind == tyBool { return "bool"; }
if kind == tyInt{ return "int"; }
if kind == tyInt64 { return "int64"; }
if kind == tyUInt { return "uint"; }
if kind == tyFloat64 { return "float64"; }
if kind == tyStr { return "String"; }
if kind == tyPointer { return "*ptr"; }
if kind == tyNamed { return "user-type"; }
if kind == tyTypeParam { return "type-param"; }
return "?";
}
@@ -111,19 +108,19 @@ func Sema_TypeName(kind: int) -> String {
// ---------------------------------------------------------------------------
func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if expr == null as *Expr { return tkUnknown; }
if expr == null as *Expr { return tyUnknown; }
let kind: int = expr.kind;
// Literal
if kind == ekLiteral {
let tk: int = expr.tokKind;
if tk == tkIntLiteral { return tkInt; }
if tk == tkFloatLiteral { return tkFloat64; }
if tk == tkStringLiteral { return tkStr; }
if tk == tkBoolLiteral { return tkBool; }
if tk == tkCharLiteral { return tkChar32; }
if tk == tkNull { return tkPointer; }
return tkUnknown;
if tk == tkIntLiteral { return tyInt; }
if tk == tkFloatLiteral { return tyFloat64; }
if tk == tkStringLiteral { return tyStr; }
if tk == tkBoolLiteral { return tyBool; }
if tk == tkCharLiteral { return tyChar32; }
if tk == tkNull { return tyPointer; }
return tyUnknown;
}
// Identifier — look up in scope
@@ -131,7 +128,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
let sym: Symbol = Scope_Lookup(sema.scope, expr.strValue);
if sym.kind == 0 && !String_Eq(sym.name, expr.strValue) {
Sema_EmitError(sema, expr.line, expr.column, "undeclared identifier");
return tkUnknown;
return tyUnknown;
}
return sym.typeKind;
}
@@ -143,31 +140,31 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
let op: int = expr.intValue;
// Comparison operators return bool
if op >= tkEq && op <= tkGe { return tkBool; }
if op >= tkEq && op <= tkGe { return tyBool; }
// Logical operators return bool
if op == tkAmpAmp || op == tkPipePipe || op == tkBang { return tkBool; }
if op == tkAmpAmp || op == tkPipePipe || op == tkBang { return tyBool; }
// Arithmetic returns wider type
if !Sema_IsNumeric(left) || !Sema_IsNumeric(right) {
Sema_EmitError(sema, expr.line, expr.column, "arithmetic requires numeric operands");
}
if left == tkFloat64 || right == tkFloat64 { return tkFloat64; }
return tkInt;
if left == tyFloat64 || right == tyFloat64 { return tyFloat64; }
return tyInt;
}
// Unary
if kind == ekUnary {
let operand: int = Sema_CheckExpr(sema, expr.child1);
let op: int = expr.intValue;
if op == tkBang { return tkBool; }
if op == tkStar { return tkUnknown; } // dereference — resolve pointee
if op == tkAmp { return tkPointer; }
if op == tkBang { return tyBool; }
if op == tkStar { return tyUnknown; } // dereference — resolve pointee
if op == tkAmp { return tyPointer; }
return operand;
}
// Call
if kind == ekCall {
let callee: int = Sema_CheckExpr(sema, expr.child1);
if callee == tkUnknown { return tkUnknown; }
if callee == tyUnknown { return tyUnknown; }
// Assume callee returns same type (simplified)
return callee;
}
@@ -182,7 +179,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if expr.refType != null as *TypeExpr {
return Sema_ResolveType(sema, expr.refType);
}
return tkUnknown;
return tyUnknown;
}
// Try (?)
@@ -191,7 +188,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
return inner; // simplified
}
return tkUnknown;
return tyUnknown;
}
// ---------------------------------------------------------------------------
@@ -227,7 +224,7 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
// If
if kind == skIf {
let condType: int = Sema_CheckExpr(sema, stmt.child1);
if !Sema_IsBool(condType) && condType != tkUnknown {
if !Sema_IsBool(condType) && condType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "if condition must be bool");
}
return;
@@ -236,7 +233,7 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
// While
if kind == skWhile {
let condType: int = Sema_CheckExpr(sema, stmt.child1);
if !Sema_IsBool(condType) && condType != tkUnknown {
if !Sema_IsBool(condType) && condType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "while condition must be bool");
}
return;
@@ -263,7 +260,7 @@ func Sema_CollectGlobals(sema: *Sema) {
var sym: Symbol;
sym.kind = skFunc;
sym.name = decl.strValue;
sym.typeKind = tkFunc;
sym.typeKind = tyFunc;
sym.isPublic = decl.isPublic;
discard Scope_Define(sema.scope, sym);
}
@@ -273,7 +270,7 @@ func Sema_CollectGlobals(sema: *Sema) {
var sym: Symbol;
sym.kind = skType;
sym.name = decl.strValue;
sym.typeKind = tkNamed;
sym.typeKind = tyNamed;
sym.isPublic = decl.isPublic;
discard Scope_Define(sema.scope, sym);
}
@@ -283,7 +280,7 @@ func Sema_CollectGlobals(sema: *Sema) {
var sym: Symbol;
sym.kind = skType;
sym.name = decl.strValue;
sym.typeKind = tkNamed;
sym.typeKind = tyNamed;
sym.isPublic = decl.isPublic;
discard Scope_Define(sema.scope, sym);
}
@@ -293,7 +290,7 @@ func Sema_CollectGlobals(sema: *Sema) {
var sym: Symbol;
sym.kind = skFunc;
sym.name = decl.strValue;
sym.typeKind = tkFunc;
sym.typeKind = tyFunc;
sym.isPublic = true;
discard Scope_Define(sema.scope, sym);
}
@@ -316,8 +313,8 @@ func Sema_Analyze(mod: *Module) -> *Sema {
s.hasError = false;
s.diagCount = 0;
s.diags = bux_alloc(256 as uint * sizeof(SemaDiag)) as *SemaDiag;
s.typeTable = null as *StringMap;
s.methodTable = null as *StringMap;
s.typeTable = null as *void;
s.methodTable = null as *void;
// First pass: collect globals
Sema_CollectGlobals(s);
@@ -334,14 +331,14 @@ func Sema_Analyze(mod: *Module) -> *Sema {
var tpSym: Symbol;
tpSym.kind = skType;
tpSym.name = decl.typeParam0;
tpSym.typeKind = tkTypeParam;
tpSym.typeKind = tyTypeParam;
discard Scope_Define(&funcScope, tpSym);
}
if decl.typeParamCount >= 2 {
var tpSym2: Symbol;
tpSym2.kind = skType;
tpSym2.name = decl.typeParam1;
tpSym2.typeKind = tkTypeParam;
tpSym2.typeKind = tyTypeParam;
discard Scope_Define(&funcScope, tpSym2);
}
@@ -350,7 +347,7 @@ func Sema_Analyze(mod: *Module) -> *Sema {
while i < decl.paramCount {
var pSym: Symbol;
pSym.kind = skVar;
pSym.typeKind = tkInt; // simplified
pSym.typeKind = tyInt; // simplified
pSym.isMutable = false;
if i == 0 { pSym.name = decl.param0.name; }
else if i == 1 { pSym.name = decl.param1.name; }