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

compiler: file types, text and file of T (CALC Stage 13, types)

The ladder makes its biggest jump yet: CALC.PAS went from ERR 102 at 9202 to
ERR 102 at 10350 - past the `var' block, past the `{$I CALC.INC}' at 9404, and
INSIDE the include.  The whole of CALC.PAS's own declarations are accepted now,
and the compiler is compiling CALC.INC's body.

  - ParseType: `text' -> TText, `file of T' -> TFile.  `file of T' parses its
    element type so the RECORD size a future Read/Write must move is known.
  - A file variable is our own control block, FileVarSize = 4 bytes (the DOS
    handle word and a mode word), because this runtime does unbuffered INT 21h
    I/O where TP3 keeps a 128-byte buffer inside the variable.  Recorded
    divergence.
  - DefVar accepts TFile/TText like the other structured types.

Fixture t66_files (`text' and `file of Integer'; data=12 = two 4-byte file
variables + the two @@T temporaries), executed under qemu and Exec86.

NOT done yet, and LOUD (unknown identifier): Assign, Reset, Rewrite, Close,
EndOfFile, IoResult, and read/write with a leading file argument.  Those are the
runtime entries (INT 21h open/create/read/write/close, text-mode newline and ^Z)
and are the next increment.  The first blocker the ladder now reports is
different: `procedure Center(S : AnyString)' - a string VALUE parameter wider
than a word, inside a nested procedure.

nonvacuity.sh gains a file-size case: FileVarSize 4 -> 2 turns t66's data=12
into data=8.  run_all.sh: ALL PASS - matrix 67/67, ladder 1/1 at ERR 102 10350,
58 .COM linked, 56 executed under qemu and Exec86.  nonvacuity 86 ok/0.
Eric Streit 11 часов назад
Родитель
Сommit
14dbcdbf6b

+ 31 - 19
CALC-COMPILE.md

@@ -95,7 +95,7 @@ Current rung (measured today, normalised position):
 
 
 | position | blocker |
 | position | blocker |
 |---|---|
 |---|---|
-| **9202** | `MCFile : file of CellRec` — a **file** type (`ParseType` `TkFile`, `ERR 102`) |
+| **10350** | `procedure Center(S : AnyString)` — a **string value parameter**, INSIDE `CALC.INC` |
 
 
 Rungs behind it (now passed):
 Rungs behind it (now passed):
 
 
@@ -107,12 +107,13 @@ Rungs behind it (now passed):
 | 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) |
 | 8705 | `Cells = array['A'..'G',1..FYMax]`, `Sheet : Cells`, `XPOS : array[…] = (…)` | Stage 8 (declarations) |
+| 9202 | `MCFile : file of CellRec` — the file type, the whole `var` block, and the `{$I}` expansion | Stage 13 (file types) |
 
 
 Rungs ahead:
 Rungs ahead:
 
 
 | position | blocker |
 | position | blocker |
 |---|---|
 |---|---|
-| 9202 | the `var` block's `file of`, then `{$I}` at 9404, and everything in `CALC.INC` |
+| 10350 | a string value parameter, then the rest of `CALC.INC`: file operations, indexing, `with`, pointers, `var` params, reals, string builtins, CRT, recursion |
 
 
 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:
@@ -163,7 +164,7 @@ stripped.
 | nested procedures / functions | `DisplayMessage` in `Help`; `Fact`/`Factor` in `Evaluate` | parses OK |
 | nested procedures / functions | `DisplayMessage` in `Help`; `Fact`/`Factor` in `Evaluate` | parses OK |
 | local `type`/`const`/`var` sections in a nested function | `CALC.INC:767-778` | `ERR 41`/`ERR 102` |
 | local `type`/`const`/`var` sections in a nested function | `CALC.INC:767-778` | `ERR 41`/`ERR 102` |
 | `case` labels `^E`, `^X, ^J`, `#27`, comma lists | `CALC.PAS:169`, `CALC.INC:91,1200,1567` | comma lists/`^X`/`#` unknown |
 | `case` labels `^E`, `^X, ^J`, `#27`, comma lists | `CALC.PAS:169`, `CALC.INC:91,1200,1567` | comma lists/`^X`/`#` unknown |
-| `file of CellRec`, `text` | `CALC.PAS:144`, `CALC.INC:582` | `ERR 102` |
+| `file of CellRec`, `text` | `CALC.PAS:144`, `CALC.INC:582` | **types done (Stage 13)** — operations next |
 | lexical `#27` and `^M` char constants | many | `ERR 41` at the `#`/`^` |
 | lexical `#27` and `^M` char constants | many | `ERR 41` at the `#`/`^` |
 | `write`/`writeln`/`read` with a leading file argument | `CALC.INC:562,625` | not implemented |
 | `write`/`writeln`/`read` with a leading file argument | `CALC.INC:562,625` | not implemented |
 
 
