Просмотр исходного кода

compiler: enumeration types (CALC Stage 5)

The ladder moves again: CALC.PAS went from ERR 41 at 8098 to ERR 102 at 8362.
The enumeration at CALC.PAS:120 is now a type, and the very next line,
`SetOfAttri = Set of Attributes', sizes correctly as a result - so both are
passed and the compiler reaches the RECORD at :126.

  - ParseType grows a `(' arm: `Attributes = (Constant, Formula, ...)'.  Each
    enumerator becomes a constant holding its ordinal position (0..n-1).
  - The enum TYPE is one byte (TP3 uses one below 256 values, RESUME-TP3.md §2)
    and carries its bounds 0..n-1 in SymEntry.lo/hi, which are new fields.
  - RdSetBase reads those bounds, so `set of Attributes' is ONE byte where
    `set of Char' is 32 - the hybrid rule doing its job.
  - RdConst now resolves a NAMED constant, not only TRUE/FALSE: a set literal
    `[Constant, Formula]' and (soon) `case' labels over enumerators need it.
  - Enumerators are TScalar, not a class of their own: they behave as ordinals
    and only the TYPE carries the enum identity and its bounds.

Fixture t62_enum: `a := Locked; a = Locked; a = Txt;

`Constant in [Constant, Formula]` ...' - .out TRUE FALSE TRUE FALSE, executed
under qemu and Exec86.

Red proof: giving every enumerator ordinal 0 leaves the compile matrix GREEN at
the same OK 187 5 and only execution sees `a = Txt' become TRUE.  nonvacuity.sh
gains that case (83 ok, 0 failed).

run_all.sh: ALL PASS - compile matrix 63/63, CALC ladder 1/1 at ERR 102 8362,
53 .COM linked and 53 executed under both qemu and Exec86.
Eric Streit 12 часов назад
Родитель
Сommit
a7d06cdf95

+ 27 - 15
CALC-COMPILE.md

@@ -95,7 +95,7 @@ Current rung (measured today, normalised position):
 
 | position | blocker |
 |---|---|
-| **8098** | `Attributes = (Constant,Formula,…)` — an **enumeration** type (`ERR 41`) |
+| **8362** | `CellRec = record` — a **record** type (`ParseType` `TkRecord`, `ERR 102`) |
 
 Rungs behind it (now passed):
 
@@ -104,13 +104,14 @@ Rungs behind it (now passed):
 | 7791 | `FXMax : Char = 'G'` — scalar typed constant | Stage 4 (scalar) |
 | 7791 | `EofLine = ^M` — caret character constant | Stage 3 |
 | 7972 | `Numbers : set of Char = ['0'..'9']` — set typed constant | sets, increment 1 |
+| 8098 | `Attributes = (Constant,…)` — enumeration; and `SetOfAttri = Set of Attributes` | Stage 5 (enums) |
 
 Rungs ahead:
 
 | position | blocker |
 |---|---|
-| 8060+ | `SetOfAttri = Set of Attributes` — a set over an enum (`ERR` — needs enums) |
-| 8237 | `CellRec = record` (`ERR 102`, `ENoLib`) |
+| past 8362 | the record body, then `Cells = array['A'..'G',1..FYMax] of CellRec` (2-D, Stage 8) |
+| then | the `var` block, `{$I}` at 9404, and everything in `CALC.INC` |
 
 Corrections the measurements force, worth stating plainly because the stage list
 below was written before them:
@@ -140,10 +141,10 @@ stripped.
 | typed constants — **set** | `CALC.PAS:114` | **done (sets, increment 1)** — code-resident bytes; `t61_setconst` |
 | typed constants — **array** | `CALC.PAS:137`; `CALC.INC:777` | `ERR 102` — needs initialised storage |
 | lexical `^M`, `#nn` | `CALC.PAS:113` | **done (Stage 3)** — `t60_charcaret` |
-| enum type `(Constant,Formula,…)` | `CALC.PAS:120` | `ERR 41` — the current rung |
+| enum type `(Constant,Formula,…)` | `CALC.PAS:120` | **done (Stage 5)** — ordinals; bounds carried; `t62_enum` |
 | subrange types `'A'..'G'`, `0..20`, `string[70]` | `CALC.PAS:117,119,130` | OK |
 | `set of Char` / `set of <subrange>` | `CALC.PAS:114` | **type + sizing done** (hybrid bytes) |
-| `set of <enum>` | `CALC.PAS:121,127` | needs the enum base |
+| `set of <enum>` | `CALC.PAS:121,127` | **sizing done** (enum bounds → 1 byte for Attributes) |
 | set constructor `[a..b, c]`, `in` (compile-time) | `t61_setconst` | **done** — folding only |
 | set `in` (run-time), `+`, `*`, `=`/`<>`, assignment, set *variables* | 47 × `in`, 6 × `+ [`, 2 × `*` | `ENoLib` — next sets increment |
 | `record` + field select `Sheet[FX,FY].CellStatus` | `CALC.PAS:126` | `ERR 102` |
@@ -412,10 +413,13 @@ The ladder row now sits on **sets**.
   literals already are), addressable through `DS` because a `.COM` has one
   segment — no `seg cs`, which is the single divergence from the original.
   Still `ENoLib`: run-time `in`, set *variables*, `+`/`*`/`=`/`<>`, assignment.
