Sfoglia il codice sorgente

compiler: array types, variables and typed constants (CALC Stage 8, declarations)

The ladder moves again: CALC.PAS went from ERR 1 at 8705 to ERR 102 at 9202.
The 2-D array type, `Sheet : Cells`, and `XPOS : array['A'..'G'] of Integer =
(...)' are all accepted, and the compiler reaches the FILE TYPE in the var
block.

  - ParseType's array arm takes SEVERAL ranges: `array['A'..'G',1..FYMax] of T'.
    Row-major, last index varying fastest (RESUME-TP3.md §2); the shape (n,
    lo[], hi[], elem) is kept in an ArrRec and the symbol points at it
    (SymEntry.arrIdx), so indexing has what it needs.
  - Array VARIABLES of any size are laid out (DefVar no longer refuses TArray).
  - Array TYPED CONSTANTS: `(v1, v2, ...)' in element order, each little-endian,
    emitted code-resident like a set.  So XPOS does NOT appear in the data area:
    t64_arrays's data=11764 is Sheet (11760) + the two @@T temporaries, and
    XPOS's 14 bytes are in the code stream.

Fixture t64_arrays (compile-only: its ~16 KB image is past the exec harness's
8 KB boot-sector read).  t14 is RE-BASELINED: `array [..] of <type>' declares
now, so the refusal moves from the declaration (pos 83) to the subscript (109).

NOT done yet, and LOUD: array/record ELEMENT and FIELD access.  An element's
width is not its array's, so folding the address and leaving the size at the
array's would make `b[1] := 2' store the whole array - a silent miscompile.
It needs a width on the expression (ERes.sz) threaded through LoadAtom,
Assignment, IoCall and SaveLeft, plus scaled/2-D addressing for a variable
index.  That is the next increment, and it is what `Sheet[FX,FY].CellStatus'
and `with' wait on.

