break and continue in loops
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user