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:
@@ -1,8 +1,10 @@
|
||||
# Bux Programming Language
|
||||
|
||||
> **Status:** Self-hosting phase — `buxc2` compiles `.bux` → QBE SSA → native binary. Bootstrap (`buxc`, Nim) maintains backward compatibility via C transpiler.
|
||||

|
||||
|
||||
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 |
|
||||
| **Error Handling** | `Result<T,E>`, `Option<T>`, and the `?` operator |
|
||||
| **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 |
|
||||
| **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking |
|
||||
| **Async/Await** | `async func`, `spawn`, `.await` with stackful coroutines |
|
||||
@@ -189,7 +191,7 @@ bux/
|
||||
# Build bootstrap compiler (Nim → C)
|
||||
make build
|
||||
|
||||
# Build self-hosted compiler (Bux → QBE SSA → native)
|
||||
# Build self-hosted compiler (Bux → C → native)
|
||||
make selfhost
|
||||
|
||||
# Run all tests
|
||||
|
||||
+13
-1
@@ -96,6 +96,11 @@ const ekMatch: int = 22;
|
||||
const ekSpawn: int = 24;
|
||||
const ekAwait: int = 25;
|
||||
|
||||
struct ExprList {
|
||||
expr: *Expr,
|
||||
next: *ExprList,
|
||||
}
|
||||
|
||||
struct Expr {
|
||||
kind: int,
|
||||
line: uint32,
|
||||
@@ -121,6 +126,9 @@ struct Expr {
|
||||
// Struct init fields
|
||||
structName: String,
|
||||
structFieldCount: int,
|
||||
// Call arguments (linked list for multi-arg support)
|
||||
callArgs: *ExprList,
|
||||
callArgCount: int,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -241,6 +249,9 @@ struct Decl {
|
||||
param3: Param,
|
||||
param4: Param,
|
||||
param5: Param,
|
||||
param6: Param,
|
||||
param7: Param,
|
||||
param8: Param,
|
||||
retType: *TypeExpr,
|
||||
// Body
|
||||
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,
|
||||
refType: null as *TypeExpr, refBlock: null as *Block,
|
||||
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 {
|
||||
|
||||
@@ -134,12 +134,29 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
||||
if kind == hCall {
|
||||
StringBuilder_Append(&cbe.sb, node.strValue);
|
||||
StringBuilder_Append(&cbe.sb, "(");
|
||||
var needsComma: bool = false;
|
||||
if node.child1 != null as *HirNode {
|
||||
CBE_EmitExpr(cbe, node.child1);
|
||||
needsComma = true;
|
||||
}
|
||||
if node.child2 != null as *HirNode {
|
||||
StringBuilder_Append(&cbe.sb, ", ");
|
||||
if needsComma {
|
||||
StringBuilder_Append(&cbe.sb, ", ");
|
||||
}
|
||||
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, ")");
|
||||
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 == 4 { pname = f.param4.name; ptype = f.param4.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
|
||||
if String_Eq(ptype, "String") || String_Eq(ptype, "str") {
|
||||
StringBuilder_Append(&cbe.sb, "String ");
|
||||
@@ -474,6 +494,9 @@ func CBE_FuncHasGeneric(f: *HirFunc) -> bool {
|
||||
if pi == 3 { ptype = f.param3.typeName; }
|
||||
if pi == 4 { ptype = f.param4.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; }
|
||||
pi = pi + 1;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
Print("DEBUG: MergeFileInto ");
|
||||
PrintLine(path);
|
||||
if !FileExists(path) {
|
||||
Print("Error: stdlib file not found: ");
|
||||
PrintLine(path);
|
||||
return 0;
|
||||
}
|
||||
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);
|
||||
if Lexer_DiagCount(lex) > 0 { return 0; }
|
||||
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
|
||||
|
||||
@@ -34,6 +34,15 @@ const hMatch: int = 28;
|
||||
const hSpawn: int = 29;
|
||||
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
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -76,6 +85,9 @@ struct HirFunc {
|
||||
param3: HirParam;
|
||||
param4: HirParam;
|
||||
param5: HirParam;
|
||||
param6: HirParam;
|
||||
param7: HirParam;
|
||||
param8: HirParam;
|
||||
retTypeKind: int;
|
||||
retTypeName: String;
|
||||
body: *HirNode;
|
||||
|
||||
+70
-11
@@ -180,11 +180,34 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
||||
// Lower receiver as first argument
|
||||
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1);
|
||||
n.child1 = recv;
|
||||
// Lower remaining arguments
|
||||
if expr.child2 != null as *Expr {
|
||||
let extra: *HirNode = Lcx_LowerExpr(ctx, expr.child2);
|
||||
// Chain extra args after receiver via child2/child3
|
||||
n.child2 = extra;
|
||||
// Lower remaining arguments from linked list
|
||||
var arg: *ExprList = expr.callArgs;
|
||||
var argIdx: int = 0;
|
||||
while arg != null as *ExprList {
|
||||
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;
|
||||
}
|
||||
@@ -192,12 +215,36 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
||||
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
|
||||
n.strValue = expr.child1.strValue;
|
||||
}
|
||||
// Lower arguments
|
||||
if expr.child2 != null as *Expr {
|
||||
n.child1 = Lcx_LowerExpr(ctx, expr.child2);
|
||||
}
|
||||
if expr.child3 != null as *Expr {
|
||||
n.child2 = Lcx_LowerExpr(ctx, expr.child3);
|
||||
// Lower arguments from linked list
|
||||
var arg: *ExprList = expr.callArgs;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -359,6 +406,9 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
|
||||
sym.name = stmt.strValue;
|
||||
sym.typeKind = alloca.typeKind;
|
||||
sym.typeName = alloca.typeName;
|
||||
sym.isMutable = false;
|
||||
sym.isPublic = false;
|
||||
sym.decl = null as *Decl;
|
||||
discard Scope_Define(ctx.scope, sym);
|
||||
// store the init value
|
||||
n.kind = hStore;
|
||||
@@ -495,6 +545,9 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
|
||||
Lcx_LowerParam(&f.param3, &decl.param3);
|
||||
Lcx_LowerParam(&f.param4, &decl.param4);
|
||||
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 {
|
||||
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 == 4 { p = &decl.param4; }
|
||||
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 {
|
||||
var sym: Symbol;
|
||||
sym.kind = skVar;
|
||||
sym.name = p.name;
|
||||
sym.typeKind = Lcx_ResolveTypeKind(p.refParamType);
|
||||
sym.typeName = p.refParamType.typeName;
|
||||
sym.isMutable = false;
|
||||
sym.isPublic = false;
|
||||
sym.decl = null as *Decl;
|
||||
discard Scope_Define(ctx.scope, sym);
|
||||
}
|
||||
pi = pi + 1;
|
||||
|
||||
@@ -711,7 +711,7 @@ func Lexer_GetToken(lex: *Lexer, index: int) -> LexToken {
|
||||
if index >= 0 && index < lex.tokenCount {
|
||||
return lex.tokens[index];
|
||||
}
|
||||
var empty: LexToken;
|
||||
var empty: LexToken = LexToken { kind: tkEndOfFile, text: "", line: 0, column: 0 };
|
||||
return empty;
|
||||
}
|
||||
|
||||
|
||||
+34
-11
@@ -214,6 +214,8 @@ func parserMakeExpr(kind: int, line: uint32, col: uint32) -> *Expr {
|
||||
e.genericTypeArgCount = 0;
|
||||
e.structName = "";
|
||||
e.structFieldCount = 0;
|
||||
e.callArgs = null as *ExprList;
|
||||
e.callArgCount = 0;
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -337,18 +339,27 @@ func parserParsePostfix(p: *Parser) -> *Expr {
|
||||
let col: uint32 = parserCurToken(p).column;
|
||||
let e: *Expr = parserMakeExpr(ekCall, line, col);
|
||||
e.child1 = left;
|
||||
// Parse arguments (up to 8)
|
||||
// child2 = first arg, child3 = second arg, extra = rest
|
||||
if !parserCheck(p, tkRParen) {
|
||||
e.child2 = parserParseExpr(p);
|
||||
if parserMatch(p, tkComma) {
|
||||
e.child3 = parserParseExpr(p);
|
||||
// Consume any remaining arguments (we only store 2)
|
||||
while parserMatch(p, tkComma) {
|
||||
discard parserParseExpr(p);
|
||||
}
|
||||
// Parse all arguments into linked list
|
||||
var argCount: int = 0;
|
||||
var firstArg: *ExprList = null as *ExprList;
|
||||
var lastArg: *ExprList = null as *ExprList;
|
||||
while !parserCheck(p, tkRParen) {
|
||||
let argExpr: *Expr = parserParseExpr(p);
|
||||
let argNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
||||
argNode.expr = argExpr;
|
||||
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 ')'");
|
||||
left = e;
|
||||
continue;
|
||||
@@ -842,7 +853,7 @@ func parserParseParamList(p: *Parser) -> *Decl {
|
||||
|
||||
discard parserExpect(p, tkLParen, "expected '('");
|
||||
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");
|
||||
discard parserExpect(p, tkColon, "expected ':' in parameter");
|
||||
let ptype: *TypeExpr = parserParseType(p);
|
||||
@@ -865,6 +876,15 @@ func parserParseParamList(p: *Parser) -> *Decl {
|
||||
} else if d.paramCount == 5 {
|
||||
d.param5.name = nameTok.text;
|
||||
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;
|
||||
|
||||
@@ -916,6 +936,9 @@ func parserParseFuncDecl(p: *Parser, isPublic: bool, isExtern: bool, isAsync: bo
|
||||
d.param3 = params.param3;
|
||||
d.param4 = params.param4;
|
||||
d.param5 = params.param5;
|
||||
d.param6 = params.param6;
|
||||
d.param7 = params.param7;
|
||||
d.param8 = params.param8;
|
||||
|
||||
// Return type
|
||||
if parserMatch(p, tkArrow) {
|
||||
|
||||
@@ -72,7 +72,7 @@ func Scope_Lookup(scope: *Scope, name: String) -> Symbol {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ func Scope_LookupLocal(scope: *Scope, name: String) -> Symbol {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
+12
-5
@@ -231,11 +231,10 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
||||
// Call
|
||||
if kind == ekCall {
|
||||
discard Sema_CheckExpr(sema, expr.child1);
|
||||
if expr.child2 != null as *Expr {
|
||||
discard Sema_CheckExpr(sema, expr.child2);
|
||||
}
|
||||
if expr.child3 != null as *Expr {
|
||||
discard Sema_CheckExpr(sema, expr.child3);
|
||||
var arg: *ExprList = expr.callArgs;
|
||||
while arg != null as *ExprList {
|
||||
discard Sema_CheckExpr(sema, arg.expr);
|
||||
arg = arg.next;
|
||||
}
|
||||
// Try to resolve return type from function declaration
|
||||
if expr.child1.kind == ekIdent {
|
||||
@@ -635,6 +634,7 @@ func Sema_Analyze(mod: *Module) -> *Sema {
|
||||
// Add type params to scope
|
||||
if decl.typeParamCount >= 1 {
|
||||
var tpSym: Symbol;
|
||||
Sema_ZeroInitSymbol(&tpSym);
|
||||
tpSym.kind = skType;
|
||||
tpSym.name = decl.typeParam0;
|
||||
tpSym.typeKind = tyTypeParam;
|
||||
@@ -643,6 +643,7 @@ func Sema_Analyze(mod: *Module) -> *Sema {
|
||||
}
|
||||
if decl.typeParamCount >= 2 {
|
||||
var tpSym2: Symbol;
|
||||
Sema_ZeroInitSymbol(&tpSym2);
|
||||
tpSym2.kind = skType;
|
||||
tpSym2.name = decl.typeParam1;
|
||||
tpSym2.typeKind = tyTypeParam;
|
||||
@@ -660,6 +661,9 @@ func Sema_Analyze(mod: *Module) -> *Sema {
|
||||
else if i == 3 { p = &decl.param3; }
|
||||
else if i == 4 { p = &decl.param4; }
|
||||
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;
|
||||
Sema_ZeroInitSymbol(&pSym);
|
||||
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 == 4 { pSym.name = decl.param4.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);
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 215 KiB |
+14
-2
@@ -32,7 +32,13 @@ make build
|
||||
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
|
||||
```
|
||||
|
||||
### Inspecting Generated C
|
||||
### Inspecting Generated C (bootstrap)
|
||||
```bash
|
||||
./buxc build
|
||||
cat build/main.c
|
||||
```
|
||||
|
||||
### Inspecting Generated C (self-hosted)
|
||||
```bash
|
||||
cd _selfhost && ../buxc build
|
||||
cat build/main.c
|
||||
```
|
||||
|
||||
### Common Errors
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|
||||
+13
-1
@@ -96,6 +96,11 @@ const ekMatch: int = 22;
|
||||
const ekSpawn: int = 24;
|
||||
const ekAwait: int = 25;
|
||||
|
||||
struct ExprList {
|
||||
expr: *Expr,
|
||||
next: *ExprList,
|
||||
}
|
||||
|
||||
struct Expr {
|
||||
kind: int,
|
||||
line: uint32,
|
||||
@@ -121,6 +126,9 @@ struct Expr {
|
||||
// Struct init fields
|
||||
structName: String,
|
||||
structFieldCount: int,
|
||||
// Call arguments (linked list for multi-arg support)
|
||||
callArgs: *ExprList,
|
||||
callArgCount: int,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -241,6 +249,9 @@ struct Decl {
|
||||
param3: Param,
|
||||
param4: Param,
|
||||
param5: Param,
|
||||
param6: Param,
|
||||
param7: Param,
|
||||
param8: Param,
|
||||
retType: *TypeExpr,
|
||||
// Body
|
||||
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,
|
||||
refType: null as *TypeExpr, refBlock: null as *Block,
|
||||
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 {
|
||||
|
||||
+24
-1
@@ -134,12 +134,29 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
||||
if kind == hCall {
|
||||
StringBuilder_Append(&cbe.sb, node.strValue);
|
||||
StringBuilder_Append(&cbe.sb, "(");
|
||||
var needsComma: bool = false;
|
||||
if node.child1 != null as *HirNode {
|
||||
CBE_EmitExpr(cbe, node.child1);
|
||||
needsComma = true;
|
||||
}
|
||||
if node.child2 != null as *HirNode {
|
||||
StringBuilder_Append(&cbe.sb, ", ");
|
||||
if needsComma {
|
||||
StringBuilder_Append(&cbe.sb, ", ");
|
||||
}
|
||||
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, ")");
|
||||
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 == 4 { pname = f.param4.name; ptype = f.param4.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
|
||||
if String_Eq(ptype, "String") || String_Eq(ptype, "str") {
|
||||
StringBuilder_Append(&cbe.sb, "String ");
|
||||
@@ -474,6 +494,9 @@ func CBE_FuncHasGeneric(f: *HirFunc) -> bool {
|
||||
if pi == 3 { ptype = f.param3.typeName; }
|
||||
if pi == 4 { ptype = f.param4.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; }
|
||||
pi = pi + 1;
|
||||
}
|
||||
|
||||
+6
-1
@@ -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 {
|
||||
Print("DEBUG: MergeFileInto ");
|
||||
PrintLine(path);
|
||||
if !FileExists(path) {
|
||||
Print("Error: stdlib file not found: ");
|
||||
PrintLine(path);
|
||||
return 0;
|
||||
}
|
||||
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);
|
||||
if Lexer_DiagCount(lex) > 0 { return 0; }
|
||||
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
|
||||
|
||||
@@ -34,6 +34,15 @@ const hMatch: int = 28;
|
||||
const hSpawn: int = 29;
|
||||
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
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -76,6 +85,9 @@ struct HirFunc {
|
||||
param3: HirParam;
|
||||
param4: HirParam;
|
||||
param5: HirParam;
|
||||
param6: HirParam;
|
||||
param7: HirParam;
|
||||
param8: HirParam;
|
||||
retTypeKind: int;
|
||||
retTypeName: String;
|
||||
body: *HirNode;
|
||||
|
||||
+70
-11
@@ -180,11 +180,34 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
||||
// Lower receiver as first argument
|
||||
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1);
|
||||
n.child1 = recv;
|
||||
// Lower remaining arguments
|
||||
if expr.child2 != null as *Expr {
|
||||
let extra: *HirNode = Lcx_LowerExpr(ctx, expr.child2);
|
||||
// Chain extra args after receiver via child2/child3
|
||||
n.child2 = extra;
|
||||
// Lower remaining arguments from linked list
|
||||
var arg: *ExprList = expr.callArgs;
|
||||
var argIdx: int = 0;
|
||||
while arg != null as *ExprList {
|
||||
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;
|
||||
}
|
||||
@@ -192,12 +215,36 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
||||
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
|
||||
n.strValue = expr.child1.strValue;
|
||||
}
|
||||
// Lower arguments
|
||||
if expr.child2 != null as *Expr {
|
||||
n.child1 = Lcx_LowerExpr(ctx, expr.child2);
|
||||
}
|
||||
if expr.child3 != null as *Expr {
|
||||
n.child2 = Lcx_LowerExpr(ctx, expr.child3);
|
||||
// Lower arguments from linked list
|
||||
var arg: *ExprList = expr.callArgs;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -359,6 +406,9 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
|
||||
sym.name = stmt.strValue;
|
||||
sym.typeKind = alloca.typeKind;
|
||||
sym.typeName = alloca.typeName;
|
||||
sym.isMutable = false;
|
||||
sym.isPublic = false;
|
||||
sym.decl = null as *Decl;
|
||||
discard Scope_Define(ctx.scope, sym);
|
||||
// store the init value
|
||||
n.kind = hStore;
|
||||
@@ -495,6 +545,9 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
|
||||
Lcx_LowerParam(&f.param3, &decl.param3);
|
||||
Lcx_LowerParam(&f.param4, &decl.param4);
|
||||
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 {
|
||||
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 == 4 { p = &decl.param4; }
|
||||
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 {
|
||||
var sym: Symbol;
|
||||
sym.kind = skVar;
|
||||
sym.name = p.name;
|
||||
sym.typeKind = Lcx_ResolveTypeKind(p.refParamType);
|
||||
sym.typeName = p.refParamType.typeName;
|
||||
sym.isMutable = false;
|
||||
sym.isPublic = false;
|
||||
sym.decl = null as *Decl;
|
||||
discard Scope_Define(ctx.scope, sym);
|
||||
}
|
||||
pi = pi + 1;
|
||||
|
||||
+1
-1
@@ -711,7 +711,7 @@ func Lexer_GetToken(lex: *Lexer, index: int) -> LexToken {
|
||||
if index >= 0 && index < lex.tokenCount {
|
||||
return lex.tokens[index];
|
||||
}
|
||||
var empty: LexToken;
|
||||
var empty: LexToken = LexToken { kind: tkEndOfFile, text: "", line: 0, column: 0 };
|
||||
return empty;
|
||||
}
|
||||
|
||||
|
||||
+34
-11
@@ -214,6 +214,8 @@ func parserMakeExpr(kind: int, line: uint32, col: uint32) -> *Expr {
|
||||
e.genericTypeArgCount = 0;
|
||||
e.structName = "";
|
||||
e.structFieldCount = 0;
|
||||
e.callArgs = null as *ExprList;
|
||||
e.callArgCount = 0;
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -337,18 +339,27 @@ func parserParsePostfix(p: *Parser) -> *Expr {
|
||||
let col: uint32 = parserCurToken(p).column;
|
||||
let e: *Expr = parserMakeExpr(ekCall, line, col);
|
||||
e.child1 = left;
|
||||
// Parse arguments (up to 8)
|
||||
// child2 = first arg, child3 = second arg, extra = rest
|
||||
if !parserCheck(p, tkRParen) {
|
||||
e.child2 = parserParseExpr(p);
|
||||
if parserMatch(p, tkComma) {
|
||||
e.child3 = parserParseExpr(p);
|
||||
// Consume any remaining arguments (we only store 2)
|
||||
while parserMatch(p, tkComma) {
|
||||
discard parserParseExpr(p);
|
||||
}
|
||||
// Parse all arguments into linked list
|
||||
var argCount: int = 0;
|
||||
var firstArg: *ExprList = null as *ExprList;
|
||||
var lastArg: *ExprList = null as *ExprList;
|
||||
while !parserCheck(p, tkRParen) {
|
||||
let argExpr: *Expr = parserParseExpr(p);
|
||||
let argNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
||||
argNode.expr = argExpr;
|
||||
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 ')'");
|
||||
left = e;
|
||||
continue;
|
||||
@@ -842,7 +853,7 @@ func parserParseParamList(p: *Parser) -> *Decl {
|
||||
|
||||
discard parserExpect(p, tkLParen, "expected '('");
|
||||
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");
|
||||
discard parserExpect(p, tkColon, "expected ':' in parameter");
|
||||
let ptype: *TypeExpr = parserParseType(p);
|
||||
@@ -865,6 +876,15 @@ func parserParseParamList(p: *Parser) -> *Decl {
|
||||
} else if d.paramCount == 5 {
|
||||
d.param5.name = nameTok.text;
|
||||
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;
|
||||
|
||||
@@ -916,6 +936,9 @@ func parserParseFuncDecl(p: *Parser, isPublic: bool, isExtern: bool, isAsync: bo
|
||||
d.param3 = params.param3;
|
||||
d.param4 = params.param4;
|
||||
d.param5 = params.param5;
|
||||
d.param6 = params.param6;
|
||||
d.param7 = params.param7;
|
||||
d.param8 = params.param8;
|
||||
|
||||
// Return type
|
||||
if parserMatch(p, tkArrow) {
|
||||
|
||||
+2
-2
@@ -72,7 +72,7 @@ func Scope_Lookup(scope: *Scope, name: String) -> Symbol {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ func Scope_LookupLocal(scope: *Scope, name: String) -> Symbol {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
+12
-5
@@ -231,11 +231,10 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
||||
// Call
|
||||
if kind == ekCall {
|
||||
discard Sema_CheckExpr(sema, expr.child1);
|
||||
if expr.child2 != null as *Expr {
|
||||
discard Sema_CheckExpr(sema, expr.child2);
|
||||
}
|
||||
if expr.child3 != null as *Expr {
|
||||
discard Sema_CheckExpr(sema, expr.child3);
|
||||
var arg: *ExprList = expr.callArgs;
|
||||
while arg != null as *ExprList {
|
||||
discard Sema_CheckExpr(sema, arg.expr);
|
||||
arg = arg.next;
|
||||
}
|
||||
// Try to resolve return type from function declaration
|
||||
if expr.child1.kind == ekIdent {
|
||||
@@ -635,6 +634,7 @@ func Sema_Analyze(mod: *Module) -> *Sema {
|
||||
// Add type params to scope
|
||||
if decl.typeParamCount >= 1 {
|
||||
var tpSym: Symbol;
|
||||
Sema_ZeroInitSymbol(&tpSym);
|
||||
tpSym.kind = skType;
|
||||
tpSym.name = decl.typeParam0;
|
||||
tpSym.typeKind = tyTypeParam;
|
||||
@@ -643,6 +643,7 @@ func Sema_Analyze(mod: *Module) -> *Sema {
|
||||
}
|
||||
if decl.typeParamCount >= 2 {
|
||||
var tpSym2: Symbol;
|
||||
Sema_ZeroInitSymbol(&tpSym2);
|
||||
tpSym2.kind = skType;
|
||||
tpSym2.name = decl.typeParam1;
|
||||
tpSym2.typeKind = tyTypeParam;
|
||||
@@ -660,6 +661,9 @@ func Sema_Analyze(mod: *Module) -> *Sema {
|
||||
else if i == 3 { p = &decl.param3; }
|
||||
else if i == 4 { p = &decl.param4; }
|
||||
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;
|
||||
Sema_ZeroInitSymbol(&pSym);
|
||||
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 == 4 { pSym.name = decl.param4.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);
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user