Harness: check_8086 clause F asserted "this region sweeps clean, therefore pure
code, therefore no 0F".  A code-resident constant breaks that premise - the data
can decode cleanly and present an 0F that is a data byte.  Fixtures that emit
code-resident constants are now listed in CODE_RESIDENT_CONST and clause F is
skipped for them; the runtime sweep and the execution oracles still cover the
emitted instructions.  (Also recorded: the exec boot sector reads 8 KB, and
CALC's measured .COM is 27,901 bytes, so running CALC needs that raised too.)

run_all.sh: ALL PASS - compile matrix 65/65, CALC ladder 1/1 at ERR 102 9202,
55 .COM linked, 54 executed under both qemu and Exec86.  nonvacuity 84 ok/0.
Eric Streit 2 giorni fa
parent
commit
123193d1e4

+ 56 - 20
CALC-COMPILE.md

@@ -95,7 +95,7 @@ Current rung (measured today, normalised position):
 
 
 | position | blocker |
 | position | blocker |
 |---|---|
 |---|---|
-| **8705** | `Cells = array['A'..'G',1..FYMax] of CellRec` — a **2-D array** (`ERR 1`) |
+| **9202** | `MCFile : file of CellRec` — a **file** type (`ParseType` `TkFile`, `ERR 102`) |
 
 
 Rungs behind it (now passed):
 Rungs behind it (now passed):
 
 
@@ -106,13 +106,13 @@ Rungs behind it (now passed):
 | 7972 | `Numbers : set of Char = ['0'..'9']` — set typed constant | sets, increment 1 |
 | 7972 | `Numbers : set of Char = ['0'..'9']` — set typed constant | sets, increment 1 |
 | 8098 | `Attributes = (Constant,…)` — enum; and `SetOfAttri = Set of Attributes` | Stage 5 (enums) |
 | 8098 | `Attributes = (Constant,…)` — enum; and `SetOfAttri = Set of Attributes` | Stage 5 (enums) |
 | 8362 | `CellRec = record … end` — record type | Stage 7 (record types) |
 | 8362 | `CellRec = record … end` — record type | Stage 7 (record types) |
+| 8705 | `Cells = array['A'..'G',1..FYMax]`, `Sheet : Cells`, `XPOS : array[…] = (…)` | Stage 8 (declarations) |
 
 
 Rungs ahead:
 Rungs ahead:
 
 
 | position | blocker |
 | position | blocker |
 |---|---|
 |---|---|
-| 8705 | the 2-D array, `Sheet : Cells`, `XPOS` (array typed constant), the `var` block |
-| then | `{$I}` at 9404, and everything in `CALC.INC` |
+| 9202 | the `var` block's `file of`, then `{$I}` at 9404, and everything in `CALC.INC` |
 
 
 Corrections the measurements force, worth stating plainly because the stage list
 Corrections the measurements force, worth stating plainly because the stage list
 below was written before them:
 below was written before them:
@@ -150,7 +150,9 @@ stripped.
 | set `in` (run-time), `+`, `*`, `=`/`<>`, assignment, set *variables* | 47 × `in`, 6 × `+ [`, 2 × `*` | `ENoLib` — next sets increment |
 | 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` | **type done (Stage 7)** — CellRec = 80 bytes; field SELECT is next |
 | `record` + field select `Sheet[FX,FY].CellStatus` | `CALC.PAS:126` | **type done (Stage 7)** — CellRec = 80 bytes; field SELECT is next |
 | `with` | 13 sites | after field selection |
 | `with` | 13 sites | after field selection |
-| 2-D array `array['A'..'G',1..FYMax] of CellRec` | `CALC.PAS:133` | **the current rung** — one range only |
+| 2-D array `array['A'..'G',1..FYMax] of CellRec` | `CALC.PAS:133` | **type + variable done (Stage 8)** — `Sheet` = 11760 |
+| array typed constant `XPOS : array[…] of Integer = (…)` | `CALC.PAS:137` | **done (Stage 8)** — code-resident |
+| array **element** access `Sheet[fx,fy]`, `b[i]` | many | `ENoLib` — needs a width on the expression |
 | array indexed by an **enum type** | `CALC.INC:773,927` | `ERR 41` |
 | array indexed by an **enum type** | `CALC.INC:773,927` | `ERR 41` |
 | pointers `^Pages`, `New`, `Dispose` | `CALC.INC:579,587,639,690` | `ERR 41` |
 | pointers `^Pages`, `New`, `Dispose` | `CALC.INC:579,587,639,690` | `ERR 41` |
 | `var` parameters | 10 routines, 14 occurrences (e.g. `CALC.INC:765,979,995,1036`) | **silently by value** |
 | `var` parameters | 10 routines, 14 occurrences (e.g. `CALC.INC:765,979,995,1036`) | **silently by value** |
@@ -418,15 +420,17 @@ The ladder row now sits on **sets**.
   bounds carried so `set of Attributes` sizes to one byte. Fixture `t62_enum`.
   bounds carried so `set of Attributes` sizes to one byte. Fixture `t62_enum`.
   `RdConst` now resolves a named constant, which set literals and `case`
   `RdConst` now resolves a named constant, which set literals and `case`
   labels need.
   labels need.
-- **Record TYPES are landed (Stage 7, first half)** — non-compacted fields, a
-  per-record field table, `CellRec` = 80 bytes, and the subrange sizing that
-  makes it so. Fixture `t63_record`. **Field selection** (`c.DEC`,
-  `Sheet[FX,FY].CellStatus`) and `with` are the next increment; they need the
-  expression record to carry a width, and today they error loudly.
-- **The ladder now stops at `ERR 1` at `8705`** — `Cells =
-  array['A'..'G',1..FYMax] of CellRec`, a **2-D array**. So the next rung is
-  Stage 8 (two ranges, last index varying fastest, records as elements), then
-  `XPOS`, the `var` block, and the `{$I}` at 9404.
+- **Array TYPES, array VARIABLES and array TYPED CONSTANTS are landed (Stage 8,
+  declarations half)** — multi-range array types with row-major sizing stored
+  in an `ArrRec` (`Sheet` = 11760), globals of array type, and
+  `XPOS : array[…] of Integer = (…)` emitted code-resident like a set.
+  Fixture `t64_arrays` (compile-only: its ~16 KB image is past the exec
+  harness's 8 KB boot-sector read). `t14` re-baselined: the array DECLARES now;
+  the refusal moved to element access.
+- **Array/record FIELD/ELEMENT access is the next increment, and is loud until
+  then.** A subscript or a `.field` would need the expression to carry a width
+  (an element's size is not its array's), so `b[1] := 2` cannot store the whole
+  array by accident. The rung is now the `file of` in the `var` block.
 - **One surprise on resuming:** the working tree was found holding the
 - **One surprise on resuming:** the working tree was found holding the
   nonvacuity script's *frame-rule* mutation of `shell/Compiler.mod` (the
   nonvacuity script's *frame-rule* mutation of `shell/Compiler.mod` (the
   `locFree := 0FFFEH` / pre-decrement-base revert), i.e. a mutation from a
   `locFree := 0FFFEH` / pre-decrement-base revert), i.e. a mutation from a
@@ -436,8 +440,9 @@ The ladder row now sits on **sets**.
   byte-for-byte the same for INTEGER locals, so the compile matrix would have
   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.
   been green and only `t40_readln` (and later CALC) would have noticed.
 - **Verified baseline, run on this commit:** `./tests/run_all.sh` →
 - **Verified baseline, run on this commit:** `./tests/run_all.sh` →
-  **OVERALL: ALL PASS**, compile matrix **64/64**, CALC ladder **1/1** at
-  `ERR 1 8705`, 54 `.COM` linked and 54 executed under both qemu and Exec86.
+  **OVERALL: ALL PASS**, compile matrix **65/65**, CALC ladder **1/1** at
+  `ERR 102 9202`, 55 `.COM` linked and 54 executed under both qemu and Exec86
+  (`t64_arrays` is compile-only: its image is past the 8 KB boot-sector read).
 - **`tests/nonvacuity.sh`:** re-run after this commit's cases (the set
 - **`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
   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.
   the default `run_all.sh` and must be run **manually** after touching any check.
@@ -643,11 +648,42 @@ not its variable's), which is a small `ERes` change plus the addressing; today
 it is a **loud error, not a wrong address**.  Records as **array elements** come
 it is a **loud error, not a wrong address**.  Records as **array elements** come
 with the 2-D array in Stage 8 — `Sheet[FX,FY].CellStatus` is both.
 with the 2-D array in Stage 8 — `Sheet[FX,FY].CellStatus` is both.
 
 
-**Stage 8 — arrays at their point of use.** Two ranges `array['A'..'G',
-1..FYMax] of T`, row-major with **the last index varying fastest**
-(`RESUME-TP3.md` §2); this is the repo's existing `t14` gap. Index types are
-subranges (`'A'..'G'`), so index scaling and the `Ebyte`/`dc` arithmetic have to
-agree with §3's layout.
+**Stage 8 — arrays at their point of use. Declarations DONE; element access next.**
+
+Done: multi-range array types (`array['A'..'G',1..FYMax] of T`), row-major with
+**the last index varying fastest** (`RESUME-TP3.md` §2); the shape is kept in an
+`ArrRec` (`n`, `lo[]`, `hi[]`, `elem`) and the symbol points at it
+(`SymEntry.arrIdx`), so indexing has everything it needs.  Array **variables**
+(global, and of any size) are laid out, and array **typed constants**
+(`XPOS : array['A'..'G'] of Integer = (…)`) are accepted: their bytes are
+emitted code-resident, exactly like a set, each element little-endian.
+`Sheet` is 11760 bytes and `XPOS`'s 14 bytes are in the code stream, which is
+why `t64_arrays`'s `data=11764` is `Sheet` plus the two `@@T` temporaries.
+`t14` is re-baselined: the array declares now, and the refusal moved from the
+declaration (pos 83) to the subscript (pos 109).
+
+**Not yet, and loud:** element access (`b[1]`, `Sheet[fx,fy]`), because an
+element's WIDTH is not its array's - folding the address and leaving the size
+at the array's would make `b[1] := 2` store the whole array.  This needs the
+expression to carry a width (an `ERes.sz`), threaded through `LoadAtom`,
+`Assignment`, `IoCall` and `SaveLeft`, plus scaled/2-D address arithmetic for a
+VARIABLE index; it is the next increment and it is what `Sheet[FX,FY].CellStatus`
+and `with` wait on.
+
+**Two consequences for the harness, both now recorded:**
+
+- **`check_8086` clause F had to narrow.**  It asserted "this region sweeps
+  clean, therefore it is pure code, therefore no `0F`".  A code-resident set or
+  array constant breaks that premise - the data can decode cleanly and then
+  present an `0F` that is a data byte.  Fixtures that emit code-resident
+  constants are now listed in `CODE_RESIDENT_CONST` and clause F is skipped for
+  them; the runtime sweep (the backbone) and the execution oracles still cover
+  the emitted instructions.
+- **The execution harness reads 8192 bytes.**  `tests/exec/bootcom.s` reads 16
+  sectors, and `t64_arrays`'s image is already ~16 KB - so it is compile-only,
+  and **CALC's measured `.COM` is 27,901 bytes**.  Running CALC therefore needs
+  the boot sector's read raised as well as the layout change; both are on the
+  execution path.
 
 
 **Stage 9 — pointers and the heap.** `^T` (4 bytes, offset then segment),
 **Stage 9 — pointers and the heap.** `^T` (4 bytes, offset then segment),
 `New`/`Dispose`, `Buffer^[i]` read/write, `Buffer^[0] := st` (assignment to a
 `New`/`Dispose`, `Buffer^[i]` read/write, `Buffer^[0] := st` (assignment to a

+ 8 - 7
SUMMARY.md

@@ -2037,14 +2037,15 @@ 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,
 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
 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
 the compiler *emitting* a 16,560-byte program). The ratchet and front-end
-features through **record types** are landed: the ladder
+features through **array declarations** are landed: the ladder
 (`tests/calc_ladder.sh` + `tests/fixtures/calc_ladder.tsv`, run by `run_all.sh`;
 (`tests/calc_ladder.sh` + `tests/fixtures/calc_ladder.tsv`, run by `run_all.sh`;
-CALC.PAS now stops at `ERR 1` at pos 8705, a 2-D array), the `{$I}` include, the
-lexical `^X`/`#nn` character constants, scalar typed constants, the hybrid set
-type + constructor + set typed constants + compile-time `in`, enumeration
-types, and record types with the subrange sizing they require (`CellRec` = 80,
-fixtures through `t63_record`). The next rung is the two-dimensional array.
-Items 1-4 below are the string/robustness work and are independent of both.
+CALC.PAS now stops at `ERR 102` at pos 9202, a file type), the `{$I}` include,
+`^X`/`#nn` character constants, scalar typed constants, the hybrid set type +
+constructor + set typed constants + compile-time `in`, enumeration types, record
+types (CellRec = 80), subrange sizing, and array types/variables/typed constants
+(Sheet = 11760, XPOS code-resident; fixtures through `t64_arrays`). The next
+rungs are array/field ELEMENT ACCESS, files, CRT, reals, the string builtins,
+recursion, and the layout change. Items 1-4 below are independent of both.
 
 
 1. The rest of the string language. `v-TP3-STRVAR`/`v-TP3-STRREAD-ASSIGN` cover
 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)`
    string variables, `s := 'literal'`, `writeln (s)`, `readln (s)`/`read (s)`

+ 129 - 18
shell/Compiler.mod

@@ -81,6 +81,8 @@ CONST
    MaxPend   = 400 ;
    MaxPend   = 400 ;
    MaxFields = 2000 ;          (* record fields, in a table of their own: a
    MaxFields = 2000 ;          (* record fields, in a table of their own: a
                                   field is scoped to its record, not global *)
                                   field is scoped to its record, not global *)
+   MaxArrs   = 1000 ;          (* array SHAPES, likewise: a symbol points at
+                                  one, and 2-D indexing needs both ranges *)
 
 
    (* type codes (TP3 vartp) *)
    (* type codes (TP3 vartp) *)
    TNone    = 0 ;  TArray  = 1 ;  TRecord = 2 ;  TSet  = 3 ;
    TNone    = 0 ;  TArray  = 1 ;  TRecord = 2 ;  TSet  = 3 ;
@@ -163,6 +165,7 @@ TYPE
                                        `set of T' can size its base. *)
                                        `set of T' can size its base. *)
          fldFirst, fldLast : CARDINAL ;  (* a RECORD type's field range in the
          fldFirst, fldLast : CARDINAL ;  (* a RECORD type's field range in the
                                        field table (0,0 when not a record) *)
                                        field table (0,0 when not a record) *)
+         arrIdx : CARDINAL ;        (* an ARRAY type's shape in ArrRec (0 = no) *)
          level  : CARDINAL ;
          level  : CARDINAL ;
          local  : BOOLEAN ;
          local  : BOOLEAN ;
          resvar : CARDINAL ;
          resvar : CARDINAL ;
@@ -216,6 +219,13 @@ TYPE
          lo, hi : LONGINT ;         (* bounds, for a subrange/enum field *)
          lo, hi : LONGINT ;         (* bounds, for a subrange/enum field *)
       END ;
       END ;
 
 
+   ArrRec =
+      RECORD
+         n      : CARDINAL ;        (* number of ranges (1 for array[1..5]) *)
+         lo, hi : ARRAY [0..3] OF LONGINT ;
+         elem   : CARDINAL ;        (* element size in bytes *)
+      END ;
+
 (* ---------------------------------------------------------------- *)
 (* ---------------------------------------------------------------- *)
 (*  state                                                           *)
 (*  state                                                           *)
 (* ---------------------------------------------------------------- *)
 (* ---------------------------------------------------------------- *)
@@ -240,6 +250,11 @@ VAR
    fields : ARRAY [0..MaxFields - 1] OF FldRec ;
    fields : ARRAY [0..MaxFields - 1] OF FldRec ;
    fldTop : CARDINAL ;          (* next free field-table slot *)
    fldTop : CARDINAL ;          (* next free field-table slot *)
    recFldFirst, recFldLast : CARDINAL ;  (* field range ParseType just built *)
    recFldFirst, recFldLast : CARDINAL ;  (* field range ParseType just built *)
+   arrs : ARRAY [0..MaxArrs - 1] OF ArrRec ;
+   arrTop : CARDINAL ;          (* next free array-shape slot *)
+   newArrIdx : CARDINAL ;       (* ArrRec index+1 for the type just parsed *)
+   constBlob : ARRAY [0..511] OF CHAR ;  (* bytes of a set/array typed constant
+                                             while it is being built *)
 
 
    wrd  : ARRAY [0..MaxName] OF CHAR ;
    wrd  : ARRAY [0..MaxName] OF CHAR ;
 
 
@@ -1451,6 +1466,7 @@ BEGIN
    e.hi := 0 ;
    e.hi := 0 ;
    e.fldFirst := 0 ;
    e.fldFirst := 0 ;
    e.fldLast := 0 ;
    e.fldLast := 0 ;
+   e.arrIdx := 0 ;
    e.level := lexnest ;
    e.level := lexnest ;
    e.local := local ;
    e.local := local ;
    e.resvar := 0 ;
    e.resvar := 0 ;
@@ -2163,8 +2179,13 @@ BEGIN
             Err (ENoLib) ;
             Err (ENoLib) ;
             RETURN
             RETURN
          ELSIF symtab [r.idx].cls = TArray THEN
          ELSIF symtab [r.idx].cls = TArray THEN
-            r.boff := W16 (VAL (LONGINT, r.boff)
-                           + t.imm * VAL (LONGINT, symtab [r.idx].elem))
+            (* A constant array subscript.  The element's WIDTH is not the
+               array's, so folding the address here and leaving the size at the
+               array's would make `b[1] := 2' store the whole array.  Element
+               addressing (a width on the expression) is the next increment;
+               until then this is LOUD, not a wrong store. *)
+            Err (ENoLib) ;
+            RETURN
          ELSE
          ELSE
             Err (ESimpType) ;
             Err (ESimpType) ;
             RETURN
             RETURN