-- **The ladder now stops at `ERR 41` at `8098`** — `Attributes =
-  (Constant,…)`, an **enumeration** type. So enums are the next rung and the
-  `SetOfAttri = Set of Attributes`/`CellStatus : set of Attributes` lines come
-  right behind them.
+- **Enumerations are landed (Stage 5)** — ordinals 0..n-1, one-byte type,
+  bounds carried so `set of Attributes` sizes to one byte. Fixture `t62_enum`.
+  `RdConst` now resolves a named constant, which set literals and `case`
+  labels need.
+- **The ladder now stops at `ERR 102` at `8362`** — `CellRec = record`. So
+  records are the next rung, then the 2-D array `Cells = array['A'..'G',
+  1..FYMax] of CellRec`, then the `var` block and the `{$I}` at 9404.
 - **One surprise on resuming:** the working tree was found holding the
   nonvacuity script's *frame-rule* mutation of `shell/Compiler.mod` (the
   `locFree := 0FFFEH` / pre-decrement-base revert), i.e. a mutation from a
@@ -425,8 +429,8 @@ The ladder row now sits on **sets**.
   byte-for-byte the same for INTEGER locals, so the compile matrix would have
   been green and only `t40_readln` (and later CALC) would have noticed.
 - **Verified baseline, run on this commit:** `./tests/run_all.sh` →
-  **OVERALL: ALL PASS**, compile matrix **62/62**, CALC ladder **1/1** at
-  `ERR 41 8098`, 52 `.COM` linked and 52 executed under both qemu and Exec86.
+  **OVERALL: ALL PASS**, compile matrix **63/63**, CALC ladder **1/1** at
+  `ERR 102 8362`, 53 `.COM` linked and 53 executed under both qemu and Exec86.
 - **`tests/nonvacuity.sh`:** re-run after this commit's cases (the set
   membership bit among them); see the commit message for the count. It is not in
   the default `run_all.sh` and must be run **manually** after touching any check.
@@ -565,10 +569,18 @@ What is **not** done, deliberately, is a typed constant of a structured type:
 
 ### Then, in ladder order
 
-**Stage 5 — enumerations.** `Attributes = (Constant,Formula,Txt,OverWritten,
-Locked,Calculated)`. 1 byte if fewer than 256 values, else 2 (`RESUME-TP3.md`
-§2). Ordinal values 0..n; this feeds `Succ`/`Pred` and `set of T`.
-Ladder: 8036 → 8060.
+**Stage 5 — enumerations. DONE.**
+
+`Attributes = (Constant,Formula,Txt,OverWritten,Locked,Calculated)`.  Each
+enumerator becomes a constant holding its ordinal position (0..n-1); the TYPE is
+one byte (TP3 uses one below 256 values, `RESUME-TP3.md` §2) and carries its
+bounds `0..n-1` in the symbol, which is what `set of Attributes` sizes its base
+from — one byte, against 32 for `set of Char`.  `ParseType` grew the `(`
+arm, `SymEntry` grew `lo`/`hi`, and `RdConst` now resolves a NAMED constant, so
+`[Constant, Formula]` and (soon) `case` labels over enumerators work.
+`Succ`/`Pred` over enums are a later milestone (they are the standard-function
+milestone's job, not the type's).  Fixture `t62_enum`.
+Ladder: 8098 → 8362 (the enum AND `SetOfAttri = Set of Attributes`).
 
 **Stage 6 — sets. Increment 1 DONE; the rest is the next milestone.**
 

+ 6 - 8
SUMMARY.md

@@ -2037,15 +2037,13 @@ census of what `CALC.PAS` actually needs and the reference measurements from
 the original `TURBO.COM`. Two tracks run in parallel: the ladder (front-end,
 verifiable on small fixtures) and the layout (§3, the only thing that stops
 the compiler *emitting* a 16,560-byte program). The ratchet and front-end
