break and continue in loops

This commit is contained in:
2026-06-26 23:43:57 +02:00
parent f926bbc605
commit 81ae939bf0
11 changed files with 314 additions and 12 deletions
+57 -4
View File
@@ -269,13 +269,34 @@
- `++` concatenation (the spec's "mixing" examples) is a separate, unimplemented
operator and is out of scope here
18. add `defer` statement (inspired by zig)
18. add `break` and `continue` statements (implemented)
- `break` exits the innermost enclosing loop; `continue` skips to that loop's next
iteration (running the `while` update / `for` index increment first). Both target
the innermost loop only (no labeled break) and carry no value
- new `Keyword_Break`/`Keyword_Continue` tokens; `Break`/`Continue` AST and HIR
statement kinds (no fields beyond kind/span); parsed by `parse_loop_control`
- the checker tracks loop nesting (`Build_Ctx.loop_depth`, bumped around loop-body
builds) and rejects `break`/`continue` outside a loop; `all_paths_return` no longer
treats a `while true` whose body can `break` as non-terminating (so a non-void
function that breaks out without returning is correctly diagnosed)
- lowering keeps an innermost-last loop-target stack (`State.loops`): `break` branches
to the loop's exit label, `continue` to its update/latch label. The range-for routes
`continue` through the end-of-iteration bounds/overflow guard, so
`for 0..=255 |b: u8|` exits cleanly instead of overflowing the increment
- the LLVM emitter opens a fresh recovery block after any terminator (not just `ret`),
so dead code following a `break`/`continue` branch stays well-formed
19. unions and tagged unions
19. add `defer` statement (inspired by zig)
- now unblocked: `defer` reuses the loop-target stack and loop tracking added in
milestone 18 to flush deferred statements on `break`/`continue` exits too
20. match statements with tagged unions payload unwrapping
20. add `yield` statement (see below)
21. dynamic heap allocation
21. unions and tagged unions
22. match statements with tagged unions payload unwrapping
23. dynamic heap allocation
- see below for direction
- notes below are too big in scope for a first pass and the language is not mature enough to support it yet
- this first pass should focus on just basic heap allocation, so we have something to work with
@@ -478,6 +499,38 @@ message = "Header:\t" ++
++ "Footer"
```
## A word on `yield`
The `yield` keyword provides a value from a block to its enclosing expression and **exits the block immediately** — just as `return` exits a function, `yield` exits the enclosing scope. Code after a `yield` is unreachable, and the compiler flags it. This makes `yield` part of a consistent set of scope-exiting control flow: `return` exits a function, `yield` exits a block, `break` exits a loop, and `continue` skips to the next iteration.
It is used in scoped blocks, match arms, and catch handlers.
**General rule:** When a block needs to produce a value, single expressions yield implicitly while multi-statement blocks require explicit `yield`. This rule applies uniformly across the language:
```
# scoped block
data :: {
result := compute()
yield result
}
# match arms
label []u8 = match p {
.high: "HIGH", # single expression: implicit yield
.low: {
log("low priority")
yield "LOW" # block: explicit yield
},
}
# catch handlers
data []u8 = read(path) catch default_data # single expression: implicit
data []u8 = read(path) catch |e| {
log(e)
yield fallback_data # block: explicit yield
}
```
## A word on memory allocation
(NOTE THAT SYNTAX MAY NOT MATCH BROLANG EXACTLY AND SHOULD BE TAKEN WITH A GRAIN OF SALT - INSPIRATION ONLY)