@@ -3959,10 +3980,12 @@ VAR tok : CARDINAL ;
     subcls : CARDINAL ;
     subcls : CARDINAL ;
     strf : BOOLEAN ;
     strf : BOOLEAN ;
     consumed : BOOLEAN ;
     consumed : BOOLEAN ;
-    running, groupFirst, i : CARDINAL ;
+    running, groupFirst, i, an : CARDINAL ;
+    alo, ahi : ARRAY [0..3] OF LONGINT ;
 BEGIN
 BEGIN
    cls := TNone ; size := 0 ; elem := 0 ;
    cls := TNone ; size := 0 ; elem := 0 ;
    consumed := FALSE ;
    consumed := FALSE ;
+   newArrIdx := 0 ;
    Skip () ;                (* after ':' / '=' : skip blanks *)
    Skip () ;                (* after ':' / '=' : skip blanks *)
    IF CurCh () = '(' THEN
    IF CurCh () = '(' THEN
       (* An enumeration: `(Constant, Formula, Txt, ...)'.  Each enumerator
       (* An enumeration: `(Constant, Formula, Txt, ...)'.  Each enumerator
@@ -4005,21 +4028,62 @@ BEGIN
       tok := TkNone
       tok := TkNone
    END ;
    END ;
    IF tok = TkArray THEN
    IF tok = TkArray THEN
+      (* array [range, range, ...] of T.  Several ranges are allowed; the LAST
+         varies fastest (row-major), so `Sheet[fx,fy]' strides the first range
+         by the second's count (RESUME-TP3.md §2).  The shape is kept in the
+         array table; the symbol carries a pointer to it (SymEntry.arrIdx) so
+         indexing can scale by the element size and subtract each lower bound. *)
       ExpectDelim ('[', ENoSemi) ;
       ExpectDelim ('[', ENoSemi) ;
-      RdConst (lo, subcls, strf) ;
-      IF NOT MatchRange () THEN
-         Err (ESimpType)
+      an := 0 ;
+      LOOP
+         RdConst (lo, subcls, strf) ;
+         IF NOT MatchRange () THEN
+            Err (ESimpType) ;
+            RETURN
+         END ;
+         RdConst (hi, subcls, strf) ;
+         IF an < 4 THEN
+            alo [an] := lo ;
+            ahi [an] := hi ;
+            INC (an)
+         END ;
+         IF MatchDelim (',') THEN
+            (* another range *)
+         ELSE
+            EXIT
+         END
       END ;
       END ;
-      RdConst (hi, subcls, strf) ;
       ExpectDelim (']', ENoSemi) ;
       ExpectDelim (']', ENoSemi) ;
       IF NOT (MatchKey (tok) AND (tok = TkOf)) THEN
       IF NOT (MatchKey (tok) AND (tok = TkOf)) THEN
-         Err (ENoSemi)
+         Err (ENoSemi) ;
+         RETURN
       END ;
       END ;
       ParseType (cls, s2, e2) ;
       ParseType (cls, s2, e2) ;
       cls := TArray ;
       cls := TArray ;
       elem := s2 ;
       elem := s2 ;
-      size := s2 * (W16 (VAL (LONGINT, W16 (hi))
-                          - VAL (LONGINT, W16 (lo)) + 1))
+      size := s2 ;
+      i := 0 ;
+      WHILE i < an DO
+         size := size * W16 (VAL (LONGINT, W16 (ahi [i]))
+                             - VAL (LONGINT, W16 (alo [i])) + 1) ;
+         INC (i)
+      END ;
+      (* register the shape so indexing can scale by element size and subtract
+         each lower bound; the symbol points at this slot *)
+      IF arrTop < MaxArrs THEN
+         arrs [arrTop].n := an ;
+         arrs [arrTop].elem := s2 ;
+         i := 0 ;
+         WHILE i < an DO
+            arrs [arrTop].lo [i] := alo [i] ;
+            arrs [arrTop].hi [i] := ahi [i] ;
+            INC (i)
+         END ;
+         newArrIdx := arrTop + 1 ;
+         INC (arrTop)
+      ELSE
+         Err (ECompOvf)
+      END
    ELSIF tok = TkString THEN
    ELSIF tok = TkString THEN
       cls := TString ;
       cls := TString ;
       size := 256 ;
       size := 256 ;
@@ -4127,7 +4191,8 @@ BEGIN
             enumLo := symtab [idx].lo ;
             enumLo := symtab [idx].lo ;
             enumHi := symtab [idx].hi ;
             enumHi := symtab [idx].hi ;
             recFldFirst := symtab [idx].fldFirst ;
             recFldFirst := symtab [idx].fldFirst ;
-            recFldLast := symtab [idx].fldLast
+            recFldLast := symtab [idx].fldLast ;
+            newArrIdx := symtab [idx].arrIdx
          ELSE
          ELSE
             Err (EUnknown)
             Err (EUnknown)
          END
          END
@@ -4198,7 +4263,8 @@ BEGIN
       ParseType (cls, size, elem) ;
       ParseType (cls, size, elem) ;
       IF OK () THEN
       IF OK () THEN
          IF lexnest = 0 THEN
          IF lexnest = 0 THEN
-            IF (size > 2) AND (cls # TString) AND (cls # TRecord) THEN
+            IF (size > 2) AND (cls # TString) AND (cls # TRecord)
+               AND (cls # TArray) THEN
                (* Still the catch-all for a type whose storage this compiler does
                (* Still the catch-all for a type whose storage this compiler does
                   not lay out yet (an ARRAY at its point of use, a RECORD, ...).
                   not lay out yet (an ARRAY at its point of use, a RECORD, ...).
                   A STRING is the exception now: its layout is length byte +
                   A STRING is the exception now: its layout is length byte +
@@ -4225,6 +4291,7 @@ BEGIN
                   symtab [i].elem := elem ; symtab [i].off := off ;
                   symtab [i].elem := elem ; symtab [i].off := off ;
                   symtab [i].fldFirst := recFldFirst ;
                   symtab [i].fldFirst := recFldFirst ;
                   symtab [i].fldLast := recFldLast ;
                   symtab [i].fldLast := recFldLast ;
+                  symtab [i].arrIdx := newArrIdx ;
                   INC (i)
                   INC (i)
                END
                END
             ELSE
             ELSE
@@ -4234,13 +4301,15 @@ BEGIN
                   symtab [i].elem := elem ; symtab [i].off := dc ;
                   symtab [i].elem := elem ; symtab [i].off := dc ;
                   symtab [i].fldFirst := recFldFirst ;
                   symtab [i].fldFirst := recFldFirst ;
                   symtab [i].fldLast := recFldLast ;
                   symtab [i].fldLast := recFldLast ;
+                  symtab [i].arrIdx := newArrIdx ;
                   dc := dc + size ;
                   dc := dc + size ;
                   INC (i)
                   INC (i)
                END
                END
             END ;
             END ;
             varspc := varspc + size * (symTop - first)
             varspc := varspc + size * (symTop - first)
          ELSE
          ELSE
-            IF (size > 2) AND (cls # TString) AND (cls # TRecord) THEN
+            IF (size > 2) AND (cls # TString) AND (cls # TRecord)
+               AND (cls # TArray) THEN
                Err (ENoLib) ;
                Err (ENoLib) ;
                RETURN
                RETURN
             END ;
             END ;
@@ -4263,6 +4332,7 @@ BEGIN
                symtab [i].elem := elem ;
                symtab [i].elem := elem ;
                symtab [i].fldFirst := recFldFirst ;
                symtab [i].fldFirst := recFldFirst ;
                symtab [i].fldLast := recFldLast ;
                symtab [i].fldLast := recFldLast ;
+               symtab [i].arrIdx := newArrIdx ;
                symtab [i].off := (locFree - size + 1) MOD 10000H ;
                symtab [i].off := (locFree - size + 1) MOD 10000H ;
                locFree := (locFree - size) MOD 10000H ;
                locFree := (locFree - size) MOD 10000H ;
                INC (i)
                INC (i)
@@ -4291,11 +4361,11 @@ END DefVar ;
 PROCEDURE DefConst () ;
 PROCEDURE DefConst () ;
 VAR nm : ARRAY [0..MaxName] OF CHAR ;
 VAR nm : ARRAY [0..MaxName] OF CHAR ;
     v : LONGINT ;
     v : LONGINT ;
-    cls, typCls, typSize, typElem, soff : CARDINAL ;
+    cls, typCls, typSize, typElem, soff, ci : CARDINAL ;
     isStr : BOOLEAN ;
     isStr : BOOLEAN ;
     tok : CARDINAL ;
     tok : CARDINAL ;
-    typed, setTyped : BOOLEAN ;
-    sbuf : ARRAY [0..31] OF CHAR ;
+    typed, setTyped, arrTyped : BOOLEAN ;
+    nb, k, divi, w : CARDINAL ;
 BEGIN
 BEGIN
    LOOP
    LOOP
       PeekKw (tok) ;
       PeekKw (tok) ;
@@ -4324,6 +4394,7 @@ BEGIN
          refused loudly - those layouts come later. *)
          refused loudly - those layouts come later. *)
       typed := FALSE ;
       typed := FALSE ;
       setTyped := FALSE ;
       setTyped := FALSE ;
+      arrTyped := FALSE ;
       Skip () ;
       Skip () ;
       IF CurCh () = ':' THEN
       IF CurCh () = ':' THEN
          DropCh (GetCh ()) ;
          DropCh (GetCh ()) ;
@@ -4331,6 +4402,8 @@ BEGIN
          IF OK () THEN
          IF OK () THEN
             IF typCls = TSet THEN
             IF typCls = TSet THEN
                setTyped := TRUE
                setTyped := TRUE
+            ELSIF typCls = TArray THEN
+               arrTyped := TRUE
             ELSIF typSize > 2 THEN
             ELSIF typSize > 2 THEN
                Err (ENoLib)
                Err (ENoLib)
             ELSE
             ELSE
@@ -4340,10 +4413,45 @@ BEGIN
       END ;
       END ;
       IF OK () AND setTyped THEN
       IF OK () AND setTyped THEN
          ExpectDelim ('=', ENoSemi) ;
          ExpectDelim ('=', ENoSemi) ;
-         IF RdSetLiteral (typElem, typSize, sbuf) THEN
-            soff := EmSetBlob (sbuf, typSize) ;
+         IF RdSetLiteral (typElem, typSize, constBlob) THEN
+            soff := EmSetBlob (constBlob, typSize) ;
             DropC (NewSym (nm, KVar, TSet, typSize, typElem, soff, 0, FALSE))
             DropC (NewSym (nm, KVar, TSet, typSize, typElem, soff, 0, FALSE))
          END
          END
+      ELSIF OK () AND arrTyped THEN
+         (* array typed constant: `(v1, v2, ...)' in element order, each value
+            written little-endian in typElem bytes.  CALC's is XPOS (seven
+            Integers, 14 bytes).  An element that is a STRING (CALC.INC's
+            StandardfunctionNames) is a later increment, and is refused here. *)
+         ExpectDelim ('=', ENoSemi) ;
+         ExpectDelim ('(', ENoSemi) ;
+         nb := 0 ;
+         LOOP
+            RdConst (v, cls, isStr) ;
+            IF isStr OR (nb + typElem > 512) THEN
+               Err (ENoLib) ;
+               EXIT
+            END ;
+            w := VAL (CARDINAL, W16 (v)) ;
+            divi := 1 ;
+            k := 0 ;
+            WHILE k < typElem DO
+               constBlob [nb] := CHR ((w DIV divi) MOD 256) ;
+               divi := divi * 256 ;
+               INC (nb) ;
+               INC (k)
+            END ;
+            IF MatchDelim (',') THEN
+               (* another element *)
+            ELSE
+               EXIT
+            END
+         END ;
+         ExpectDelim (')', ENoSemi) ;
+         IF OK () THEN
+            soff := EmSetBlob (constBlob, typSize) ;
+            ci := NewSym (nm, KVar, TArray, typSize, typElem, soff, 0, FALSE) ;
+            symtab [ci].arrIdx := newArrIdx
+         END
       ELSIF OK () THEN
       ELSIF OK () THEN
          ExpectDelim ('=', ENoSemi) ;
          ExpectDelim ('=', ENoSemi) ;
          RdConst (v, cls, isStr) ;
          RdConst (v, cls, isStr) ;
