Phase 7.10: buxc2 now compiles multi-statement Bux programs

Parser (src_bux/parser.bux):
- Block stores statements as linked list (firstStmt/lastStmt/nextStmt)
- Block struct: added firstStmt, lastStmt fields
- Stmt struct: added nextStmt field

HIR Lowering (src_bux/hir_lower.bux):
- Lcx_LowerBlock: iterates statement linked list, chains HirNodes via child3
- hIf: else block stored in extraData (child3 reserved for block chaining)

C Backend (src_bux/c_backend.bux):
- hBlock: emits statements via child3 linked list with proper indentation
- hStore: combines alloca + value into single declaration (int x = value;)
- hIf: full if/else emission with proper newlines
- Function decl: proper return types and parameter types (not just "int")
- int main() wrapper: generated when Main function exists
- No duplicate return 0 when body already has return

Tested:
- buxc2 compiles hello world program → runs correctly
- buxc2 compiles multi-statement program (let, if, function calls) → runs correctly
- All 18 examples still pass, all unit tests pass
This commit is contained in:
2026-05-31 17:25:03 +03:00
parent e8084b2840
commit 35b856bab4
8 changed files with 354 additions and 40 deletions
+4
View File
@@ -127,6 +127,8 @@ struct Block {
line: uint32,
column: uint32,
stmtCount: int,
firstStmt: *Stmt,
lastStmt: *Stmt,
}
// ---------------------------------------------------------------------------
@@ -170,6 +172,8 @@ struct Stmt {
refStmtElse: *Block, // else block
// Else-if chain
elseIfCount: int,
// Linked list
nextStmt: *Stmt,
}
// ---------------------------------------------------------------------------
+134 -15
View File
@@ -160,14 +160,58 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
return;
}
// Store
// Store: combine alloca + value into single declaration
if kind == hStore {
if node.child1 != null as *HirNode && node.child1.kind == hAlloca {
// Declaration with initializer: int x = value;
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(node.child1.intValue));
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, node.child1.strValue);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// Plain assignment
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// If
if kind == hIf {
StringBuilder_Append(&cbe.sb, "if (");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ") {\n");
if node.child2 != null as *HirNode {
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child2);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}");
let elseBlock: *HirNode = node.extraData as *HirNode;
if elseBlock != null as *HirNode {
StringBuilder_Append(&cbe.sb, " else {\n");
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, elseBlock);
cbe.indent = cbe.indent - 1;
sp = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}\n");
} else {
StringBuilder_Append(&cbe.sb, "\n");
}
return;
}
// Cast
if kind == hCast {
StringBuilder_Append(&cbe.sb, "((");
@@ -177,6 +221,27 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Block — emit each statement via child3 linked list
if kind == hBlock {
var child: *HirNode = node.child1;
while child != null as *HirNode {
// Indent
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
CBE_EmitExpr(cbe, child);
// Add semicolon for statements that need it
if child.kind != hBlock && child.kind != hIf && child.kind != hWhile && child.kind != hLoop {
StringBuilder_Append(&cbe.sb, ";");
}
StringBuilder_Append(&cbe.sb, "\n");
child = child.child3;
}
return;
}
}
// ---------------------------------------------------------------------------
@@ -187,8 +252,20 @@ func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
// Return type
if String_Eq(f.retTypeName, "") || String_Eq(f.retTypeName, "void") {
StringBuilder_Append(&cbe.sb, "void ");
} else if String_Eq(f.retTypeName, "String") || String_Eq(f.retTypeName, "str") {
StringBuilder_Append(&cbe.sb, "const char* ");
} else if String_Eq(f.retTypeName, "bool") {
StringBuilder_Append(&cbe.sb, "bool ");
} else if String_Eq(f.retTypeName, "int") {
StringBuilder_Append(&cbe.sb, "int ");
} else if String_Eq(f.retTypeName, "int64") {
StringBuilder_Append(&cbe.sb, "int64_t ");
} else if String_Eq(f.retTypeName, "uint") {
StringBuilder_Append(&cbe.sb, "unsigned int ");
} else if String_Eq(f.retTypeName, "float64") {
StringBuilder_Append(&cbe.sb, "double ");
} else {
StringBuilder_Append(&cbe.sb, "int "); // simplified
StringBuilder_Append(&cbe.sb, "int "); // fallback
}
StringBuilder_Append(&cbe.sb, f.name);
@@ -200,14 +277,31 @@ func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
if i > 0 {
StringBuilder_Append(&cbe.sb, ", ");
}
StringBuilder_Append(&cbe.sb, "int "); // simplified param type
// Use param type info
var pname: String = "";
if i == 0 { pname = f.param0.name; }
if i == 1 { pname = f.param1.name; }
if i == 2 { pname = f.param2.name; }
if i == 3 { pname = f.param3.name; }
if i == 4 { pname = f.param4.name; }
if i == 5 { pname = f.param5.name; }
var ptype: String = "";
if i == 0 { pname = f.param0.name; ptype = f.param0.typeName; }
if i == 1 { pname = f.param1.name; ptype = f.param1.typeName; }
if i == 2 { pname = f.param2.name; ptype = f.param2.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 == 5 { pname = f.param5.name; ptype = f.param5.typeName; }
// Emit type
if String_Eq(ptype, "String") || String_Eq(ptype, "str") {
StringBuilder_Append(&cbe.sb, "const char* ");
} else if String_Eq(ptype, "bool") {
StringBuilder_Append(&cbe.sb, "bool ");
} else if String_Eq(ptype, "int") {
StringBuilder_Append(&cbe.sb, "int ");
} else if String_Eq(ptype, "int64") {
StringBuilder_Append(&cbe.sb, "int64_t ");
} else if String_Eq(ptype, "uint") {
StringBuilder_Append(&cbe.sb, "unsigned int ");
} else if String_Eq(ptype, "float64") {
StringBuilder_Append(&cbe.sb, "double ");
} else {
StringBuilder_Append(&cbe.sb, "int "); // fallback
}
StringBuilder_Append(&cbe.sb, pname);
i = i + 1;
}
@@ -242,20 +336,45 @@ func CBackend_Generate(mod: *HirModule) -> String {
StringBuilder_Append(&cbe.sb, "\n");
// Function definitions
var hasMain: bool = false;
i = 0;
while i < mod.funcCount {
if String_Eq(mod.funcs[i].name, "Main") { hasMain = true; }
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
StringBuilder_Append(&cbe.sb, " {\n");
// Body (simplified)
if mod.funcs[i].body != null as *HirNode {
StringBuilder_Append(&cbe.sb, " ");
CBE_EmitExpr(cbe, mod.funcs[i].body);
StringBuilder_Append(&cbe.sb, ";\n");
// Body
var hasReturn: bool = false;
let body: *HirNode = mod.funcs[i].body;
if body != null as *HirNode {
cbe.indent = 1;
CBE_EmitExpr(cbe, body);
cbe.indent = 0;
// Check if body has a return statement
var stmt: *HirNode = body.child1;
while stmt != null as *HirNode {
if stmt.kind == hReturn { hasReturn = true; }
stmt = stmt.child3;
}
}
StringBuilder_Append(&cbe.sb, " return 0;\n}\n\n");
// Add default return 0 only if function returns int and has no explicit return
if String_Eq(mod.funcs[i].retTypeName, "int") && !hasReturn {
StringBuilder_Append(&cbe.sb, " return 0;\n");
}
StringBuilder_Append(&cbe.sb, "}\n\n");
i = i + 1;
}
// Generate C main wrapper if Main function exists
if hasMain {
StringBuilder_Append(&cbe.sb, "extern int g_argc;\n");
StringBuilder_Append(&cbe.sb, "extern char** g_argv;\n");
StringBuilder_Append(&cbe.sb, "int main(int argc, char** argv) {\n");
StringBuilder_Append(&cbe.sb, " g_argc = argc;\n");
StringBuilder_Append(&cbe.sb, " g_argv = argv;\n");
StringBuilder_Append(&cbe.sb, " return Main();\n");
StringBuilder_Append(&cbe.sb, "}\n");
}
return StringBuilder_Build(&cbe.sb);
}
+29 -5
View File
@@ -170,7 +170,8 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
n.child2 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
}
if stmt.refStmtElse != null as *Block {
n.child3 = Lcx_LowerBlock(ctx, stmt.refStmtElse, -1);
// Store else block in extraData (child3 is reserved for block chaining)
n.extraData = Lcx_LowerBlock(ctx, stmt.refStmtElse, -1) as *void;
}
return n;
}
@@ -203,15 +204,38 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode {
if block == null as *Block { return null as *HirNode; }
if block.stmtCount == 0 { return null as *HirNode; }
// For simplicity, create a block node with first statement
// Build a linked list of HirNodes via child3:
// node1 (stmt1) → child3 → node2 (stmt2) → child3 → node3 (stmt3) → null
// child3 is safe for chaining because:
// hStore: child1=alloca, child2=value, child3 unused
// hReturn: child1=value, child2/child3 unused
// hCall: child1=arg1, child2=arg2, child3 unused
var firstNode: *HirNode = null as *HirNode;
var prevNode: *HirNode = null as *HirNode;
var stmt: *Stmt = block.firstStmt;
while stmt != null as *Stmt {
let lowered: *HirNode = Lcx_LowerStmt(ctx, stmt);
if lowered != null as *HirNode {
if firstNode == null as *HirNode {
firstNode = lowered;
prevNode = lowered;
} else {
prevNode.child3 = lowered;
prevNode = lowered;
}
}
stmt = stmt.nextStmt;
}
// Wrap in an hBlock node with child1 = first statement in chain
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBlock;
n.line = block.line;
n.column = block.column;
n.boolValue = true; // isScope
// In a full implementation, lower all statements
n.boolValue = true;
n.child1 = firstNode;
return n;
}
+10
View File
@@ -583,6 +583,8 @@ func parserParseBlock(p: *Parser) -> *Block {
b.line = parserCurToken(p).line;
b.column = parserCurToken(p).column;
b.stmtCount = 0;
b.firstStmt = null as *Stmt;
b.lastStmt = null as *Stmt;
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
@@ -591,6 +593,14 @@ func parserParseBlock(p: *Parser) -> *Block {
}
let s: *Stmt = parserParseStmt(p);
if s != null as *Stmt {
s.nextStmt = null as *Stmt;
if b.firstStmt == null as *Stmt {
b.firstStmt = s;
b.lastStmt = s;
} else {
b.lastStmt.nextStmt = s;
b.lastStmt = s;
}
b.stmtCount = b.stmtCount + 1;
}
}
+4
View File
@@ -127,6 +127,8 @@ struct Block {
line: uint32,
column: uint32,
stmtCount: int,
firstStmt: *Stmt,
lastStmt: *Stmt,
}
// ---------------------------------------------------------------------------
@@ -170,6 +172,8 @@ struct Stmt {
refStmtElse: *Block, // else block
// Else-if chain
elseIfCount: int,
// Linked list
nextStmt: *Stmt,
}
// ---------------------------------------------------------------------------
+134 -15
View File
@@ -160,14 +160,58 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
return;
}
// Store
// Store: combine alloca + value into single declaration
if kind == hStore {
if node.child1 != null as *HirNode && node.child1.kind == hAlloca {
// Declaration with initializer: int x = value;
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(node.child1.intValue));
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, node.child1.strValue);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// Plain assignment
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// If
if kind == hIf {
StringBuilder_Append(&cbe.sb, "if (");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ") {\n");
if node.child2 != null as *HirNode {
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child2);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}");
let elseBlock: *HirNode = node.extraData as *HirNode;
if elseBlock != null as *HirNode {
StringBuilder_Append(&cbe.sb, " else {\n");
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, elseBlock);
cbe.indent = cbe.indent - 1;
sp = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}\n");
} else {
StringBuilder_Append(&cbe.sb, "\n");
}
return;
}
// Cast
if kind == hCast {
StringBuilder_Append(&cbe.sb, "((");
@@ -177,6 +221,27 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Block — emit each statement via child3 linked list
if kind == hBlock {
var child: *HirNode = node.child1;
while child != null as *HirNode {
// Indent
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
CBE_EmitExpr(cbe, child);
// Add semicolon for statements that need it
if child.kind != hBlock && child.kind != hIf && child.kind != hWhile && child.kind != hLoop {
StringBuilder_Append(&cbe.sb, ";");
}
StringBuilder_Append(&cbe.sb, "\n");
child = child.child3;
}
return;
}
}
// ---------------------------------------------------------------------------
@@ -187,8 +252,20 @@ func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
// Return type
if String_Eq(f.retTypeName, "") || String_Eq(f.retTypeName, "void") {
StringBuilder_Append(&cbe.sb, "void ");
} else if String_Eq(f.retTypeName, "String") || String_Eq(f.retTypeName, "str") {
StringBuilder_Append(&cbe.sb, "const char* ");
} else if String_Eq(f.retTypeName, "bool") {
StringBuilder_Append(&cbe.sb, "bool ");
} else if String_Eq(f.retTypeName, "int") {
StringBuilder_Append(&cbe.sb, "int ");
} else if String_Eq(f.retTypeName, "int64") {
StringBuilder_Append(&cbe.sb, "int64_t ");
} else if String_Eq(f.retTypeName, "uint") {
StringBuilder_Append(&cbe.sb, "unsigned int ");
} else if String_Eq(f.retTypeName, "float64") {
StringBuilder_Append(&cbe.sb, "double ");
} else {
StringBuilder_Append(&cbe.sb, "int "); // simplified
StringBuilder_Append(&cbe.sb, "int "); // fallback
}
StringBuilder_Append(&cbe.sb, f.name);
@@ -200,14 +277,31 @@ func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
if i > 0 {
StringBuilder_Append(&cbe.sb, ", ");
}
StringBuilder_Append(&cbe.sb, "int "); // simplified param type
// Use param type info
var pname: String = "";
if i == 0 { pname = f.param0.name; }
if i == 1 { pname = f.param1.name; }
if i == 2 { pname = f.param2.name; }
if i == 3 { pname = f.param3.name; }
if i == 4 { pname = f.param4.name; }
if i == 5 { pname = f.param5.name; }
var ptype: String = "";
if i == 0 { pname = f.param0.name; ptype = f.param0.typeName; }
if i == 1 { pname = f.param1.name; ptype = f.param1.typeName; }
if i == 2 { pname = f.param2.name; ptype = f.param2.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 == 5 { pname = f.param5.name; ptype = f.param5.typeName; }
// Emit type
if String_Eq(ptype, "String") || String_Eq(ptype, "str") {
StringBuilder_Append(&cbe.sb, "const char* ");
} else if String_Eq(ptype, "bool") {
StringBuilder_Append(&cbe.sb, "bool ");
} else if String_Eq(ptype, "int") {
StringBuilder_Append(&cbe.sb, "int ");
} else if String_Eq(ptype, "int64") {
StringBuilder_Append(&cbe.sb, "int64_t ");
} else if String_Eq(ptype, "uint") {
StringBuilder_Append(&cbe.sb, "unsigned int ");
} else if String_Eq(ptype, "float64") {
StringBuilder_Append(&cbe.sb, "double ");
} else {
StringBuilder_Append(&cbe.sb, "int "); // fallback
}
StringBuilder_Append(&cbe.sb, pname);
i = i + 1;
}
@@ -242,20 +336,45 @@ func CBackend_Generate(mod: *HirModule) -> String {
StringBuilder_Append(&cbe.sb, "\n");
// Function definitions
var hasMain: bool = false;
i = 0;
while i < mod.funcCount {
if String_Eq(mod.funcs[i].name, "Main") { hasMain = true; }
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
StringBuilder_Append(&cbe.sb, " {\n");
// Body (simplified)
if mod.funcs[i].body != null as *HirNode {
StringBuilder_Append(&cbe.sb, " ");
CBE_EmitExpr(cbe, mod.funcs[i].body);
StringBuilder_Append(&cbe.sb, ";\n");
// Body
var hasReturn: bool = false;
let body: *HirNode = mod.funcs[i].body;
if body != null as *HirNode {
cbe.indent = 1;
CBE_EmitExpr(cbe, body);
cbe.indent = 0;
// Check if body has a return statement
var stmt: *HirNode = body.child1;
while stmt != null as *HirNode {
if stmt.kind == hReturn { hasReturn = true; }
stmt = stmt.child3;
}
}
StringBuilder_Append(&cbe.sb, " return 0;\n}\n\n");
// Add default return 0 only if function returns int and has no explicit return
if String_Eq(mod.funcs[i].retTypeName, "int") && !hasReturn {
StringBuilder_Append(&cbe.sb, " return 0;\n");
}
StringBuilder_Append(&cbe.sb, "}\n\n");
i = i + 1;
}
// Generate C main wrapper if Main function exists
if hasMain {
StringBuilder_Append(&cbe.sb, "extern int g_argc;\n");
StringBuilder_Append(&cbe.sb, "extern char** g_argv;\n");
StringBuilder_Append(&cbe.sb, "int main(int argc, char** argv) {\n");
StringBuilder_Append(&cbe.sb, " g_argc = argc;\n");
StringBuilder_Append(&cbe.sb, " g_argv = argv;\n");
StringBuilder_Append(&cbe.sb, " return Main();\n");
StringBuilder_Append(&cbe.sb, "}\n");
}
return StringBuilder_Build(&cbe.sb);
}
+29 -5
View File
@@ -170,7 +170,8 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
n.child2 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
}
if stmt.refStmtElse != null as *Block {
n.child3 = Lcx_LowerBlock(ctx, stmt.refStmtElse, -1);
// Store else block in extraData (child3 is reserved for block chaining)
n.extraData = Lcx_LowerBlock(ctx, stmt.refStmtElse, -1) as *void;
}
return n;
}
@@ -203,15 +204,38 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode {
if block == null as *Block { return null as *HirNode; }
if block.stmtCount == 0 { return null as *HirNode; }
// For simplicity, create a block node with first statement
// Build a linked list of HirNodes via child3:
// node1 (stmt1) → child3 → node2 (stmt2) → child3 → node3 (stmt3) → null
// child3 is safe for chaining because:
// hStore: child1=alloca, child2=value, child3 unused
// hReturn: child1=value, child2/child3 unused
// hCall: child1=arg1, child2=arg2, child3 unused
var firstNode: *HirNode = null as *HirNode;
var prevNode: *HirNode = null as *HirNode;
var stmt: *Stmt = block.firstStmt;
while stmt != null as *Stmt {
let lowered: *HirNode = Lcx_LowerStmt(ctx, stmt);
if lowered != null as *HirNode {
if firstNode == null as *HirNode {
firstNode = lowered;
prevNode = lowered;
} else {
prevNode.child3 = lowered;
prevNode = lowered;
}
}
stmt = stmt.nextStmt;
}
// Wrap in an hBlock node with child1 = first statement in chain
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBlock;
n.line = block.line;
n.column = block.column;
n.boolValue = true; // isScope
// In a full implementation, lower all statements
n.boolValue = true;
n.child1 = firstNode;
return n;
}
+10
View File
@@ -583,6 +583,8 @@ func parserParseBlock(p: *Parser) -> *Block {
b.line = parserCurToken(p).line;
b.column = parserCurToken(p).column;
b.stmtCount = 0;
b.firstStmt = null as *Stmt;
b.lastStmt = null as *Stmt;
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
@@ -591,6 +593,14 @@ func parserParseBlock(p: *Parser) -> *Block {
}
let s: *Stmt = parserParseStmt(p);
if s != null as *Stmt {
s.nextStmt = null as *Stmt;
if b.firstStmt == null as *Stmt {
b.firstStmt = s;
b.lastStmt = s;
} else {
b.lastStmt.nextStmt = s;
b.lastStmt = s;
}
b.stmtCount = b.stmtCount + 1;
}
}