@@ -428,14 +429,15 @@ The ladder row now sits on **sets**.
   Fixture `t64_arrays` (compile-only: its ~16 KB image is past the exec
   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;
   harness's 8 KB boot-sector read). `t14` re-baselined: the array DECLARES now;
   the refusal moved to element access.
   the refusal moved to element access.
-- **Array/record ELEMENT and FIELD access is landed for CONSTANT indices**
-  (constant subscript + `.field`), with the value's width carried on the
-  expression (`ERes.sz`/`VarSize`). Fixture `t65_array_access`; `t14`
-  re-baselined `ERR 102 109` → `OK 41 15`. A VARIABLE index (and `with`) is the
-  next increment and is still loud.
-- **The ladder now stops at `ERR 102` at `9202`** — `MCFile : file of
-  CellRec`, the file type in the `var` block. So the next stage is files, then
-  the `{$I}` at 9404 and the `CALC.INC` body.
+- **File TYPES are landed (Stage 13, first half).** `text` and `file of T`; a
+  file variable is our control block (`FileVarSize` = 4). Fixture `t66_files`.
+  This is the step that carried the compiler **inside `CALC.INC`**: the whole
+  `var` block passed and the `{$I}` expanded, so the rung jumped from 9202 to
+  **10350**, where the first blocker is a string VALUE parameter in a nested
+  procedure. File OPERATIONS (`Assign`/`Reset`/…, read/write with a file
+  argument) are the next increment and are loud until then.
+- **The ladder now stops at `ERR 102` at `10350`** — `procedure Center(S :
+  AnyString)` in `CALC.INC`, a value parameter wider than a word.
 - **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
@@ -445,8 +447,8 @@ 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 **66/66**, CALC ladder **1/1** at
-  `ERR 102 9202`, 57 `.COM` linked and 55 executed under both qemu and Exec86
+  **OVERALL: ALL PASS**, compile matrix **67/67**, CALC ladder **1/1** at
+  `ERR 102 10350`, 58 `.COM` linked and 56 executed under both qemu and Exec86
   (`t64_arrays` is compile-only: its image is past the 8 KB boot-sector read).
   (`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
@@ -730,12 +732,22 @@ currently reserves a fresh **256-byte** global scratch (`NewScratchStr`), which
 for CALC's concat sites buys ~2 KiB of the budget in §3 — it needs to be sized
 for CALC's concat sites buys ~2 KiB of the budget in §3 — it needs to be sized
 to the operand, or reused.
 to the operand, or reused.
 
 
-**Stage 13 — files.** `text` and `file of CellRec`; `Assign`, `Reset`,
-`Rewrite`, `Close`, `EndOfFile`, `IoResult`; `read`/`write` with a leading file
-argument (`writeln (MCFile, Line)`, `Readln (H, Buffer^[count])`). DOS
-`INT 21h` open/create/read/write/close in the runtime, with text-mode newline
-and `^Z` semantics (`RESUME-TP3.md` §2). `{$I-}`/`{$I+}` (Stage 2) already gate
-the checking that `IoResult` reports.
+**Stage 13 — files. Types DONE; operations next.**
+
+Done: `text` and `file of T`.  A file variable is our own control block
+(`FileVarSize = 4`: the DOS handle word and a mode word), because this runtime
+does unbuffered `INT 21h` I/O where TP3 keeps a 128-byte buffer inside the
+variable - a recorded divergence.  `file of T` parses its element type so the
+RECORD size a Read/Write must move is known.  Fixture `t66_files`; this let the
+whole `var` block pass and the `{$I CALC.INC}` expand, which is why the ladder
+moved from `9202` to **`10350` - inside the include**.
+
+Not yet, and loud (unknown identifier): `Assign`, `Reset`, `Rewrite`, `Close`,
+`EndOfFile`, `IoResult`, and `read`/`write` with a leading file argument
+(`writeln (MCFile, Line)`, `Readln (H, Buffer^[count])`).  These need runtime
+entries doing `INT 21h` open/create/read/write/close, with text-mode newline
+and `^Z` semantics (`RESUME-TP3.md` §2).  `{$I-}`/`{$I+}` (Stage 2) already
+gate the checking that `IoResult` reports.
 
 
 **Stage 14 — the CRT identifiers.** `GotoXY` (25), `LowVideo`/`NormVideo`
 **Stage 14 — the CRT identifiers.** `GotoXY` (25), `LowVideo`/`NormVideo`
 (16 + 16), `ClrEol` (10), `ClrScr` (6), `KeyPressed` (3), `Delay` (1),
 (16 + 16), `ClrEol` (10), `ClrScr` (6), `KeyPressed` (3), `Delay` (1),

+ 8 - 8
SUMMARY.md

@@ -2037,15 +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 **array/field element access** are landed: the ladder
+features through **file types** 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 102` at pos 9202, a file type), the `{$I}` include,
-`^X`/`#nn`, scalar typed constants, sets, enums, records (CellRec = 80),
-subranges, array types/variables/typed constants, and constant-index element and
-field access (fixtures through `t65_array_access`). The next rungs are a
-variable-index subscript (`Sheet[FX,FY]`) + `with`, files, CRT, reals, the
-string builtins, recursion, and the layout change. Items 1-4 below are
-independent of both.
+CALC.PAS now stops at `ERR 102` at pos 10350, a string value parameter INSIDE
+`CALC.INC`), the `{$I}` include, `^X`/`#nn`, scalar typed constants, sets,
+enums, records (CellRec = 80), subranges, arrays, element/field access, and file
+types (`text`, `file of T`; fixtures through `t66_files`). The next rungs are
+file operations, a variable-index subscript (`Sheet[FX,FY]`) + `with`, CRT,
+reals, the string builtins, pointers, `var` params, 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)`

+ 28 - 7
shell/Compiler.mod

@@ -83,6 +83,10 @@ CONST
                                   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
    MaxArrs   = 1000 ;          (* array SHAPES, likewise: a symbol points at
                                   one, and 2-D indexing needs both ranges *)
                                   one, and 2-D indexing needs both ranges *)
+   FileVarSize = 4 ;           (* a file variable is our control block: the DOS
+                                  handle (word) and a mode word (0 closed, 1
+                                  read, 2 write).  TP3 keeps a buffer here too;
+                                  we do unbuffered INT 21h I/O. *)
 
 
    (* type codes (TP3 vartp) *)
    (* type codes (TP3 vartp) *)
    TNone    = 0 ;  TArray  = 1 ;  TRecord = 2 ;  TSet  = 3 ;
    TNone    = 0 ;  TArray  = 1 ;  TRecord = 2 ;  TSet  = 3 ;
@@ -4246,11 +4250,28 @@ BEGIN
       cls := TRecord ;
       cls := TRecord ;
       size := running ;
       size := running ;
       elem := running
       elem := running
-   ELSIF (tok = TkFile) OR (tok = TkText) THEN
-      cls := TFile ;
-      size := 0 ;
-      elem := 0 ;
-      Err (ENoLib)
+   ELSIF tok = TkText THEN
+      (* A TEXT file variable.  We do our own unbuffered INT 21h I/O, so the
+         file variable is just our control block (see FileVarSize) and the
+         element is one byte; TP3 keeps a 128-byte buffer in its own, which is
+         a divergence we do not need. *)
+      cls := TText ;
+      size := FileVarSize ;
+      elem := 1
+   ELSIF tok = TkFile THEN
+      (* file of T.  The element type is parsed for its SIZE, which is what a
+         Read/Write moves per record; the variable itself is the control
+         block. *)
+      IF NOT (MatchKey (tok) AND (tok = TkOf)) THEN
+         Err (ENoSemi) ;
+         RETURN
+      END ;
+      ParseType (subcls, s2, e2) ;
+      IF OK () THEN
+         cls := TFile ;
+         size := FileVarSize ;
+         elem := s2
+      END
    ELSE
    ELSE
       IF consumed THEN
       IF consumed THEN
          IF Search (wrd, idx) AND (symtab [idx].tag = KType) THEN
          IF Search (wrd, idx) AND (symtab [idx].tag = KType) THEN
@@ -4333,7 +4354,7 @@ BEGIN
       IF OK () THEN
       IF OK () THEN
          IF lexnest = 0 THEN
          IF lexnest = 0 THEN
             IF (size > 2) AND (cls # TString) AND (cls # TRecord)
             IF (size > 2) AND (cls # TString) AND (cls # TRecord)
-               AND (cls # TArray) THEN
+               AND (cls # TArray) AND (cls # TFile) AND (cls # TText) 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 +
@@ -4378,7 +4399,7 @@ BEGIN
             varspc := varspc + size * (symTop - first)
             varspc := varspc + size * (symTop - first)
          ELSE
          ELSE
             IF (size > 2) AND (cls # TString) AND (cls # TRecord)
             IF (size > 2) AND (cls # TString) AND (cls # TRecord)
-               AND (cls # TArray) THEN
+               AND (cls # TArray) AND (cls # TFile) AND (cls # TText) THEN
                Err (ENoLib) ;
                Err (ENoLib) ;
                RETURN
                RETURN
             END ;
             END ;

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

@@ -25,9 +25,11 @@
 # 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): 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
+# Current rung (2026-10-19): INSIDE CALC.INC — `procedure Center(S : AnyString)',
+# a STRING VALUE parameter.  The rung moved from 9202 (MCFile : file of
+# CellRec) when FILE TYPES landed (`text' and `file of T'), which let the whole
+# `var' block pass and the `{$I CALC.INC}' at 9404 expand, so the compiler is
+# now compiling the include's body.  This is a big step: past the include is
+# where all of CALC's own code lives.  The immediate blocker is a value
+# parameter whose type is bigger than a word (string[70]).
+CALC.PAS	ERR	102	10350

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

