summary_two-phase-m1-2026-10-15.md 5.1 KB

Session summary — 2026-10-15: two-phase parse, milestone 1 (probe)

Branch resolve-check, HEAD 6c64b46, tag v3-two-phase-m1. Suite 196/196; fixpoint OK (3,517,333 bytes); tree clean.

Design doc: docs/plan-two-phase-parse.md (now carries a Progress section). Previous session: docs/summary_resolve-check-2026-10-15.md (step 4d, the conclusion that FwdVar* can only die with a two-phase parse).

Goal of the thread

Delete SymTab.FwdVarRef/FwdVarName/FwdVarResolve, Check.NoteForward/FlushForward, the Design forward branch and the Op=1 marker — by parsing module-level declarations before procedure bodies, so a forward reference binds normally instead of being deferred.

What milestone 1 landed (all green)

Each outermost procedure/method body is shadow-skipped at the token level and the skip verified before the body parses normally, behind -twophase (default off — fully inert):

Piece File What
State save/restore compiler/src/scanner.frm PushPos/PopPos: full scanner state (cursor, all published positions, errors) on an 8-deep stack
Switch + helpers compiler/src/M2.atg SetTwoPhase, procNest (hooked once in Block, so ProcDecl and method bodies probe), TokIs (token-text compare), Adv, ProbeBody (opener counting + END <name> check)
Flag plumbing compiler/src/compiler.frm, parser.frm -twophase → M2P.SetTwoPhase at both driver Parse sites
Gate compiler/run_tests.sh twophase_same: .ssa byte-compared flag-off vs flag-on (3 files)
Generated fallout compiler/src/M2.lst Coco/R listing regenerated (line shifts only)

Opener set: MODULE (nested modules), PROCEDURE, CLASS (incl. CLASS IMPLEMENTATION), RECORD (incl. variant CASE), IF, CASE, WHILE, FOR, LOOP, WITH. BEGIN is not an opener; REPEAT…UNTIL needs none. The probe is silent on success; a bad skip prints twophase probe mismatch to StdOut — never into the listing.

Verification

  • ./build.sh + ./run_tests.sh → 196/196 (193 + 3 probe gates).
  • Flag-on sweep: all 142 single-file tests plus session spot-checks (DBasicProg, TString, Showcase12, Library1) and the compiler's own sources (SymTab.mod, M2P.mod, Lower.mod) produce byte-identical .ssa/.LST, zero mismatches.
  • Positive control: a temporary success print proved the probe fires exactly when the flag is on (then reverted).
  • bootstrap/fixpoint.sh → FIXPOINT OK (3,517,333 bytes).

Finding worth keeping (V3 name resolution)

The first cut called the frame's Get from the new atg helper. gm2 accepts it (whole-module scope), but the self-hosted V3 compiler resolves single-pass: the bare Get bound the imported M2S.Get(VAR sym) and stage 2 failed with 233s (plus knock-on 201s at END M2P). Fix: advance via Adv (M2S.Get(sym); INC(errDist) — exactly the frame's Get body for this pragma-free grammar, where the scanner never returns a token above maxT). Rule going forward: atg helpers must never call a later-declared frame procedure by a name that collides with an import. (Same family as the known declaration-before-use constraint on Check.)

What is next (milestone 2)

Deferral + body re-parse, still behind the flag, byte-compared:

  1. Record body spans in the probe: table of (procedure name, start nextPos) for outermost bodies; dump it in diagnostic mode and calibrate (stability across the suite is the gate).
  2. Restart mechanism — two candidates, decision open:
    • (a, recommended) driver-level replay: M2S.Reset + repeated Get to the recorded offset (reproduces exact scanner state, incl. pos/len, with no new scanner internals). Caveat: Reset re-reads the src handle — check FileIO reposition semantics or reopen the file per body.
    • (b) scanner SeekTo(p) with line recompute (plan's original piece 1); must handle the stale pos/len model (they describe the previous token — see the resolve-check summary §"scanner's position model").
  3. Body-entry production: parse one Block for an already-entered heading at end of unit, walking the span table; bodies then see all module-level declarations.
  4. Flip + delete: default on when suite and fixpoint byte-compare; then delete FwdVar*, NoteForward/FlushForward, the Design forward branch and the Op=1 marker (the §9 list in docs/plan-resolve-check.md, plus PushPos/PopPos/ProbeBody if unused).

Watch items: nested-module procedures (probe covers them; decide whether deferral does), generic re-parse (DoInst interplay), errDist save/restore across restarts (error suppression must not shift), the M2.lst churn convention (commit it with the .atg).

Resume

cd .../m2compiler-V3
git checkout resolve-check          # HEAD 6c64b46, tag v3-two-phase-m1
cd compiler && ./build.sh && ./run_tests.sh   # 196/196
cd .. && ./bootstrap/fixpoint.sh    # FIXPOINT OK

Probe check on any file: ./M2 -twophase tests/<f>.mod — identical .ssa, no twophase probe mismatch on StdOut.