The generator does not migrate to a `Cursor` and should not: transf, destructor
injection, closure lifting and the tree codegen builds as it goes all CONSTRUCT
nodes, and a cursor is a read pointer into a shared token buffer. `nodebridge`
is the seam instead — a rewriting pass keeps producing a `PNode`, and anything
that only reads is handed a `TokenBuf`, from which a `Cursor` (and so a `BNode`)
is a pointer.
The bridge is NOT the `.bif` format, and the difference is the point. A `.bif`
is read by a different process, so every symbol and type has to be written as a
NAME to look up again. A bridged buffer is read by the process that built it, so
a symbol reference is `(bsym <idx>)` into a side table holding the very `PSym`
the encoder was handed, and the type slot is `(btyp <idx>)` the same way. The
node shape is otherwise identical to the file format, so `bnode` reads a bridged
buffer with the accessors it already has; `bodynav` grows one branch each in
`symAt`/`typeAt`, and `bnode` learns that `bsym` is an `nkSym`.
That buys the property this branch has been blocked on: **`sym` IS IDEMPOTENT ON
A BRIDGED BUFFER, FIELDS INCLUDED.** On the file path it cannot be —
`loadFieldStub` mints a fresh stub per use because two distinct fields can share
a name and a position across types — which is what stops `aliases.isPartOf`
moving to the seam. A bridge hands back the object it was given. The grinder now
asserts exactly that: field syms are excluded from the idempotence check on the
file path and INCLUDED on a bridged one.
Verification is the existing oracle pointed at a harder target. `grindBNode`
compares two decodings of one file and has to excuse two differences; the bridge
is compared against its own live input and must excuse NEITHER, so both
tolerances are counted and the bridge asserts it took neither. `toPNode` is
covered without a hand-written comparator that could share the encoder's bugs:
decode, RE-ENCODE, and grade the second buffer against the ORIGINAL tree, so
anything the decoder drops shows up as a disagreement.
Reach, measured: 1431 bodies and 260_431 nodes, against 782 and 67_857 for the
file path — the bridge sees every body, including the one-line `nkAsgn` ones
`ast2nif` never defers and the grinder therefore never saw. 0 disagreements.
Not vacuous: dropping node flags, perturbing the sym index and dropping the type
slot each make it fail immediately (`flags`, `sym identity`, `typ nil-ness`).
A bridged buffer must never be written to a file — `(bsym …)` means nothing
without the tables beside it. `ast2nif` remains the only serializer.
Verified: 215/215 byte-identical `.c` against HEAD on the default path; all four
build configurations compile, plus `nodebridge` standalone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XEF7FJvUkGKvG9LSGuEaNR