@@ -511,3 +511,11 @@ t64_arrays	OK	67	11764
 #                   lower-bound subtraction or a wrong store width all show up.
 #                   lower-bound subtraction or a wrong store width all show up.
 #                   data=16 = v (8) + r (4) + the two @@T temporaries.
 #                   data=16 = v (8) + r (4) + the two @@T temporaries.
 t65_array_access	OK	101	16
 t65_array_access	OK	101	16
+#   t66_files              `text' and `file of T' - CALC.PAS:144.  A file
+#                   variable is our control block (FileVarSize = 4: DOS handle
+#                   + mode), because this runtime does unbuffered INT 21h I/O
+#                   where TP3 buffers in the variable.  `file of T' parses its
+#                   element type for the record size a Read/Write moves.
+#                   data=12 = two file variables (4 each) + the two @@T
+#                   temporaries.  File OPERATIONS are the next increment.
+t66_files	OK	50	12

+ 1 - 0
shell/tests/fixtures/t66_files.out

@@ -0,0 +1 @@
+files declared

+ 23 - 0
shell/tests/fixtures/t66_files.pas

@@ -0,0 +1,23 @@
+program t66_files;
+
+{ t66 -- FILE types: `text' and `file of T'.  CALC.PAS:144 is
+
+      MCFile : file of CellRec;      -- File to store sheets in
+
+  A file variable is our own control block (FileVarSize = 4 bytes: the DOS
+  handle and a mode word), because this runtime does unbuffered INT 21h I/O
+  where TP3 keeps a 128-byte buffer inside the variable - a recorded
+  divergence.  `file of T' parses its element type for the RECORD size a
+  Read/Write moves (here 2 bytes, an Integer); `text' is one byte per element.
+
+  This increment is the TYPES and the variable layout only.  The file
+  operations - Assign, Reset, Rewrite, Close, EndOfFile, IoResult, and
+  read/write with a leading file argument - are the next one, and are loud
+  (unknown identifier) until then, not silently wrong. }
+
+var
+   t : text;
+   g : file of Integer;
+begin
+   writeln ('files declared')
+end.

