Explorar el Código

two-phase slice 2: build scalar expression AST alongside emit

The Expr/SimExpr/Term spine now builds NkBinExpr/NkUnary and the Fact
literals build NkIntLit/NkCharLit/NkRealLit/NkStrLit, via a module-level
astCur result slot and a per-invocation astIsLit flag in Fact.  No
production signatures change, so no call sites change.  Nodes are inert
(nothing lowers them); emitted output is unchanged.

Still NoNode: Design/selectors, calls, brace/set literals, builtins,
NOT.  No Lower/byte-compare yet.  Correctness of precedence,
left-associativity, unary minus and parentheses was checked with a
temporary AST.Dump (removed).

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

See docs/summary_two-phase-slice2.md.
Eric Streit hace 1 semana
padre
commit
8baf5f3e3f
Se han modificado 4 ficheros con 3338 adiciones y 3133 borrados
  1. 88 23
      compiler/src/M2.atg
  2. 3172 3107
      compiler/src/M2.lst
  3. 7 3
      docs/plan-two-phase.md
  4. 71 0
      docs/summary_two-phase-slice2.md

+ 88 - 23
compiler/src/M2.atg

@@ -60,6 +60,13 @@ VAR
      the emitted output is unchanged. *)
      the emitted output is unchanged. *)
   twoPhase: BOOLEAN;
   twoPhase: BOOLEAN;
 
 
+  (* Result slot for the expression-AST builder (slice 2).  Every
+     Expr/SimExpr/Term/Fact leaves its node here; the combination
+     productions save it into locals before parsing the next operand.
+     `astIsLit` in Fact is per-invocation, so a nested Fact cannot make
+     an outer non-literal Fact look literal. *)
+  astCur: AST.Node;
+
   (* Class of the method named by the last `obj.Method` designator
   (* Class of the method named by the last `obj.Method` designator
      (InvalidType when the callee is an ordinary procedure). Set by
      (InvalidType when the callee is an ordinary procedure). Set by
      Design, consumed by the following ArgList. *)
      Design, consumed by the following ArgList. *)
@@ -156,7 +163,7 @@ TOKENS
 
 
 PRODUCTIONS
 PRODUCTIONS
   M2
   M2
