# 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.