-features through **sets (increment 1)** are landed: the ladder
+features through **enumerations** are landed: the ladder
 (`tests/calc_ladder.sh` + `tests/fixtures/calc_ladder.tsv`, run by `run_all.sh`;
-CALC.PAS now stops at `ERR 41` at pos 8098, an enumeration), the `{$I}` include,
-the lexical `^X`/`#nn` character constants, scalar typed constants, and the
-hybrid set type + constructor + set typed constants + compile-time `in`
-(fixtures `t56_include`, `t57_include_missing`, `t58_include_nest`,
-`t60_charcaret`, `t59_typedconst`, `t61_setconst`). The next rung is an
-enumeration. Items 1-4 below are the string/robustness work and are independent
-of both.
+CALC.PAS now stops at `ERR 102` at pos 8362, a record), the `{$I}` include, the
+lexical `^X`/`#nn` character constants, scalar typed constants, the hybrid set
+type + constructor + set typed constants + compile-time `in`, and enumeration
+types (`t62_enum`; fixtures up to `t62`). The next rung is a record. Items 1-4
+below are the string/robustness work and are independent of both.
 
 1. The rest of the string language. `v-TP3-STRVAR`/`v-TP3-STRREAD-ASSIGN` cover
    string variables, `s := 'literal'`, `writeln (s)`, `readln (s)`/`read (s)`

+ 65 - 8
shell/Compiler.mod

@@ -96,6 +96,8 @@ CONST
       `CMP varsize,#$01 / JA -> rdint, else rdbyte'), so the one thing class
       alone cannot tell apart is told apart where the size is in hand. *)
    TChar    = 12 ;
+   TEnum    = 13 ;         (* an enumeration type: ordinal, size 1 (TP3 uses 1
+                              byte below 256 values), bounds in SymEntry.lo/hi *)
 
    (* symbol kinds *)
    KLabel   = 100H ;  KConst  = 200H ;  KType   = 300H ;
@@ -154,6 +156,9 @@ TYPE
          elem   : CARDINAL ;
          off    : CARDINAL ;
          lval   : LONGINT ;
+         lo, hi : LONGINT ;         (* an ORDINAL type's bounds: an enumeration
+                                       (0..n-1) or a subrange.  Needed so
+                                       `set of T' can size its base. *)
          level  : CARDINAL ;
          local  : BOOLEAN ;
          resvar : CARDINAL ;
@@ -211,12 +216,13 @@ VAR
       region currently being scanned and inclActive says one is open - a
       nested {$I} inside that region is error 96, since TP3 permits only one
       level.  incBuf holds one whole include file while it is spliced. *)
-   srcName    : ARRAY [0..255] OF CHAR ;
-   inclName   : ARRAY [0..127] OF CHAR ;
+   srcName    : ARRAY [0..255] OF CHAR ;   inclName   : ARRAY [0..127] OF CHAR ;
    inclPath   : ARRAY [0..383] OF CHAR ;
    inclEnd    : CARDINAL ;
    inclActive : BOOLEAN ;
    incBuf     : ARRAY [0..TextLimit] OF CHAR ;
+   enumLo, enumHi : LONGINT ;   (* bounds of the enumeration ParseType just read,
+                                   handed to DefType which owns the type NAME *)
 
    wrd  : ARRAY [0..MaxName] OF CHAR ;
 
@@ -1424,6 +1430,8 @@ BEGIN
    e.elem := elem ;
    e.off := off ;
    e.lval := v ;
+   e.lo := 0 ;
+   e.hi := 0 ;
    e.level := lexnest ;
    e.local := local ;
    e.resvar := 0 ;
@@ -1874,6 +1882,7 @@ PROCEDURE RdConst (VAR v : LONGINT ; VAR cls : CARDINAL ;
    TScalar holding its character code, which is what "c := 'a'" wants. *)
 CONST q = AposC ;
 VAR ch : CHAR ;
+    idx : CARDINAL ;
 BEGIN
    isStr := FALSE ;
    cls := TScalar ;
@@ -1920,9 +1929,9 @@ BEGIN
          Err (EUnknown)
       END
    ELSIF Alpha (CurCh ()) THEN
