← board

Seven frontends borrow rparser.inc's helpers

The measurement (2026-08-19)

-dPXX_NO_RUST alone: 198 errors, none of them Rust's. With Zig also omitted: 74. With Zig and the six esoteric probes omitted: 0.

symbol callers outside rparser what it actually is
RMakeIdent (72 refs) zparser, gparser, eparser, fparser, lparser AllocNode(AN_IDENT) — an AST constructor
RSeqAppend (65) same AST statement-list append
RBinOp (42) same AllocNode(AN_BINOP)
RStoredName (10) zparser (9), eparser (1) token-char-buffer interning
RWiden (7) zparser (3), fparser, lparser, gparser (2 each) numeric widening — language semantics
REmitParamRegSpill (1) zparser emits raw x86-64 (REX bytes)

Rust was the first skeleton written, so the ones after it reached for what was there. The R prefix then hid what had happened: it reads as "Rust's", so nobody asked whether an ALGOL parser should be calling it.

Three layers, three different right answers

This is why the fix is not "move the R functions to a shared file".

  1. RMakeIdent / RSeqAppend / RBinOp — share, and they already are. Per [[the-substrate-is-ast-and-ir-not-the-parser]] the AST is the shared substrate; these are AST constructors and sharing them is correct. The bug is only the file and the prefix. Move to a shared astbuild.inc, drop the R.

  2. RStoredName — share, but it lies in the error path. Its overflow message reads 'Rust: token char buffer overflow'. An Erlang or Zig program that overflows the buffer is told it is Rust. Move and reword.

  3. RWiden — do NOT share. This is a numeric widening rule, i.e. a piece of one language's specification, and the same doc says duplicate semantics across languages because "a shared parser helper couples two specs and is wrong in both." The concrete case: Zig has no implicit numeric widening at all — it requires explicit casts — yet zparser.inc calls Rust's widening in three places. Whether those three sites sit on a path where it is observable is a Track Z question and is NOT asserted here; what is asserted is that the coupling exists and is the wrong shape. Each frontend should own its widening.

  4. REmitParamRegSpill — does not belong in a frontend at all. Raw x86-64 register-spill emission, called from Zig's parser. Same species as cparser.inc's _start stub ([[refactor-a-backend-machine-code-lives-in-six-shared-files]]) and as the Pascal-only signal runtime ([[bug-a-only-the-pascal-driver-emits-the-signal-runtime]]) — three frontends now, each holding a private piece of target machinery. That is no longer a coincidence; it is the missing layer between the frontends and the backends showing up once per frontend.

Consequence today

PXX_NO_RUST ships, but it is only usable together with PXX_NO_ZIG and the six probe defines — documented in the feature ticket rather than enforced, because an implicit "this define turns on those six" would hide exactly the coupling this ticket exists to remove. Doing 1-3 makes PXX_NO_RUST stand alone.


MERGED 2026-08-29 — refactor-a-the-greenfield-frontends-share-each-others-parser-helpers

Same finding, filed twice, with two independent measurements of it. Surfaced by progress check's NEAR-DUP scan (4 shared slug words). Kept here because this ticket carries the layer analysis; the other is a tombstone in rejected/.

What the duplicate measured that this ticket did not: omitting rparser.inc breaks zparser.inc in 123 places, plus gparser/eparser/fparser. This ticket counted 198 errors for PXX_NO_RUST across six frontends. Two different cuts of one coupling — worth keeping both numbers, since they bound the job from different directions.

Its framing, which is the stronger statement of why this matters: the greenfield frontends call each other's support functions, which is exactly what devdocs/dev/the-substrate-is-ast-and-ir-not-the-parser.md says not to do — share the AST and the IR, duplicate the parser and its helpers per language. It costs nothing today and couples two language specs; the price is paid the first time one spec needs its helper to behave differently, and then it is paid in the wrong file.

Prio: this ticket 22, the duplicate 18. Left at 22, not averaged and not raised — the higher of two independent low estimates is still a low estimate, and re-ranking on a merge invents a signal neither author gave.