@@ -4640,6 +4748,9 @@ BEGIN
          symtab [tidx].fldFirst := recFldFirst ;
          symtab [tidx].fldFirst := recFldFirst ;
          symtab [tidx].fldLast := recFldLast
          symtab [tidx].fldLast := recFldLast
       END ;
       END ;
+      IF cls = TArray THEN
+         symtab [tidx].arrIdx := newArrIdx
+      END ;
       DropC (tidx) ;
       DropC (tidx) ;
       IF NOT MatchDelim (';') THEN
       IF NOT MatchDelim (';') THEN
          Err (ENoSemi) ;
          Err (ENoSemi) ;

+ 12 - 1
shell/tests/check_8086.py

@@ -167,6 +167,17 @@ def probe_runtime():
     return bytes(blob), code_end, size
     return bytes(blob), code_end, size
 
 
 
 
+# Fixtures whose CODE REGION deliberately holds DATA.  A set or array typed
+# constant is emitted in the code stream behind a JMP - the storage decision in
+# CALC-COMPILE.md - because a .COM carries no initialised data.  A linear sweep
+# cannot tell those bytes from code: it may decode them cleanly, and then find
+# an `0F' that is a data byte, not an opcode.  Clause F's premise ("the region
+# sweeps clean, so the whole thing is code") is simply false for them.  They are
+# listed rather than detected, and the runtime sweep (the backbone) plus the
+# execution oracles still cover the emitted instructions.
+CODE_RESIDENT_CONST = {"t61_setconst", "t64_arrays"}
+
+
 def sweep(code, base=0):
 def sweep(code, base=0):
     """Linear sweep.  Returns (instructions, offset_it_stopped_at_or_None).
     """Linear sweep.  Returns (instructions, offset_it_stopped_at_or_None).
     An instruction is (offset, opcode_byte, length)."""
     An instruction is (offset, opcode_byte, length)."""
