Преглед изворни кода

two-phase slice 6: declaration AST (const/type/var/proc)

DeclSeq builds NkDeclSeq; CONST/TYPE/VAR/PROC build NkConstDecl,
NkTypeDecl, NkVarDecl and NkProcDecl.  Declaration nodes reference the
resolved SymTab.TypeIndex via their ty field instead of building
structural type trees.  Block resets astStmt after its DeclSeq so a
body-less procedure yields NoNode.  Nodes are inert.

Deferred: unit/NkUnit root, imports, CLASS, nested modules; remaining
statements/expressions; Lower.

Gates: suite 178/178; fixpoint OK (stage2 == stage3, 3007851 bytes).

See docs/summary_two-phase-slice6.md.
Eric Streit пре 1 недеља
родитељ
комит
fff907760a
5 измењених фајлова са 3571 додато и 3393 уклоњено
  1. 74 19
      compiler/src/M2.atg
  2. 3424 3369
      compiler/src/M2.lst
  3. 5 3
      docs/plan-two-phase.md
  4. 4 2
      docs/session-handoff-2026-10-03.md
  5. 64 0
      docs/summary_two-phase-slice6.md

+ 74 - 19
compiler/src/M2.atg

@@ -77,6 +77,10 @@ VAR
      statement.  StatSeq folds these into an NkBlock. *)
   astStmt: AST.Node;
 
+  (* Declaration-AST result slot (slice 6): the most recently built
+     declaration; DeclSeq folds these into an NkDeclSeq. *)
+  astDecl: AST.Node;
+
   (* Class of the method named by the last `obj.Method` designator
      (InvalidType when the callee is an ordinary procedure). Set by
      Design, consumed by the following ArgList. *)
@@ -186,7 +190,7 @@ TOKENS
 
 PRODUCTIONS
   M2
-    = (. AST.Init; twoPhase := TRUE; astCur := AST.NoNode; astStmt := AST.NoNode; .)
+    = (. AST.Init; twoPhase := TRUE; astCur := AST.NoNode; astStmt := AST.NoNode; astDecl := AST.NoNode; .)
       Unit "." .
   (* Units: program modules compile fully; DEFINITION and
      IMPLEMENTATION modules parse + check now but lower in step 4
@@ -292,9 +296,15 @@ PRODUCTIONS
                                              SemError(202) END;
                                            QbeGen.EndModule(m1);
                                            SymTab.EndUnit; .) .
-  DeclSeq
-    = { ConstBlock | TypeBlock<FALSE> | VarBlock | ProcDecl ";"
-      | NestedModule ";" | ClassItem ";" } .
+  DeclSeq                               (. VAR astSeq: AST.Node; .)
+    =                                   (. astSeq := AST.MakeNode(AST.NkDeclSeq); .)
+      { (. astDecl := AST.NoNode; .)
+        ( ConstBlock | TypeBlock<FALSE> | VarBlock | ProcDecl ";"
+        | NestedModule ";" | ClassItem ";" )
+                                        (. IF astDecl # AST.NoNode THEN
+                                             AST.SetChild(astSeq,
+                                               AST.NChild(astSeq), astDecl) END; .) }
+                                        (. astDecl := astSeq; .) .
   (* Local module, Wirth form. Declarations lower like top-level ones
      (same QBE module prefix); a BEGIN body becomes an init function
      that main calls; the EXPORT list is hoisted into the enclosing
@@ -374,14 +384,20 @@ PRODUCTIONS
       { TypeItem<isDef> ";" | ClassItem ";" }
                                         (. SymTab.EndTypeBlock; .) .
   TypeItem<isDef: BOOLEAN>              (. VAR n: SymTab.Name;
-                                             t, op: SymTab.TypeIndex; .)
+                                             t, op: SymTab.TypeIndex;
+                                             astNode: AST.Node; .)
     = GetIdent<n>                       (. op := SymTab.OpaqueBase(n);
                                            IF op = SymTab.InvalidType THEN
                                              IF NOT SymTab.Enter(n,
                                                 SymTab.KindType) THEN
                                                SemError(200) END
                                            END; .)
-      ( "=" Type<t, FALSE>              (. IF op # SymTab.InvalidType THEN
+      ( "=" Type<t, FALSE> (. astNode := AST.MakeNode(AST.NkTypeDecl);
+                                             AST.SetChild(astNode, 0,
+                                               AST.MakeLeaf(AST.NkIdent, n));
+                                             AST.SetTy(astNode, t);
+                                             astDecl := astNode;
+                                             IF op # SymTab.InvalidType THEN
                                              SymTab.SetTarget(op, t)
                                            ELSE SymTab.SetSymType(n, t)
                                            END; .)
@@ -802,17 +818,29 @@ PRODUCTIONS
                                            methRes := SymTab.CurRes();
                                            SymTab.CloseProc;
                                            QbeGen.EndFunc(methRes); .) ) .
-  ConstBlock
-    = "CONST" { ConstDecl ";" } .
+  ConstBlock                            (. VAR astSeq: AST.Node; .)
+    = "CONST"                           (. astSeq := AST.MakeNode(AST.NkDeclSeq);
+                                           astDecl := AST.NoNode; .)
+      { ConstDecl ";"                   (. IF astDecl # AST.NoNode THEN
+                                             AST.SetChild(astSeq,
+                                               AST.NChild(astSeq), astDecl) END; .) }
+                                        (. astDecl := astSeq; .) .
   ConstDecl                             (. VAR n: SymTab.Name;
                                              t: SymTab.TypeIndex;
                                              qv: QbeGen.QVal;
-                                             cls: INTEGER; .)
+                                             cls: INTEGER;
+                                             astNode: AST.Node; .)
     = GetIdent<n>                       (. IF NOT SymTab.Enter(n,
                                              SymTab.KindConst) THEN
                                              SemError(200) END; .)
       "="
-      Expr<t, qv>                       (. SymTab.SetSymType(n, t);
+      Expr<t, qv> (. astNode := AST.MakeNode(AST.NkConstDecl);
+                                           AST.SetChild(astNode, 0,
+                                             AST.MakeLeaf(AST.NkIdent, n));
+                                           AST.SetChild(astNode, 1, astCur);
+                                           AST.SetTy(astNode, t);
+                                           astDecl := astNode;
+                                           SymTab.SetSymType(n, t);
                                            cls := SymTab.ClassOf(t);
                                            IF (cls = SymTab.ClArray)
                                               OR (cls = SymTab.ClRecord)
@@ -830,14 +858,31 @@ PRODUCTIONS
                                              SymTab.SetSymVal(n, qv);
                                              QbeGen.DeclConst(n, qv, t)
                                            END; .) .
-  VarBlock
-    = "VAR" { VarDecl ";" } .
+  VarBlock                              (. VAR astSeq: AST.Node; .)
+    = "VAR"                             (. astSeq := AST.MakeNode(AST.NkDeclSeq);
+                                           astDecl := AST.NoNode; .)
+      { VarDecl ";"                     (. IF astDecl # AST.NoNode THEN
+                                             AST.SetChild(astSeq,
+                                               AST.NChild(astSeq), astDecl) END; .) }
+                                        (. astDecl := astSeq; .) .
   VarDecl                               (. VAR nm: SymTab.Name;
                                              t: SymTab.TypeIndex;
                                              i: CARDINAL;
-                                             cls: INTEGER; .)
+                                             cls: INTEGER;
+                                             astNode: AST.Node; .)
     = VarIdents ":"
