Skip to content

continue N: the mirror of break N (coop patch) - #25

Merged
schivei merged 2 commits into
mainfrom
continue-n
Sep 5, 2026
Merged

continue N: the mirror of break N (coop patch)#25
schivei merged 2 commits into
mainfrom
continue-n

Conversation

@schivei

@schivei schivei commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

What

continue N;, the mirror of break N;. continue; is continue 1;, continue N; restarts the
N-th enclosing loop counted the way break N counts, continue 0; is continue expects a positive level and a level past the loop depth is continue out of range. continue outside loop is 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 changed lcont_at(nloops - lv)).
stage0/ untouched, 2848/3000 (git diff origin/main -- stage0/ is empty).

Why

The coop consumer (the ngen port of teko) lowers a switch as a one-iteration loop whose
arms leave it. An arm that wants the enclosing loop's next round has no way to say so today:
break leaves the switch and falls into the code after it, and continue restarts the switch
itself. continue N is that missing edge, and it is the shape break N already 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; leaves nd_val at 0 -- the
node the pre-level compiler built, byte for byte -- and dump_node prints val= only when it is
non-zero, so --dump-ast for a bare continue is what the frozen seed prints. The walker reads
0 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, with pre built from origin/main:

ok   33 objects identical (tests/*.mc and src/mc.mc)
ok   taught examples/api -> build/api
ok   taught examples/lang -> build/lang-demo
ok   taught examples/conc -> build/conc-demo
ok   taught examples/desktop -> build/desktop-ui
ok   taught examples/kernel -> build/kernel.bin
check-inert: everything identical

check-obj is 32/32 against the frozen seed and the --dump-asm diff between mc1 and mc2 is
empty.

Two example modules that read a jump's level

examples/lang/lang_stmt.mc and examples/conc/conc_stmt.mc decide how many scopes (resp. locks)
to release from the jump's level, and both tested for N_BREAK before reading nd_val -- so a
continue N would have released one loop's worth 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); check-inert above is the proof, since it rebuilds both
examples 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 a
    switch, continue 2 from an arm, with the outer loop advancing and the statement after the
    switch skipped.
  • tests/mc/095-continue-one.mc -- continue 1; is continue; (two halves of the same
    function printing the same number) and a level counts enclosing loops, not enclosing
    blocks: continue 3 from inside two if 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, asserted with their exact message
    in scripts/check-surface.sh with the default compiler (the feature is core, not taught,
    so err_case gained an optional third argument).

Measured on five targets: macOS/arm64 (check-mc 15/15 = 11 tests + 4 seed refusals),
linux/aarch64 and linux/x86_64 (object mode 41/41 and 38/38, --exe mode 44/44 and 41/41 on musl
and on glibc), windows/aarch64 and windows/x86_64 (42/42 and 40/40 objects cross-compiled and
linked with lld-link).

Full check

make check RC 0, zero FAIL: test 32/32, check-lex/check-ast/check-asm 135/135
(2 skipped), check-obj 32/32 identical to the frozen seed, check-bundle, bootstrap at a
fixed point (mc2.o == mc3.o, 1108184 B), check-surface 32/32 + the two new rows, test-exe
32/32, check-mc 15/15, check-standalone, check-parts, check-toml, check-build 53/53,
check-stubs 9/9, check-limits 17/17 under 90%, check-minimal, test-linux,
test-linux-exe (musl + gnu, both arches), test-windows 42/42, test-windows-x86_64 40/40,
check-examples, check-lang, check-conc, check-desktop, check-float, check-wide,
check-kernel, check-avr, check-sandbox 55 ok, check-docs (196 symbols, 33 flags, 20 TOML
keys, 10 directives, 51 samples, 320 links), site + check-site.
make check-linux-host RC 0 over all four cells, each after its own fixed point and with the
cross proof green.

Goldens

Rewritten once, each only after its own criterion (the bundle carries parse.mc and
gen_walk.mc, so all five move):

golden value
mc2.sha256 897b18875ff43f3baee95036db3651269ed2dba4c764185a12882b43c5fcdf7d
mc2-linux-arm64.sha256 3544cfff8f7fa37710ccd65e76d952d4e3dfdbbc753706e301d02f83a6199e31
mc2-linux-x86_64.sha256 0888bb6627ca2e778e54522794337bc6c0ec842decbb07f156fa3976ee41cef2
mc2-windows-arm64.sha256 b4b7bbc873f4a28865492c44a9992e09d279191bddb3f348c5a8fb78523b44f0
mc2-windows-x86_64.sha256 7fe8f0832ebe7cc3a037d76e142c20435e015fe109edf2de51bb01cb51923e06

The Linux pair was deleted and re-recorded by make check-linux-host; the Windows pair was
cross-computed per tests/golden/README.md and is also written byte for byte by build/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 prelude for -- it
skips the outer step, measured, not assumed), docs/guide/10-single-file.md,
docs/reference/objects.md, 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, so a handler
reading 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

schivei and others added 2 commits September 5, 2026 11:35
`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>
Copilot AI lite review requested due to automatic review settings September 5, 2026 14:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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 (bare continue; keeps nd_val = 0).
  • Update lowering to treat nd_val == 0 as 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.

@schivei
schivei merged commit 167d540 into main Sep 5, 2026
15 checks passed
@schivei
schivei deleted the continue-n branch September 5, 2026 15:02
schivei added a commit that referenced this pull request Sep 5, 2026
….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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants