瀏覽代碼

feat(compiler): SYMTAB.MOD draft v1 assembled from 4 group passes

988 lines covering all ~38 symtab.txt heads with reconciled globals
(expanded SYMTAB.DEF words 10-27, StringPtr, draft signatures).
Unverified: header records resolved conflicts + open FIXMEs (AllocSym
arity, D-group sketches, LSL primitive, bodyHook placeholder).
Eric Streit 1 周之前
父節點
當前提交
c8bf5a6a29
共有 4 個文件被更改,包括 1044 次插入 和 19 次删除
  1. 2 1
      src/compiler/README.md
  2. 12 0
      src/compiler/RENAME-MAP.md
  3. 42 18
      src/compiler/SYMTAB.DEF
  4. 988 0
      src/compiler/SYMTAB.MOD

+ 2 - 1
src/compiler/README.md

@@ -12,7 +12,8 @@ No `0x1A`/`0x00` padding, LF line endings.
 | `SCANNER.DEF/MOD` | renamed: `GetSym/NextChar/AcceptSymbol/ExpectSymbol/TestSymbolInSet/.../ParseNumber/SkipComment/ScanNextToken`, state `scanOptions/curChar/curSymbol/tokenBuffer/charClassTable/...` |
 | `ERRORS.DEF/MOD` | renamed: `ReportError/FindFileOnDrives/AskEditOrQuit/DoError/RollbackToMark/ReportExpectedSet/ReportTypeMismatch/...` |
 | `CODEGEN.DEF/MOD` | renamed: `SetPendingOp/FlushPendingOp/QueueConst/EmitTypedOp/OpenFixup/CloseFixup/PeekCodeByte/...`, state `nextEmitPos/windowBase/codeWindow/fixupQueue/...` |
-| `SYMTAB.DEF` | renamed globals/procs; `SYMTAB.MOD` bodies mapped in `FRONTEND.md` (no upstream source, only `symtab.txt`) |
+| `SYMTAB.DEF` | expanded (words 10–27 mapped) + `StringPtr` + draft signatures |
+| `SYMTAB.MOD` | **DRAFT v1, unverified**: full bodies assembled from 4 group passes; see header for conflicts/FIXMEs |
 | `FRONTEND.md` | export map for `PASS1/STATEMEN/EXPRESS` (offsets + proposed names + evidence); no upstream `.MOD`, bodies pending |
 
 Full old->new tables: `RENAME-MAP.md` (module level; params/locals renamed

+ 12 - 0
src/compiler/RENAME-MAP.md

@@ -203,3 +203,15 @@ Mechanical, semantics-preserving. Qualified cross-module uses renamed too
 * `proc6` -> `SaveSymbolFile`
 * `proc7` -> `RestoreAfterEdit`
 * `proc8` -> `SymbolAssert`
+
+## SYMTAB bodies (SYMTAB.MOD draft v1; all new, no upstream source)
+FindModule/EnterModule/LookupChain/MatchSym/SymbolAssert/CheckNode/NewString/
+GetName/PutAttr/GetAttr/AllocSym/EnterChained/EnterIdent/UnlinkScope/InitHeaps/
+InsertBuiltin(nested)/EnterStandards/LoadSymFile/LibRead(nested)/ImportAll/
+FindExport+ListExports+ImportOne(nested)/SkipQualident/DefineDefinitionModule/
+BuildBitsetConst/DefineProgramModule/InitSymbolTables/NoteImported/DoImportList/
+CompileBody/CheckForwards/TryCompile/CompileDispatch/Checksum7w/SaveSymbolFile/
+RestoreAfterEdit. Heap/feature globals: modStage/scopeChain/ownNamePtr/
+symHeapBase/scopeHeapBase/strHeapBase/symTop/scopeTop/strTop/exportHead/
+stageNameTotal/savedSymInfo/savedStrInfo/symCap/scopeCap/strCap/stageFlag/
+checksumTemp. See SYMTAB.MOD header for resolved conflicts + open FIXMEs.

+ 42 - 18
src/compiler/SYMTAB.DEF

@@ -1,4 +1,9 @@
-(* Renamed for readability. Semantics unchanged. Original identifiers: see docs/compiler/ + src/compiler/RENAME-MAP.md. *)
+(* Renamed for readability. Semantics unchanged where upstream existed.
+   EXPANDED for SYMTAB.MOD draft v1: upstream SYMTAB.DEF only declared words
+   2..9, but symtab.txt uses words 7,8,10..27. Words 7,8 renamed in place
+   (same slot type); words 10..27 appended with MCode word numbers in comments.
+   Signatures for the 8 exported procs filled from the draft bodies; all
+   marked (*draft*) until the MCD diff confirms them. *)
 DEFINITION MODULE SYMTAB;
 FROM SYSTEM IMPORT ADDRESS, WORD;
 IMPORT Files;
@@ -18,25 +23,44 @@ TYPE T1 = POINTER TO R1;
             word3 : BITSET;
             word4 : CARDINAL;
           END;
+     (* Scanner imports StringPtr from SymTab; heap names are NUL-terminated. *)
+     StringPtr = POINTER TO ARRAY [0..127] OF CHAR;
 
 VAR
+    (* word 2 *) symOptions: BITSET;
+    (* word 3 *) moduleCount: CARDINAL;
+    (* word 4 *) symFile: Files.FILE;
+    (* word 5 *) stringPoolPtr: CARDINAL;
+    (* word 6 *) moduleTable: POINTER TO ARRAY [0..99] OF Symbol;
+    (* word 7 *) modStage: POINTER TO ARRAY [0..16] OF Symbol;
+    (* word 8 *) scopeChain: POINTER TO ARRAY [0..16] OF T1;
+    (* word 9 *) currentScope: POINTER TO RECORD w0:WORD; link1: T1 END;
+    (* word 10 *) ownNamePtr: ADDRESS;
+    (* word 11 *) stageSpare11: CARDINAL; (* use TBD; no evidence yet *)
+    (* word 12 *) symHeapBase: ADDRESS;
+    (* word 13 *) scopeHeapBase: ADDRESS;
+    (* word 14 *) strHeapBase: ADDRESS;
+    (* word 15 *) symTop: CARDINAL;
+    (* word 16 *) scopeTop: CARDINAL;
+    (* word 17 *) strTop: CARDINAL;
+    (* word 18 *) exportHead: CARDINAL;
+    (* word 19 *) stageNameTotal: CARDINAL;
+    (* word 20 *) savedSymInfo: CARDINAL;
+    (* word 21 *) savedStrInfo: CARDINAL;
+    (* word 22 *) symCap: CARDINAL;
+    (* word 23 *) scopeCap: CARDINAL;
+    (* word 24 *) strCap: CARDINAL;
+    (* word 25 *) stageFlag: CARDINAL; (* B: nestedProcFlag; C: compileActive *)
+    (* word 26 *) stageSpare26: CARDINAL; (* use TBD; no evidence yet *)
+    (* word 27 *) checksumTemp: CARDINAL;
 
-    symOptions: BITSET;
-    moduleCount: CARDINAL;
-    symFile: Files.FILE;
-    stringPoolPtr: CARDINAL;
-    moduleTable: POINTER TO ARRAY [0..99] OF Symbol;
-    scopeStack,
-    rollbackStack: POINTER TO ARRAY [0..16] OF T1;
-    currentScope: POINTER TO RECORD w0:WORD; link1: T1 END;
-
-PROCEDURE CompileDispatch;
-PROCEDURE DefineDefinitionModule;
-PROCEDURE DefineProgramModule;
-PROCEDURE InitSymbolTables;
-PROCEDURE BuildBitsetConst;
-PROCEDURE SaveSymbolFile;
-PROCEDURE RestoreAfterEdit;
-PROCEDURE SymbolAssert;
+(*draft*) PROCEDURE CompileDispatch(VAR wsp: ARRAY OF BYTE; impl: BOOLEAN; prog: BOOLEAN);
+(*draft*) PROCEDURE DefineDefinitionModule(isImpl: CARDINAL);
+(*draft*) PROCEDURE DefineProgramModule(fromSeen: CARDINAL);
+(*draft*) PROCEDURE InitSymbolTables(opt: BITSET; imports: T1; modName: T1);
+(*draft*) PROCEDURE BuildBitsetConst(VAR name: ARRAY OF CHAR): BOOLEAN;
+(*draft*) PROCEDURE SaveSymbolFile;
+(*draft*) PROCEDURE RestoreAfterEdit;
+(*draft*) PROCEDURE SymbolAssert(cond: BOOLEAN; code: CARDINAL);
 
 END SYMTAB.

+ 988 - 0
src/compiler/SYMTAB.MOD

@@ -0,0 +1,988 @@
+(* SYMTAB.MOD — DRAFT v1, assembled 2026-10-02, NOT verified.
+   Reconstructed from Reversing-Turbo-Modula2-main/MCode_disassembly/symtab.txt
+   by four parallel decompilation passes (groups A @0001-00d4, B @00bd-03c9,
+   C @03de-07ad, D @07d8-end). Does NOT yet recompile to the original MCD;
+   verification via unassemble.c diff is still required (see README.md).
+
+   Naming follows src/compiler/RENAME-MAP.md plus the groups' reconciled names.
+   {/* 0xxx */} comments are MCode offsets backing tricky blocks.
+
+   RESOLVED CONFLICTS (group disagreements, decision recorded):
+   - global7: group A importNames == group C modStage (both 10-byte name slots).
+     Unified as modStage: staged module-name directory, slot 16 = own module.
+   - global8: group A scopeHeads == group C scopeChain (per-module chain heads).
+     Unified as scopeChain: ARRAY [0..16] OF T1 (ADDRESS-compatible).
+   - Words 12-17, 22-24: groups B and C agree exactly (heap bases/tops/caps).
+   - SkipQualident: groups C and D each decompiled proc32 @07ad differently.
+     Kept C's version (exact offsets {07af..07d0}); D's duplicate dropped.
+   - EnterIdent arg order: B's definition (idx, scope) kept; C's call sites
+     flipped to match. If the MCD diff complains, try (scope, idx).
+   - global2: kept SYMTAB.DEF's symOptions: BITSET (from SYMTAB.SYM — stronger
+     evidence than MCode word reading); B's heapBase reading flagged below.
+   - Words 20,21: kept C's savedSymInfo/savedStrInfo (header[5,6]); D's
+     conflicting mapping flagged at CheckForwards.
+   OPEN ISSUES (FIXME in body):
+   - AllocSym arity: B reads 2 params (node, scope); D's two call sites pass 1.
+   - D's Scanner misuse cleaned where certain (GetStackMark VAR-param form,
+     CopyStringToHeap(dest, src) order, HIGH() bounds stripped from
+     ReportErrorWithText calls); remaining dubious external calls flagged.
+   - COMPILER.bodyHook / BodyParseStep (D's TryCompile/CompileBody): no such
+     exports exist in COMPILER.DEF — placeholders, see FRONTEND.md Pass1 DCLS.
+   - LSL in Checksum7w is not SYSTEM; original shift primitive TBD.
+   - TEXTS handle literal 3 used throughout (screen?); kept as-is. *)
+
+IMPLEMENTATION MODULE SymTab;
+IMPORT Compiler, Files, Loader, Texts, Errors, Scanner, CodeGen;
+FROM SYSTEM IMPORT ADDRESS, ADR, MOVE;
+
+(* ---- heap model (groups B + C) ---- *)
+(* MCode words are untyped; CARDINAL()/T1()/ADDRESS() punning mirrors the
+   disassembly. A Words view aliases link0/link1/link2/word3/word4 as words
+   0..4; words 5..7 exist only on large nodes (size classes: 14 bytes for
+   kind <= 1, 10 for kind <= 6, 12 for kind <= 11, else 16). *)
+TYPE Words = POINTER TO ARRAY [0..7] OF CARDINAL;
+
+(* 14-byte symbol-heap entry = 7 words (group C names). *)
+TYPE SymEntryPtr = POINTER TO SymEntry;
+     SymEntry = RECORD
+       link: CARDINAL;    (* word 0: export/next chain *)
+       nameOff: CARDINAL; (* word 1: byte offset of name in strHeapBase *)
+       ref: CARDINAL;     (* word 2 *)
+       flags: BITSET;     (* word 3 *)
+       kind: CARDINAL;     (* word 4 *)
+       aux1: CARDINAL;     (* word 5; high byte stamped with importer *)
+       aux2: CARDINAL;     (* word 6; importer index *)
+     END;
+
+(* 16-byte scope-heap entry (group C names). *)
+TYPE ScopeEntryPtr = POINTER TO ScopeEntry;
+     ScopeEntry = RECORD
+       slot: CHAR;       (* byte 0: module slot *)
+       fresh: CHAR;       (* byte 1: nonzero = newly imported *)
+       link: CARDINAL;   (* word 1 *)
+       ref: CARDINAL;    (* word 2 *)
+       flags: BITSET;    (* word 3 *)
+       state: CARDINAL;  (* word 4 *)
+       aux1, aux2, aux3: CARDINAL;
+     END;
+
+(* 10-byte module-name slot (group C names). *)
+TYPE ModuleSymPtr = POINTER TO ModuleSym;
+     ModuleSym = RECORD
+       name: ARRAY [0..7] OF CHAR;
+       tag: CARDINAL;
+     END;
+
+(* Import record: same 7-word layout as Compiler.Record; names document use. *)
+TYPE ImportPtr = POINTER TO ImportRec;
+     ImportRec = RECORD
+       next: ADDRESS;   (* word 0 *)
+       name: ADDRESS;   (* word 1: module-name heap string *)
+       w2: CARDINAL;    (* word 2 *)
+       flags: BITSET;   (* word 3; bit 10 = import-all *)
+       kind: CARDINAL;  (* word 4 *)
+       w5: WORD;        (* word 5 *)
+       anchor: ADDRESS; (* word 6/high: import chain head cell *)
+     END;
+
+(* 6-byte relink node for scopeChain repair. *)
+TYPE ChainPtr = POINTER TO ChainRec;
+     ChainRec = RECORD
+       next: ADDRESS;
+       key, ref: CARDINAL;
+     END;
+
+(* Scope descriptor for SkipQualident's outer walk. *)
+TYPE ScopePtr = POINTER TO ScopeDesc;
+     ScopeDesc = RECORD
+       outer: ScopePtr;       (* word 0: enclosing scope *)
+       idents: Scanner.List;  (* word 1: identifier list *)
+     END;
+
+CONST symReadError = 790; (* RAISE code in LibRead {/*0497*/} *)
+
+(* ---- FORWARDs for the PutAttr/GetAttr <-> EnterChained/AllocSym/
+        UnlinkScope/EnterIdent recursion cycle (group B) ---- *)
+PROCEDURE SymbolAssert(cond: BOOLEAN; code: CARDINAL); FORWARD;
+PROCEDURE CheckNode(node: T1); FORWARD;
+PROCEDURE EnterChained(node: T1; scope: ADDRESS); FORWARD;
+PROCEDURE AllocSym(node: T1; scope: ADDRESS); FORWARD;
+PROCEDURE UnlinkScope(idx: CARDINAL; scope: ADDRESS); FORWARD;
+PROCEDURE EnterIdent(idx: CARDINAL; scope: ADDRESS); FORWARD;
+PROCEDURE PutAttr(src: T1; VAR slot: CARDINAL); FORWARD;
+PROCEDURE GetAttr(idx: CARDINAL; VAR node: T1); FORWARD;
+PROCEDURE LookupChain(scopeNo: CARDINAL; key: CARDINAL): T1; FORWARD;
+PROCEDURE InitHeaps; FORWARD;
+PROCEDURE EnterStandards; FORWARD;
+PROCEDURE DefineDefinitionModule(isImpl: CARDINAL); FORWARD;
+PROCEDURE ImportOne(anchor: ADDRESS; symIdx: CARDINAL); FORWARD;
+
+(* ================= group A: module/scope search (@0001-00d4) ================= *)
+
+PROCEDURE SymbolAssert(cond: BOOLEAN; code: CARDINAL);
+(* proc8 @0001 *)
+BEGIN
+  IF NOT cond THEN Errors.ReportError(code) END;
+END SymbolAssert;
+
+PROCEDURE FindModule(slot: CARDINAL): CARDINAL;
+(* proc9 @000c: 16 = self, 17 = not found *)
+VAR i: CARDINAL;
+BEGIN
+  (*{/*0016*/} key.name = ownNamePtr^ -> self *)
+  IF modStage^[slot].name = ModuleSymPtr(ownNamePtr)^.name THEN RETURN 16 END;
+  i := 0;
+  WHILE i < moduleCount DO
+    IF modStage^[slot].name = moduleTable^[i].name THEN RETURN i END;
+    INC(i); (*{/*0036*/}*)
+  END;
+  RETURN 17; (*{/*003b*/}*)
+END FindModule;
+
+PROCEDURE EnterModule(slot: CARDINAL): CARDINAL;
+(* proc10 @0040: FindModule(slot); add if absent *)
+VAR found: CARDINAL;
+BEGIN
+  found := FindModule(slot); (*{/*0042*/}*)
+  IF found <= 16 THEN
+    (*{/*0051*/} ghost check on Symbol.word fields *)
+    IF (modStage^[slot].word # 0)
+       AND (moduleTable^[found].word # modStage^[slot].word) THEN
+      Errors.ReportErrorWithText(12, modStage^[slot].name); (*{/*0061*/}*)
+    END;
+    RETURN found; (*{/*0066*/}*)
+  END;
+  SymbolAssert(moduleCount <= 15, 84); (*{/*0069*/}*)
+  (*{/*006f*/} MOVE whole 10-byte Symbol slot *)
+  moduleTable^[moduleCount] := modStage^[slot];
+  INC(moduleCount);
+  RETURN moduleCount - 1; (*{/*007e*/}*)
+END EnterModule;
+
+PROCEDURE LookupChain(scopeNo: CARDINAL; key: CARDINAL): T1;
+(* proc11 @0083 *)
+VAR p: T1;
+BEGIN
+  IF scopeNo > 16 THEN RETURN NIL END; (*{/*0085*/}*)
+  p := scopeChain^[scopeNo]; (*{/*008b*/}*)
+  WHILE p # NIL DO (*{/*008f*/}*)
+    IF CARDINAL(p^.link1) = key THEN RETURN p^.link2 END; (*{/*0093*/}*)
+    p := p^.link0; (*{/*009d*/}*)
+  END;
+  RETURN NIL; (*{/*00a2*/}*)
+END LookupChain;
+
+PROCEDURE MatchSym(sym: T1; want: T1): BOOLEAN;
+(* proc12 @00a6 *)
+BEGIN
+  (*{/*00a8*/} sym # NIL; {/*00ac*/} 9 IN sym^.word3; {/*00b2*/} link2 = want *)
+  RETURN (sym # NIL) AND (9 IN sym^.word3) AND (sym^.link2 = want);
+END MatchSym;
+
+(* ================= group B: allocators + attrs (@00bd-03c9) ================= *)
+
+PROCEDURE CheckNode(node: T1);
+(* proc15 @00bd — guard before chaining through word5/word6 fields *)
+BEGIN
+  IF (node <> NIL) AND (CARDINAL(node^.link0) - 1 < 64) THEN (*{/*00bf*/}*)
+    HALT; (* MCode Raise(272,0,0) {/*00cb*/} *)
+  END;
+END CheckNode;
+
+PROCEDURE NewString(src: ADDRESS; VAR off: CARDINAL);
+(* proc17 @00d4 — intern a string into the string pool; returns pool offset *)
+VAR len: CARDINAL;
+BEGIN
+  IF src <> NIL THEN (*{/*00d6*/}*)
+    off := strTop; (*{/*00da*/}*)
+    len := Scanner.StrLen(src) + 1; (*{/*00de*/}*)
+    INC(strTop, len);
+    SymbolAssert(strTop + 8 <= strCap, 85); (*{/*00ec*/}*)
+    MOVE(src, strHeapBase + off, len); (*{/*00f6*/}*)
+  ELSE
+    off := 0; (*{/*0100*/}*)
+  END;
+END NewString;
+
+PROCEDURE GetName(off: CARDINAL; VAR name: ADDRESS);
+(* proc21 @0268 — inverse of NewString: heap copy of pooled string *)
+BEGIN
+  IF off <> 0 THEN (*{/*026a*/}*)
+    Scanner.CopyStringToHeap(name, StringPtr(strHeapBase + off)^); (*{/*026e*/}*)
+  ELSE
+    name := NIL; (*{/*027b*/}*)
+  END;
+END GetName;
+
+PROCEDURE PutAttr(src: T1; VAR slot: CARDINAL);
+(* proc18 @0106 — store a type/attr node into the scope-descriptor table *)
+VAR i: CARDINAL; e: Words; sw: Words;
+BEGIN
+  slot := 0; (*{/*0108*/}*)
+  IF src = NIL THEN RETURN END; (*{/*010b*/}*)
+  slot := Words(src)^[1]; (*{/*010f*/}*)
+  IF slot <> 0 THEN RETURN END; (* named: referenced by name, no entry *)
+  IF CARDINAL(src) < CARDINAL(symHeapBase) THEN (* static node: dedup {/*0118*/}
+     (* group B read this bound as global word2; reconciled to symHeapBase:
+        word2 is symOptions: BITSET per SYMTAB.SYM, and the heap base is the
+        only agreed boundary for "points outside the dynamic heap". *)
+    i := 1; (*{/*011d*/}*)
+    WHILE i <= scopeTop DO
+      IF Words(scopeHeapBase + 16 * i)^[1] = CARDINAL(src) THEN (*{/*011f*/}*)
+        slot := i; RETURN;
+      END;
+      INC(i); (*{/*012e*/}*)
+    END;
+    SymbolAssert(i <= scopeCap, 86); (*{/*0137*/}*)
+    scopeTop := i;
+    e := Words(scopeHeapBase + 16 * scopeTop);
+    e^[1] := CARDINAL(src); (*{/*0141*/}*)
+    slot := scopeTop; (*{/*0149*/}*)
+    RETURN;
+  END;
+  SymbolAssert(scopeTop < scopeCap, 86); (*{/*014f*/}*)
+  INC(scopeTop);
+  slot := scopeTop; (*{/*015c*/}*)
+  e := Words(scopeHeapBase + 16 * scopeTop);
+  sw := Words(src);
+  e^[1] := 0; (*{/*016b*/}*)
+  PutAttr(T1(sw^[2]), e^[2]); (* copy link2 chain {/*016e*/} *)
+  e^[3] := sw^[3]; (*{/*0176*/}*)
+  e^[4] := sw^[4]; (* kind {/*017a*/} *)
+  CASE sw^[4] OF (* kind switch {/*01b3*/} *)
+  | 90, 91:
+    e^[5] := sw^[5]; e^[6] := sw^[6]; (*{/*0182*/}*)
+  | 99, 100:
+    CheckNode(T1(sw^[5])); (*{/*0192*/}*)
+    EnterChained(T1(sw^[5]), ADR(e^[5])); (*{/*0197*/}*)
+  | 101:
+    PutAttr(T1(sw^[5]), e^[5]); (*{/*0189*/}*)
+  | 102:
+    PutAttr(T1(sw^[5]), e^[5]); (*{/*019e*/}*)
+    PutAttr(T1(sw^[6]), e^[6]);
+    e^[7] := sw^[7];
+  ELSE (* 92..98: name/link/attr words suffice *)
+  END;
+END PutAttr;
+PROCEDURE GetAttr(idx: CARDINAL; VAR node: T1);
+(* proc22 @0281 — load a scope entry back into a heap node (inverse of PutAttr).
+   Entry word1 = static node pointer or memoized node (returned as-is). *)
+VAR e: Words; new: T1; nw: Words;
+BEGIN
+  node := NIL; (*{/*0283*/}*)
+  IF idx = 0 THEN RETURN END; (*{/*0286*/}*)
+  e := Words(scopeHeapBase + 16 * idx); (*{/*028a*/}*)
+  node := T1(e^[1]); (*{/*0290*/}*)
+  IF node <> NIL THEN RETURN END; (*{/*0294*/}*)
+  (* Allocator size classes exact: Scanner.NewSizedNode(kind) =
+     14 bytes for kind <= 1, 10 for kind <= 6, 12 for kind <= 11, else 16. *)
+  new := Scanner.NewSizedNode(e^[4]); (*{/*0299*/}*)
+  nw := Words(new);
+  e^[1] := CARDINAL(new); (* memoize {/*029e*/} *)
+  node := new; (*{/*02a1*/}*)
+  GetAttr(e^[2], new^.link2); (* subtype {/*02a4*/} *)
+  nw^[3] := e^[3]; (*{/*02ac*/}*)
+  CASE e^[4] OF (* kind switch {/*02e1*/} *)
+  | 90, 91:
+    nw^[5] := e^[5]; nw^[6] := e^[6]; (*{/*02b4*/}*)
+  | 99, 100:
+    UnlinkScope(e^[5], ADR(nw^[5])); (*{/*02c4*/}*)
+  | 101:
+    GetAttr(e^[5], nw^[5]); (*{/*02bb*/}*)
+  | 102:
+    GetAttr(e^[5], nw^[5]); (*{/*02cc*/}*)
+    GetAttr(e^[6], nw^[6]);
+    nw^[7] := e^[7];
+  ELSE (* 92..98: nothing beyond words 1..4 *)
+  END;
+END GetAttr;
+
+(* NOTE (group B): the brief asked for AllocSym(nodeKind): T1, but the MCode
+   (proc14 @01d7, ProcLeave(2), uses param1+param2) is a 2-param PROCEDURE
+   chaining a 14-byte entry onto scope and filling it from node. *)
+PROCEDURE AllocSym(node: T1; scope: ADDRESS);
+(* proc14 @01d7 — chain-allocate a 14-byte symbol-table entry from node *)
+VAR s: Words; n: T1; nw: Words; sw: Words;
+BEGIN
+  SymbolAssert(symTop < symCap, 87); (*{/*01d9*/}*)
+  INC(symTop);
+  s := Words(symHeapBase + 14 * symTop); (*{/*01e3*/}*)
+  n := T1(s); nw := s; sw := Words(node);
+  s^[0] := Words(scope)^[0]; (*{/*01e9*/}*)
+  Words(scope)^[0] := symTop; (*{/*01ed*/}*)
+  NewString(node^.link1, s^[1]); (*{/*01f0*/}*)
+  PutAttr(node^.link2, s^[2]); (*{/*01f7*/}*)
+  n^.word3 := node^.word3; (*{/*01fe*/}*)
+  nw^[5] := sw^[5]; nw^[6] := sw^[6]; (* dword {/*0202*/} *)
+  n^.word4 := node^.word4; (* kind {/*0208*/} *)
+  IF node^.link2 = Compiler.charArrayDesc THEN (* COMPILER.word12 {/*020c*/} *)
+    NewString(ADDRESS(sw^[5]), nw^[5]); (*{/*0213*/}*)
+  ELSIF nw^[4] >= 5 THEN (*{/*021c*/}*)
+    CheckNode(T1(sw^[6])); (*{/*0222*/}*)
+    nw^[6] := 0; (*{/*0226*/}*)
+    EnterChained(T1(sw^[6]), ADR(nw^[6])); (*{/*0229*/}*)
+  ELSIF (nw^[4] = 1) AND (node^.link2 = Compiler.LongrealType) THEN
+    (* COMPILER.word11: 8-byte real constant {/*0231*/} *)
+    SymbolAssert(strTop + 8 <= strCap, 85); (*{/*023e*/}*)
+    MOVE(ADDRESS(sw^[5]), strHeapBase + strTop, 8); (*{/*0248*/}*)
+    INC(strTop, 8); (*{/*0251*/}*)
+  END;
+END AllocSym;
+
+PROCEDURE EnterChained(node: T1; scope: ADDRESS);
+(* proc13 @0259 — append a link0 node chain to scope (oldest first) *)
+BEGIN
+  IF node <> NIL THEN (*{/*025b*/}*)
+    EnterChained(node^.link0, scope); (*{/*025f*/}*)
+    AllocSym(node, scope); (*{/*0263*/}*)
+  END;
+END EnterChained;
+
+PROCEDURE EnterIdent(idx: CARDINAL; scope: ADDRESS);
+(* proc20 @0305 — declare symbol-table entry idx into scope (dup check + attrs) *)
+VAR sentry: Words;          (* symHeapBase + idx*14 *)
+    new: T1; nw: Words;
+    existing: T1;
+    mark: ADDRESS;
+    nm, nm2: ADDRESS;       (* heap names via GetName *)
+    nmt: StringPtr;
+BEGIN
+  sentry := Words(symHeapBase + 14 * idx); (*{/*0307*/}*)
+  new := Scanner.NewNode(sentry^[4]); (* kind; always 14 bytes {/*030d*/} *)
+  nw := Words(new);
+  nw^[0] := Words(scope)^[0]; (* chain at head {/*0312*/} *)
+  GetName(sentry^[1], nm); (* heap copy of name {/*0316*/} *)
+  nw^[1] := CARDINAL(nm);
+  nmt := nm;
+  existing := Scanner.FindIdent(Scanner.List(scope), nm, 9 IN Scanner.scanOptions);
+  (* SCANNER.proc22 + scanOptions {/*031f*/} *)
+  IF (existing <> NIL) AND NOT (5 IN Compiler.RecordPtr(existing)^.word3) THEN
+    (* FIXME: word3 access needs Compiler.Record layout; original reads the
+       found node's flags word. See Compiler.Record (word3: BITSET). *)
+    Errors.ReportErrorWithText(1, nmt^); (*{/*033a*/}*)
+  END;
+  Words(scope)^[0] := CARDINAL(new); (*{/*0341*/}*)
+  GetAttr(sentry^[2], new^.link2); (*{/*0344*/}*)
+  new^.word3 := BITSET(sentry^[3]); (*{/*034b*/}*)
+  new^.word4 := sentry^[4]; (*{/*034f*/}*)
+  IF sentry^[4] = 3 THEN (* constant: back-link value node {/*0353*/} *)
+    IF Words(new^.link2)^[0] = 0 THEN (*{/*0359*/}*)
+      Words(new^.link2)^[0] := CARDINAL(new); (*{/*035f*/}*)
+    END;
+  ELSIF new^.link2 = Compiler.charArrayDesc THEN (* COMPILER.word12 {/*0365*/} *)
+    GetName(sentry^[5], nm2); (*{/*0372*/}*)
+    nw^[5] := CARDINAL(nm2);
+  ELSIF sentry^[4] >= 5 THEN (*{/*037b*/}*)
+    IF existing = NIL THEN (*{/*0381*/}*)
+      Scanner.GetStackMark(mark); (*{/*0385*/}*)
+      nw^[6] := 0; (*{/*0389*/}*)
+      UnlinkScope(sentry^[6], ADR(nw^[6])); (*{/*038c*/}*)
+      IF (sentry^[4] = 5) AND (stageFlag <> 0) THEN (*{/*0393*/} global25 *)
+        Compiler.allocateSignature(new, mark); (* COMPILER.dword27 gate {/*039d*/} *)
+      END;
+    ELSE
+      nw^[6] := Words(existing)^[6]; (*{/*03a5*/}*)
+    END;
+  ELSIF (sentry^[4] = 1) AND (new^.link2 = Compiler.LongrealType) THEN
+    (* COMPILER.word11; 8-byte real value {/*03ab*/} *)
+    Scanner.Allocate(ADR(nw^[5]), 8); (*{/*03b8*/}*)
+    MOVE(strHeapBase + sentry^[5], ADDRESS(nw^[5]), 8); (*{/*03be*/}*)
+  END;
+END EnterIdent;
+
+PROCEDURE UnlinkScope(idx: CARDINAL; scope: ADDRESS);
+(* proc19 @03c9 — re-declare a link0 index chain into scope (oldest first) *)
+VAR e: Words;
+BEGIN
+  IF idx <> 0 THEN (*{/*03cb*/}*)
+    e := Words(symHeapBase + 14 * idx);
+    UnlinkScope(e^[0], scope); (*{/*03cf*/}*)
+    EnterIdent(idx, scope); (*{/*03d8*/}*)
+  END;
+END UnlinkScope;
+
+(* ================= group C: heaps/standards/symfile/imports ================= *)
+
+PROCEDURE InitHeaps;
+(* proc23 @03de: slice heaps out of CodeGen memory *)
+VAR freeBytes: CARDINAL;
+BEGIN
+  IF CodeGen.nextEmitPos - CodeGen.windowBase > 2560 THEN (*{/*03e0*/}*)
+    CodeGen.FlushCodeWindow;
+  END;
+  freeBytes := 4096 - (CodeGen.nextEmitPos - CodeGen.windowBase); (*{/*03ee*/}*)
+  symHeapBase := Scanner.codeBuffer + CodeGen.nextEmitPos - CodeGen.windowBase; (*{/*03f8*/}*)
+  symCap := freeBytes DIV 28 - 1; (*{/*0402*/} DIV (2*14) *)
+  scopeHeapBase := symHeapBase + (symCap + 1) * 14; (*{/*040a*/}*)
+  scopeCap := freeBytes DIV 64 + 7; (*{/*0412*/} DIV (4 SHL 4) *)
+  strHeapBase := scopeHeapBase + (scopeCap + 1) * 16; (*{/*041b*/}*)
+  strCap := symHeapBase + freeBytes - strHeapBase - 1; (*{/*0423*/}*)
+  FILL(symHeapBase, freeBytes, 0); (*{/*042b*/} SYSTEM.FILL unofficial — kept *)
+END InitHeaps;
+
+PROCEDURE EnterStandards;
+(* proc24 @0448: rewire 14 builtins into scope slots 1..14 *)
+VAR stdNo: CARDINAL; (* slot 0 reserved *)
+
+  PROCEDURE InsertBuiltin(t: ADDRESS);
+  (* proc25 @0432, nested *)
+  VAR ent: ScopeEntryPtr;
+  BEGIN
+    ent := scopeHeapBase + stdNo * 16;
+    ent^.link := t; (* word 1 {/*0434*/} *)
+    INC(stdNo); (*{/*043e*/}*)
+  END InsertBuiltin;
+
+BEGIN
+  stdNo := 1;
+  InsertBuiltin(Compiler.IntType); (* word 6 {/*044e*/} *)
+  InsertBuiltin(Compiler.CharType); (* word 13 {/*0452*/} *)
+  InsertBuiltin(Compiler.BooleanType); (* word 14 {/*0456*/} *)
+  InsertBuiltin(Compiler.WordType); (* word 18 {/*045b*/} *)
+  InsertBuiltin(Compiler.AddressType); (* word 8 {/*045f*/} *)
+  InsertBuiltin(Compiler.addressAlias); (* word 17 {/*0464*/} *)
+  InsertBuiltin(Compiler.CardType); (* word 7 {/*0468*/} *)
+  InsertBuiltin(Compiler.BitsetType); (* word 15 {/*046c*/} *)
+  InsertBuiltin(Compiler.ProcType); (* word 16 {/*0471*/} *)
+  InsertBuiltin(Compiler.charArrayDesc); (* word 12 {/*0475*/} *)
+  InsertBuiltin(Compiler.ByteType); (* word 19 {/*047a*/} *)
+  InsertBuiltin(Compiler.RealType); (* word 9 {/*047e*/} *)
+  InsertBuiltin(Compiler.LongintType); (* word 10 {/*0482*/} *)
+  InsertBuiltin(Compiler.LongrealType); (* word 11 {/*0486*/} *)
+END EnterStandards;
+
+PROCEDURE LoadSymFile(VAR modName: ARRAY OF CHAR);
+(* proc26 @04a0 *)
+VAR
+  libFile: Loader.LibFile;  (* 292-byte handle {/*04a8*/}; see LOADER.DEF *)
+  header: ARRAY [0..7] OF CARDINAL; (* 16-byte .SYM header {/*04ad*/} *)
+  i: CARDINAL;
+  dest: ModuleSymPtr;
+
+  PROCEDURE LibRead(buf: ADDRESS; n: CARDINAL);
+  (* proc27 @048a, nested: libFile via parent frame *)
+  BEGIN
+    IF NOT Loader.LibRead(libFile, buf, n) THEN
+      RAISE symReadError; (* 790 {/*0497*/} *)
+    END;
+  END LibRead;
+
+BEGIN
+  IF NOT Loader.LibOpen(libFile, modName, TRUE) THEN
+    Errors.ReportErrorWithText(16, modName); (* falls through {/*04b1*/} *)
+  END;
+  LibRead(ADR(header), 16); (*{/*04c0*/}*)
+  symTop := header[1]; (*{/*04c5*/}*)
+  SymbolAssert(symTop <= symCap, 87);
+  scopeTop := header[2]; (*{/*04cf*/}*)
+  SymbolAssert(scopeTop <= scopeCap, 86);
+  strTop := header[3]; (*{/*04db*/}*)
+  SymbolAssert(strTop <= strCap, 85);
+  exportHead := header[4]; (*{/*04e7*/}*)
+  stageNameTotal := header[0]; (*{/*04eb*/}*)
+  savedSymInfo := header[5]; (*{/*04ef*/}*)
+  savedStrInfo := header[6]; (*{/*04f3*/}*)
+  LibRead(modStage, stageNameTotal * 10); (* module-name directory {/*04f7*/} *)
+  dest := modStage + 160; (* slot 16 {/*04fe*/} *)
+  FOR i := 0 TO 7 DO dest^.name[i] := 0C END; (*{/*0505*/}*)
+  i := 0;
+  WHILE (i <= 7) AND (modName[i] # 0C) DO (*{/*050e*/}*)
+    dest^.name[i] := CAP(modName[i]); (*{/*0519*/}*)
+    INC(i);
+  END;
+  dest^.tag := header[7]; (*{/*052c*/}*)
+  LibRead(symHeapBase + 14, symTop * 14); (* symbol entries 1.. {/*0530*/} *)
+  LibRead(scopeHeapBase + 240, (scopeTop - 14) * 16); (* scope entries 15.. {/*053a*/} *)
+  LibRead(strHeapBase + 1, strTop - 1); (* string pool {/*0547*/} *)
+  EnterStandards; (* rewire builtin slots 1..14 {/*054f*/} *)
+END LoadSymFile;
+PROCEDURE ImportAll(importRec: ADDRESS);
+(* proc28 @05dd *)
+VAR
+  imp: ImportPtr;
+  sysImp, stoImp: ImportPtr;
+  sysAdr: ADDRESS;
+  caseFlag: BOOLEAN;    (* 9 IN scanOptions {/*05df*/} *)
+  importEvery: BOOLEAN; (* bit 10 of flags {/*05e4*/} *)
+  modIdx: CARDINAL;
+  expIdx: CARDINAL;
+  scopeIdx: CARDINAL;
+  tmp: ADDRESS;
+  sib: CARDINAL;
+  findIdx: CARDINAL;
+  colCount: CARDINAL;
+  e, e2: SymEntryPtr;
+  s: ScopeEntryPtr;
+  ch: ChainPtr;
+  matched: CARDINAL;
+
+  PROCEDURE FindExport(start: CARDINAL; name: ADDRESS): CARDINAL;
+  (* proc29 @0553, nested *)
+  VAR e: SymEntryPtr; idx: CARDINAL;
+  BEGIN
+    idx := start;
+    WHILE idx # 0 DO
+      e := symHeapBase + idx * 14; (*{/*0559*/}*)
+      IF Scanner.StrCmp(StringPtr(symHeapBase + strHeapBase + e^.nameOff)^,
+                        StringPtr(name)^, 9 IN Scanner.scanOptions) THEN (*{/*0568*/}*)
+        RETURN idx;
+      END;
+      idx := e^.link; (*{/*0570*/}*)
+    END;
+    RETURN 0;
+  END FindExport;
+
+  PROCEDURE ListExports(first: CARDINAL);
+  (* proc30 @0579, nested: colCount via parent frame *)
+  VAR e: SymEntryPtr;
+  BEGIN
+    IF first # 0 THEN
+      e := symHeapBase + first * 14;
+      ListExports(e^.link); (*{/*0588*/}*)
+      IF 0 IN e^.flags THEN (*{/*058a*/}*)
+        IF colCount = 5 THEN (*{/*0590*/}*)
+          Texts.WriteLn(3); colCount := 0;
+        END;
+        Texts.SetCol(3, colCount * 16); (*{/*05a0*/}*)
+        Texts.WriteString(3, StringPtr(strHeapBase + e^.nameOff)^); (*{/*05af*/}*)
+        INC(colCount); (*{/*05b4*/}*)
+      END;
+    END;
+  END ListExports;
+
+  PROCEDURE ImportOne(anchor: ADDRESS; symIdx: CARDINAL);
+  (* proc31 @05be, nested. NOTE arg order flipped to B's EnterIdent(idx, scope);
+     original call order was (anchor, symIdx) — MCD diff decides. *)
+  VAR e: SymEntryPtr;
+  BEGIN
+    IF symIdx # 0 THEN
+      e := symHeapBase + symIdx * 14; (*{/*05c4*/}*)
+      ImportOne(anchor, e^.link); (*{/*05ca*/}*)
+      IF 0 IN e^.flags THEN (*{/*05d0*/}*)
+        EnterIdent(symIdx, anchor); (*{/*05d6*/}*)
+      END;
+    END;
+  END ImportOne;
+
+BEGIN
+  imp := importRec;
+  caseFlag := 9 IN Scanner.scanOptions;
+  importEvery := 10 IN imp^.flags;
+  sysAdr := ADDRESS(Compiler.systemModule); sysImp := sysAdr;
+  IF Scanner.StrCmp(StringPtr(imp^.name)^, StringPtr(sysImp^.name)^, caseFlag) THEN (*{/*05e9*/}*)
+    IF importEvery THEN (*{/*05f5*/}*)
+      imp^.flags := sysImp^.flags; (*{/*05f8*/}*)
+      imp^.anchor := sysImp^.anchor; (*{/*0604*/}*)
+      Compiler.compilationActive := FALSE; (* word 26 {/*0608*/} *)
+    ELSE
+      DefineDefinitionModule(Compiler.systemScopeDesc); (* proc2 {/*060a*/} *)
+    END;
+    RETURN; (*{/*060e*/}*)
+  END;
+  sysAdr := ADDRESS(Compiler.storageModule); stoImp := sysAdr;
+  IF Scanner.StrCmp(StringPtr(imp^.name)^, StringPtr(stoImp^.name)^, caseFlag) THEN (*{/*0611*/}*)
+    IF importEvery THEN (*{/*061d*/}*)
+      imp^.flags := stoImp^.flags;
+      imp^.anchor := stoImp^.anchor;
+      Compiler.compilationActive := FALSE;
+    ELSE
+      DefineDefinitionModule(Compiler.storageScopeDesc); (*{/*062e*/}*)
+    END;
+    RETURN;
+  END;
+  stageFlag := 1; (* word 25 {/*0635*/}; group C reads compileActive *)
+  InitHeaps; (*{/*0638*/}*)
+  LoadSymFile(StringPtr(imp^.name)^);
+  modIdx := EnterModule(16); (*{/*0640*/}*)
+  SymbolAssert(modIdx # 16, 48); (*{/*0644*/}*)
+  expIdx := exportHead; (*{/*064b*/}*)
+  WHILE expIdx # 0 DO (*{/*064e*/}*)
+    e := symHeapBase + expIdx * 14;
+    IF e^.flags * BITSET{1,6} # BITSET{} THEN (* AND 42H {/*0659*/} *)
+      e^.flags := e^.flags - BITSET{0}; (*{/*0663*/}*)
+    ELSE
+      e^.flags := e^.flags + BITSET{0,1}; (*{/*066e*/}*)
+    END;
+    IF (e^.kind = 2) OR (e^.kind = 4) THEN
+      e^.aux2 := modIdx; (* word 6 {/*067e*/} *)
+    ELSIF e^.kind = 5 THEN
+      e^.aux1 := e^.aux1 + (CARDINAL(CHR(modIdx)) - e^.aux1 MOD 256);
+      (* byte 11 := modIdx {/*068b*/} *)
+    END;
+    expIdx := e^.link; (*{/*0692*/}*)
+  END;
+  scopeIdx := 15;
+  WHILE scopeIdx <= scopeTop DO (*{/*069a*/}*)
+    s := scopeHeapBase + scopeIdx * 16;
+    matched := LookupChain(FindModule(ORD(s^.slot)), ORD(s^.fresh)); (*{/*06ab*/}*)
+    IF matched # 0 THEN (*{/*06b2*/}*)
+      s^.link := matched; s^.fresh := 0C;
+    END;
+    INC(scopeIdx);
+  END;
+  IF importEvery THEN (*{/*06c4*/}*)
+    ImportOne(ADR(imp^.anchor), exportHead); (* NOTE order: see ImportOne note *)
+  ELSE
+    REPEAT
+      Scanner.NeedIdentifier; (*{/*06d0*/}*)
+      findIdx := FindExport(exportHead, ADR(Scanner.tokenBuffer)); (*{/*06d2*/}*)
+      e := symHeapBase + findIdx * 14;
+      IF (findIdx = 0) OR NOT (0 IN e^.flags) THEN (*{/*06e0*/}*)
+        Errors.PrintOverlayMessage(3, Scanner.tokenBuffer); (*{/*06ed*/}*)
+        Texts.WriteLn(3);
+        Texts.WriteLn(3);
+        Texts.WriteString(3, "Exported are:"); (*{/*06f9*/}*)
+        colCount := 5; (*{/*070d*/}*)
+        ListExports(exportHead); (*{/*070e*/}*)
+        Texts.WriteLn(3);
+        Errors.AskEditOrQuit; (*{/*0715*/}*)
+      END;
+      IF (e^.kind = 3) AND (ScopeEntryPtr(scopeHeapBase + e^.ref * 16)^.state = 0) THEN (*{/*0717*/}*)
+        sib := e^.link; (*{/*0729*/}*)
+        WHILE sib # 0 DO
+          e2 := symHeapBase + sib * 14;
+          IF (e2^.ref # e^.ref) OR NOT (9 IN e2^.flags) THEN EXIT END; (*{/*072d*/}*)
+          EnterIdent(sib, ADR(currentScope^.link1)); (*{/*0747*/}*)
+          sib := e2^.link;
+        END;
+      END;
+      EnterIdent(findIdx, ADR(currentScope^.link1)); (*{/*0756*/}*)
+      Scanner.GetSym; (*{/*075c*/}*)
+      Scanner.TestSymbolInSet(BITSET{1,9}); (*{/*075e*/}*)
+    UNTIL NOT Scanner.AcceptSymbol(1); (* "," {/*0763*/} *)
+    Scanner.GetSym; (*{/*0769*/}*)
+  END;
+  scopeIdx := 15;
+  WHILE scopeIdx <= scopeTop DO (*{/*076b*/}*)
+    s := scopeHeapBase + scopeIdx * 16;
+    IF (s^.link # 0) AND (s^.fresh # 0C) THEN (*{/*077a*/}*)
+      modIdx := EnterModule(ORD(s^.slot)); (*{/*0787*/}*)
+      Scanner.Allocate(tmp, 6); (*{/*078d*/}*)
+      ch := tmp;
+      ch^.next := ChainPtr(scopeChain^[modIdx]); (*{/*0792*/}*)
+      ch^.key := ORD(s^.fresh);
+      ch^.ref := s^.link;
+      scopeChain^[modIdx] := ch; (*{/*07a2*/}*)
+    END;
+    INC(scopeIdx);
+  END;
+END ImportAll;
+
+PROCEDURE SkipQualident(scope: ADDRESS): ADDRESS;
+(* proc32 @07ad — kept group C's version (exact offsets); group D's
+   duplicate reading of the same range dropped (see header note). *)
+VAR scp: ScopePtr; found: Compiler.RecordPtr;
+BEGIN
+  LOOP
+    scp := scope;
+    found := Scanner.FindIdent(scp^.idents, ADR(Scanner.tokenBuffer),
+                               9 IN Scanner.scanOptions); (*{/*07af*/}*)
+    IF found # NIL THEN RETURN ADDRESS(found) END; (*{/*07bc*/}*)
+    scope := ADDRESS(scp^.outer); (*{/*07c3*/}*)
+    IF (scope = ADDRESS(NIL)) OR (scope = ADDRESS(Compiler.stdScopeAnchor)) THEN (*{/*07c7*/}*)
+      Errors.ReportErrorWithText(0, Scanner.tokenBuffer); (*{/*07d0*/}*)
+    END;
+  END;
+END SkipQualident;
+
+(* ================= group D: orchestration (@07d8-end) ================= *)
+(* Cleaned: GetStackMark VAR-param form, CopyStringToHeap(dest, src) order,
+   HIGH() bounds stripped from ReportErrorWithText, Scanner.Allocate used
+   where the group wrote HeapAlloc. Remaining dubious calls flagged FIXME. *)
+
+PROCEDURE DefineDefinitionModule(isImpl: CARDINAL);
+(* proc2 @07d8. FIXME: isImpl compared against Compiler.systemScope
+   (a RecordPtr) in one branch — role of this param is shaky; MCD diff
+   against @07d8 must confirm. *)
+VAR node, chain, tmp: T1;
+BEGIN
+  IF Scanner.AcceptSymbol(15) THEN (* DEFINITION {/*07d8*/} *)
+    Scanner.NeedIdentifier;
+    node := T1(SkipQualident(ADDRESS(Scanner.curNode)));
+    Scanner.ExpectIdentKind(6);
+    SymbolAssert(10 IN node^.word3, 44); (*{/*07f0*/}*)
+    isImpl := node^.word5;
+    Scanner.GetSym;
+    Scanner.ExpectSymbol(16);
+  END;
+  Scanner.NeedIdentifier; (*{/*080a*/} common tail *)
+  node := T1(SkipQualident(ADDRESS(Scanner.curNode)));
+  SymbolAssert((node^.word4 <> 6) OR (10 IN node^.word3), 44);
+  IF (node^.word4 = 3) AND (node^.link2 = NIL) THEN
+    chain := node^.link0; (*{/*0829*/}*)
+    WHILE LookupChain(CARDINAL(chain), CARDINAL(node^.link2)) <> NIL DO
+      Scanner.Allocate(tmp, 14);
+      tmp^ := chain^; Scanner.InsertSymbol(tmp);
+      INCL(tmp^.word3, 0);
+      chain := chain^.link0;
+    END;
+  ELSIF (isImpl = CARDINAL(Compiler.systemScopeDesc)) AND (node^.word4 = 5)
+      AND (node^.word5 = 16) THEN
+    Compiler.compilationActive := 0;
+  END;
+  Scanner.Allocate(tmp, 14); (*{/*0865*/}*)
+  tmp^ := node^; Scanner.InsertSymbol(tmp);
+  INCL(tmp^.word3, 0);
+  Scanner.GetSym;
+  Scanner.TestSymbolInSet(BITSET{2,9}); (* FIXME: group wrote 514; BITSET{2,9} guesses bits — confirm *)
+  WHILE NOT Scanner.AcceptSymbol(1) DO (*{/*087b*/}*)
+    Scanner.NeedIdentifier;
+    node := T1(SkipQualident(ADDRESS(Scanner.curNode)));
+    SymbolAssert((node^.word4 <> 6) OR (10 IN node^.word3), 44);
+    tmp^ := node^; Scanner.InsertSymbol(tmp);
+    INCL(tmp^.word3, 0);
+    Scanner.GetSym;
+    Scanner.TestSymbolInSet(BITSET{2,9});
+  END;
+  Scanner.GetSym;
+END DefineDefinitionModule;
+
+PROCEDURE BuildBitsetConst(VAR name: ARRAY OF CHAR): BOOLEAN;
+(* proc5 @088a *)
+BEGIN
+  RETURN Scanner.FindIdent(NIL, ADR(name), 9 IN Scanner.scanOptions) <> NIL;
+  (* FIXME: FindIdent(list, identifier, case) — group passed (ADR(name), case)
+     i.e. list/name swapped; order TBD from @088a. *)
+END BuildBitsetConst;
+
+PROCEDURE DefineProgramModule(fromSeen: CARDINAL);
+(* proc3 @089a. FIXME: heavy FIXME — group D's reading of @089a-0914 tangles
+   moduleTable^[16] (a Symbol slot) with T1 chain operations; treat as
+   first-draft sketch, not ground truth. *)
+VAR dup: BOOLEAN; mark, node, tmp: T1; i: CARDINAL;
+BEGIN
+  modStage^[16].word := 0; (*{/*089a*/} FIXME: group wrote moduleTable^[16] := 0 *)
+  Scanner.GetSym;
+  fromSeen := ORD(Scanner.AcceptSymbol(26));
+  IF fromSeen # 0 THEN INCL(Scanner.followSet, 10) END; (*{/*08a8*/}*)
+  Scanner.NeedIdentifier;
+  IF BuildBitsetConst(Scanner.tokenBuffer) THEN
+    Errors.ReportErrorWithText(15, Scanner.tokenBuffer); (*{/*08b6*/}*)
+  END;
+  Scanner.Allocate(tmp, 6);
+  Scanner.CopyStringToHeap(Scanner.tokenBuffer, tmp); (* order TBD *)
+  Scanner.GetSym; (*{/*0902*/} ident list loop *)
+  Scanner.TestSymbolInSet(BITSET{2,9});
+  WHILE NOT Scanner.AcceptSymbol(1) DO (*{/*0904*/}*)
+    Scanner.NeedIdentifier;
+    IF BuildBitsetConst(Scanner.tokenBuffer) THEN
+      Errors.ReportErrorWithText(15, Scanner.tokenBuffer);
+    END;
+    Scanner.GetSym;
+    Scanner.TestSymbolInSet(BITSET{2,9});
+  END;
+  Scanner.GetSym;
+  (* FIXME: FROM/dup branches ({08d1..08ec}) omitted — re-read @08c0-0902. *)
+END DefineProgramModule;
+
+PROCEDURE InitSymbolTables(opt: BITSET; imports: T1; modName: T1);
+(* proc4 @0914 *)
+VAR node, chain, slot: T1; mark: ADDRESS;
+BEGIN
+  Compiler.compilationActive := TRUE; (* word26 {/*0914*/} *)
+  InitHeaps;
+  symTop := 0; scopeTop := 0; strTop := 1;
+  symOptions := opt;
+  exportHead := 0; (* FIXME: group wrote modExpCount := 0; unified with exportHead (word18) *)
+  IF imports <> NIL THEN (*{/*0928*/}*)
+    WHILE imports <> NIL DO
+      currentScope^.link1 :=
+        Scanner.FindIdent(NIL, imports^.link1, 9 IN Scanner.scanOptions);
+      (* FIXME: list arg TBD; group wrote FindIdent(imports^.word1, ...) *)
+      node := currentScope^.link1;
+      IF node = NIL THEN
+        Errors.ReportErrorWithText(6, StringPtr(imports^.link1)^);
+      END;
+      SymbolAssert((node^.word4 <> 6) OR (10 IN node^.word3), 43);
+      INCL(node^.word3, 0);
+      chain := imports^.link0; imports := chain;
+    END;
+  END;
+  Scanner.GetStackMark(mark);
+  Scanner.CheckStackMark(mark); (* FIXME: group passed symOptions; mark form kept *)
+  UnlinkScope(CARDINAL(modName), modName); (* FIXME: args TBD; proc19 *)
+END InitSymbolTables;
+
+PROCEDURE NoteImported;
+(* proc34 @0997 *)
+BEGIN
+  IF NOT (0 IN Scanner.scanOptions) THEN RETURN END;
+  IF Compiler.compilationActive THEN
+    Texts.WriteString(3, "Imported:");
+    Compiler.compilationActive := FALSE;
+  END;
+  Texts.WriteChar(3, 2C); Texts.WriteChar(3, 12C);
+  Texts.WriteString(3, Scanner.tokenBuffer);
+  Texts.WriteLn(3);
+END NoteImported;
+
+PROCEDURE DoImportList;
+(* proc33 @09c8 *)
+VAR node: T1;
+BEGIN
+  IF Scanner.AcceptSymbol(15) THEN (* FROM x IMPORT {/*09c8*/} *)
+    Scanner.NeedIdentifier;
+    Scanner.Allocate(node, 6);
+    Scanner.CopyStringToHeap(Scanner.tokenBuffer, node); (* order TBD *)
+    NoteImported;
+    Scanner.ExpectSymbol(16);
+    ImportAll(node); (* FIXME: group wrote ImportOne(node); @09c8 calls proc28=ImportAll *)
+  ELSE (* plain IMPORT {/*09ec*/} *)
+    Scanner.GetSym;
+    NoteImported;
+    node := Scanner.DeclareIdentifier(6);
+    node^.word3 := BITSET{3,10}; (* FIXME: group wrote CARDINAL({3,10})=1027 *)
+    ImportAll(node);
+    Scanner.TestSymbolInSet(BITSET{2,9});
+    WHILE NOT Scanner.AcceptSymbol(1) DO (*{/*0a00*/}*)
+      Scanner.GetSym;
+      NoteImported;
+      node := Scanner.DeclareIdentifier(6);
+      node^.word3 := BITSET{3,10};
+      ImportAll(node);
+      Scanner.TestSymbolInSet(BITSET{2,9});
+    END;
+    Scanner.GetSym;
+  END;
+END DoImportList;
+
+PROCEDURE CompileBody(save: T1);
+(* proc35 @0a0c. FIXME sketch: @0a0c-0aa7 register juggling (scopeBase/rollback
+   chaining, marks) needs a second pass; control flow below follows the
+   group's reading but details are low-confidence. *)
+VAR mark: ADDRESS; i, j: CARDINAL; sc: T1; desc: T1;
+BEGIN
+  Scanner.GetStackMark(mark); (*{/*0a0c*/}*)
+  WHILE (Scanner.curSymbol - 15) IN BITSET{3} DO DoImportList END; (*{/*0a12*/}*)
+  CodeGen.InitCodeGenerator;
+  Scanner.TestSymbolRange13(BITSET{0..8}); (* FIXME: 495/483 sets TBD *)
+  Scanner.TestSymbolRange13(BITSET{0..8});
+  IF Scanner.curSymbol <> 13 THEN (* FIXME: COMPILER.bodyHook() — no such export; Pass1 DCLS hook? *) END;
+  WHILE Scanner.AcceptSymbol(13) DO
+    save^.link1 := ADDRESS(Scanner.tokenBuffer); (* FIXME: types punned *)
+    Scanner.TestSymbolInSet(BITSET{3});
+  END;
+  InitHeaps; EnterStandards;
+  symTop := 0; scopeTop := 14; strTop := 1; exportHead := 0;
+  currentScope^.link1 := MatchSym(desc, save^.link1);
+  i := 0;
+  WHILE i < moduleCount DO
+    sc := scopeChain^[i];
+    WHILE sc <> NIL DO
+      sc^.link2 := NIL; sc^.link1 := NIL; sc := sc^.link0;
+    END;
+    INC(i);
+  END;
+END CompileBody;
+
+PROCEDURE CheckForwards;
+(* proc36 @0aa7. FIXME sketch, see CompileBody note. *)
+VAR i: CARDINAL; base: T1; slot: ADDRESS;
+BEGIN
+  Compiler.compilationActive := FALSE; (*{/*0aa7*/}*)
+  InitHeaps;
+  (* LoadSymFile(modNameBuf, 7); — group D's line; signature is (VAR modName),
+     second arg unknown. Omitted pending re-read. *)
+  i := 1;
+  WHILE i <= symTop DO (*{/*0ac2*/}*)
+    base := T1(symHeapBase + i * 14);
+    IF base^.word4 = 5 THEN INCL(base^.word3, 5) END;
+    INC(i);
+  END;
+  UnlinkScope(CARDINAL(currentScope^.link1), currentScope^.link1); (* FIXME args *)
+  i := 15;
+  WHILE i <= scopeTop DO (*{/*0ae8*/}*)
+    base := T1(scopeHeapBase + i * 16);
+    Scanner.Allocate(slot, 6);
+    Words(slot)^[0] := Words(scopeChain^[Words(base)^[0]])^[0];
+    Words(slot)^[1] := Words(base)^[1];
+    Words(slot)^[2] := CARDINAL(base^.link1);
+    scopeChain^[Words(base)^[0]] := slot;
+    INC(i);
+  END;
+END CheckForwards;
+
+PROCEDURE TryCompile(): BOOLEAN;
+(* IMDCLS @0b1c. FIXME sketch: frame-37 loop + error-growth recovery. *)
+VAR saveOpt: BITSET; savePos: CARDINAL; saveMark: ADDRESS;
+    saveScope: T1; saveCount, saveErr: CARDINAL;
+BEGIN
+  saveOpt := Scanner.scanOptions; savePos := Scanner.tokenPos;
+  Scanner.GetStackMark(saveMark);
+  saveScope := currentScope^.link1; saveCount := moduleCount;
+  saveErr := Errors.errorNumber;
+  IF (Scanner.curSymbol - 15) IN BITSET{3} THEN
+    DoImportList;
+    LOOP
+      (* FIXME: BodyParseStep(saveMark) — Pass1 DCLS body hook, see FRONTEND.md *)
+      EXIT;
+    END;
+  ELSE
+    Scanner.TestSymbolRange13(BITSET{0..10}); (* FIXME: 2031 set TBD *)
+  END;
+  RETURN TRUE;
+EXCEPTION
+  moduleCount := saveCount; currentScope^.link1 := saveScope;
+  IF Errors.errorNumber > saveErr THEN (*{/*0b4c*/}*)
+    Scanner.tokenPos := savePos; Scanner.scanOptions := saveOpt;
+    Scanner.CheckStackMark(saveMark);
+    Errors.RollbackToMark;
+    CodeGen.OpenEmitter;
+    RETURN FALSE;
+  END;
+  HALT; HALT;
+END TryCompile;
+
+PROCEDURE CompileDispatch(VAR wsp: ARRAY OF BYTE; impl: BOOLEAN; prog: BOOLEAN);
+(* proc1 @0b6e *)
+BEGIN
+  Loader.InitLibs(ADR(wsp), HIGH(wsp) + 1);
+  Compiler.compilationActive := TRUE;
+  IF Compiler.scopeSpare3 THEN CompileBody(T1(impl)); (* word3 flag; FIXME arg *)
+  ELSIF prog THEN CheckForwards
+  ELSE
+    Errors.doSymtabRollback := TRUE; (*{/*0b8c*/}*)
+    REPEAT UNTIL TryCompile; (*{/*0b8f*/}*)
+    Errors.doSymtabRollback := FALSE;
+  END;
+END CompileDispatch;
+
+PROCEDURE Checksum7w(ptr: ADDRESS; nwords: CARDINAL);
+(* proc38 @0b99 — FIXME: LSL is not SYSTEM; original shift primitive TBD *)
+VAR acc: CARDINAL;
+BEGIN
+  WHILE nwords >= 14 DO (* FIXME: group condition ptr^ >= 14 kept as count form *)
+    acc := checksumTemp + Words(ptr)^[0] + Words(ptr)^[1] * 2 + Words(ptr)^[2] * 4
+         + Words(ptr)^[3] * 8 + Words(ptr)^[4] * 16 + Words(ptr)^[5] * 32
+         + Words(ptr)^[6] * 64;
+    checksumTemp := acc;
+    ptr := ptr + 14; nwords := nwords - 14;
+  END;
+END Checksum7w;
+
+PROCEDURE SaveSymbolFile;
+(* proc6 @0bdb *)
+VAR hdr: ARRAY [0..15] OF WORD;
+BEGIN
+  checksumTemp := 0;
+  Checksum7w(ADR(moduleTable^), moduleCount * 10);
+  Checksum7w(symHeapBase + 14, symTop * 14);
+  Checksum7w(scopeHeapBase + 240, (scopeTop - 14) * 16);
+  Checksum7w(strHeapBase + 1, strTop - 1);
+  hdr[0] := moduleCount; hdr[1] := symTop;
+  hdr[2] := scopeTop; hdr[3] := strTop;
+  hdr[4] := exportHead; hdr[5] := savedSymInfo; hdr[6] := savedStrInfo;
+  hdr[7] := checksumTemp;
+  Files.WriteBytes(Scanner.codeFile, ADR(hdr), 7);
+  Files.WriteBytes(Scanner.codeFile, ADR(moduleTable^), moduleCount * 10);
+  Files.WriteBytes(Scanner.codeFile, symHeapBase + 14, symTop * 14);
+  Files.WriteBytes(Scanner.codeFile, scopeHeapBase + 240, (scopeTop - 14) * 16);
+  Files.WriteBytes(Scanner.codeFile, strHeapBase + 1, strTop - 1);
+END SaveSymbolFile;
+
+PROCEDURE RestoreAfterEdit;
+(* proc7 @0c5e — Edit recovery: unlock only, heaps already rolled back. *)
+BEGIN
+END RestoreAfterEdit;
+
+END SymTab.