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:
2026-06-08 21:08:55 +03:00
parent a67271b08c
commit d9aceeac6e
10 changed files with 223 additions and 1 deletions
+10
View File
@@ -239,6 +239,7 @@ type
skComptime
skEmit
skDefer
skSwitch
skDecl
ElseIf* = object
@@ -246,6 +247,11 @@ type
cond*: Expr
blk*: Block
SwitchCase* = object
loc*: SourceLocation
caseValue*: Expr
caseBody*: Block
Block* = ref object
loc*: SourceLocation
stmts*: seq[Stmt]
@@ -301,6 +307,10 @@ type
stmtEmitEvaluated*: string ## filled by sema CTFE
of skDefer:
stmtDeferBody*: Expr
of skSwitch:
stmtSwitchExpr*: Expr
stmtSwitchCases*: seq[SwitchCase]
stmtSwitchDefault*: Block
of skDecl:
stmtDecl*: Decl
+18
View File
@@ -1127,6 +1127,24 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
return ctx.flushPending(HirNode(kind: hMatch, matchSubject: subject, matchArms: arms,
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:
let body = ctx.lowerExpr(stmt.stmtDeferBody)
ctx.deferStmts.add(body)
+31
View File
@@ -901,6 +901,37 @@ proc parseStmt(p: var Parser): Stmt =
discard p.advance()
discard p.expect(tkRBrace, "expected '}' to close match")
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:
discard p.advance()
var val: Expr = nil
+8
View File
@@ -1338,6 +1338,14 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
of skDefer:
discard sema.checkExpr(stmt.stmtDeferBody, scope)
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:
# Local declaration inside block
case stmt.stmtDecl.kind
+10 -1
View File
@@ -25,6 +25,9 @@ type
tkContinue # continue
tkReturn # return
tkMatch # match
tkSwitch # switch
tkCase # case
tkDefault # default
##Declaration keywords
tkFunc # func
@@ -150,7 +153,7 @@ type
proc isKeyword*(kind: TokenKind): bool =
case kind
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,
tkUnion, tkInterface, tkExtend, tkModule, tkImport,
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 "return": tkReturn
of "match": tkMatch
of "switch": tkSwitch
of "case": tkCase
of "default": tkDefault
of "as": tkAs
of "is": tkIs
of "null": tkNull
@@ -242,6 +248,9 @@ proc tokenKindName*(kind: TokenKind): string =
of tkContinue: "'continue'"
of tkReturn: "'return'"
of tkMatch: "'match'"
of tkSwitch: "'switch'"
of tkCase: "'case'"
of tkDefault: "'default'"
of tkFunc: "'func'"
of tkLet: "'let'"
of tkVar: "'var'"