@@ -445,7 +456,7 @@ def main(argv):
             # is extra coverage, not the backbone, and how much of the suite
             # is extra coverage, not the backbone, and how much of the suite
             # it reached is printed rather than implied.
             # it reached is printed rather than implied.
             instrs, stopped = sweep(code, comimage.LOAD_BIAS + start)
             instrs, stopped = sweep(code, comimage.LOAD_BIAS + start)
-            if stopped is None:
+            if stopped is None and name not in CODE_RESIDENT_CONST:
                 swept_fixtures += 1
                 swept_fixtures += 1
                 swept_bytes += len(code)
                 swept_bytes += len(code)
                 for o, b, _ in instrs:
                 for o, b, _ in instrs:

+ 6 - 6
shell/tests/fixtures/calc_ladder.tsv

@@ -25,9 +25,9 @@
 # positions shift — which is exactly why this must be a measured number, not a
 # positions shift — which is exactly why this must be a measured number, not a
 # remembered one.
 # remembered one.
 #
 #
-# Current rung (2026-10-19): Cells = array['A'..'G',1..FYMax] of CellRec — a
-# TWO-dimensional array.  The array arm accepts one range and then expects ']',
-# so the second range is ENoSemi (1).  The rung moved from 8362 (the record
-# `CellRec = record') when record TYPES landed: field list, non-compacted
-# layout, and the subrange sizing that makes CellRec 80 bytes.
-CALC.PAS	ERR	1	8705
+# Current rung (2026-10-19): MCFile : file of CellRec — a FILE type, so
+# ParseType reaches its TkFile arm and reports ENoLib (102).  The rung moved
+# from 8705 (the 2-D array) when array TYPES, array VARIABLES, and array TYPED
+# CONSTANTS landed: `Cells = array['A'..'G',1..FYMax] of CellRec', `Sheet :
+# Cells' and `XPOS : array['A'..'G'] of Integer = (...)' are all accepted now.
+CALC.PAS	ERR	102	9202

