feat: struct init in all phases, 9/14 modules pass check

This commit is contained in:
2026-05-31 20:21:20 +03:00
parent a6d3fedf83
commit 48ee40e7c5
12 changed files with 308 additions and 13 deletions
+1
View File
@@ -90,6 +90,7 @@ const ekTuple: int = 17;
const ekCast: int = 18;
const ekIs: int = 19;
const ekTry: int = 20;
const ekUnwrap: int = 23;
const ekBlock: int = 21;
const ekMatch: int = 22;
+33 -2
View File
@@ -163,8 +163,12 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
// 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));
// Declaration with initializer: Type x = value;
if !String_Eq(node.child1.typeName, "") {
StringBuilder_Append(&cbe.sb, node.child1.typeName);
} else {
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(node.child1.intValue));
}
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, node.child1.strValue);
StringBuilder_Append(&cbe.sb, " = ");
@@ -222,6 +226,29 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
return;
}
// Struct init: ((TypeName){.field = value, ...})
if kind == hStructInit {
StringBuilder_Append(&cbe.sb, "((");
StringBuilder_Append(&cbe.sb, node.strValue);
StringBuilder_Append(&cbe.sb, "){");
// Emit fields (chained via child3)
var field: *HirNode = node.child1;
var first: bool = true;
while field != null as *HirNode {
if !first {
StringBuilder_Append(&cbe.sb, ", ");
}
StringBuilder_Append(&cbe.sb, ".");
StringBuilder_Append(&cbe.sb, field.strValue);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, field.child1);
first = false;
field = field.child3;
}
StringBuilder_Append(&cbe.sb, "})");
return;
}
// Block — emit each statement via child3 linked list
if kind == hBlock {
var child: *HirNode = node.child1;
@@ -264,6 +291,10 @@ func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
StringBuilder_Append(&cbe.sb, "unsigned int ");
} else if String_Eq(f.retTypeName, "float64") {
StringBuilder_Append(&cbe.sb, "double ");
} else if f.retTypeKind == tyNamed || String_StartsWith(f.retTypeName, "struct ") {
// Use the struct name directly (e.g., "Point", "Type")
StringBuilder_Append(&cbe.sb, f.retTypeName);
StringBuilder_Append(&cbe.sb, " ");
} else {
StringBuilder_Append(&cbe.sb, "int "); // fallback
}
+35
View File
@@ -113,6 +113,34 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
return n;
}
// Struct init: TypeName { field: value, ... }
if kind == ekStructInit {
n.kind = hStructInit;
n.strValue = expr.structName;
// Lower each field (fields are chained via child3 on synthetic ekField exprs)
var field: *Expr = expr.child1;
var firstField: *HirNode = null as *HirNode;
var lastField: *HirNode = null as *HirNode;
while field != null as *Expr {
let fNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fNode.kind = hBlock; // placeholder, field is identified by name+value
fNode.line = expr.line;
fNode.column = expr.column;
fNode.strValue = field.strValue; // field name
fNode.child1 = Lcx_LowerExpr(ctx, field.child1); // field value
if firstField == null as *HirNode {
firstField = fNode;
lastField = fNode;
} else {
lastField.child3 = fNode;
lastField = fNode;
}
field = field.child3;
}
n.child1 = firstField;
return n;
}
return n;
}
@@ -141,6 +169,11 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
alloca.line = line;
alloca.column = col;
alloca.strValue = stmt.strValue;
// Set type from the declared type expression
if stmt.refStmtType != null as *TypeExpr {
alloca.intValue = stmt.refStmtType.kind;
alloca.typeName = stmt.refStmtType.typeName;
}
// store the init value
n.kind = hStore;
n.child1 = alloca;
@@ -272,8 +305,10 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
if decl.retType != null as *TypeExpr {
f.retTypeName = decl.retType.typeName;
f.retTypeKind = decl.retType.kind;
} else {
f.retTypeName = "";
f.retTypeKind = 0;
}
if decl.refBody != null as *Block {
+64
View File
@@ -270,6 +270,10 @@ func parserParsePostfix(p: *Parser) -> *Expr {
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);
}
}
}
discard parserExpect(p, tkRParen, "expected ')'");
@@ -328,6 +332,17 @@ func parserParsePostfix(p: *Parser) -> *Expr {
continue;
}
// ! (unwrap operator)
if kind == tkBang {
discard parserAdvance(p);
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let e: *Expr = parserMakeExpr(ekUnwrap, line, col);
e.child1 = left;
left = e;
continue;
}
// ++, --
if kind == tkPlusPlus || kind == tkMinusMinus {
discard parserAdvance(p);
@@ -340,6 +355,47 @@ func parserParsePostfix(p: *Parser) -> *Expr {
continue;
}
// Struct init: TypeName { field: value, ... }
if kind == tkLBrace {
if p.structInitAllowed && left.kind == ekIdent {
discard parserAdvance(p); // consume {
let siLine: uint32 = parserCurToken(p).line;
let siCol: uint32 = parserCurToken(p).column;
let typeName: String = left.strValue;
var fieldCount: int = 0;
var firstField: *Expr = null as *Expr;
var lastField: *Expr = null as *Expr;
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile { break; }
let fName: LexToken = parserExpect(p, tkIdent, "expected field name");
discard parserExpect(p, tkColon, "expected ':'");
let fValue: *Expr = parserParseExpr(p);
let fExpr: *Expr = parserMakeExpr(ekField, fName.line, fName.column);
fExpr.strValue = fName.text;
fExpr.child1 = fValue;
if firstField == null as *Expr {
firstField = fExpr;
lastField = fExpr;
} else {
lastField.child3 = fExpr;
lastField = fExpr;
}
fieldCount = fieldCount + 1;
parserMatch(p, tkComma);
}
discard parserExpect(p, tkRBrace, "expected '}'");
let e: *Expr = parserMakeExpr(ekStructInit, siLine, siCol);
e.structName = typeName;
e.structFieldCount = fieldCount;
e.child1 = firstField;
left = e;
continue;
} else {
break;
}
}
break;
}
return left;
@@ -442,7 +498,9 @@ func parserParseStmt(p: *Parser) -> *Stmt {
// if
if kind == tkIf {
discard parserAdvance(p);
p.structInitAllowed = false;
let cond: *Expr = parserParseExpr(p);
p.structInitAllowed = true;
let thenBlock: *Block = parserParseBlock(p);
var elseBlock: *Block = null as *Block;
if parserMatch(p, tkElse) {
@@ -473,7 +531,9 @@ func parserParseStmt(p: *Parser) -> *Stmt {
// while
if kind == tkWhile {
discard parserAdvance(p);
p.structInitAllowed = false;
let cond: *Expr = parserParseExpr(p);
p.structInitAllowed = true;
let body: *Block = parserParseBlock(p);
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skWhile;
@@ -501,7 +561,9 @@ func parserParseStmt(p: *Parser) -> *Stmt {
discard parserAdvance(p);
let varName: LexToken = parserExpect(p, tkIdent, "expected loop variable");
discard parserExpect(p, tkIn, "expected 'in'");
p.structInitAllowed = false;
let iter: *Expr = parserParseExpr(p);
p.structInitAllowed = true;
let body: *Block = parserParseBlock(p);
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skFor;
@@ -545,7 +607,9 @@ func parserParseStmt(p: *Parser) -> *Stmt {
// match
if kind == tkMatch {
discard parserAdvance(p);
p.structInitAllowed = false;
let subject: *Expr = parserParseExpr(p);
p.structInitAllowed = true;
discard parserExpect(p, tkLBrace, "expected '{' to start match body");
// Skip match body (simplified — just parse arms as empty)
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
+5
View File
@@ -188,6 +188,11 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
return inner; // simplified
}
// Struct init: TypeName { field: value, ... }
if kind == ekStructInit {
return tyNamed;
}
return tyUnknown;
}