Else if chains

This commit is contained in:
allexanderbergmns
2026-08-24 15:44:27 +02:00
parent 55a3ee0b18
commit fc0ff47459
6 changed files with 257 additions and 10 deletions
+25 -2
View File
@@ -109,6 +109,26 @@ fn main() {
let i = 100 let i = 100
for i in 0..3 { ... } // loop vars are scoped to the loop for i in 0..3 { ... } // loop vars are scoped to the loop
println(i) // 100 println(i) // 100
if score >= 90 { // else-if chains
grade = "A"
} else if score >= 80 {
grade = "B"
} else {
grade = "F"
}
match day { // match on any comparable value
"sat" {
println("weekend")
}
"sun" {
println("weekend")
}
else { // optional fallback arm
println("workday")
}
}
} }
``` ```
@@ -120,9 +140,12 @@ fn main() {
`for i in 0...10 { }`; loop variables are scoped to the loop body `for i in 0...10 { }`; loop variables are scoped to the loop body
- `break` / `continue` inside `while` and `for` loops (in `for` loops - `break` / `continue` inside `while` and `for` loops (in `for` loops
`continue` advances the loop variable / iterator first) `continue` advances the loop variable / iterator first)
- else-if chains: `if a { } else if b { } else { }`
- `match`: `match expr { v1 { } v2 { } else { } }` — arms test equality on
any comparable value (ints, strings, ...); the `else` arm is optional
- operators: `+ - * / %`, `== != < <= > >=`, `and or not`, unary `-` - operators: `+ - * / %`, `== != < <= > >=`, `and or not`, unary `-`
- statements: `let`, assignment, `if/else`, `while`, `for`, `break`, - statements: `let`, assignment, `if/else`, `match`, `while`, `for`,
`continue`, `return`, `assert`, calls, `print(...)` / `println(...)` `break`, `continue`, `return`, `assert`, calls, `print(...)` / `println(...)`
- comments: `//` - comments: `//`
## Assembly ## Assembly
+34
View File
@@ -190,6 +190,40 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
} }
g.emit_label(end_l) g.emit_label(end_l)
} }
.match_stmt {
// match x { v1 {..} v2 {..} else {..} } → subject := x; a chain of
// equality tests jumping to the matching arm; else falls through.
subj_idx := g.new_local()
end_l := g.new_label()
g.gen_expr(st.expr)!
g.emit_store(subj_idx)
for i, arm in st.arms {
next_l := g.new_label()
g.emit_load(subj_idx)
g.gen_expr(arm.val)!
g.code << op_eq
g.code << op_jz
g.code << obj.encode_i64(0)
g.fixups << Fixup{ name: next_l, off: u32(g.code.len) - 8 }
for s in arm.body {
g.gen_stmt(s)!
}
g.code << op_jmp
g.code << obj.encode_i64(0)
g.fixups << Fixup{ name: end_l, off: u32(g.code.len) - 8 }
g.emit_label(next_l)
if i == st.arms.len - 1 && !st.has_else {
// no else: fall through to the end label
g.emit_label(end_l)
}
}
if st.has_else {
for s in st.els_body {
g.gen_stmt(s)!
}
g.emit_label(end_l)
}
}
.while_stmt { .while_stmt {
loop_l := g.new_label() loop_l := g.new_label()
end_l := g.new_label() end_l := g.new_label()
+2
View File
@@ -34,6 +34,7 @@ pub enum TokKind {
kw_while kw_while
kw_for kw_for
kw_in kw_in
kw_match
kw_break kw_break
kw_continue kw_continue
kw_return kw_return
@@ -253,6 +254,7 @@ fn (mut l Lexer) lex_ident(line int) Tok {
'while' { TokKind.kw_while } 'while' { TokKind.kw_while }
'for' { TokKind.kw_for } 'for' { TokKind.kw_for }
'in' { TokKind.kw_in } 'in' { TokKind.kw_in }
'match' { TokKind.kw_match }
'break' { TokKind.kw_break } 'break' { TokKind.kw_break }
'continue' { TokKind.kw_continue } 'continue' { TokKind.kw_continue }
'return' { TokKind.kw_return } 'return' { TokKind.kw_return }
+55 -8
View File
@@ -7,7 +7,8 @@
// stmt := 'let' IDENT '=' expr // stmt := 'let' IDENT '=' expr
// | IDENT '=' expr // | IDENT '=' expr
// | postfix '=' expr (a[i] = v) // | postfix '=' expr (a[i] = v)
// | 'if' cond block ['else' block] // | 'if' cond block ['else' ('if' ... | block)]
// | 'match' expr '{' (expr block | 'else' block)* '}'
// | 'while' cond block // | 'while' cond block
// | 'for' IDENT 'in' range block (range := expr '..' expr | expr '...' expr) // | 'for' IDENT 'in' range block (range := expr '..' expr | expr '...' expr)
// | 'for' IDENT 'in' expr block (iterate an array) // | 'for' IDENT 'in' expr block (iterate an array)
@@ -58,6 +59,7 @@ pub enum StmtKind {
assign_stmt assign_stmt
index_assign index_assign
if_stmt if_stmt
match_stmt
while_stmt while_stmt
for_range_stmt for_range_stmt
for_in_stmt for_in_stmt
@@ -67,6 +69,13 @@ pub enum StmtKind {
assert_stmt assert_stmt
} }
// MatchArm is a single `value { body }` arm of a match statement.
pub struct MatchArm {
pub mut:
val Expr
body []Stmt
}
pub struct Stmt { pub struct Stmt {
pub mut: pub mut:
kind StmtKind kind StmtKind
@@ -77,6 +86,9 @@ pub mut:
idx Expr // index_assign: the index expression idx Expr // index_assign: the index expression
body []Stmt body []Stmt
els []Stmt els []Stmt
arms []MatchArm // match_stmt: the arms (val + body)
has_else bool // match_stmt: a trailing else arm exists
els_body []Stmt // match_stmt: body of the else arm
has_val bool has_val bool
inclusive bool // for_range_stmt: `..` (false) vs `...` (true) inclusive bool // for_range_stmt: `..` (false) vs `...` (true)
line int line int
@@ -194,15 +206,31 @@ fn (mut p Parser) parse_stmt() !Stmt {
e := p.parse_expr()! e := p.parse_expr()!
return Stmt{ kind: .let_stmt, target: name.lit, expr: e, line: t.line } return Stmt{ kind: .let_stmt, target: name.lit, expr: e, line: t.line }
} .kw_if { } .kw_if {
return p.parse_if(t)!
}
.kw_match {
p.advance() p.advance()
cond := p.parse_cond()! subject := p.parse_expr()!
body := p.parse_block()! p.expect(.lbrace, "'{'")!
mut els := []Stmt{} mut arms := []MatchArm{}
if p.cur().kind == .kw_else { mut has_else := false
p.advance() mut els_body := []Stmt{}
els = p.parse_block()! for p.cur().kind != .rbrace {
if p.cur().kind == .eof {
return error('unexpected end of file inside match (missing "}")')
}
if p.cur().kind == .kw_else {
p.advance()
els_body = p.parse_block()!
has_else = true
continue
}
val := p.parse_expr()!
body := p.parse_block()!
arms << MatchArm{ val: val, body: body }
} }
return Stmt{ kind: .if_stmt, cond: cond, body: body, els: els, line: t.line } p.expect(.rbrace, "'}'")!
return Stmt{ kind: .match_stmt, expr: subject, arms: arms, has_else: has_else, els_body: els_body, line: t.line }
} }
.kw_while { .kw_while {
p.advance() p.advance()
@@ -284,6 +312,25 @@ fn (mut p Parser) parse_stmt() !Stmt {
} }
} }
// parse_if parses `if cond block ['else' ('if' ... | block)]`. An `else if`
// chain is represented by putting the nested if-statement in the else list,
// so codegen needs no special casing.
fn (mut p Parser) parse_if(t Tok) !Stmt {
p.advance()
cond := p.parse_cond()!
body := p.parse_block()!
mut els := []Stmt{}
if p.cur().kind == .kw_else {
p.advance()
if p.cur().kind == .kw_if {
els << p.parse_if(p.cur())!
} else {
els = p.parse_block()!
}
}
return Stmt{ kind: .if_stmt, cond: cond, body: body, els: els, line: t.line }
}
// bin_node allocates a binary-operator node. It takes copies of the operands // bin_node allocates a binary-operator node. It takes copies of the operands
// so that `&l`/`&r` target fresh heap objects (taking the address of a local // so that `&l`/`&r` target fresh heap objects (taking the address of a local
// that is later reassigned would create a self-referential node). // that is later reassigned would create a self-referential node).
+81
View File
@@ -0,0 +1,81 @@
// match.vr — else-if chains and match statements.
// vr run examples/match.vr
// classify returns a label using an else-if chain.
fn classify(n) {
if n < 0 {
return "negative"
} else if n == 0 {
return "zero"
} else if n < 10 {
return "small"
} else {
return "big"
}
}
// describe names a number with a match statement.
fn describe(n) {
match n {
0 {
println("nothing")
}
1 {
println("lonely")
}
2 {
println("a pair")
}
7 {
println("lucky")
}
else {
println("just " + n)
}
}
}
fn main() {
// else-if chain
println(classify(-5))
println(classify(0))
println(classify(7))
println(classify(100))
// match with else
describe(0)
describe(1)
describe(2)
describe(7)
describe(42)
// match on strings
let day = "fri"
match day {
"sat" {
println("weekend!")
}
"sun" {
println("weekend!")
}
else {
println("workday")
}
}
// match inside a loop (break/continue still work)
let found = 0
for i in 0..10 {
match i {
4 {
found = i
break
}
else {
// keep looking
}
}
}
println("found " + found)
assert found == 4
}
+60
View File
@@ -125,6 +125,66 @@ fn test_break_continue() {
assert odds[2] == 5 assert odds[2] == 5
} }
fn test_else_if() {
let score = 85
let grade = ""
if score >= 90 {
grade = "A"
} else if score >= 80 {
grade = "B"
} else if score >= 70 {
grade = "C"
} else {
grade = "F"
}
assert grade == "B"
assert 10 > 5
}
fn test_match() {
let total = 0
match 2 {
1 {
total = total + 1
}
2 {
total = total + 10
}
else {
total = total + 100
}
}
assert total == 10 // matched the 2 arm
// match on strings
let word = "cat"
let sound = ""
match word {
"dog" {
sound = "woof"
}
"cat" {
sound = "meow"
}
else {
sound = "?"
}
}
assert sound == "meow"
// no else arm: nothing runs when nothing matches
let hit = 0
match 99 {
1 {
hit = 1
}
2 {
hit = 2
}
}
assert hit == 0
}
fn test_failing() { fn test_failing() {
// this one is meant to fail — shows up in `vr test` output // this one is meant to fail — shows up in `vr test` output
assert 1 == 2 assert 1 == 2