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).
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.
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.
./build.sh + ./run_tests.sh → 196/196 (193 + 3 probe gates).DBasicProg, TString, Showcase12, Library1) and
the compiler's own sources (SymTab.mod, M2P.mod, Lower.mod)
produce byte-identical .ssa/.LST, zero mismatches.bootstrap/fixpoint.sh → FIXPOINT OK (3,517,333 bytes).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.)
Deferral + body re-parse, still behind the flag, byte-compared:
nextPos) for outermost bodies; dump it in diagnostic mode and
calibrate (stability across the suite is the gate).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.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").Block for an already-entered
heading at end of unit, walking the span table; bodies then see all
module-level declarations.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).
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.