+ 16 - 2
shell/tests/fixtures/expected.tsv

@@ -16,7 +16,11 @@
 #
 #
 # ERR 102 = ENoLib, the original's "not implemented" path.  The remaining ERR
 # ERR 102 = ENoLib, the original's "not implemented" path.  The remaining ERR
 # rows are unimplemented features, not parser bugs:
 # rows are unimplemented features, not parser bugs:
-#   t14       'array [..] of <type>' at its point of use
+#   t14       'array [..] of <type>' DECLARES now (CALC Stage 8, type half);
+#             what remains unimplemented is ELEMENT access - `b[1]' is a loud
+#             ENoLib, because an element's width is not the array's and a
+#             store would otherwise write the whole array.  The position moved
+#             from the declaration (83) to the subscript (109) as a result.
 #   t25       a string literal used where a 16-bit word is wanted
 #   t25       a string literal used where a 16-bit word is wanted
 #   uierror   deliberate syntax error, pinned by the editor UI test
 #   uierror   deliberate syntax error, pinned by the editor UI test
 #
 #
@@ -262,7 +266,7 @@ t10_while	OK	151	6
 t11_for	OK	144	6
 t11_for	OK	144	6
 t12_repeat	OK	137	6
 t12_repeat	OK	137	6
 t13_proc	OK	126	6
 t13_proc	OK	126	6
