break and continue in loops
This commit is contained in:
@@ -16,10 +16,19 @@ State :: struct {
|
||||
func_locals: []hir.Local,
|
||||
func_result: types.Type,
|
||||
expr_stack: [dynamic]Lower_Expr_Frame,
|
||||
// Innermost-last stack of enclosing loop targets for `break`/`continue`.
|
||||
loops: [dynamic]Loop_Ctx,
|
||||
next_label: i64,
|
||||
allocator: mem.Allocator,
|
||||
}
|
||||
|
||||
// `break` branches to `exit_lbl`; `continue` branches to `continue_lbl` (the
|
||||
// loop's update/latch, which runs the update clause then re-tests the condition).
|
||||
Loop_Ctx :: struct {
|
||||
exit_lbl: i64,
|
||||
continue_lbl: i64,
|
||||
}
|
||||
|
||||
fresh_label :: proc(state: ^State) -> i64 {
|
||||
id := state.next_label
|
||||
state.next_label += 1
|
||||
@@ -742,6 +751,18 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
case .Break, .Continue:
|
||||
// The checker guarantees these only appear inside a loop, so the
|
||||
// stack is non-empty; guard defensively regardless.
|
||||
if len(state.loops) > 0 {
|
||||
target := state.loops[len(state.loops)-1]
|
||||
label := target.exit_lbl if statement.kind == .Break else target.continue_lbl
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Br, span=statement.span, type=types.VOID, integer=label,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
case .Expression, .Sink:
|
||||
_ = lower_expr(state, statement.expr)
|
||||
case .Trap:
|
||||
@@ -919,7 +940,9 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append(&state.loops, Loop_Ctx{exit_lbl=exit_lbl, continue_lbl=update_lbl})
|
||||
lower_statements(state, statement.then_body)
|
||||
pop(&state.loops)
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Br, span=statement.span, type=types.VOID, integer=update_lbl,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
@@ -1043,7 +1066,25 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
// `continue` rejoins the normal end-of-iteration path (via a fresh
|
||||
// label before the bounds/overflow guard) rather than jumping straight
|
||||
// to the increment, so it behaves exactly like falling off the body —
|
||||
// e.g. `for 0..=255 |b: u8| { ... continue }` exits cleanly instead of
|
||||
// overflowing the increment on the final element.
|
||||
continue_lbl := fresh_label(state)
|
||||
append(&state.loops, Loop_Ctx{exit_lbl=exit_lbl, continue_lbl=continue_lbl})
|
||||
lower_statements(state, statement.then_body)
|
||||
pop(&state.loops)
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Br, span=statement.span, type=types.VOID, integer=continue_lbl,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Label, span=statement.span, type=types.VOID, integer=continue_lbl,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
after_body := append_instruction(state, ir.Instruction{
|
||||
op=.Load, span=statement.span, type=child,
|
||||
target=ir.INVALID_REF, a=current_slot, b=ir.INVALID_INSTRUCTION,
|
||||
@@ -1216,7 +1257,9 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||
target=ir.INVALID_REF, a=capture_slot, b=captured,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append(&state.loops, Loop_Ctx{exit_lbl=exit_lbl, continue_lbl=update_lbl})
|
||||
lower_statements(state, statement.then_body)
|
||||
pop(&state.loops)
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Br, span=statement.span, type=types.VOID, integer=update_lbl,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
@@ -1267,10 +1310,12 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
|
||||
}
|
||||
state.instructions.allocator = allocator
|
||||
state.expr_stack.allocator = allocator
|
||||
state.loops.allocator = allocator
|
||||
defer {
|
||||
delete(state.local_values, allocator)
|
||||
delete(state.local_slots, allocator)
|
||||
delete(state.expr_stack)
|
||||
delete(state.loops)
|
||||
}
|
||||
for _, index in state.local_values {
|
||||
state.local_values[index] = ir.INVALID_INSTRUCTION
|
||||
|
||||
Reference in New Issue
Block a user