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:
@@ -239,6 +239,7 @@ type
|
|||||||
skComptime
|
skComptime
|
||||||
skEmit
|
skEmit
|
||||||
skDefer
|
skDefer
|
||||||
|
skSwitch
|
||||||
skDecl
|
skDecl
|
||||||
|
|
||||||
ElseIf* = object
|
ElseIf* = object
|
||||||
@@ -246,6 +247,11 @@ type
|
|||||||
cond*: Expr
|
cond*: Expr
|
||||||
blk*: Block
|
blk*: Block
|
||||||
|
|
||||||
|
SwitchCase* = object
|
||||||
|
loc*: SourceLocation
|
||||||
|
caseValue*: Expr
|
||||||
|
caseBody*: Block
|
||||||
|
|
||||||
Block* = ref object
|
Block* = ref object
|
||||||
loc*: SourceLocation
|
loc*: SourceLocation
|
||||||
stmts*: seq[Stmt]
|
stmts*: seq[Stmt]
|
||||||
@@ -301,6 +307,10 @@ type
|
|||||||
stmtEmitEvaluated*: string ## filled by sema CTFE
|
stmtEmitEvaluated*: string ## filled by sema CTFE
|
||||||
of skDefer:
|
of skDefer:
|
||||||
stmtDeferBody*: Expr
|
stmtDeferBody*: Expr
|
||||||
|
of skSwitch:
|
||||||
|
stmtSwitchExpr*: Expr
|
||||||
|
stmtSwitchCases*: seq[SwitchCase]
|
||||||
|
stmtSwitchDefault*: Block
|
||||||
of skDecl:
|
of skDecl:
|
||||||
stmtDecl*: Decl
|
stmtDecl*: Decl
|
||||||
|
|
||||||
|
|||||||
@@ -1127,6 +1127,24 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
|||||||
return ctx.flushPending(HirNode(kind: hMatch, matchSubject: subject, matchArms: arms,
|
return ctx.flushPending(HirNode(kind: hMatch, matchSubject: subject, matchArms: arms,
|
||||||
typ: makeVoid(), loc: loc))
|
typ: makeVoid(), loc: loc))
|
||||||
|
|
||||||
|
of skSwitch:
|
||||||
|
let subject = ctx.lowerExpr(stmt.stmtSwitchExpr)
|
||||||
|
var current: HirNode = nil
|
||||||
|
# Build if-else chain from bottom up (default first)
|
||||||
|
if stmt.stmtSwitchDefault != nil:
|
||||||
|
current = ctx.lowerBlock(stmt.stmtSwitchDefault)
|
||||||
|
# Cases in reverse order
|
||||||
|
for i in countdown(stmt.stmtSwitchCases.len - 1, 0):
|
||||||
|
let caseBranch = stmt.stmtSwitchCases[i]
|
||||||
|
let caseVal = ctx.lowerExpr(caseBranch.caseValue)
|
||||||
|
let caseBody = ctx.lowerBlock(caseBranch.caseBody)
|
||||||
|
let cond = HirNode(kind: hBinary, binaryOp: tkEq,
|
||||||
|
binaryLeft: subject, binaryRight: caseVal,
|
||||||
|
typ: makeBool(), loc: caseBranch.loc)
|
||||||
|
current = HirNode(kind: hIf, ifCond: cond, ifThen: caseBody, ifElse: current,
|
||||||
|
typ: makeVoid(), loc: caseBranch.loc)
|
||||||
|
return ctx.flushPending(current)
|
||||||
|
|
||||||
of skDefer:
|
of skDefer:
|
||||||
let body = ctx.lowerExpr(stmt.stmtDeferBody)
|
let body = ctx.lowerExpr(stmt.stmtDeferBody)
|
||||||
ctx.deferStmts.add(body)
|
ctx.deferStmts.add(body)
|
||||||
|
|||||||
@@ -901,6 +901,37 @@ proc parseStmt(p: var Parser): Stmt =
|
|||||||
discard p.advance()
|
discard p.advance()
|
||||||
discard p.expect(tkRBrace, "expected '}' to close match")
|
discard p.expect(tkRBrace, "expected '}' to close match")
|
||||||
return Stmt(kind: skExpr, loc: loc, stmtExpr: Expr(kind: ekMatch, loc: loc, exprMatchSubject: subject, exprMatchArms: arms))
|
return Stmt(kind: skExpr, loc: loc, stmtExpr: Expr(kind: ekMatch, loc: loc, exprMatchSubject: subject, exprMatchArms: arms))
|
||||||
|
of tkSwitch:
|
||||||
|
discard p.advance()
|
||||||
|
p.structInitAllowed = false
|
||||||
|
let subject = p.parseExpr()
|
||||||
|
p.structInitAllowed = true
|
||||||
|
while p.check(tkNewLine):
|
||||||
|
discard p.advance()
|
||||||
|
discard p.expect(tkLBrace, "expected '{' to start switch body")
|
||||||
|
var cases: seq[SwitchCase] = @[]
|
||||||
|
var defaultBlock: Block = nil
|
||||||
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
||||||
|
while p.check(tkNewLine):
|
||||||
|
discard p.advance()
|
||||||
|
if p.check(tkRBrace) or p.isAtEnd:
|
||||||
|
break
|
||||||
|
if p.check(tkDefault):
|
||||||
|
discard p.advance()
|
||||||
|
discard p.expect(tkColon, "expected ':' after default")
|
||||||
|
defaultBlock = Block(loc: p.currentLoc, stmts: @[p.parseStmt()])
|
||||||
|
continue
|
||||||
|
if p.check(tkCase):
|
||||||
|
discard p.advance()
|
||||||
|
let caseVal = p.parseExpr()
|
||||||
|
discard p.expect(tkColon, "expected ':' after case value")
|
||||||
|
let caseBody = Block(loc: p.currentLoc, stmts: @[p.parseStmt()])
|
||||||
|
cases.add(SwitchCase(loc: caseVal.loc, caseValue: caseVal, caseBody: caseBody))
|
||||||
|
continue
|
||||||
|
# Unknown token in switch body — skip
|
||||||
|
discard p.advance()
|
||||||
|
discard p.expect(tkRBrace, "expected '}' to close switch")
|
||||||
|
return Stmt(kind: skSwitch, loc: loc, stmtSwitchExpr: subject, stmtSwitchCases: cases, stmtSwitchDefault: defaultBlock)
|
||||||
of tkReturn:
|
of tkReturn:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
var val: Expr = nil
|
var val: Expr = nil
|
||||||
|
|||||||
@@ -1338,6 +1338,14 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
|
|||||||
of skDefer:
|
of skDefer:
|
||||||
discard sema.checkExpr(stmt.stmtDeferBody, scope)
|
discard sema.checkExpr(stmt.stmtDeferBody, scope)
|
||||||
return makeVoid()
|
return makeVoid()
|
||||||
|
of skSwitch:
|
||||||
|
discard sema.checkExpr(stmt.stmtSwitchExpr, scope)
|
||||||
|
for caseBranch in stmt.stmtSwitchCases:
|
||||||
|
discard sema.checkExpr(caseBranch.caseValue, scope)
|
||||||
|
discard sema.checkStmt(Stmt(kind: skExpr, loc: caseBranch.caseBody.loc, stmtExpr: Expr(kind: ekBlock, loc: caseBranch.caseBody.loc, exprBlock: caseBranch.caseBody)), scope)
|
||||||
|
if stmt.stmtSwitchDefault != nil:
|
||||||
|
discard sema.checkStmt(Stmt(kind: skExpr, loc: stmt.stmtSwitchDefault.loc, stmtExpr: Expr(kind: ekBlock, loc: stmt.stmtSwitchDefault.loc, exprBlock: stmt.stmtSwitchDefault)), scope)
|
||||||
|
return makeVoid()
|
||||||
of skDecl:
|
of skDecl:
|
||||||
# Local declaration inside block
|
# Local declaration inside block
|
||||||
case stmt.stmtDecl.kind
|
case stmt.stmtDecl.kind
|
||||||
|
|||||||
+10
-1
@@ -25,6 +25,9 @@ type
|
|||||||
tkContinue # continue
|
tkContinue # continue
|
||||||
tkReturn # return
|
tkReturn # return
|
||||||
tkMatch # match
|
tkMatch # match
|
||||||
|
tkSwitch # switch
|
||||||
|
tkCase # case
|
||||||
|
tkDefault # default
|
||||||
|
|
||||||
##Declaration keywords
|
##Declaration keywords
|
||||||
tkFunc # func
|
tkFunc # func
|
||||||
@@ -150,7 +153,7 @@ type
|
|||||||
proc isKeyword*(kind: TokenKind): bool =
|
proc isKeyword*(kind: TokenKind): bool =
|
||||||
case kind
|
case kind
|
||||||
of tkIf, tkElse, tkWhile, tkDo, tkLoop, tkFor, tkIn,
|
of tkIf, tkElse, tkWhile, tkDo, tkLoop, tkFor, tkIn,
|
||||||
tkBreak, tkContinue, tkReturn, tkMatch,
|
tkBreak, tkContinue, tkReturn, tkMatch, tkSwitch, tkCase, tkDefault,
|
||||||
tkFunc, tkLet, tkVar, tkConst, tkType, tkStruct, tkEnum,
|
tkFunc, tkLet, tkVar, tkConst, tkType, tkStruct, tkEnum,
|
||||||
tkUnion, tkInterface, tkExtend, tkModule, tkImport,
|
tkUnion, tkInterface, tkExtend, tkModule, tkImport,
|
||||||
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkMut, tkBorrow, tkDiscard, tkAsync, tkAwait, tkSpawn, tkStaticAssert, tkComptime, tkDyn, tkDefer:
|
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkMut, tkBorrow, tkDiscard, tkAsync, tkAwait, tkSpawn, tkStaticAssert, tkComptime, tkDyn, tkDefer:
|
||||||
@@ -201,6 +204,9 @@ proc keywordKind*(text: string): TokenKind =
|
|||||||
of "continue": tkContinue
|
of "continue": tkContinue
|
||||||
of "return": tkReturn
|
of "return": tkReturn
|
||||||
of "match": tkMatch
|
of "match": tkMatch
|
||||||
|
of "switch": tkSwitch
|
||||||
|
of "case": tkCase
|
||||||
|
of "default": tkDefault
|
||||||
of "as": tkAs
|
of "as": tkAs
|
||||||
of "is": tkIs
|
of "is": tkIs
|
||||||
of "null": tkNull
|
of "null": tkNull
|
||||||
@@ -242,6 +248,9 @@ proc tokenKindName*(kind: TokenKind): string =
|
|||||||
of tkContinue: "'continue'"
|
of tkContinue: "'continue'"
|
||||||
of tkReturn: "'return'"
|
of tkReturn: "'return'"
|
||||||
of tkMatch: "'match'"
|
of tkMatch: "'match'"
|
||||||
|
of tkSwitch: "'switch'"
|
||||||
|
of tkCase: "'case'"
|
||||||
|
of tkDefault: "'default'"
|
||||||
of tkFunc: "'func'"
|
of tkFunc: "'func'"
|
||||||
of tkLet: "'let'"
|
of tkLet: "'let'"
|
||||||
of tkVar: "'var'"
|
of tkVar: "'var'"
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ const skBreak: int = 9;
|
|||||||
const skContinue: int = 10;
|
const skContinue: int = 10;
|
||||||
const skDecl: int = 11;
|
const skDecl: int = 11;
|
||||||
const skDefer: int = 12;
|
const skDefer: int = 12;
|
||||||
|
const skSwitch: int = 13;
|
||||||
|
|
||||||
struct ElseIf {
|
struct ElseIf {
|
||||||
line: uint32;
|
line: uint32;
|
||||||
|
|||||||
@@ -491,6 +491,72 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
|
|||||||
return n;
|
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;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -302,6 +302,9 @@ func lexKeywordKind(text: String) -> int {
|
|||||||
if String_Eq(text, "sizeof") { return tkSizeOf; }
|
if String_Eq(text, "sizeof") { return tkSizeOf; }
|
||||||
if String_Eq(text, "discard") { return tkDiscard; }
|
if String_Eq(text, "discard") { return tkDiscard; }
|
||||||
if String_Eq(text, "defer") { return tkDefer; }
|
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, "async") { return tkAsync; }
|
||||||
if String_Eq(text, "await") { return tkAwait; }
|
if String_Eq(text, "await") { return tkAwait; }
|
||||||
if String_Eq(text, "spawn") { return tkSpawn; }
|
if String_Eq(text, "spawn") { return tkSpawn; }
|
||||||
|
|||||||
@@ -855,6 +855,79 @@ func parserParseStmt(p: *Parser) -> *Stmt {
|
|||||||
return s;
|
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
|
// Expression statement
|
||||||
if kind == tkNewLine || kind == tkSemicolon {
|
if kind == tkNewLine || kind == tkSemicolon {
|
||||||
discard parserAdvance(p);
|
discard parserAdvance(p);
|
||||||
|
|||||||
@@ -139,6 +139,9 @@ const tkAwait: int = 103;
|
|||||||
const tkSpawn: int = 104;
|
const tkSpawn: int = 104;
|
||||||
const tkDiscard: int = 105;
|
const tkDiscard: int = 105;
|
||||||
const tkDefer: int = 106;
|
const tkDefer: int = 106;
|
||||||
|
const tkSwitch: int = 107;
|
||||||
|
const tkCase: int = 108;
|
||||||
|
const tkDefault: int = 109;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Token struct
|
// Token struct
|
||||||
|
|||||||
Reference in New Issue
Block a user