feat: switch/case statement
- Add syntax - Desugars to if-else chain in HIR lowering (both compilers) - No new HIR/C backend nodes needed — reuses hIf/hBinary/hBlock - Supports integer, char, and enum tag dispatch - Case body can be single statement (no braces required)
This commit is contained in:
@@ -158,6 +158,7 @@ const skBreak: int = 9;
|
||||
const skContinue: int = 10;
|
||||
const skDecl: int = 11;
|
||||
const skDefer: int = 12;
|
||||
const skSwitch: int = 13;
|
||||
|
||||
struct ElseIf {
|
||||
line: uint32;
|
||||
|
||||
@@ -491,6 +491,72 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
|
||||
return n;
|
||||
}
|
||||
|
||||
// Switch — desugar to if-else chain
|
||||
if kind == skSwitch {
|
||||
let subject: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
|
||||
var current: *HirNode = null as *HirNode;
|
||||
// Default first (bottom of chain)
|
||||
if stmt.refStmtElse != null as *Block {
|
||||
current = Lcx_LowerBlock(ctx, stmt.refStmtElse, -1);
|
||||
}
|
||||
// Cases in reverse order (from caseBlock)
|
||||
if stmt.refStmtBlock != null as *Block {
|
||||
let caseBlock: *Block = stmt.refStmtBlock;
|
||||
var caseCount: int = caseBlock.stmtCount;
|
||||
// Collect cases into array for reverse iteration
|
||||
var c0: *Stmt = null as *Stmt;
|
||||
var c1: *Stmt = null as *Stmt;
|
||||
var c2: *Stmt = null as *Stmt;
|
||||
var c3: *Stmt = null as *Stmt;
|
||||
var c4: *Stmt = null as *Stmt;
|
||||
var c5: *Stmt = null as *Stmt;
|
||||
var c6: *Stmt = null as *Stmt;
|
||||
var c7: *Stmt = null as *Stmt;
|
||||
var ci: int = 0;
|
||||
var cs: *Stmt = caseBlock.firstStmt;
|
||||
while cs != null as *Stmt && ci < 8 {
|
||||
if ci == 0 { c0 = cs; }
|
||||
if ci == 1 { c1 = cs; }
|
||||
if ci == 2 { c2 = cs; }
|
||||
if ci == 3 { c3 = cs; }
|
||||
if ci == 4 { c4 = cs; }
|
||||
if ci == 5 { c5 = cs; }
|
||||
if ci == 6 { c6 = cs; }
|
||||
if ci == 7 { c7 = cs; }
|
||||
ci = ci + 1;
|
||||
cs = cs.nextStmt;
|
||||
}
|
||||
while caseCount > 0 {
|
||||
caseCount = caseCount - 1;
|
||||
var c: *Stmt = null as *Stmt;
|
||||
if caseCount == 0 { c = c0; }
|
||||
if caseCount == 1 { c = c1; }
|
||||
if caseCount == 2 { c = c2; }
|
||||
if caseCount == 3 { c = c3; }
|
||||
if caseCount == 4 { c = c4; }
|
||||
if caseCount == 5 { c = c5; }
|
||||
if caseCount == 6 { c = c6; }
|
||||
if caseCount == 7 { c = c7; }
|
||||
if c != null as *Stmt {
|
||||
let caseVal: *HirNode = Lcx_LowerExpr(ctx, c.child1);
|
||||
let caseBody: *HirNode = Lcx_LowerBlock(ctx, c.refStmtBlock, -1);
|
||||
let cond: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
cond.kind = hBinary;
|
||||
cond.intValue = 74; // tkEq
|
||||
cond.child1 = subject;
|
||||
cond.child2 = caseVal;
|
||||
let ifNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
ifNode.kind = hIf;
|
||||
ifNode.child1 = cond;
|
||||
ifNode.child2 = caseBody;
|
||||
ifNode.child3 = current;
|
||||
current = ifNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
@@ -302,6 +302,9 @@ func lexKeywordKind(text: String) -> int {
|
||||
if String_Eq(text, "sizeof") { return tkSizeOf; }
|
||||
if String_Eq(text, "discard") { return tkDiscard; }
|
||||
if String_Eq(text, "defer") { return tkDefer; }
|
||||
if String_Eq(text, "switch") { return tkSwitch; }
|
||||
if String_Eq(text, "case") { return tkCase; }
|
||||
if String_Eq(text, "default") { return tkDefault; }
|
||||
if String_Eq(text, "async") { return tkAsync; }
|
||||
if String_Eq(text, "await") { return tkAwait; }
|
||||
if String_Eq(text, "spawn") { return tkSpawn; }
|
||||
|
||||
@@ -855,6 +855,79 @@ func parserParseStmt(p: *Parser) -> *Stmt {
|
||||
return s;
|
||||
}
|
||||
|
||||
// switch
|
||||
if kind == tkSwitch {
|
||||
discard parserAdvance(p);
|
||||
p.structInitAllowed = false;
|
||||
let subject: *Expr = parserParseExpr(p);
|
||||
p.structInitAllowed = true;
|
||||
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
|
||||
discard parserExpect(p, tkLBrace, "expected '{' to start switch body");
|
||||
var caseBlock: *Block = bux_alloc(sizeof(Block)) as *Block;
|
||||
caseBlock.line = line;
|
||||
caseBlock.column = col;
|
||||
caseBlock.stmtCount = 0;
|
||||
caseBlock.firstStmt = null as *Stmt;
|
||||
caseBlock.lastStmt = null as *Stmt;
|
||||
var defaultBody: *Block = null as *Block;
|
||||
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
||||
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
|
||||
if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile { break; }
|
||||
if parserCheck(p, tkDefault) {
|
||||
discard parserAdvance(p);
|
||||
discard parserExpect(p, tkColon, "expected ':' after default");
|
||||
let defaultStmt: *Stmt = parserParseStmt(p);
|
||||
defaultBody = bux_alloc(sizeof(Block)) as *Block;
|
||||
defaultBody.line = defaultStmt.line;
|
||||
defaultBody.column = defaultStmt.column;
|
||||
defaultBody.stmtCount = 1;
|
||||
defaultBody.firstStmt = defaultStmt;
|
||||
defaultBody.lastStmt = defaultStmt;
|
||||
defaultStmt.nextStmt = null as *Stmt;
|
||||
continue;
|
||||
}
|
||||
if parserCheck(p, tkCase) {
|
||||
discard parserAdvance(p);
|
||||
let caseVal: *Expr = parserParseExpr(p);
|
||||
discard parserExpect(p, tkColon, "expected ':' after case value");
|
||||
let caseStmt: *Stmt = parserParseStmt(p);
|
||||
let caseBody: *Block = bux_alloc(sizeof(Block)) as *Block;
|
||||
caseBody.line = caseStmt.line;
|
||||
caseBody.column = caseStmt.column;
|
||||
caseBody.stmtCount = 1;
|
||||
caseBody.firstStmt = caseStmt;
|
||||
caseBody.lastStmt = caseStmt;
|
||||
caseStmt.nextStmt = null as *Stmt;
|
||||
let c: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
||||
c.kind = skExpr;
|
||||
c.line = caseVal.line;
|
||||
c.column = caseVal.column;
|
||||
c.child1 = caseVal;
|
||||
c.refStmtBlock = caseBody;
|
||||
c.nextStmt = null as *Stmt;
|
||||
if caseBlock.firstStmt == null as *Stmt {
|
||||
caseBlock.firstStmt = c;
|
||||
caseBlock.lastStmt = c;
|
||||
} else {
|
||||
caseBlock.lastStmt.nextStmt = c;
|
||||
caseBlock.lastStmt = c;
|
||||
}
|
||||
caseBlock.stmtCount = caseBlock.stmtCount + 1;
|
||||
continue;
|
||||
}
|
||||
discard parserAdvance(p);
|
||||
}
|
||||
discard parserExpect(p, tkRBrace, "expected '}' to close switch");
|
||||
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
||||
s.kind = skSwitch;
|
||||
s.line = line;
|
||||
s.column = col;
|
||||
s.child1 = subject;
|
||||
s.refStmtBlock = caseBlock;
|
||||
s.refStmtElse = defaultBody;
|
||||
return s;
|
||||
}
|
||||
|
||||
// Expression statement
|
||||
if kind == tkNewLine || kind == tkSemicolon {
|
||||
discard parserAdvance(p);
|
||||
|
||||
@@ -139,6 +139,9 @@ const tkAwait: int = 103;
|
||||
const tkSpawn: int = 104;
|
||||
const tkDiscard: int = 105;
|
||||
const tkDefer: int = 106;
|
||||
const tkSwitch: int = 107;
|
||||
const tkCase: int = 108;
|
||||
const tkDefault: int = 109;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Token struct
|
||||
|
||||
Reference in New Issue
Block a user