continue/break

This commit is contained in:
allexanderbergmns
2026-08-24 15:41:37 +02:00
parent 18b0b38490
commit 55a3ee0b18
6 changed files with 135 additions and 2 deletions
+13 -2
View File
@@ -94,6 +94,15 @@ fn main() {
for i in 0..5 { ... } // 0 1 2 3 4 (exclusive ..) for i in 0..5 { ... } // 0 1 2 3 4 (exclusive ..)
for i in 1...3 { ... } // 1 2 3 (inclusive ...) for i in 1...3 { ... } // 1 2 3 (inclusive ...)
for i in 0..10 {
if i == 2 {
continue // skip this iteration
}
if i == 5 {
break // leave the loop early
}
}
let grid = [[1, 2], [3, 4]] // nested arrays let grid = [[1, 2], [3, 4]] // nested arrays
println(grid[1][0]) // 3 println(grid[1][0]) // 3
@@ -109,9 +118,11 @@ fn main() {
`push(a, v)`; array literals may nest `push(a, v)`; array literals may nest
- for loops: `for x in arr { }` and ranges `for i in 0..10 { }` / - for loops: `for x in arr { }` and ranges `for i in 0..10 { }` /
`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
`continue` advances the loop variable / iterator first)
- operators: `+ - * / %`, `== != < <= > >=`, `and or not`, unary `-` - operators: `+ - * / %`, `== != < <= > >=`, `and or not`, unary `-`
- statements: `let`, assignment, `if/else`, `while`, `for`, `return`, `assert`, - statements: `let`, assignment, `if/else`, `while`, `for`, `break`,
calls, `print(...)` / `println(...)` `continue`, `return`, `assert`, calls, `print(...)` / `println(...)`
- comments: `//` - comments: `//`
## Assembly ## Assembly
+37
View File
@@ -66,6 +66,14 @@ struct Fixup {
off u32 off u32
} }
// LoopCtx records where `break` and `continue` should jump while generating
// the body of a loop. For `for` loops `continue` targets the increment, not
// the condition check, so the loop variable still advances.
struct LoopCtx {
break_l string
continue_l string
}
struct Gen { struct Gen {
mut: mut:
code []u8 code []u8
@@ -79,6 +87,7 @@ mut:
cur_fn string cur_fn string
labels map[string]int labels map[string]int
fixups []Fixup fixups []Fixup
loops []LoopCtx
enter_off u32 enter_off u32
next_lbl int next_lbl int
} }
@@ -189,9 +198,11 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
g.code << op_jz g.code << op_jz
g.code << obj.encode_i64(0) g.code << obj.encode_i64(0)
g.fixups << Fixup{ name: end_l, off: u32(g.code.len) - 8 } g.fixups << Fixup{ name: end_l, off: u32(g.code.len) - 8 }
g.loops << LoopCtx{ break_l: end_l, continue_l: loop_l }
for s in st.body { for s in st.body {
g.gen_stmt(s)! g.gen_stmt(s)!
} }
g.loops.delete_last()
g.code << op_jmp g.code << op_jmp
g.code << obj.encode_i64(0) g.code << obj.encode_i64(0)
g.fixups << Fixup{ name: loop_l, off: u32(g.code.len) - 8 } g.fixups << Fixup{ name: loop_l, off: u32(g.code.len) - 8 }
@@ -202,6 +213,7 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
var_idx := g.new_local() var_idx := g.new_local()
bound_idx := g.new_local() bound_idx := g.new_local()
loop_l := g.new_label() loop_l := g.new_label()
inc_l := g.new_label()
end_l := g.new_label() end_l := g.new_label()
g.gen_expr(st.expr)! g.gen_expr(st.expr)!
g.gen_expr(st.cond)! g.gen_expr(st.cond)!
@@ -214,6 +226,7 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
g.code << op_jz g.code << op_jz
g.code << obj.encode_i64(0) g.code << obj.encode_i64(0)
g.fixups << Fixup{ name: end_l, off: u32(g.code.len) - 8 } g.fixups << Fixup{ name: end_l, off: u32(g.code.len) - 8 }
g.loops << LoopCtx{ break_l: end_l, continue_l: inc_l }
prev := g.locals[st.target] or { -1 } prev := g.locals[st.target] or { -1 }
g.locals[st.target] = var_idx g.locals[st.target] = var_idx
for s in st.body { for s in st.body {
@@ -224,6 +237,8 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
} else { } else {
g.locals.delete(st.target) g.locals.delete(st.target)
} }
g.loops.delete_last()
g.emit_label(inc_l)
g.emit_load(var_idx) g.emit_load(var_idx)
g.code << op_push_i g.code << op_push_i
g.code << obj.encode_i64(1) g.code << obj.encode_i64(1)
@@ -240,6 +255,7 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
idx_idx := g.new_local() idx_idx := g.new_local()
elem_idx := g.new_local() elem_idx := g.new_local()
loop_l := g.new_label() loop_l := g.new_label()
inc_l := g.new_label()
end_l := g.new_label() end_l := g.new_label()
g.gen_expr(st.expr)! g.gen_expr(st.expr)!
g.emit_store(arr_idx) g.emit_store(arr_idx)
@@ -254,6 +270,7 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
g.code << op_jz g.code << op_jz
g.code << obj.encode_i64(0) g.code << obj.encode_i64(0)
g.fixups << Fixup{ name: end_l, off: u32(g.code.len) - 8 } g.fixups << Fixup{ name: end_l, off: u32(g.code.len) - 8 }
g.loops << LoopCtx{ break_l: end_l, continue_l: inc_l }
g.emit_load(arr_idx) g.emit_load(arr_idx)
g.emit_load(idx_idx) g.emit_load(idx_idx)
g.code << op_aget g.code << op_aget
@@ -268,6 +285,8 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
} else { } else {
g.locals.delete(st.target) g.locals.delete(st.target)
} }
g.loops.delete_last()
g.emit_label(inc_l)
g.emit_load(idx_idx) g.emit_load(idx_idx)
g.code << op_push_i g.code << op_push_i
g.code << obj.encode_i64(1) g.code << obj.encode_i64(1)
@@ -290,6 +309,24 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
g.gen_expr(st.expr)! g.gen_expr(st.expr)!
g.code << op_assert g.code << op_assert
} }
.break_stmt {
if g.loops.len == 0 {
return error('break outside of a loop (line ${st.line})')
}
ctx := g.loops[g.loops.len - 1]
g.code << op_jmp
g.code << obj.encode_i64(0)
g.fixups << Fixup{ name: ctx.break_l, off: u32(g.code.len) - 8 }
}
.continue_stmt {
if g.loops.len == 0 {
return error('continue outside of a loop (line ${st.line})')
}
ctx := g.loops[g.loops.len - 1]
g.code << op_jmp
g.code << obj.encode_i64(0)
g.fixups << Fixup{ name: ctx.continue_l, off: u32(g.code.len) - 8 }
}
} }
} }
+4
View File
@@ -34,6 +34,8 @@ pub enum TokKind {
kw_while kw_while
kw_for kw_for
kw_in kw_in
kw_break
kw_continue
kw_return kw_return
kw_true kw_true
kw_false kw_false
@@ -251,6 +253,8 @@ 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 }
'break' { TokKind.kw_break }
'continue' { TokKind.kw_continue }
'return' { TokKind.kw_return } 'return' { TokKind.kw_return }
'true' { TokKind.kw_true } 'true' { TokKind.kw_true }
'false' { TokKind.kw_false } 'false' { TokKind.kw_false }
+11
View File
@@ -11,6 +11,7 @@
// | '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)
// | 'break' | 'continue'
// | 'return' [expr] // | 'return' [expr]
// | 'assert' expr // | 'assert' expr
// | expr // | expr
@@ -60,6 +61,8 @@ pub enum StmtKind {
while_stmt while_stmt
for_range_stmt for_range_stmt
for_in_stmt for_in_stmt
break_stmt
continue_stmt
ret_stmt ret_stmt
assert_stmt assert_stmt
} }
@@ -230,6 +233,14 @@ fn (mut p Parser) parse_stmt() !Stmt {
e = p.parse_expr()! e = p.parse_expr()!
} }
return Stmt{ kind: .ret_stmt, expr: e, has_val: has_val, line: t.line } return Stmt{ kind: .ret_stmt, expr: e, has_val: has_val, line: t.line }
}
.kw_break {
p.advance()
return Stmt{ kind: .break_stmt, line: t.line }
}
.kw_continue {
p.advance()
return Stmt{ kind: .continue_stmt, line: t.line }
} .kw_assert { } .kw_assert {
p.advance() p.advance()
mut e := Expr{} mut e := Expr{}
+23
View File
@@ -65,6 +65,29 @@ fn main() {
println("i is back to ") println("i is back to ")
println(i) // 100 println(i) // 100
// break and continue
for i in 0..10 {
if i == 2 {
continue // skip 2
}
if i == 5 {
break // stop at 5
}
print(i)
print(" ")
}
println("")
// find the first even number in an array
let found = -1
for x in a {
if x % 2 == 0 {
found = x
break
}
}
println("first even in a: " + found) // 10
assert sum([1, 2, 3, 4]) == 10 assert sum([1, 2, 3, 4]) == 10
assert len(squares) == 5 assert len(squares) == 5
assert squares[4] == 16 assert squares[4] == 16
+47
View File
@@ -78,6 +78,53 @@ fn test_for_in() {
assert squares[3] == 9 assert squares[3] == 9
} }
fn test_break_continue() {
// break exits the loop early
let total = 0
for i in 0..100 {
if i == 5 {
break
}
total = total + i
}
assert total == 10 // 0+1+2+3+4
// continue skips to the next iteration
let sum = 0
for i in 0..6 {
if i == 3 {
continue
}
sum = sum + i
}
assert sum == 12 // 0+1+2+4+5 (3 skipped)
// break and continue in while loops
let n = 0
while true {
n = n + 1
if n == 3 {
continue
}
if n == 5 {
break
}
}
assert n == 5
// continue advances the iterator in for-in loops (no infinite loop)
let odds = []
for x in [1, 2, 3, 4, 5] {
if x % 2 == 0 {
continue
}
push(odds, x)
}
assert odds == odds
assert len(odds) == 3
assert odds[2] == 5
}
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