continue N: the mirror of break N (coop patch) - #25
Conversation
`continue;` has meant "the innermost loop" since the core had loops, and
`break N;` has had a level since M2. The consumer (the ngen port of teko)
lowers a switch as a one-iteration `loop` whose arms leave it, so an arm that
wants the ENCLOSING loop's next round has no way to say it: `break` leaves the
switch and falls into the code after it, `continue` restarts the switch itself.
`continue N;` is that missing edge, counted the way `break N` counts.
The whole point is that nothing moves for a program that does not write a
level, and the parser is where that is decided: the level is STORED only when
it was written, so a plain `continue;` leaves nd_val at 0 -- the node the
pre-level compiler built, byte for byte -- and `dump_node` prints no `val=` for
it. The walker reads 0 as 1.
* src/parse.mc: the optional T_INT after `continue`, refused at 0 with
`continue expects a positive level` (the `break 0;` message, mirrored) at
the statement's own position.
* src/gen_walk.mc: `continue out of range` when the level is past the loop
depth, `lcont_at(nloops - lv)` in place of `lcont_at(nloops - 1)`.
`continue outside loop` is untouched and still comes first, because it says
the more useful thing when there is no loop at all.
Cost: 8 code lines in src/ (5 in parse.mc, 3 in gen_walk.mc).
Tests. tests/mc/094-continue-level.mc is the consumer's shape -- a
one-iteration inner loop used as a switch, `continue 2` from an arm -- and
tests/mc/095-continue-one.mc proves `continue 1;` is `continue;` and that a
level counts enclosing LOOPS and not enclosing blocks. Both are portable to all
five targets and are picked up by the `tests/mc/0[89]*` globs in
scripts/test-linux.sh and scripts/test-windows.sh and by scripts/check-mc.sh,
which also asserts that the frozen seed REFUSES them (`expected ; after
continue`) -- the reason they live in tests/mc/ and not in tests/.
tests/err/075-continue-zero.mc and 076-continue-range.mc are asserted with
their exact message in scripts/check-surface.sh, with the DEFAULT compiler:
the feature is core, not taught.
Two modules read a jump's level to decide how many scopes to release, and both
tested for N_BREAK before reading nd_val -- so a `continue N` would have
released one loop's worth of scopes instead of N. Both now read the value for
either jump and clamp it, which is inert for every source that writes no level
(0 clamps to 1, exactly what the old code hardcoded): examples/lang/lang_stmt.mc
and examples/conc/conc_stmt.mc.
Docs: the grammar and the two new diagnostics in docs/reference/language.md and
docs/reference/diagnostics.md, the `continue;` line in docs/core-language.md and
docs/guide/10-single-file.md (including what the level means inside a prelude
`for`, whose step it skips for the same reason a bare `continue` does), and
docs/reference/hooks.md § on_jump -- which is where the 0 matters to somebody
else: the hook sees the N_CONTINUE before any level check, and a handler
reading its level must read 0 as 1.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The bundle carries src/parse.mc and src/gen_walk.mc, so every golden moves --
once for the code and once for the blob (tests/golden/README.md). Each was
rewritten only after its own criterion:
* mc2.sha256 after the empty `--dump-asm` diff between build/mc1 and build/mc2
and `cmp build/mc2.o build/mc3.o` (1108184 bytes).
* the Linux pair deleted and re-recorded by `make check-linux-host`, RC 0 over
all four cells (musl and gnu on each architecture), each after its own fixed
point `mc2l.o == mc3l.o` and with the cross proof -- the Mach-O object the
Linux-hosted compiler writes is byte for byte the one macOS writes -- green.
* the Windows pair cross-computed per tests/golden/README.md, and produced
byte for byte by build/mc2 as well as build/mc1.
CLAUDE.md § State gains the entry, and its `- Next:` paragraph is rewritten: it
still named M40, which has landed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Approval recommended
The parsing, lowering, tests, scripts, examples, and docs are consistent with the intended semantics and preserve bare-continue inertness while adding clear coverage for both success and failure cases.
Pull request overview
Adds core-language support for continue N; as the level-counted mirror of break N;, enabling control-flow patterns (notably a switch lowered as a one-iteration loop) to restart an outer loop without introducing labels. The implementation keeps the “inertness” property for existing programs by preserving the legacy AST shape for a bare continue; while extending lowering to honor explicit levels.
Changes:
- Parse optional integer levels after
continue, storing a level only when written (barecontinue;keepsnd_val = 0). - Update lowering to treat
nd_val == 0as level 1, validate level range, and jump to the correct enclosing loop’s continue label. - Add tests, script coverage, example-module fixes (level-reading), and documentation updates reflecting the new syntax/diagnostics.
File summaries
| File | Description |
|---|---|
src/parse.mc |
Parses continue [INT] ; and stores the level only when present, preserving bare-continue AST shape. |
src/gen_walk.mc |
Interprets nd_val==0 as level 1, validates level, and jumps to lcont_at(nloops - lv). |
tests/mc/094-continue-level.mc |
New positive test exercising continue 2 to restart an outer loop from a one-iteration inner loop. |
tests/mc/095-continue-one.mc |
New test proving continue; == continue 1; and that levels count loops (not blocks). |
tests/err/075-continue-zero.mc |
New parser-level error test for continue 0;. |
tests/err/076-continue-range.mc |
New lowering-time error test for out-of-range continue N;. |
scripts/check-surface.sh |
Extends err_case to support specifying the compiler and asserts the two new core errors with the default compiler. |
scripts/check-mc.sh |
Asserts the frozen seed refuses the new tests/mc/094 and 095 sources. |
scripts/test-linux.sh |
Ensures tests/mc/0[89]*.mc commentary covers the new continue-level tests. |
scripts/test-windows.sh |
Same as Linux script: comment updates covering the new continue-level tests. |
examples/lang/lang_stmt.mc |
Fixes jump-level reading to apply to both break and continue (clamping 0→1). |
examples/conc/conc_stmt.mc |
Same fix for the concurrency example’s on_jump logic (clamping 0→1). |
docs/reference/language.md |
Updates grammar and semantics for continue [INT] ; and documents new diagnostics. |
docs/reference/diagnostics.md |
Adds/updates diagnostic rows for continue expects a positive level and continue out of range. |
docs/reference/hooks.md |
Documents nd_val encoding for continue levels (bare continue stored as 0) for on_jump consumers. |
docs/reference/objects.md |
Updates gen_loop description to mention continue N. |
docs/guide/10-single-file.md |
Updates introductory control-flow text for continue N. |
docs/core-language.md |
Updates core-language summary to include continue N and related for lowering note. |
tests/golden/mc2.sha256 |
Updates golden hash for the new compiler build artifact. |
tests/golden/mc2-linux-arm64.sha256 |
Updates Linux/arm64 golden hash. |
tests/golden/mc2-linux-x86_64.sha256 |
Updates Linux/x86_64 golden hash. |
tests/golden/mc2-windows-arm64.sha256 |
Updates Windows/arm64 golden hash. |
tests/golden/mc2-windows-x86_64.sha256 |
Updates Windows/x86_64 golden hash. |
Review details
- Files reviewed: 24/25 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
….0.1 Copilot on #27: three goldens carried the bare hash where scripts/bootstrap.sh and scripts/bootstrap-windows.sh record 'hash file' (the format main has had since #25); the values do not move. The mathx-2.0.1 fixture's header comment said 1.2.1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
What
continue N;, the mirror ofbreak N;.continue;iscontinue 1;,continue N;restarts theN-th enclosing loop counted the way
break Ncounts,continue 0;iscontinue expects a positive leveland a level past the loop depth iscontinue out of range.continue outside loopis untouched.Cost: 23 added lines in
src/, 11 of them neither comment nor blank (src/parse.mc+15/7,src/gen_walk.mc+8/4, one of those four being the changedlcont_at(nloops - lv)).stage0/untouched, 2848/3000 (git diff origin/main -- stage0/is empty).Why
The coop consumer (the
ngenport of teko) lowers a switch as a one-iterationloopwhosearms leave it. An arm that wants the enclosing loop's next round has no way to say so today:
breakleaves the switch and falls into the code after it, andcontinuerestarts the switchitself.
continue Nis that missing edge, and it is the shapebreak Nalready established --a count, no labels.
The inertness rule, and where it is decided
The level is stored only when it was written. A plain
continue;leavesnd_valat 0 -- thenode the pre-level compiler built, byte for byte -- and
dump_nodeprintsval=only when it isnon-zero, so
--dump-astfor a barecontinueis what the frozen seed prints. The walker reads0 as 1.
break;can default to 1 in the parser and does;continue;cannot, because 0 is what"absent" has to mean on a node the seed also builds.
scripts/check-inert.sh build/mc1.pre build/mc1, withprebuilt fromorigin/main:check-objis 32/32 against the frozen seed and the--dump-asmdiff betweenmc1andmc2isempty.
Two example modules that read a jump's level
examples/lang/lang_stmt.mcandexamples/conc/conc_stmt.mcdecide how many scopes (resp. locks)to release from the jump's level, and both tested for
N_BREAKbefore readingnd_val-- so acontinue Nwould have released one loop's worth instead of N. Both now read the value for eitherjump and clamp it, which is inert for every source that writes no level (0 clamps to 1,
exactly what the old code hardcoded);
check-inertabove is the proof, since it rebuilds bothexamples through the taught compiler each compiler produces.
Tests
tests/mc/094-continue-level.mc-- the consumer's shape: a one-iteration inner loop used as aswitch,
continue 2from an arm, with the outer loop advancing and the statement after theswitch skipped.
tests/mc/095-continue-one.mc--continue 1;iscontinue;(two halves of the samefunction printing the same number) and a level counts enclosing loops, not enclosing
blocks:
continue 3from inside twoifblocks.tests/mc/0[89]*globs inscripts/test-linux.shandscripts/test-windows.shand byscripts/check-mc.sh, which alsoasserts that the frozen seed refuses them (
expected ; after continue) -- the reason theylive in
tests/mc/and not intests/.tests/err/075-continue-zero.mcand076-continue-range.mc, asserted with their exact messagein
scripts/check-surface.shwith the default compiler (the feature is core, not taught,so
err_casegained an optional third argument).Measured on five targets: macOS/arm64 (
check-mc15/15 = 11 tests + 4 seed refusals),linux/aarch64 and linux/x86_64 (object mode 41/41 and 38/38,
--exemode 44/44 and 41/41 on musland on glibc), windows/aarch64 and windows/x86_64 (42/42 and 40/40 objects cross-compiled and
linked with
lld-link).Full check
make checkRC 0, zero FAIL:test32/32,check-lex/check-ast/check-asm135/135(2 skipped),
check-obj32/32 identical to the frozen seed,check-bundle,bootstrapat afixed point (
mc2.o == mc3.o, 1108184 B),check-surface32/32 + the two new rows,test-exe32/32,
check-mc15/15,check-standalone,check-parts,check-toml,check-build53/53,check-stubs9/9,check-limits17/17 under 90%,check-minimal,test-linux,test-linux-exe(musl + gnu, both arches),test-windows42/42,test-windows-x86_6440/40,check-examples,check-lang,check-conc,check-desktop,check-float,check-wide,check-kernel,check-avr,check-sandbox55 ok,check-docs(196 symbols, 33 flags, 20 TOMLkeys, 10 directives, 51 samples, 320 links),
site+check-site.make check-linux-hostRC 0 over all four cells, each after its own fixed point and with thecross proof green.
Goldens
Rewritten once, each only after its own criterion (the bundle carries
parse.mcandgen_walk.mc, so all five move):mc2.sha256897b18875ff43f3baee95036db3651269ed2dba4c764185a12882b43c5fcdf7dmc2-linux-arm64.sha2563544cfff8f7fa37710ccd65e76d952d4e3dfdbbc753706e301d02f83a6199e31mc2-linux-x86_64.sha2560888bb6627ca2e778e54522794337bc6c0ec842decbb07f156fa3976ee41cef2mc2-windows-arm64.sha256b4b7bbc873f4a28865492c44a9992e09d279191bddb3f348c5a8fb78523b44f0mc2-windows-x86_64.sha2567fe8f0832ebe7cc3a037d76e142c20435e015fe109edf2de51bb01cb51923e06The Linux pair was deleted and re-recorded by
make check-linux-host; the Windows pair wascross-computed per
tests/golden/README.mdand is also written byte for byte bybuild/mc2.Docs
docs/reference/language.md§ 3 (grammar + the three messages),docs/reference/diagnostics.md(two rows),
docs/core-language.md(including what a level means inside a preludefor-- itskips the outer step, measured, not assumed),
docs/guide/10-single-file.md,docs/reference/objects.md, anddocs/reference/hooks.md§on_jump, which is where the 0matters to somebody else: the hook sees the
N_CONTINUEbefore any level check, so a handlerreading its level must read 0 as 1 and may see a level the function's loop depth does not support.
Patch, not a milestone: no release label.
🤖 Generated with Claude Code