-      (* TRUE / FALSE as a constant's value, which is how TP3 writes a typed
-         boolean constant: `Yes : Boolean = TRUE'.  Anything else that is not
-         a literal or a caret/decimal character is not a constant here. *)
+      (* TRUE / FALSE, or a NAMED constant - an enumerator (`[Constant,
+         Formula]'), or any earlier const.  A constant expression may name a
+         constant, and set literals and `case' labels are full of enumerators. *)
       GetWord () ;
       IF NameEq (wrd, "TRUE") THEN
          v := 1 ;
@@ -1930,6 +1939,9 @@ BEGIN
       ELSIF NameEq (wrd, "FALSE") THEN
          v := 0 ;
          cls := TBool
+      ELSIF Search (wrd, idx) AND (symtab [idx].tag = KConst) THEN
+         v := symtab [idx].lval ;
+         cls := symtab [idx].cls
       ELSE
          Err (EUnknown)
       END
@@ -2615,7 +2627,11 @@ BEGIN
    IF Alpha (CurCh ()) THEN
       GetWord () ;
       IF Search (wrd, idx) AND (symtab [idx].tag = KType) THEN
-         IF (symtab [idx].cls = TChar)
+         IF symtab [idx].cls = TEnum THEN
+            lo := symtab [idx].lo ;
+            hi := symtab [idx].hi ;
+            baseCls := TEnum
+         ELSIF (symtab [idx].cls = TChar)
             OR ((symtab [idx].cls = TScalar) AND (symtab [idx].size = 1)) THEN
             lo := 0 ; hi := 255 ; baseCls := symtab [idx].cls
          ELSE
@@ -3928,6 +3944,40 @@ BEGIN
    cls := TNone ; size := 0 ; elem := 0 ;
    consumed := FALSE ;
    Skip () ;                (* after ':' / '=' : skip blanks *)
+   IF CurCh () = '(' THEN
+      (* An enumeration: `(Constant, Formula, Txt, ...)'.  Each enumerator
+         becomes a constant with its ordinal position as its value, and the
+         TYPE is 1 byte (TP3 uses one below 256 values) with bounds 0..n-1,
+         which DefType records so `set of Attributes' can size its base.
+         Enumerators are TScalar, not TEnum: they behave as ordinals; only the
+         TYPE carries the enum identity and its bounds. *)
+      DropCh (GetCh ()) ;
+      lo := 0 ;
+      hi := -1 ;
+      LOOP
+         Skip () ;
+         IF NOT Alpha (CurCh ()) THEN
+            Err (EUnknown) ;
+            RETURN
+         END ;
+         GetWord () ;
+         DupTest (wrd) ;
+         DropC (NewSym (wrd, KConst, TScalar, 1, 1, 0, hi + 1, FALSE)) ;
+         hi := hi + 1 ;
+         IF MatchDelim (',') THEN
+            (* another enumerator *)
+         ELSE
+            EXIT
+         END
+      END ;
+      ExpectDelim (')', ENoSemi) ;
+      enumLo := lo ;
+      enumHi := hi ;
+      cls := TEnum ;
+      size := 1 ;
+      elem := 1 ;
+      RETURN
+   END ;
    IF Alpha (CurCh ()) THEN
       DropB (MatchKey (tok)) ;
       consumed := TRUE
@@ -4470,7 +4520,7 @@ END ProcFunc ;
 PROCEDURE DefType () ;
 (* 'name' '=' typeDef ; ... until a declaration keyword appears *)
 VAR nm : ARRAY [0..MaxName] OF CHAR ;
-    cls, size, elem : CARDINAL ;
+    cls, size, elem, tidx : CARDINAL ;
     tok : CARDINAL ;
 BEGIN
    LOOP
@@ -4491,7 +4541,14 @@ BEGIN
       IF NOT OK () THEN
          RETURN
       END ;
-      DropC (NewSym (nm, KType, cls, size, elem, 0, 0, FALSE)) ;
+      tidx := NewSym (nm, KType, cls, size, elem, 0, 0, FALSE) ;
+      IF cls = TEnum THEN
+         (* the enumeration's bounds, set by ParseType, belong to the type - so
+            `set of Attributes' can size its base (0..n-1, one byte) *)
+         symtab [tidx].lo := enumLo ;
+         symtab [tidx].hi := enumHi
+      END ;
+      DropC (tidx) ;
       IF NOT MatchDelim (';') THEN
          Err (ENoSemi) ;
          RETURN

+ 5 - 7
shell/tests/fixtures/calc_ladder.tsv

@@ -25,10 +25,8 @@
 # positions shift — which is exactly why this must be a measured number, not a
 # remembered one.
 #
-# Current rung (2026-10-19): Attributes = (Constant, Formula, ...) — an
-# ENUMERATION type.  ParseType has no arm for the '(' that begins an enumerated
-# list, so the enumerator word is an unknown identifier: EUnknown (41).  The
-# rung moved from 7972 (the set typed constant `Numbers : set of Char =
-# ['0'..'9']') when sets landed: the hybrid set type, the constructor, the set
-# typed constant, and compile-time `in'.
-CALC.PAS	ERR	41	8098
+# Current rung (2026-10-19): CellRec = record — a RECORD type, so ParseType
+# reaches its TkRecord arm and reports ENoLib (102).  The rung moved from 8098
+# (the enumeration `Attributes = (Constant, ...)') when enumerations landed,
+# which also let `SetOfAttri = Set of Attributes' size its set base 0..5.
+CALC.PAS	ERR	102	8362

+ 8 - 0
shell/tests/fixtures/expected.tsv

@@ -471,3 +471,11 @@ t60_charcaret	OK	75	4
 #                   literal.  data=4 is the @@T temporaries; the set's 32 bytes
 #                   are in the code stream, not the data area.
 t61_setconst	OK	183	4
+#   t62_enum               `Attributes = (Constant, Formula, Txt, ...)' -
+#                   CALC.PAS:120.  Each enumerator is a constant holding its
+#                   ordinal position; the TYPE is one byte and carries its
+#                   bounds 0..n-1, which sizes `set of Attributes' to ONE byte
+#                   (vs 32 for `set of Char').  A named constant may now be a
+#                   set element (RdConst resolved identifiers), which is what
+#                   `[Constant, Formula]' needs.  .out TRUE FALSE TRUE FALSE.
+t62_enum	OK	187	5

+ 4 - 0
shell/tests/fixtures/t62_enum.out

@@ -0,0 +1,4 @@
+TRUE
+FALSE
+TRUE
+FALSE

+ 30 - 0
shell/tests/fixtures/t62_enum.pas

@@ -0,0 +1,30 @@
+program t62_enum;
+
+{ t62 -- enumeration types: CALC.PAS:120 is
+
+      Attributes = (Constant, Formula, Txt, OverWritten, Locked, Calculated);
+
+  Each enumerator becomes a constant holding its ordinal position, so the six
+  names are 0..5 and compare as ordinals.  The TYPE is one byte (TP3 uses one
+  below 256 values) and carries its bounds 0..n-1, which the next line needs:
+  `SetOfAttri = Set of Attributes' sizes its set base from 0..5 -> ONE byte,
+  where `set of Char' is 32.
+
+  Enumerators are TScalar constants rather than a class of their own: they
+  behave as ordinals, and only the TYPE carries the enum identity.  A set
+  literal over enumerators (`[Constant, Formula]') defaults to `set of Char',
+  which is a superset and still answers membership correctly; a `set of
+  Attributes' VARIABLE, when set variables land, will be the 1-byte one. }
+
+type
+   Attributes = (Constant, Formula, Txt, OverWritten, Locked, Calculated);
+   SetOfAttri = Set of Attributes;
+var
+   a : Attributes;
+begin
+   a := Locked;
+   writeln (a = Locked);                    { same enumerator }
+   writeln (a = Txt);                       { different one }
+   writeln (Constant in [Constant, Formula]);
+   writeln (Txt in [Constant, Formula])
+end.

+ 26 - 0
shell/tests/nonvacuity.sh

@@ -1536,6 +1536,32 @@ else
 fi
 cp "$SAVED_C" Compiler.mod
 
+# --- an enumerator's ordinal ----------------------------------------------
+# Every enumerator must get its own ordinal (0,1,2,..).  Give them all 0 and
+# every `a = SomeEnumerator' is TRUE, while the sizes do not move: the compile
+# matrix stays green (OK 187 5) and only execution sees it.
+cp "$SAVED_C" Compiler.mod
+if python3 - <<'PYX'
+p = 'Compiler.mod'
+s = open(p).read()
+old = 'KConst, TScalar, 1, 1, 0, hi + 1, FALSE'
+assert s.count(old) == 1, 'the enumerator ordinal found %d times -- update this mutation' % s.count(old)
+open(p, 'w').write(s.replace(old, 'KConst, TScalar, 1, 1, 0, 0, FALSE'))
+PYX
+then
+    if rebuild_compiler; then
+        expect_red "t62 red when every enumerator gets ordinal 0" \
+            "t62_enum" python3 tests/run_com_exec.py t62_enum
+    else
+        echo "  FAIL: the compiler would not rebuild with the enumerator ordinals flattened"
+        fail=$((fail + 1))
+    fi
+else
+    echo "  BROKEN CASE: the enumerator-ordinal mutation did not apply"
+    fail=$((fail + 1))
+fi
+cp "$SAVED_C" Compiler.mod
+
 if rebuild_compiler; then
     if python3 tests/run_com_exec.py >/dev/null 2>&1; then
         echo "  ok: every executed fixture passes on the restored sources"