Spec M21.5 — Tier 3 follow-ups exposed by examples/lang (mechanisms only)

Source: the M22 report (2026-09-03). Each item is a generic mechanism; none mentions classes, generics, namespaces or memory policy.

  1. The bundle must cost zero nodes. src/bundle_data.mc's u64 bundle_blob[] = { ... } is parsed into ~48 000 N_INT nodes (5 MB of arena) every time a taught compiler includes <mc/core>, which is why examples/lang had to ship its own copy of the core's module list. Carry the blob as bytes: #embed bundle_blob "bundle.bin" (M15's own directive; the file is generated next to bundle_data.mc and bundled by name so <mc/core> still works from the binary alone) — zero nodes, byte-identical output. tools/bundle.mc emits both; check-bundle compares both. Then delete examples/lang/lang_core.mc and lang_main.mc and point [compiler].core back to <mc/core> (acceptance: examples/lang builds on the bundled core with the default 32 MiB arena and mc limits shows headroom).
  2. arena exhausted names its caller. xalloc takes no position, so the error has none. Add a one-line context: the last err_at-style position seen by the parser (file:line of the current token) plus the sizes requested, e.g. mc: arena exhausted (32 MiB) while parsing x.mc:120 — raise [limits] tolerance or heap. With M23 the arena is mmap'd from the estimate, so also print the estimate that fell short.
  3. mc build --compiler-only. Builds the taught compiler from [compiler] and stops; prints its path. test.sh scripts and the LSP (M28) need it.
  4. on_stmt(uptr fn). Registered in user_init; called by parse_stmt after every statement node is produced (core or taught), with the node index; the handler may return the same node, a replacement, or 0 (drop). Justification: any language that must wrap, rewrite or observe core statements (return/break/continue/i64 x = ...) without re-teaching them — scope tracking, instrumentation, ownership/borrow rules, coverage — exactly the class of bookkeeping M22 did by walking the tree afterwards. Order: taught syntax_stmt handlers first, then on_stmt hooks in registration order.
  5. parse_block() dispatches through syntax_stmt("{") when one is registered, so a module that tracks scopes sees every block, including the bodies parse_function and #rule block holes parse. Without a registration the behaviour is unchanged.
  6. Core-declared locals are observable. Covered by 4 (on_stmt sees the N_VAR node); document it as the intended way instead of a separate hook.

Acceptance: lib/user_syntax_demo.mc gains an on_stmt demo (statement counter that rewrites nothing) and a scope-tracking demo through syntax_stmt("{") inside a #rule body; examples/lang migrates to on_stmt for scope exits and to <mc/core> (deleting its copies); make check green; golden rewritten once; the bundle blob file reproducible; check-obj 32/32 (mechanisms are inert).

Edit this page