-    = (. AST.Init; twoPhase := TRUE; .)
+    = (. AST.Init; twoPhase := TRUE; astCur := AST.NoNode; .)
       Unit "." .
       Unit "." .
   (* Units: program modules compile fully; DEFINITION and
   (* Units: program modules compile fully; DEFINITION and
      IMPLEMENTATION modules parse + check now but lower in step 4
      IMPLEMENTATION modules parse + check now but lower in step 4
@@ -2198,10 +2205,23 @@ PRODUCTIONS
                                         (. VAR t2: SymTab.TypeIndex;
                                         (. VAR t2: SymTab.TypeIndex;
                                              op: INTEGER;
                                              op: INTEGER;
                                              q2, qt, wl: QbeGen.QVal;
                                              q2, qt, wl: QbeGen.QVal;
+                                              astA, astB: AST.Node;
+                                              astOp: INTEGER;
+                                              astMade: BOOLEAN;
                                              isR: BOOLEAN; .)
                                              isR: BOOLEAN; .)
-    = SimExpr<t, q>
+    = SimExpr<t, q>                     (. astA := astCur; astMade := FALSE; .)
       [ Rel<op> SimExpr<t2, q2>
       [ Rel<op> SimExpr<t2, q2>
-        (. IF op = SymTab.OpIn THEN
+        (. astB := astCur; astMade := TRUE;
+            astOp := AST.OpEq;
+            IF op = SymTab.OpNeq1 THEN astOp := AST.OpNe
+            ELSIF op = SymTab.OpNeq2 THEN astOp := AST.OpNe
+            ELSIF op = SymTab.OpLt THEN astOp := AST.OpLt
+            ELSIF op = SymTab.OpLe THEN astOp := AST.OpLe
+            ELSIF op = SymTab.OpGt THEN astOp := AST.OpGt
+            ELSIF op = SymTab.OpGe THEN astOp := AST.OpGe
+            ELSIF op = SymTab.OpIn THEN astOp := AST.OpIn
+            END;
+            IF op = SymTab.OpIn THEN
              IF SymTab.InCheck(t, t2) THEN
              IF SymTab.InCheck(t, t2) THEN
                IF (t = SymTab.InvalidType)
                IF (t = SymTab.InvalidType)
                   OR (t2 = SymTab.InvalidType) THEN
                   OR (t2 = SymTab.InvalidType) THEN
@@ -2256,8 +2276,10 @@ PRODUCTIONS
                QbeGen.CopyOp(qt, q)
                QbeGen.CopyOp(qt, q)
              END
              END
            ELSE SemError(213); t := SymTab.InvalidType;
            ELSE SemError(213); t := SymTab.InvalidType;
-             QbeGen.CopyOp("0", q)
-           END; .) ] .
+              QbeGen.CopyOp("0", q)
+            END;
+            astCur := AST.MakeBin(AST.NkBinExpr, astOp, astA, astB); .) ]
+       (. IF NOT astMade THEN astCur := astA END; .) .
   Rel<VAR op: INTEGER>
   Rel<VAR op: INTEGER>
     = "="                               (. op := SymTab.OpEq; .)
     = "="                               (. op := SymTab.OpEq; .)
     | "#"                               (. op := SymTab.OpNeq1; .)
     | "#"                               (. op := SymTab.OpNeq1; .)
@@ -2276,11 +2298,13 @@ PRODUCTIONS
                                                BOOLEAN;
                                                BOOLEAN;
                                              fok: BOOLEAN;
                                              fok: BOOLEAN;
                                              lw, rw, mw: CARDINAL;
                                              lw, rw, mw: CARDINAL;
-                                             lTrue, lNext, lDone, qr, qs:
-                                               QbeGen.QVal; .)
-    =                                   (. neg := FALSE; .)
-      [ "+" | "-"                       (. neg := TRUE; .) ]
-      Term<t, q>                        (. IF neg THEN
+                                             lTrue, lNext, lDone, qr, qs: QbeGen.QVal;
+                                              astA, astB: AST.Node;
+                                              astSign, astOp: INTEGER; .)
+    =                                   (. neg := FALSE; astSign := 0; .)
+      [ "+"                         (. neg := TRUE; astSign := 1; .)
+      | "-"                         (. neg := TRUE; astSign := -1; .) ]
+      Term<t, q> (. astA := astCur; IF neg THEN
                                            IF QbeGen.IsImm(q) THEN
                                            IF QbeGen.IsImm(q) THEN
                                              QbeGen.NegFold(q, q)
                                              QbeGen.NegFold(q, q)
                                            ELSE QbeGen.NewTemp(qt);
                                            ELSE QbeGen.NewTemp(qt);
@@ -2289,12 +2313,22 @@ PRODUCTIONS
                                                = SymTab.ClReal);
                                                = SymTab.ClReal);
                                              QbeGen.CopyOp(qt, q)
                                              QbeGen.CopyOp(qt, q)
                                            END
                                            END