-      Type<t, FALSE>                    (. cls := SymTab.ClassOf(t);
+      Type<t, FALSE> (. astNode := AST.MakeNode(AST.NkVarDecl);
+                                           i := 0;
+                                           WHILE i < SymTab.PendCount() DO
+                                             SymTab.PendName(i, nm);
+                                             AST.SetChild(astNode,
+                                               AST.NChild(astNode),
+                                               AST.MakeLeaf(AST.NkIdent, nm));
+                                             INC(i)
+                                           END;
+                                           AST.SetTy(astNode, t);
+                                           astDecl := astNode;
+                                           cls := SymTab.ClassOf(t);
                                            IF (t # SymTab.InvalidType)
  AND NOT SymTab.IsUnresolved(t)
  AND (cls # SymTab.ClInt)
@@ -986,20 +1031,30 @@ PRODUCTIONS
   (* Nested procedures lower like top-level ones (4.2): the
      static link gives them their parent's frame. Methods keep
      parse-now/230-later. *)
-  ProcDecl                              (. VAR pn: SymTab.Name; .)
+  ProcDecl                              (. VAR pn: SymTab.Name;
+                                             astNode: AST.Node; .)
     = ProcHeading<pn, SymTab.InvalidType> ";"
-      ( "FORWARD"                       (. SymTab.MarkFwd;
+                                        (. astNode := AST.MakeNode(AST.NkProcDecl);
+                                           AST.SetChild(astNode, 0,
+                                             AST.MakeLeaf(AST.NkIdent, pn)); .)
+      ( "FORWARD"                       (. AST.SetOp(astNode, 1);
+                                           astDecl := astNode;
+                                           SymTab.MarkFwd;
                                            SymTab.CloseProc;
                                            QbeGen.AbortFunc; .)
-      | "EXTERNAL"                      (. SymTab.MarkExternal("");
+      | "EXTERNAL"                      (. AST.SetOp(astNode, 2);
+                                           astDecl := astNode;
+                                           SymTab.MarkExternal("");
                                            SymTab.CloseProc;
                                            QbeGen.AbortFunc; .)
       |                                 (. QbeGen.EndFuncHeader; .)
-        Block<pn>                       (. SymTab.CloseProc;
+        Block<pn>                       (. AST.SetChild(astNode, 1, astStmt);
+                                           astDecl := astNode;
+                                           SymTab.CloseProc;
                                            QbeGen.EndFunc(
                                              SymTab.ProcRes(pn)); .) ) .
   Block<pn: SymTab.Name>                (. VAR m2: SymTab.Name; .)
-    = DeclSeq
+    = DeclSeq                           (. astStmt := AST.NoNode; .)
       [ "BEGIN"
         [ StatSeq ] ]
       "END"

Разлика између датотеке није приказан због своје велике величине
+ 3424 - 3369
compiler/src/M2.lst


+ 5 - 3
docs/plan-two-phase.md

@@ -86,9 +86,11 @@ Suggested slices, each ending in `build.sh` + `run_tests.sh` +
    literals, `ResultComp`, and `Lower`.  Green (178/178, fixpoint OK).
    Finding: **local arrays in a PROCEDURE are miscompiled**
    (`docs/wip/local-array-bug.mod`) — pre-existing, worked around here.
-4. **Declarations → AST**: `ConstBlock`/`TypeBlock`/`VarBlock`/
-   `ProcDecl`; the unit's decls complete SymTab before any body is
-   lowered.
+4. **Declarations → AST** — PARTIAL (branch `ast-stage-c`,
+   `docs/summary_two-phase-slice6.md`): `DeclSeq` builds `NkDeclSeq`;
+   `CONST`/`TYPE`/`VAR`/`PROC` build `NkConstDecl`/`NkTypeDecl`/
+   `NkVarDecl`/`NkProcDecl` (types referenced by the node `ty` index).
+   Deferred: unit/`NkUnit` root, imports, `CLASS`, nested modules.
 5. **Flip**: bodies lower from the AST; delete `FwdPatchAll` /
    `FixLoadClass` / `FixStoreClass`, the `Design` forward-variable
    placeholder, and `pendVar*` if superseded.

+ 4 - 2
docs/session-handoff-2026-10-03.md

@@ -1,7 +1,7 @@
 # Session handoff — 2026-10-03 (two-phase frontend, slices 1–5)
 
 State: branch **`ast-stage-c`** (off `1c93f19`, not merged to `master`).
-Suite **178/178**; self-hosting fixpoint **OK** (**3,000,988 bytes**).
+Suite **178/178**; self-hosting fixpoint **OK** (**3,007,851 bytes**).
 Working tree clean except the user's `compiler/toto.mod`.
 
 Plan of record: `docs/plan-two-phase.md`.  Start: §4 slice 3
@@ -16,6 +16,7 @@ Plan of record: `docs/plan-two-phase.md`.  Start: §4 slice 3
 | `v3-ast-slice3` | `summary_two-phase-slice3.md` | `Design`: identifiers, `[i]`, `.f`, `^`, qualified names |
 | `v3-ast-slice4` | `summary_two-phase-slice4.md` | calls/`NkCall` with actuals, `NOT`, builtins as named `NkCall`s |
 | `v3-ast-slice5` | `summary_two-phase-slice5.md` | statements: `NkBlock`, assign, `IF`/`WHILE`/`REPEAT`/`LOOP`/`EXIT`/`FOR`/`RETURN`/`HALT`, calls |
+| `v3-ast-slice6` | `summary_two-phase-slice6.md` | declarations: `NkDeclSeq`, `NkConstDecl`, `NkTypeDecl`, `NkVarDecl`, `NkProcDecl` (types by `ty` index) |
 
 The AST is **built alongside** the existing lowering; nothing consumes it
 yet.  Each slice was gated by `build.sh` + `run_tests.sh` + `fixpoint.sh`,
@@ -26,7 +27,8 @@ production (removed before each commit).
 
 - Statements: `CASE`, `WITH`, `INCL`/`EXCL`/`INC`/`DEC`/`NEW`/`DISPOSE`.
 - Expressions: brace/set literals, `ResultComp` call suffixes.
-- Declarations (`CONST`/`TYPE`/`VAR`/`PROC` → AST).
+- Declarations: unit/`NkUnit` root, imports, `CLASS`, nested modules
+  (`CONST`/`TYPE`/`VAR`/`PROC` are done as of slice 6).
 - **`Lower` + `.ssa` byte-compare** (the point where the old emit path
   gets deleted) — not started.
 

+ 64 - 0
docs/summary_two-phase-slice6.md

@@ -0,0 +1,64 @@
+# Two-phase refactor — slice 6 (declarations → AST)
+
+Branch `ast-stage-c`.  Suite **178/178**; self-hosting fixpoint **OK**
+(**3,007,851 bytes**; slice 5 was 3,000,988).
+
+## What landed
+
+Declarations now build AST nodes.  A module-level `astDecl` carries each
+declaration's node; `DeclSeq` folds them into an `NkDeclSeq`.
+
+| Declaration | AST |
+| --- | --- |
+| `DeclSeq` | `NkDeclSeq` of the contained declarations |
+| `CONST …` | `NkDeclSeq` of `NkConstDecl(name=Ident, value=expr)`, `ty` = const type |
+| `TYPE T = …` | `NkTypeDecl(name=Ident)`, `ty` = the resolved `SymTab.TypeIndex` |
+| `VAR x, y : T` | `NkVarDecl(names = Ident… )`, `ty` = the type index |
+| `PROCEDURE P …` | `NkProcDecl(name=Ident, body)`, `op` = 0 normal / 1 forward / 2 external |
+
+Each block (`CONST`/`VAR`) that holds several declarations builds its own
+`NkDeclSeq`; `DeclSeq` adds one node per block.  A leading
+`astDecl := NoNode` in `DeclSeq`/statement loops prevents stale results.
+
+### Design note — types by index, not by tree
+
+Declaration nodes do **not** build structural type-constructor trees
+(`NkRecord`, `NkArray`, …).  They record the *resolved*
+`SymTab.TypeIndex` in the node's `ty` field, which is exactly what
+`AST.def` intends (`ty : ` a `SymTab.TypeIndex` set by the checker).
+This keeps declarations cheap and avoids duplicating the type table; a
+future `Lower` can read `ty` directly.
+
+`Block` now resets `astStmt := NoNode` after its `DeclSeq`, so a
+procedure body with no `BEGIN` yields `NoNode` instead of a stale node
+from a nested declaration.
+
+## Verified (temporary `AST.Dump`, then removed)
+
+```modula2
+CONST c = 5;
+TYPE R = RECORD f : INTEGER END;
+VAR x : INTEGER;
+PROCEDURE P(a : INTEGER); BEGIN x := a END P;
+```
+
+produced an `NkDeclSeq`:
+
+- `NkDeclSeq[ NkConstDecl(Ident c, 5) ty=INTEGER ]`
+- `NkTypeDecl(Ident R) ty=<record>`
+- `NkDeclSeq[ NkVarDecl(Ident x) ty=INTEGER ]`
+- `NkProcDecl(Ident P, NkBlock[ NkAssign(x, a) ])`
+
+## Deliberately deferred
+
+- Unit roots (`NkUnit`), imports (`NkImport`), `CLASS` items and nested
+  modules.
+- Remaining statements (`CASE`/`WITH`, builtins) and expressions
+  (brace/set literals, `ResultComp`).
+- `Lower` + `.ssa` byte-compare.
+
+## Next
+
+A unit/`NkUnit` root and imports, then the remaining statements, then the
+`Lower` walk (where declaration `ty`/body nodes finally replace the
+inline emit).

Неке датотеке нису приказане због велике количине промена