fix: multi-arg calls, uninitialized symbols, param limit; add banner img; update docs

- Fix segfault from uninitialized Symbol structs in scope/hir_lower/sema
- Fix null ReadFile crash in Cli_MergeFileInto (missing stdlib files)
- Extend function params from 6 to 9 (bux_str_format needs 9 args)
- Add ExprList/HirArgList linked lists for >2 call arguments
- Add banner image to README
- Update docs: C backend is now primary (not QBE)
This commit is contained in:
2026-06-05 15:21:15 +03:00
parent 0c1f230286
commit 3e46e67d48
21 changed files with 368 additions and 72 deletions
+6 -4
View File
@@ -1,8 +1,10 @@
# Bux Programming Language # Bux Programming Language
> **Status:** Self-hosting phase — `buxc2` compiles `.bux` → QBE SSA → native binary. Bootstrap (`buxc`, Nim) maintains backward compatibility via C transpiler. ![Bux Language](bux-lang-01.jpeg)
Bux is a fast, compiled, strongly-typed systems programming language. Features a QBE backend for native code generation, raw multi-line strings, gradual ownership, async/await, generics, algebraic enums, and a package manager. > **Status:** Self-hosting phase — `buxc2` compiles `.bux` → C → native binary. Bootstrap (`buxc`, Nim) builds the self-hosted compiler.
Bux is a fast, compiled, strongly-typed systems programming language. Features a C backend for native code generation, raw multi-line strings, gradual ownership, async/await, generics, algebraic enums, and a package manager.
--- ---
@@ -148,7 +150,7 @@ func Main() -> int {
| **Interfaces** | `interface` + `extend` for trait-like behavior | | **Interfaces** | `interface` + `extend` for trait-like behavior |
| **Error Handling** | `Result<T,E>`, `Option<T>`, and the `?` operator | | **Error Handling** | `Result<T,E>`, `Option<T>`, and the `?` operator |
| **Standard Library** | `Io`, `Array`, `String`, `Map`, `Fs`, `Mem`, `Set`, `Path`, `Math`, `Task`, `Channel` | | **Standard Library** | `Io`, `Array`, `String`, `Map`, `Fs`, `Mem`, `Set`, `Path`, `Math`, `Task`, `Channel` |
| **Backend** | QBE native codegen (self-hosting), C transpiler (bootstrap) | | **Backend** | C transpiler (self-hosting + bootstrap) |
| **Strings** | Raw multi-line backtick strings (`...`), C-string interop | | **Strings** | Raw multi-line backtick strings (`...`), C-string interop |
| **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking | | **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking |
| **Async/Await** | `async func`, `spawn`, `.await` with stackful coroutines | | **Async/Await** | `async func`, `spawn`, `.await` with stackful coroutines |
@@ -189,7 +191,7 @@ bux/
# Build bootstrap compiler (Nim → C) # Build bootstrap compiler (Nim → C)
make build make build
# Build self-hosted compiler (Bux → QBE SSA → native) # Build self-hosted compiler (Bux → C → native)
make selfhost make selfhost
# Run all tests # Run all tests
+13 -1
View File
@@ -96,6 +96,11 @@ const ekMatch: int = 22;
const ekSpawn: int = 24; const ekSpawn: int = 24;
const ekAwait: int = 25; const ekAwait: int = 25;
struct ExprList {
expr: *Expr,
next: *ExprList,
}
struct Expr { struct Expr {
kind: int, kind: int,
line: uint32, line: uint32,
@@ -121,6 +126,9 @@ struct Expr {
// Struct init fields // Struct init fields
structName: String, structName: String,
structFieldCount: int, structFieldCount: int,
// Call arguments (linked list for multi-arg support)
callArgs: *ExprList,
callArgCount: int,
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -241,6 +249,9 @@ struct Decl {
param3: Param, param3: Param,
param4: Param, param4: Param,
param5: Param, param5: Param,
param6: Param,
param7: Param,
param8: Param,
retType: *TypeExpr, retType: *TypeExpr,
// Body // Body
refBody: *Block, refBody: *Block,
@@ -361,7 +372,8 @@ func Ast_MakeExpr(kind: int, line: uint32, col: uint32) -> Expr {
child1: null as *Expr, child2: null as *Expr, child3: null as *Expr, child1: null as *Expr, child2: null as *Expr, child3: null as *Expr,
refType: null as *TypeExpr, refBlock: null as *Block, refType: null as *TypeExpr, refBlock: null as *Block,
genericCallee: "", genericTypeArg0: "", genericTypeArg1: "", genericTypeArgCount: 0, genericCallee: "", genericTypeArg0: "", genericTypeArg1: "", genericTypeArgCount: 0,
structName: "", structFieldCount: 0 }; structName: "", structFieldCount: 0,
callArgs: null as *ExprList, callArgCount: 0 };
} }
func Ast_MakeIdent(name: String, line: uint32, col: uint32) -> Expr { func Ast_MakeIdent(name: String, line: uint32, col: uint32) -> Expr {
+23
View File
@@ -134,12 +134,29 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
if kind == hCall { if kind == hCall {
StringBuilder_Append(&cbe.sb, node.strValue); StringBuilder_Append(&cbe.sb, node.strValue);
StringBuilder_Append(&cbe.sb, "("); StringBuilder_Append(&cbe.sb, "(");
var needsComma: bool = false;
if node.child1 != null as *HirNode { if node.child1 != null as *HirNode {
CBE_EmitExpr(cbe, node.child1); CBE_EmitExpr(cbe, node.child1);
needsComma = true;
} }
if node.child2 != null as *HirNode { if node.child2 != null as *HirNode {
if needsComma {
StringBuilder_Append(&cbe.sb, ", "); StringBuilder_Append(&cbe.sb, ", ");
}
CBE_EmitExpr(cbe, node.child2); CBE_EmitExpr(cbe, node.child2);
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, ", ");
}
CBE_EmitExpr(cbe, curExtra.node);
needsComma = true;
curExtra = curExtra.next;
ai = ai + 1;
} }
StringBuilder_Append(&cbe.sb, ")"); StringBuilder_Append(&cbe.sb, ")");
return; return;
@@ -414,6 +431,9 @@ func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
if i == 3 { pname = f.param3.name; ptype = f.param3.typeName; } if i == 3 { pname = f.param3.name; ptype = f.param3.typeName; }
if i == 4 { pname = f.param4.name; ptype = f.param4.typeName; } if i == 4 { pname = f.param4.name; ptype = f.param4.typeName; }
if i == 5 { pname = f.param5.name; ptype = f.param5.typeName; } if i == 5 { pname = f.param5.name; ptype = f.param5.typeName; }
if i == 6 { pname = f.param6.name; ptype = f.param6.typeName; }
if i == 7 { pname = f.param7.name; ptype = f.param7.typeName; }
if i == 8 { pname = f.param8.name; ptype = f.param8.typeName; }
// Emit type // Emit type
if String_Eq(ptype, "String") || String_Eq(ptype, "str") { if String_Eq(ptype, "String") || String_Eq(ptype, "str") {
StringBuilder_Append(&cbe.sb, "String "); StringBuilder_Append(&cbe.sb, "String ");
@@ -474,6 +494,9 @@ func CBE_FuncHasGeneric(f: *HirFunc) -> bool {
if pi == 3 { ptype = f.param3.typeName; } if pi == 3 { ptype = f.param3.typeName; }
if pi == 4 { ptype = f.param4.typeName; } if pi == 4 { ptype = f.param4.typeName; }
if pi == 5 { ptype = f.param5.typeName; } if pi == 5 { ptype = f.param5.typeName; }
if pi == 6 { ptype = f.param6.typeName; }
if pi == 7 { ptype = f.param7.typeName; }
if pi == 8 { ptype = f.param8.typeName; }
if CBE_IsGenericTypeName(ptype) { return true; } if CBE_IsGenericTypeName(ptype) { return true; }
pi = pi + 1; pi = pi + 1;
} }
+6 -1
View File
@@ -400,8 +400,13 @@ func Cli_CollectStdlibImports(mod: *Module, outPaths: *String, maxCount: int, st
func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCount: int) -> int { func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCount: int) -> int {
Print("DEBUG: MergeFileInto "); Print("DEBUG: MergeFileInto ");
PrintLine(path); PrintLine(path);
if !FileExists(path) {
Print("Error: stdlib file not found: ");
PrintLine(path);
return 0;
}
let source: String = ReadFile(path); let source: String = ReadFile(path);
if String_Eq(source, "") { return 0; } if source == null as String || String_Eq(source, "") { return 0; }
let lex: *Lexer = Lexer_Tokenize(source); let lex: *Lexer = Lexer_Tokenize(source);
if Lexer_DiagCount(lex) > 0 { return 0; } if Lexer_DiagCount(lex) > 0 { return 0; }
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount); let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
+12
View File
@@ -34,6 +34,15 @@ const hMatch: int = 28;
const hSpawn: int = 29; const hSpawn: int = 29;
const hAwait: int = 30; const hAwait: int = 30;
// ---------------------------------------------------------------------------
// HirArgList — linked list for call arguments beyond 2
// ---------------------------------------------------------------------------
struct HirArgList {
node: *HirNode,
next: *HirArgList,
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// HirNode — unified struct with tagged union pattern // HirNode — unified struct with tagged union pattern
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -76,6 +85,9 @@ struct HirFunc {
param3: HirParam; param3: HirParam;
param4: HirParam; param4: HirParam;
param5: HirParam; param5: HirParam;
param6: HirParam;
param7: HirParam;
param8: HirParam;
retTypeKind: int; retTypeKind: int;
retTypeName: String; retTypeName: String;
body: *HirNode; body: *HirNode;
+69 -10
View File
@@ -180,11 +180,34 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
// Lower receiver as first argument // Lower receiver as first argument
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1); let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1);
n.child1 = recv; n.child1 = recv;
// Lower remaining arguments // Lower remaining arguments from linked list
if expr.child2 != null as *Expr { var arg: *ExprList = expr.callArgs;
let extra: *HirNode = Lcx_LowerExpr(ctx, expr.child2); var argIdx: int = 0;
// Chain extra args after receiver via child2/child3 while arg != null as *ExprList {
n.child2 = extra; let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr);
if argIdx == 0 {
n.child2 = lowered;
} else if argIdx == 1 {
// Third argument — start linked list
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
firstExtra.node = lowered;
firstExtra.next = null as *HirArgList;
n.extraData = firstExtra as *void;
n.extraCount = 1;
} else {
// Additional args — append to linked list
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 = lowered;
newNode.next = null as *HirArgList;
cur.next = newNode;
n.extraCount = n.extraCount + 1;
}
arg = arg.next;
argIdx = argIdx + 1;
} }
return n; return n;
} }
@@ -192,12 +215,36 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent { if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
n.strValue = expr.child1.strValue; n.strValue = expr.child1.strValue;
} }
// Lower arguments // Lower arguments from linked list
if expr.child2 != null as *Expr { var arg: *ExprList = expr.callArgs;
n.child1 = Lcx_LowerExpr(ctx, expr.child2); var argIdx: int = 0;
while arg != null as *ExprList {
let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr);
if argIdx == 0 {
n.child1 = lowered;
} else if argIdx == 1 {
n.child2 = lowered;
} else if argIdx == 2 {
// Third argument — start linked list
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
firstExtra.node = lowered;
firstExtra.next = null as *HirArgList;
n.extraData = firstExtra as *void;
n.extraCount = 1;
} else {
// Additional args — append to linked list
var cur: *HirArgList = n.extraData as *HirArgList;
while cur.next != null as *HirArgList {
cur = cur.next;
} }
if expr.child3 != null as *Expr { let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
n.child2 = Lcx_LowerExpr(ctx, expr.child3); newNode.node = lowered;
newNode.next = null as *HirArgList;
cur.next = newNode;
n.extraCount = n.extraCount + 1;
}
arg = arg.next;
argIdx = argIdx + 1;
} }
return n; return n;
} }
@@ -359,6 +406,9 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
sym.name = stmt.strValue; sym.name = stmt.strValue;
sym.typeKind = alloca.typeKind; sym.typeKind = alloca.typeKind;
sym.typeName = alloca.typeName; sym.typeName = alloca.typeName;
sym.isMutable = false;
sym.isPublic = false;
sym.decl = null as *Decl;
discard Scope_Define(ctx.scope, sym); discard Scope_Define(ctx.scope, sym);
// store the init value // store the init value
n.kind = hStore; n.kind = hStore;
@@ -495,6 +545,9 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
Lcx_LowerParam(&f.param3, &decl.param3); Lcx_LowerParam(&f.param3, &decl.param3);
Lcx_LowerParam(&f.param4, &decl.param4); Lcx_LowerParam(&f.param4, &decl.param4);
Lcx_LowerParam(&f.param5, &decl.param5); Lcx_LowerParam(&f.param5, &decl.param5);
Lcx_LowerParam(&f.param6, &decl.param6);
Lcx_LowerParam(&f.param7, &decl.param7);
Lcx_LowerParam(&f.param8, &decl.param8);
if decl.retType != null as *TypeExpr { if decl.retType != null as *TypeExpr {
f.retTypeName = decl.retType.typeName; f.retTypeName = decl.retType.typeName;
@@ -514,12 +567,18 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
else if pi == 3 { p = &decl.param3; } else if pi == 3 { p = &decl.param3; }
else if pi == 4 { p = &decl.param4; } else if pi == 4 { p = &decl.param4; }
else if pi == 5 { p = &decl.param5; } else if pi == 5 { p = &decl.param5; }
else if pi == 6 { p = &decl.param6; }
else if pi == 7 { p = &decl.param7; }
else if pi == 8 { p = &decl.param8; }
if p != null as *Param && p.refParamType != null as *TypeExpr { if p != null as *Param && p.refParamType != null as *TypeExpr {
var sym: Symbol; var sym: Symbol;
sym.kind = skVar; sym.kind = skVar;
sym.name = p.name; sym.name = p.name;
sym.typeKind = Lcx_ResolveTypeKind(p.refParamType); sym.typeKind = Lcx_ResolveTypeKind(p.refParamType);
sym.typeName = p.refParamType.typeName; sym.typeName = p.refParamType.typeName;
sym.isMutable = false;
sym.isPublic = false;
sym.decl = null as *Decl;
discard Scope_Define(ctx.scope, sym); discard Scope_Define(ctx.scope, sym);
} }
pi = pi + 1; pi = pi + 1;
+1 -1
View File
@@ -711,7 +711,7 @@ func Lexer_GetToken(lex: *Lexer, index: int) -> LexToken {
if index >= 0 && index < lex.tokenCount { if index >= 0 && index < lex.tokenCount {
return lex.tokens[index]; return lex.tokens[index];
} }
var empty: LexToken; var empty: LexToken = LexToken { kind: tkEndOfFile, text: "", line: 0, column: 0 };
return empty; return empty;
} }
+34 -11
View File
@@ -214,6 +214,8 @@ func parserMakeExpr(kind: int, line: uint32, col: uint32) -> *Expr {
e.genericTypeArgCount = 0; e.genericTypeArgCount = 0;
e.structName = ""; e.structName = "";
e.structFieldCount = 0; e.structFieldCount = 0;
e.callArgs = null as *ExprList;
e.callArgCount = 0;
return e; return e;
} }
@@ -337,18 +339,27 @@ func parserParsePostfix(p: *Parser) -> *Expr {
let col: uint32 = parserCurToken(p).column; let col: uint32 = parserCurToken(p).column;
let e: *Expr = parserMakeExpr(ekCall, line, col); let e: *Expr = parserMakeExpr(ekCall, line, col);
e.child1 = left; e.child1 = left;
// Parse arguments (up to 8) // Parse all arguments into linked list
// child2 = first arg, child3 = second arg, extra = rest var argCount: int = 0;
if !parserCheck(p, tkRParen) { var firstArg: *ExprList = null as *ExprList;
e.child2 = parserParseExpr(p); var lastArg: *ExprList = null as *ExprList;
if parserMatch(p, tkComma) { while !parserCheck(p, tkRParen) {
e.child3 = parserParseExpr(p); let argExpr: *Expr = parserParseExpr(p);
// Consume any remaining arguments (we only store 2) let argNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
while parserMatch(p, tkComma) { argNode.expr = argExpr;
discard parserParseExpr(p); argNode.next = null as *ExprList;
} if firstArg == null as *ExprList {
firstArg = argNode;
lastArg = argNode;
} else {
lastArg.next = argNode;
lastArg = argNode;
} }
argCount = argCount + 1;
if !parserMatch(p, tkComma) { break; }
} }
e.callArgs = firstArg;
e.callArgCount = argCount;
discard parserExpect(p, tkRParen, "expected ')'"); discard parserExpect(p, tkRParen, "expected ')'");
left = e; left = e;
continue; continue;
@@ -842,7 +853,7 @@ func parserParseParamList(p: *Parser) -> *Decl {
discard parserExpect(p, tkLParen, "expected '('"); discard parserExpect(p, tkLParen, "expected '('");
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile { while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
if d.paramCount >= 6 { break; } if d.paramCount >= 9 { break; }
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name"); let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name");
discard parserExpect(p, tkColon, "expected ':' in parameter"); discard parserExpect(p, tkColon, "expected ':' in parameter");
let ptype: *TypeExpr = parserParseType(p); let ptype: *TypeExpr = parserParseType(p);
@@ -865,6 +876,15 @@ func parserParseParamList(p: *Parser) -> *Decl {
} else if d.paramCount == 5 { } else if d.paramCount == 5 {
d.param5.name = nameTok.text; d.param5.name = nameTok.text;
d.param5.refParamType = ptype; d.param5.refParamType = ptype;
} else if d.paramCount == 6 {
d.param6.name = nameTok.text;
d.param6.refParamType = ptype;
} else if d.paramCount == 7 {
d.param7.name = nameTok.text;
d.param7.refParamType = ptype;
} else if d.paramCount == 8 {
d.param8.name = nameTok.text;
d.param8.refParamType = ptype;
} }
d.paramCount = d.paramCount + 1; d.paramCount = d.paramCount + 1;
@@ -916,6 +936,9 @@ func parserParseFuncDecl(p: *Parser, isPublic: bool, isExtern: bool, isAsync: bo
d.param3 = params.param3; d.param3 = params.param3;
d.param4 = params.param4; d.param4 = params.param4;
d.param5 = params.param5; d.param5 = params.param5;
d.param6 = params.param6;
d.param7 = params.param7;
d.param8 = params.param8;
// Return type // Return type
if parserMatch(p, tkArrow) { if parserMatch(p, tkArrow) {
+2 -2
View File
@@ -72,7 +72,7 @@ func Scope_Lookup(scope: *Scope, name: String) -> Symbol {
} }
cur = cur.parent; cur = cur.parent;
} }
var empty: Symbol; var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", isMutable: false, isPublic: false, decl: null as *Decl };
return empty; return empty;
} }
@@ -84,7 +84,7 @@ func Scope_LookupLocal(scope: *Scope, name: String) -> Symbol {
} }
i = i + 1; i = i + 1;
} }
var empty: Symbol; var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", isMutable: false, isPublic: false, decl: null as *Decl };
return empty; return empty;
} }
+12 -5
View File
@@ -231,11 +231,10 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
// Call // Call
if kind == ekCall { if kind == ekCall {
discard Sema_CheckExpr(sema, expr.child1); discard Sema_CheckExpr(sema, expr.child1);
if expr.child2 != null as *Expr { var arg: *ExprList = expr.callArgs;
discard Sema_CheckExpr(sema, expr.child2); while arg != null as *ExprList {
} discard Sema_CheckExpr(sema, arg.expr);
if expr.child3 != null as *Expr { arg = arg.next;
discard Sema_CheckExpr(sema, expr.child3);
} }
// Try to resolve return type from function declaration // Try to resolve return type from function declaration
if expr.child1.kind == ekIdent { if expr.child1.kind == ekIdent {
@@ -635,6 +634,7 @@ func Sema_Analyze(mod: *Module) -> *Sema {
// Add type params to scope // Add type params to scope
if decl.typeParamCount >= 1 { if decl.typeParamCount >= 1 {
var tpSym: Symbol; var tpSym: Symbol;
Sema_ZeroInitSymbol(&tpSym);
tpSym.kind = skType; tpSym.kind = skType;
tpSym.name = decl.typeParam0; tpSym.name = decl.typeParam0;
tpSym.typeKind = tyTypeParam; tpSym.typeKind = tyTypeParam;
@@ -643,6 +643,7 @@ func Sema_Analyze(mod: *Module) -> *Sema {
} }
if decl.typeParamCount >= 2 { if decl.typeParamCount >= 2 {
var tpSym2: Symbol; var tpSym2: Symbol;
Sema_ZeroInitSymbol(&tpSym2);
tpSym2.kind = skType; tpSym2.kind = skType;
tpSym2.name = decl.typeParam1; tpSym2.name = decl.typeParam1;
tpSym2.typeKind = tyTypeParam; tpSym2.typeKind = tyTypeParam;
@@ -660,6 +661,9 @@ func Sema_Analyze(mod: *Module) -> *Sema {
else if i == 3 { p = &decl.param3; } else if i == 3 { p = &decl.param3; }
else if i == 4 { p = &decl.param4; } else if i == 4 { p = &decl.param4; }
else if i == 5 { p = &decl.param5; } else if i == 5 { p = &decl.param5; }
else if i == 6 { p = &decl.param6; }
else if i == 7 { p = &decl.param7; }
else if i == 8 { p = &decl.param8; }
var pSym: Symbol; var pSym: Symbol;
Sema_ZeroInitSymbol(&pSym); Sema_ZeroInitSymbol(&pSym);
pSym.kind = skVar; pSym.kind = skVar;
@@ -679,6 +683,9 @@ func Sema_Analyze(mod: *Module) -> *Sema {
else if i == 3 { pSym.name = decl.param3.name; } else if i == 3 { pSym.name = decl.param3.name; }
else if i == 4 { pSym.name = decl.param4.name; } else if i == 4 { pSym.name = decl.param4.name; }
else if i == 5 { pSym.name = decl.param5.name; } else if i == 5 { pSym.name = decl.param5.name; }
else if i == 6 { pSym.name = decl.param6.name; }
else if i == 7 { pSym.name = decl.param7.name; }
else if i == 8 { pSym.name = decl.param8.name; }
discard Scope_Define(&funcScope, pSym); discard Scope_Define(&funcScope, pSym);
i = i + 1; i = i + 1;
} }
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

+14 -2
View File
@@ -32,7 +32,13 @@ make build
make dev make dev
``` ```
The output is a single binary: `buxc`. The output is a single binary: `buxc` (bootstrap compiler in Nim).
The self-hosted compiler `buxc2` is built from `src_bux/*.bux` sources via:
```bash
make selfhost
```
This compiles `buxc2` using the bootstrap compiler. The self-hosted compiler generates C code and invokes `cc` to produce native binaries.
--- ---
@@ -164,12 +170,18 @@ bux/
./buxc build -v ./buxc build -v
``` ```
### Inspecting Generated C ### Inspecting Generated C (bootstrap)
```bash ```bash
./buxc build ./buxc build
cat build/main.c cat build/main.c
``` ```
### Inspecting Generated C (self-hosted)
```bash
cd _selfhost && ../buxc build
cat build/main.c
```
### Common Errors ### Common Errors
| Error | Cause | Fix | | Error | Cause | Fix |
+13 -1
View File
@@ -96,6 +96,11 @@ const ekMatch: int = 22;
const ekSpawn: int = 24; const ekSpawn: int = 24;
const ekAwait: int = 25; const ekAwait: int = 25;
struct ExprList {
expr: *Expr,
next: *ExprList,
}
struct Expr { struct Expr {
kind: int, kind: int,
line: uint32, line: uint32,
@@ -121,6 +126,9 @@ struct Expr {
// Struct init fields // Struct init fields
structName: String, structName: String,
structFieldCount: int, structFieldCount: int,
// Call arguments (linked list for multi-arg support)
callArgs: *ExprList,
callArgCount: int,
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -241,6 +249,9 @@ struct Decl {
param3: Param, param3: Param,
param4: Param, param4: Param,
param5: Param, param5: Param,
param6: Param,
param7: Param,
param8: Param,
retType: *TypeExpr, retType: *TypeExpr,
// Body // Body
refBody: *Block, refBody: *Block,
@@ -361,7 +372,8 @@ func Ast_MakeExpr(kind: int, line: uint32, col: uint32) -> Expr {
child1: null as *Expr, child2: null as *Expr, child3: null as *Expr, child1: null as *Expr, child2: null as *Expr, child3: null as *Expr,
refType: null as *TypeExpr, refBlock: null as *Block, refType: null as *TypeExpr, refBlock: null as *Block,
genericCallee: "", genericTypeArg0: "", genericTypeArg1: "", genericTypeArgCount: 0, genericCallee: "", genericTypeArg0: "", genericTypeArg1: "", genericTypeArgCount: 0,
structName: "", structFieldCount: 0 }; structName: "", structFieldCount: 0,
callArgs: null as *ExprList, callArgCount: 0 };
} }
func Ast_MakeIdent(name: String, line: uint32, col: uint32) -> Expr { func Ast_MakeIdent(name: String, line: uint32, col: uint32) -> Expr {
+23
View File
@@ -134,12 +134,29 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
if kind == hCall { if kind == hCall {
StringBuilder_Append(&cbe.sb, node.strValue); StringBuilder_Append(&cbe.sb, node.strValue);
StringBuilder_Append(&cbe.sb, "("); StringBuilder_Append(&cbe.sb, "(");
var needsComma: bool = false;
if node.child1 != null as *HirNode { if node.child1 != null as *HirNode {
CBE_EmitExpr(cbe, node.child1); CBE_EmitExpr(cbe, node.child1);
needsComma = true;
} }
if node.child2 != null as *HirNode { if node.child2 != null as *HirNode {
if needsComma {
StringBuilder_Append(&cbe.sb, ", "); StringBuilder_Append(&cbe.sb, ", ");
}
CBE_EmitExpr(cbe, node.child2); CBE_EmitExpr(cbe, node.child2);
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, ", ");
}
CBE_EmitExpr(cbe, curExtra.node);
needsComma = true;
curExtra = curExtra.next;
ai = ai + 1;
} }
StringBuilder_Append(&cbe.sb, ")"); StringBuilder_Append(&cbe.sb, ")");
return; return;
@@ -414,6 +431,9 @@ func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
if i == 3 { pname = f.param3.name; ptype = f.param3.typeName; } if i == 3 { pname = f.param3.name; ptype = f.param3.typeName; }
if i == 4 { pname = f.param4.name; ptype = f.param4.typeName; } if i == 4 { pname = f.param4.name; ptype = f.param4.typeName; }
if i == 5 { pname = f.param5.name; ptype = f.param5.typeName; } if i == 5 { pname = f.param5.name; ptype = f.param5.typeName; }
if i == 6 { pname = f.param6.name; ptype = f.param6.typeName; }
if i == 7 { pname = f.param7.name; ptype = f.param7.typeName; }
if i == 8 { pname = f.param8.name; ptype = f.param8.typeName; }
// Emit type // Emit type
if String_Eq(ptype, "String") || String_Eq(ptype, "str") { if String_Eq(ptype, "String") || String_Eq(ptype, "str") {
StringBuilder_Append(&cbe.sb, "String "); StringBuilder_Append(&cbe.sb, "String ");
@@ -474,6 +494,9 @@ func CBE_FuncHasGeneric(f: *HirFunc) -> bool {
if pi == 3 { ptype = f.param3.typeName; } if pi == 3 { ptype = f.param3.typeName; }
if pi == 4 { ptype = f.param4.typeName; } if pi == 4 { ptype = f.param4.typeName; }
if pi == 5 { ptype = f.param5.typeName; } if pi == 5 { ptype = f.param5.typeName; }
if pi == 6 { ptype = f.param6.typeName; }
if pi == 7 { ptype = f.param7.typeName; }
if pi == 8 { ptype = f.param8.typeName; }
if CBE_IsGenericTypeName(ptype) { return true; } if CBE_IsGenericTypeName(ptype) { return true; }
pi = pi + 1; pi = pi + 1;
} }
+6 -1
View File
@@ -400,8 +400,13 @@ func Cli_CollectStdlibImports(mod: *Module, outPaths: *String, maxCount: int, st
func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCount: int) -> int { func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCount: int) -> int {
Print("DEBUG: MergeFileInto "); Print("DEBUG: MergeFileInto ");
PrintLine(path); PrintLine(path);
if !FileExists(path) {
Print("Error: stdlib file not found: ");
PrintLine(path);
return 0;
}
let source: String = ReadFile(path); let source: String = ReadFile(path);
if String_Eq(source, "") { return 0; } if source == null as String || String_Eq(source, "") { return 0; }
let lex: *Lexer = Lexer_Tokenize(source); let lex: *Lexer = Lexer_Tokenize(source);
if Lexer_DiagCount(lex) > 0 { return 0; } if Lexer_DiagCount(lex) > 0 { return 0; }
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount); let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
+12
View File
@@ -34,6 +34,15 @@ const hMatch: int = 28;
const hSpawn: int = 29; const hSpawn: int = 29;
const hAwait: int = 30; const hAwait: int = 30;
// ---------------------------------------------------------------------------
// HirArgList — linked list for call arguments beyond 2
// ---------------------------------------------------------------------------
struct HirArgList {
node: *HirNode,
next: *HirArgList,
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// HirNode — unified struct with tagged union pattern // HirNode — unified struct with tagged union pattern
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -76,6 +85,9 @@ struct HirFunc {
param3: HirParam; param3: HirParam;
param4: HirParam; param4: HirParam;
param5: HirParam; param5: HirParam;
param6: HirParam;
param7: HirParam;
param8: HirParam;
retTypeKind: int; retTypeKind: int;
retTypeName: String; retTypeName: String;
body: *HirNode; body: *HirNode;
+69 -10
View File
@@ -180,11 +180,34 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
// Lower receiver as first argument // Lower receiver as first argument
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1); let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1);
n.child1 = recv; n.child1 = recv;
// Lower remaining arguments // Lower remaining arguments from linked list
if expr.child2 != null as *Expr { var arg: *ExprList = expr.callArgs;
let extra: *HirNode = Lcx_LowerExpr(ctx, expr.child2); var argIdx: int = 0;
// Chain extra args after receiver via child2/child3 while arg != null as *ExprList {
n.child2 = extra; let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr);
if argIdx == 0 {
n.child2 = lowered;
} else if argIdx == 1 {
// Third argument — start linked list
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
firstExtra.node = lowered;
firstExtra.next = null as *HirArgList;
n.extraData = firstExtra as *void;
n.extraCount = 1;
} else {
// Additional args — append to linked list
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 = lowered;
newNode.next = null as *HirArgList;
cur.next = newNode;
n.extraCount = n.extraCount + 1;
}
arg = arg.next;
argIdx = argIdx + 1;
} }
return n; return n;
} }
@@ -192,12 +215,36 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent { if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
n.strValue = expr.child1.strValue; n.strValue = expr.child1.strValue;
} }
// Lower arguments // Lower arguments from linked list
if expr.child2 != null as *Expr { var arg: *ExprList = expr.callArgs;
n.child1 = Lcx_LowerExpr(ctx, expr.child2); var argIdx: int = 0;
while arg != null as *ExprList {
let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr);
if argIdx == 0 {
n.child1 = lowered;
} else if argIdx == 1 {
n.child2 = lowered;
} else if argIdx == 2 {
// Third argument — start linked list
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
firstExtra.node = lowered;
firstExtra.next = null as *HirArgList;
n.extraData = firstExtra as *void;
n.extraCount = 1;
} else {
// Additional args — append to linked list
var cur: *HirArgList = n.extraData as *HirArgList;
while cur.next != null as *HirArgList {
cur = cur.next;
} }
if expr.child3 != null as *Expr { let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
n.child2 = Lcx_LowerExpr(ctx, expr.child3); newNode.node = lowered;
newNode.next = null as *HirArgList;
cur.next = newNode;
n.extraCount = n.extraCount + 1;
}
arg = arg.next;
argIdx = argIdx + 1;
} }
return n; return n;
} }
@@ -359,6 +406,9 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
sym.name = stmt.strValue; sym.name = stmt.strValue;
sym.typeKind = alloca.typeKind; sym.typeKind = alloca.typeKind;
sym.typeName = alloca.typeName; sym.typeName = alloca.typeName;
sym.isMutable = false;
sym.isPublic = false;
sym.decl = null as *Decl;
discard Scope_Define(ctx.scope, sym); discard Scope_Define(ctx.scope, sym);
// store the init value // store the init value
n.kind = hStore; n.kind = hStore;
@@ -495,6 +545,9 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
Lcx_LowerParam(&f.param3, &decl.param3); Lcx_LowerParam(&f.param3, &decl.param3);
Lcx_LowerParam(&f.param4, &decl.param4); Lcx_LowerParam(&f.param4, &decl.param4);
Lcx_LowerParam(&f.param5, &decl.param5); Lcx_LowerParam(&f.param5, &decl.param5);
Lcx_LowerParam(&f.param6, &decl.param6);
Lcx_LowerParam(&f.param7, &decl.param7);
Lcx_LowerParam(&f.param8, &decl.param8);
if decl.retType != null as *TypeExpr { if decl.retType != null as *TypeExpr {
f.retTypeName = decl.retType.typeName; f.retTypeName = decl.retType.typeName;
@@ -514,12 +567,18 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
else if pi == 3 { p = &decl.param3; } else if pi == 3 { p = &decl.param3; }
else if pi == 4 { p = &decl.param4; } else if pi == 4 { p = &decl.param4; }
else if pi == 5 { p = &decl.param5; } else if pi == 5 { p = &decl.param5; }
else if pi == 6 { p = &decl.param6; }
else if pi == 7 { p = &decl.param7; }
else if pi == 8 { p = &decl.param8; }
if p != null as *Param && p.refParamType != null as *TypeExpr { if p != null as *Param && p.refParamType != null as *TypeExpr {
var sym: Symbol; var sym: Symbol;
sym.kind = skVar; sym.kind = skVar;
sym.name = p.name; sym.name = p.name;
sym.typeKind = Lcx_ResolveTypeKind(p.refParamType); sym.typeKind = Lcx_ResolveTypeKind(p.refParamType);
sym.typeName = p.refParamType.typeName; sym.typeName = p.refParamType.typeName;
sym.isMutable = false;
sym.isPublic = false;
sym.decl = null as *Decl;
discard Scope_Define(ctx.scope, sym); discard Scope_Define(ctx.scope, sym);
} }
pi = pi + 1; pi = pi + 1;
+1 -1
View File
@@ -711,7 +711,7 @@ func Lexer_GetToken(lex: *Lexer, index: int) -> LexToken {
if index >= 0 && index < lex.tokenCount { if index >= 0 && index < lex.tokenCount {
return lex.tokens[index]; return lex.tokens[index];
} }
var empty: LexToken; var empty: LexToken = LexToken { kind: tkEndOfFile, text: "", line: 0, column: 0 };
return empty; return empty;
} }
+34 -11
View File
@@ -214,6 +214,8 @@ func parserMakeExpr(kind: int, line: uint32, col: uint32) -> *Expr {
e.genericTypeArgCount = 0; e.genericTypeArgCount = 0;
e.structName = ""; e.structName = "";
e.structFieldCount = 0; e.structFieldCount = 0;
e.callArgs = null as *ExprList;
e.callArgCount = 0;
return e; return e;
} }
@@ -337,18 +339,27 @@ func parserParsePostfix(p: *Parser) -> *Expr {
let col: uint32 = parserCurToken(p).column; let col: uint32 = parserCurToken(p).column;
let e: *Expr = parserMakeExpr(ekCall, line, col); let e: *Expr = parserMakeExpr(ekCall, line, col);
e.child1 = left; e.child1 = left;
// Parse arguments (up to 8) // Parse all arguments into linked list
// child2 = first arg, child3 = second arg, extra = rest var argCount: int = 0;
if !parserCheck(p, tkRParen) { var firstArg: *ExprList = null as *ExprList;
e.child2 = parserParseExpr(p); var lastArg: *ExprList = null as *ExprList;
if parserMatch(p, tkComma) { while !parserCheck(p, tkRParen) {
e.child3 = parserParseExpr(p); let argExpr: *Expr = parserParseExpr(p);
// Consume any remaining arguments (we only store 2) let argNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
while parserMatch(p, tkComma) { argNode.expr = argExpr;
discard parserParseExpr(p); argNode.next = null as *ExprList;
} if firstArg == null as *ExprList {
firstArg = argNode;
lastArg = argNode;
} else {
lastArg.next = argNode;
lastArg = argNode;
} }
argCount = argCount + 1;
if !parserMatch(p, tkComma) { break; }
} }
e.callArgs = firstArg;
e.callArgCount = argCount;
discard parserExpect(p, tkRParen, "expected ')'"); discard parserExpect(p, tkRParen, "expected ')'");
left = e; left = e;
continue; continue;
@@ -842,7 +853,7 @@ func parserParseParamList(p: *Parser) -> *Decl {
discard parserExpect(p, tkLParen, "expected '('"); discard parserExpect(p, tkLParen, "expected '('");
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile { while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
if d.paramCount >= 6 { break; } if d.paramCount >= 9 { break; }
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name"); let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name");
discard parserExpect(p, tkColon, "expected ':' in parameter"); discard parserExpect(p, tkColon, "expected ':' in parameter");
let ptype: *TypeExpr = parserParseType(p); let ptype: *TypeExpr = parserParseType(p);
@@ -865,6 +876,15 @@ func parserParseParamList(p: *Parser) -> *Decl {
} else if d.paramCount == 5 { } else if d.paramCount == 5 {
d.param5.name = nameTok.text; d.param5.name = nameTok.text;
d.param5.refParamType = ptype; d.param5.refParamType = ptype;
} else if d.paramCount == 6 {
d.param6.name = nameTok.text;
d.param6.refParamType = ptype;
} else if d.paramCount == 7 {
d.param7.name = nameTok.text;
d.param7.refParamType = ptype;
} else if d.paramCount == 8 {
d.param8.name = nameTok.text;
d.param8.refParamType = ptype;
} }
d.paramCount = d.paramCount + 1; d.paramCount = d.paramCount + 1;
@@ -916,6 +936,9 @@ func parserParseFuncDecl(p: *Parser, isPublic: bool, isExtern: bool, isAsync: bo
d.param3 = params.param3; d.param3 = params.param3;
d.param4 = params.param4; d.param4 = params.param4;
d.param5 = params.param5; d.param5 = params.param5;
d.param6 = params.param6;
d.param7 = params.param7;
d.param8 = params.param8;
// Return type // Return type
if parserMatch(p, tkArrow) { if parserMatch(p, tkArrow) {
+2 -2
View File
@@ -72,7 +72,7 @@ func Scope_Lookup(scope: *Scope, name: String) -> Symbol {
} }
cur = cur.parent; cur = cur.parent;
} }
var empty: Symbol; var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", isMutable: false, isPublic: false, decl: null as *Decl };
return empty; return empty;
} }
@@ -84,7 +84,7 @@ func Scope_LookupLocal(scope: *Scope, name: String) -> Symbol {
} }
i = i + 1; i = i + 1;
} }
var empty: Symbol; var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", isMutable: false, isPublic: false, decl: null as *Decl };
return empty; return empty;
} }
+12 -5
View File
@@ -231,11 +231,10 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
// Call // Call
if kind == ekCall { if kind == ekCall {
discard Sema_CheckExpr(sema, expr.child1); discard Sema_CheckExpr(sema, expr.child1);
if expr.child2 != null as *Expr { var arg: *ExprList = expr.callArgs;
discard Sema_CheckExpr(sema, expr.child2); while arg != null as *ExprList {
} discard Sema_CheckExpr(sema, arg.expr);
if expr.child3 != null as *Expr { arg = arg.next;
discard Sema_CheckExpr(sema, expr.child3);
} }
// Try to resolve return type from function declaration // Try to resolve return type from function declaration
if expr.child1.kind == ekIdent { if expr.child1.kind == ekIdent {
@@ -635,6 +634,7 @@ func Sema_Analyze(mod: *Module) -> *Sema {
// Add type params to scope // Add type params to scope
if decl.typeParamCount >= 1 { if decl.typeParamCount >= 1 {
var tpSym: Symbol; var tpSym: Symbol;
Sema_ZeroInitSymbol(&tpSym);
tpSym.kind = skType; tpSym.kind = skType;
tpSym.name = decl.typeParam0; tpSym.name = decl.typeParam0;
tpSym.typeKind = tyTypeParam; tpSym.typeKind = tyTypeParam;
@@ -643,6 +643,7 @@ func Sema_Analyze(mod: *Module) -> *Sema {
} }
if decl.typeParamCount >= 2 { if decl.typeParamCount >= 2 {
var tpSym2: Symbol; var tpSym2: Symbol;
Sema_ZeroInitSymbol(&tpSym2);
tpSym2.kind = skType; tpSym2.kind = skType;
tpSym2.name = decl.typeParam1; tpSym2.name = decl.typeParam1;
tpSym2.typeKind = tyTypeParam; tpSym2.typeKind = tyTypeParam;
@@ -660,6 +661,9 @@ func Sema_Analyze(mod: *Module) -> *Sema {
else if i == 3 { p = &decl.param3; } else if i == 3 { p = &decl.param3; }
else if i == 4 { p = &decl.param4; } else if i == 4 { p = &decl.param4; }
else if i == 5 { p = &decl.param5; } else if i == 5 { p = &decl.param5; }
else if i == 6 { p = &decl.param6; }
else if i == 7 { p = &decl.param7; }
else if i == 8 { p = &decl.param8; }
var pSym: Symbol; var pSym: Symbol;
Sema_ZeroInitSymbol(&pSym); Sema_ZeroInitSymbol(&pSym);
pSym.kind = skVar; pSym.kind = skVar;
@@ -679,6 +683,9 @@ func Sema_Analyze(mod: *Module) -> *Sema {
else if i == 3 { pSym.name = decl.param3.name; } else if i == 3 { pSym.name = decl.param3.name; }
else if i == 4 { pSym.name = decl.param4.name; } else if i == 4 { pSym.name = decl.param4.name; }
else if i == 5 { pSym.name = decl.param5.name; } else if i == 5 { pSym.name = decl.param5.name; }
else if i == 6 { pSym.name = decl.param6.name; }
else if i == 7 { pSym.name = decl.param7.name; }
else if i == 8 { pSym.name = decl.param8.name; }
discard Scope_Define(&funcScope, pSym); discard Scope_Define(&funcScope, pSym);
i = i + 1; i = i + 1;
} }