+ 26 - 0
shell/tests/nonvacuity.sh

@@ -1616,6 +1616,32 @@ else
 fi
 fi
 cp "$SAVED_C" Compiler.mod
 cp "$SAVED_C" Compiler.mod
 
 
+# --- the file variable's size ---------------------------------------------
+# Our file control block is FileVarSize bytes; shrink it and every file
+# variable's slot moves, so t66's `data=12' becomes `data=8'.  Visible at
+# compile time, so the matrix carries it.
+cp "$SAVED_C" Compiler.mod
+if python3 - <<'PYX'
+p = 'Compiler.mod'
+s = open(p).read()
+old = 'FileVarSize = 4 ;'
+assert s.count(old) == 1, 'the file variable size found %d times -- update this mutation' % s.count(old)
+open(p, 'w').write(s.replace(old, 'FileVarSize = 2 ;'))
+PYX
+then
+    if rebuild_compiler; then
+        expect_red "t66 red when the file variable is the wrong size" \
+            "expected: OK 50 12" tests/run_compile_tests.sh
+    else
+        echo "  FAIL: the compiler would not rebuild with the file size changed"
+        fail=$((fail + 1))
+    fi
+else
+    echo "  BROKEN CASE: the file-size mutation did not apply"
+    fail=$((fail + 1))
+fi
+cp "$SAVED_C" Compiler.mod
+
 if rebuild_compiler; then
 if rebuild_compiler; then
     if python3 tests/run_com_exec.py >/dev/null 2>&1; then
     if python3 tests/run_com_exec.py >/dev/null 2>&1; then
         echo "  ok: every executed fixture passes on the restored sources"
         echo "  ok: every executed fixture passes on the restored sources"