diff --git a/README.MD b/README.MD index a886fba..fe8fcd9 100644 --- a/README.MD +++ b/README.MD @@ -94,6 +94,15 @@ fn main() { for i in 0..5 { ... } // 0 1 2 3 4 (exclusive ..) 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 println(grid[1][0]) // 3 @@ -109,9 +118,11 @@ fn main() { `push(a, v)`; array literals may nest - 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 +- `break` / `continue` inside `while` and `for` loops (in `for` loops + `continue` advances the loop variable / iterator first) - operators: `+ - * / %`, `== != < <= > >=`, `and or not`, unary `-` -- statements: `let`, assignment, `if/else`, `while`, `for`, `return`, `assert`, - calls, `print(...)` / `println(...)` +- statements: `let`, assignment, `if/else`, `while`, `for`, `break`, + `continue`, `return`, `assert`, calls, `print(...)` / `println(...)` - comments: `//` ## Assembly diff --git a/compiler/compiler.v b/compiler/compiler.v index 1937b57..bfecf28 100644 --- a/compiler/compiler.v +++ b/compiler/compiler.v @@ -66,6 +66,14 @@ struct Fixup { 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 { mut: code []u8 @@ -79,6 +87,7 @@ mut: cur_fn string labels map[string]int fixups []Fixup + loops []LoopCtx enter_off u32 next_lbl int } @@ -189,9 +198,11 @@ fn (mut g Gen) gen_stmt(st Stmt) ! { g.code << op_jz g.code << obj.encode_i64(0) 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 { g.gen_stmt(s)! } + g.loops.delete_last() g.code << op_jmp g.code << obj.encode_i64(0) 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() bound_idx := g.new_local() loop_l := g.new_label() + inc_l := g.new_label() end_l := g.new_label() g.gen_expr(st.expr)! g.gen_expr(st.cond)! @@ -214,6 +226,7 @@ fn (mut g Gen) gen_stmt(st Stmt) ! { g.code << op_jz g.code << obj.encode_i64(0) 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 } g.locals[st.target] = var_idx for s in st.body { @@ -224,6 +237,8 @@ fn (mut g Gen) gen_stmt(st Stmt) ! { } else { g.locals.delete(st.target) } + g.loops.delete_last() + g.emit_label(inc_l) g.emit_load(var_idx) g.code << op_push_i g.code << obj.encode_i64(1) @@ -240,6 +255,7 @@ fn (mut g Gen) gen_stmt(st Stmt) ! { idx_idx := g.new_local() elem_idx := g.new_local() loop_l := g.new_label() + inc_l := g.new_label() end_l := g.new_label() g.gen_expr(st.expr)! g.emit_store(arr_idx) @@ -254,6 +270,7 @@ fn (mut g Gen) gen_stmt(st Stmt) ! { g.code << op_jz g.code << obj.encode_i64(0) 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(idx_idx) g.code << op_aget @@ -268,6 +285,8 @@ fn (mut g Gen) gen_stmt(st Stmt) ! { } else { g.locals.delete(st.target) } + g.loops.delete_last() + g.emit_label(inc_l) g.emit_load(idx_idx) g.code << op_push_i g.code << obj.encode_i64(1) @@ -290,6 +309,24 @@ fn (mut g Gen) gen_stmt(st Stmt) ! { g.gen_expr(st.expr)! 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 } + } } } diff --git a/compiler/lexer.v b/compiler/lexer.v index c8048c2..d0af92c 100644 --- a/compiler/lexer.v +++ b/compiler/lexer.v @@ -34,6 +34,8 @@ pub enum TokKind { kw_while kw_for kw_in + kw_break + kw_continue kw_return kw_true kw_false @@ -251,6 +253,8 @@ fn (mut l Lexer) lex_ident(line int) Tok { 'while' { TokKind.kw_while } 'for' { TokKind.kw_for } 'in' { TokKind.kw_in } + 'break' { TokKind.kw_break } + 'continue' { TokKind.kw_continue } 'return' { TokKind.kw_return } 'true' { TokKind.kw_true } 'false' { TokKind.kw_false } diff --git a/compiler/parser.v b/compiler/parser.v index 4d2b723..1b808b7 100644 --- a/compiler/parser.v +++ b/compiler/parser.v @@ -11,6 +11,7 @@ // | 'while' cond block // | 'for' IDENT 'in' range block (range := expr '..' expr | expr '...' expr) // | 'for' IDENT 'in' expr block (iterate an array) +// | 'break' | 'continue' // | 'return' [expr] // | 'assert' expr // | expr @@ -60,6 +61,8 @@ pub enum StmtKind { while_stmt for_range_stmt for_in_stmt + break_stmt + continue_stmt ret_stmt assert_stmt } @@ -230,6 +233,14 @@ fn (mut p Parser) parse_stmt() !Stmt { e = p.parse_expr()! } 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 { p.advance() mut e := Expr{} diff --git a/examples/arrays.vr b/examples/arrays.vr index 53f327b..d8598cc 100644 --- a/examples/arrays.vr +++ b/examples/arrays.vr @@ -65,6 +65,29 @@ fn main() { println("i is back to ") 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 len(squares) == 5 assert squares[4] == 16 diff --git a/examples/tests.vr b/examples/tests.vr index 7646e64..eee77e7 100644 --- a/examples/tests.vr +++ b/examples/tests.vr @@ -78,6 +78,53 @@ fn test_for_in() { 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() { // this one is meant to fail — shows up in `vr test` output assert 1 == 2