-t14_types	ERR	102	83
+t14_types	ERR	102	109
 t15_label	OK	90	6
 t15_label	OK	90	6
 t16_str1	OK	42	4
 t16_str1	OK	42	4
 t17_two_str	OK	45	4
 t17_two_str	OK	45	4
@@ -487,3 +491,13 @@ t62_enum	OK	187	5
 #                   the layout to the byte.  Subrange sizing is part of it.
 #                   the layout to the byte.  Subrange sizing is part of it.
 #                   Field SELECTION is still a loud error (the next increment).
 #                   Field SELECTION is still a loud error (the next increment).
 t63_record	OK	57	84
 t63_record	OK	57	84
+#   t64_arrays             the 2-D array TYPE and an array TYPED CONSTANT -
+#                   CALC.PAS:133 and :137.  Ranges are row-major, last fastest,
+#                   both kept in an ArrRec the symbol points at.  Sheet is
+#                   7*21*80 = 11760; XPOS is 7*2 = 14 and, like a set, its
+#                   bytes are CODE-RESIDENT (behind a JMP) because an array
+#                   constant cannot fold - so data=11764 is Sheet + the two
+#                   @@T temporaries, with XPOS in the code stream.  Compile
+#                   only: the image is ~16 KB and the exec harness's boot
+#                   sector reads 8 KB (see the layout/execution stage).
+t64_arrays	OK	67	11764

+ 39 - 0
shell/tests/fixtures/t64_arrays.pas

@@ -0,0 +1,39 @@
+program t64_arrays;
+
+{ t64 -- array TYPES and array typed constants: the 2-D sheet and XPOS.
+  CALC.PAS:133 and :137:
+
+      Cells = array['A'..'G',1..FYMax] of CellRec;   (FYMax = 21)
+      XPOS  : array['A'..'G'] of Integer = (3,14,25,36,47,58,68);
+
+  Ranges are row-major with the LAST varying fastest, and both ranges are
+  stored in an ArrRec the symbol points at, so the shape survives for
+  indexing.  `Sheet' is 7 * 21 * 80 = 11760 bytes; `XPOS' is 7 * 2 = 14.
+
+  An array TYPED CONSTANT cannot fold to a word, so - like a set - its bytes
+  are emitted in the code stream behind a JMP and the name becomes an
+  addressable value; each element is written little-endian in the element's
+  bytes.  So XPOS does NOT appear in the data area: data=11764 is Sheet
+  (11760) + the two @@T temporaries, and XPOS's 14 bytes are in the code
+  stream (code=67).  That is the storage decision showing up in the numbers.
+
+  Array ELEMENT ACCESS (`Sheet[fx,fy]', `XPOS['C']') is the next increment: an
+  element's width is not the array's, so a subscript is a LOUD error today
+  rather than a store of the whole array. }
+
+type
+   Attributes = (Constant, Formula, Txt, OverWritten, Locked, Calculated);
+   CellRec = record
+      CellStatus : set of Attributes;
+      Contents   : String[70];
+      Value      : Real;
+      DEC, FW    : 0..20;
+   end;
+   Cells = array ['A'..'G', 1..21] of CellRec;
+const
+   XPOS : array ['A'..'G'] of Integer = (3, 14, 25, 36, 47, 58, 68);
+var
+   Sheet : Cells;
+begin
+   writeln ('arrays declared')
+end.