+                                         END;
+                                         IF astSign < 0 THEN
+                                           astCur := AST.MakeUn(
+                                             AST.NkUnary, AST.OpSub, astA);
+                                           astA := astCur
                                          END; .)
                                          END; .)
       { AddOp<op>                       (. IF op = SymTab.OpOr THEN
       { AddOp<op>                       (. IF op = SymTab.OpOr THEN
                                              QbeGen.DelayBegin END; .)
                                              QbeGen.DelayBegin END; .)
-        Term<t2, q2>                     (. IF op = SymTab.OpOr THEN
+        Term<t2, q2> (. astB := astCur; IF op = SymTab.OpOr THEN
                                              QbeGen.DelayEnd END; .)
                                              QbeGen.DelayEnd END; .)
-        (. IF op = SymTab.OpOr THEN
+        (. astOp := AST.OpAdd;
+           IF op = SymTab.OpSub THEN astOp := AST.OpSub
+           ELSIF op = SymTab.OpOr THEN astOp := AST.OpOr END;
+           astA := AST.MakeBin(AST.NkBinExpr, astOp, astA, astB);
+           astCur := astA;
+           IF op = SymTab.OpOr THEN
              (* short-circuit: if q is true the RHS is skipped *)
              (* short-circuit: if q is true the RHS is skipped *)
              IF SymTab.BoolCheck(t) AND SymTab.BoolCheck(t2) THEN
              IF SymTab.BoolCheck(t) AND SymTab.BoolCheck(t2) THEN
                t := SymTab.BoolType()
                t := SymTab.BoolType()
@@ -2438,13 +2472,21 @@ PRODUCTIONS
                                                QbeGen.QVal;
                                                QbeGen.QVal;
                                              isR, isL, folded: BOOLEAN;
                                              isR, isL, folded: BOOLEAN;
                                              lw, rw, mw: CARDINAL;
                                              lw, rw, mw: CARDINAL;
-                                             lNext, lFalse, lDone, qr, qs:
-                                               QbeGen.QVal; .)
-    = Fact<t, q> { MulOp<op>            (. IF op = SymTab.OpAnd THEN
+                                             lNext, lFalse, lDone, qr, qs: QbeGen.QVal;
+                                              astA, astB: AST.Node;
+                                              astOp: INTEGER; .)
+    = Fact<t, q> (. astA := astCur; .) { MulOp<op>            (. IF op = SymTab.OpAnd THEN
                                              QbeGen.DelayBegin END; .)
                                              QbeGen.DelayBegin END; .)
-        Fact<t2, q2>                    (. IF op = SymTab.OpAnd THEN
-                                             QbeGen.DelayEnd END; .)
-      (. IF op = SymTab.OpAnd THEN
+        Fact<t2, q2> (. astB := astCur; IF op = SymTab.OpAnd THEN
+                                              QbeGen.DelayEnd END; .)
+      (. astOp := AST.OpMul;
+         IF op = SymTab.OpSlash THEN astOp := AST.OpDiv
+         ELSIF op = SymTab.OpDiv THEN astOp := AST.OpDiv
+         ELSIF op = SymTab.OpMod THEN astOp := AST.OpMod
+         ELSIF op = SymTab.OpAnd THEN astOp := AST.OpAnd END;
+         astA := AST.MakeBin(AST.NkBinExpr, astOp, astA, astB);
+         astCur := astA;
+         IF op = SymTab.OpAnd THEN
            (* short-circuit: if q is false the RHS is skipped *)
            (* short-circuit: if q is false the RHS is skipped *)
            IF SymTab.BoolCheck(t) AND SymTab.BoolCheck(t2) THEN
            IF SymTab.BoolCheck(t) AND SymTab.BoolCheck(t2) THEN
              t := SymTab.BoolType()
              t := SymTab.BoolType()
@@ -2556,17 +2598,33 @@ PRODUCTIONS
                                              isMax: BOOLEAN;
                                              isMax: BOOLEAN;
                                              called, isHigh, sfx, isCh,
                                              called, isHigh, sfx, isCh,
                                              isU, uok, isStr: BOOLEAN;
                                              isU, uok, isStr: BOOLEAN;
-                                             ucp: INTEGER; astScratch: AST.Node; .)
-    = integer                           (. LexString(s);
-                                           QbeGen.NormInt(s, q); IF twoPhase THEN astScratch := AST.MakeLeaf(AST.NkIntLit, s) END;
+                                             ucp: INTEGER; astIsLit: BOOLEAN; .)
+    = (. astIsLit := FALSE; .)
+    ( integer                           (. LexString(s);
+                                           QbeGen.NormInt(s, q); IF twoPhase THEN astIsLit := TRUE; astCur := AST.MakeLeaf(AST.NkIntLit, s) END;
                                            t := SymTab.IntType(); .)
                                            t := SymTab.IntType(); .)
     | charConst                         (. LexString(s);
     | charConst                         (. LexString(s);
                                            QbeGen.NormLit(s, q, isCh);
                                            QbeGen.NormLit(s, q, isCh);
+                                           IF twoPhase THEN
+                                             astIsLit := TRUE;
+                                             astCur := AST.MakeLeaf(
+                                               AST.NkCharLit, s)
+                                           END;
                                            t := SymTab.CharType(); .)
                                            t := SymTab.CharType(); .)
     | real                              (. LexString(s);
     | real                              (. LexString(s);
                                            QbeGen.NormReal(s, q);
                                            QbeGen.NormReal(s, q);
+                                           IF twoPhase THEN
+                                             astIsLit := TRUE;
+                                             astCur := AST.MakeLeaf(
+                                               AST.NkRealLit, s)
+                                           END;
                                            t := SymTab.RealType(); .)
                                            t := SymTab.RealType(); .)
     | string                            (. LexString(s);
     | string                            (. LexString(s);
+                                           IF twoPhase THEN
+                                             astIsLit := TRUE;
+                                             astCur := AST.MakeLeaf(
+                                               AST.NkStrLit, s)
+                                           END;
                                            IF SymTab.StrLen(s) = 3 THEN
                                            IF SymTab.StrLen(s) = 3 THEN
                                              t := SymTab.CharType();
                                              t := SymTab.CharType();
                                              QbeGen.IntStr(
                                              QbeGen.IntStr(
@@ -2578,6 +2636,11 @@ PRODUCTIONS
                                              QbeGen.NoteAddr(q, q)
                                              QbeGen.NoteAddr(q, q)
                                            END; .)
                                            END; .)
     | ustring                           (. LexString(s);
     | ustring                           (. LexString(s);
+                                           IF twoPhase THEN
+                                             astIsLit := TRUE;
+                                             astCur := AST.MakeLeaf(
+                                               AST.NkStrLit, s)
+                                           END;
                                            QbeGen.DeclUStr(s, q, isU, ucp,
                                            QbeGen.DeclUStr(s, q, isU, ucp,
                                              uok);
                                              uok);
                                            IF NOT uok THEN
                                            IF NOT uok THEN
@@ -2986,7 +3049,7 @@ PRODUCTIONS
                                                END
                                                END
                                              END
                                              END
                                            END; .)
                                            END; .)
-    | "(" Expr<et, q> ")"               (. t := et; .)
+    | "(" Expr<et, q> ")"               (. t := et; astIsLit := TRUE; .)
     | SetLit<st, sq>                    (. t := st;
     | SetLit<st, sq>                    (. t := st;
                                            QbeGen.CopyOp(sq, q); .)
                                            QbeGen.CopyOp(sq, q); .)
     | ( "NOT" | "~" ) Fact<t2, q2>      (. IF SymTab.BoolCheck(t2) THEN
     | ( "NOT" | "~" ) Fact<t2, q2>      (. IF SymTab.BoolCheck(t2) THEN
@@ -2996,7 +3059,9 @@ PRODUCTIONS
                                            IF t # SymTab.InvalidType THEN
                                            IF t # SymTab.InvalidType THEN
                                              QbeGen.NotQ(q2, q)
                                              QbeGen.NotQ(q2, q)
                                            ELSE QbeGen.CopyOp("0", q)
                                            ELSE QbeGen.CopyOp("0", q)
-                                           END; .) .
+                                           END; .)
+    )
+    (. IF NOT astIsLit THEN astCur := AST.NoNode END; .) .
   (* Set literals are SET OF [0..255] (8 words); elements validated
   (* Set literals are SET OF [0..255] (8 words); elements validated
      0..255 statically when foldable (222 otherwise), runtime trap
      0..255 statically when foldable (222 otherwise), runtime trap
      for computed elements. Ranges always lower via SetRange. *)
      for computed elements. Ranges always lower via SetRange. *)

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 3172 - 3107
compiler/src/M2.lst


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

@@ -69,9 +69,13 @@ Suggested slices, each ending in `build.sh` + `run_tests.sh` +
    `docs/summary_two-phase-slice1.md`): `twoPhase` flag; `AST` linked
    `docs/summary_two-phase-slice1.md`): `twoPhase` flag; `AST` linked
    into the compiler build; the `integer` leaf also builds `NkIntLit`.
    into the compiler build; the `integer` leaf also builds `NkIntLit`.
    Nothing consumes it.  Green (178/178, fixpoint OK).
    Nothing consumes it.  Green (178/178, fixpoint OK).
-2. **Expressions → AST**: `Expr`/`SimExpr`/`Term`/`Fact` build
-   `NkBinExpr`/`NkUnary`/`NkIdent`/literals, *in addition* to emitting.
-   Add `Lower` for expressions, exercised in a test-only compare.
+2. **Expressions → AST** — PARTIAL (branch `ast-stage-c`,
+   `docs/summary_two-phase-slice2.md`): the scalar spine (`Expr` /
+   `SimExpr` / `Term` / literals / parens / unary minus) builds nodes
+   via a module-level `astCur` result slot and a per-invocation
+   `astIsLit` flag in `Fact` (no signature changes).  `Design`,
+   selectors, calls, braces, sets and builtins still leave `NoNode`;
+   `Lower` + `.ssa` compare not started.  Green (178/178, fixpoint OK).
 3. **Statements → AST**: `Block`/`Stat` build `NkAssign`/`NkIf`/`NkWhile`/
 3. **Statements → AST**: `Block`/`Stat` build `NkAssign`/`NkIf`/`NkWhile`/
    …; `Lower` them.
    …; `Lower` them.
 4. **Declarations → AST**: `ConstBlock`/`TypeBlock`/`VarBlock`/
 4. **Declarations → AST**: `ConstBlock`/`TypeBlock`/`VarBlock`/

+ 71 - 0
docs/summary_two-phase-slice2.md

@@ -0,0 +1,71 @@
+# Two-phase refactor — slice 2 (expression spine → AST)
+
+Branch `ast-stage-c`.  Suite **178/178**; self-hosting fixpoint **OK**
+(**2,966,245 bytes**; slice 1 was 2,957,466, +8,779 for the builder).
+
+## What landed
+
+The scalar expression hierarchy now builds an AST **alongside** the
+legacy emit path.  The nodes are still inert (nothing lowers them), so
+the generated `.ssa` is unchanged (fixpoint byte-identical).
+
+| Production | Now builds |
+| --- | --- |
+| `Expr` | `NkBinExpr` for a relation (`=` `#` `<` `<=` `>` `>=` `IN`) |
+| `SimExpr` | `NkUnary` (`-`, `OpSub`) for a leading minus; `NkBinExpr` for `+` `-` `OR` |
+| `Term` | `NkBinExpr` for `*` `/` `DIV` `MOD` `AND` |
+| `Fact` | `NkIntLit` / `NkCharLit` / `NkRealLit` / `NkStrLit`; `( Expr )` passes the inner node through |
+
+### Design: one result slot + a per-invocation literal flag
+
+The productions keep their existing `t`/`q` attributes (no signature
+changes, so **no call sites change**).  A single module-level
+`astCur : AST.Node` carries the result of the most recently completed
+`Expr`/`SimExpr`/`Term`/`Fact`; the combination productions save it into
+locals before parsing the next operand.
+
+The one hazard is `Fact`: alternatives such as `CHR(x)` or `a[i]` call
+`Expr` internally (and `Design` calls `Expr` for indices), which would
+leave an *inner* node in `astCur`.  The fix is a **per-invocation**
+`astIsLit : BOOLEAN` local in `Fact`: it is set only by the literal
+alternatives, and a trailing action does
+
+```modula2
+IF NOT astIsLit THEN astCur := AST.NoNode END
+```
+
+Because `astIsLit` is a local (stack) variable, a nested `Fact` has its
+own copy and cannot make an outer non-literal `Fact` look literal.
+`( Expr )` is the single pure-grouping alternative and explicitly sets
+`astIsLit := TRUE` so the inner node survives.
+
+## Verified (temporary `AST.Dump`, then removed)
+
+`AST.Dump(astCur, 0)` was wired into the `M2` root for one build and
+three shapes checked; the dump was then removed (the committed grammar
+has no debug output):
+
+- `2 + 3 * 4 - 5` → `Sub(Add(2, Mul(3,4)), 5)` — precedence + left assoc.
+- `-2 + 3` → `Add(Unary(Sub, 2), 3)`.
+- `-(2 + 3) * 4` → `Unary(Sub, Mul(Add(2,3), 4))` — unary binds to the
+  whole `Term`, as the grammar specifies.
+- `2 * (3 + 4)` → `Mul(2, Add(3,4))`; `2 < 3` → `BinExpr(op=OpLt)(2,3)`.
+
+Two builder bugs were caught this way and fixed (unary minus not fed
+back into the left accumulator; parentheses discarding the inner node).
+
+## Deliberately deferred
+
+Still `astCur := NoNode` (to be converted in later slices):
+
+- **`Design`** (identifiers, qualified names, selectors `.f` `[i]` `^`)
+  — the largest piece; `NkIdent`/`NkDesignator`/`NkSelector`.
+- **Calls** (`ArgList`, result suffixes) and `NkBraceLit` / `NkSetLit`.
+- Builtins `CHR`/`ORD`/`ABS`/`HIGH`/`LEN`/…  and `NOT`.
+- **`Lower` + `.ssa` byte-compare** (plan §3 phase 2) — not started; the
+  inert builder is validated by the fixpoint and the manual dump above.
+
+## Next
+
+Slice 3: convert `Design` and the `Fact` designator/call alternatives,
+which turns the spine into a useful AST for real expressions.

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio