Quellcode durchsuchen

compiler: multi-name declarations, and an enforced 4 KiB code window

Two of the four "Next steps" items, both in the declaration parser and
the emitter.

Multi-name declarations (v-TP3-MULTIDECL).  TP3 reads a whole
comma-separated list of NAMES and then one `: type' (TPSRC7
rdvarlst/rdvloop for VAR, prfcnt/ccomma for formal parameters), groups
separated by `;'.  DefVar and ProcFunc now do the same: each name is
created as a placeholder and given the group's single cls/size/elem, and
parmOff advances once per name.  The older `name : type, name : type'
spelling still parses, since both reach the same symbols.

The bug that fix exposed is the `GetWord'-does-not-Skip class again:
after MatchDelim(',') the cursor sits on the space, so the first name of
a VAR group parsed and every later name became `" B"'.  t42_multidecl
prints every name and is the fixture that made it red.

The 4 KiB code window (v-TP3-CODEWINDOW).  dataBase is rtSz + 1000H, so
code and data share one 64 KiB segment and code may not reach it.  Ebyte
now reports TP3's error 98 ("Memory overflow") at pc >= dataBase, gated
by a new imgReady flag because dataBase is 0 until Compile lays it out.
With the gate disabled t43_codewindow compiles OK -- the silent overrun
-- which is what makes the fixture non-vacuous.

Gates: compile matrix 44/44, run_com_exec 39/39, exec86 39/39, runtime
entries 51/51, OVERALL: ALL PASS.  tpshell 191104 bytes.

Both tags point at this commit: v-TP3-MULTIDECL, v-TP3-CODEWINDOW.
Eric Streit vor 2 Stunden
Ursprung
Commit
4b61844989

+ 31 - 22
SUMMARY.md

@@ -31,6 +31,8 @@ manual, not guessed.
 | A `BOOLEAN` read refused with TP3's own error 66 | `v-TP3-BOOLREAD` | done, **`readln (b)` of a boolean is an error, not a word store into a one-byte variable — exactly TPSRC8 `prderrtp`; the compile matrix demands `ERR 66` at the character after the variable, and the accepting dispatch is one of the in-suite reverts** |
 | String *variables* — `s : string[10]`, `s := 'hi'`, `writeln(s)` | `v-TP3-STRVAR` | done, **the first declared type bigger than a word that is not an error. `string [n]` is n+1 bytes — a length byte and characters, the layout TPSRC8's `string` type gives a variable (`INC BX / MOV varsize,BX`) — so `DefVar`'s `size > 2` catch-all (still the answer for an `array` at its point of use) exempts `TString`. `s := 'hi'` copies the literal byte by byte and truncates a longer one exactly as TPSRC2 `strstore` does; `writeln(s)` pushes the variable's address and calls a new `wrstr` entry (TPSRC4 `xwrtstr` minus its field-width formatting), which prints the length byte's worth of characters and nothing for `''`. Both a global and a local string are exercised — the absolute and the `[BP+off]` address paths — and the `.out` is compared byte for byte under qemu and Exec86 (36/36)** |
 | String *reads* and string-to-string assignment — `readln(s)`, `s := t` | `v-TP3-STRREAD-ASSIGN` | done, **`readln(s)`/`read(s)` and `s := t` on string variables. Two runtime entries carry selectors 16/17 (`rdstr`, `strstore`); the caller sets the limit in `CX` with a full `MOV CX,imm16`. `rdstr` (TPSRC4 `xrdstr`) reads a line, stops at CR/LF/^Z, leaves the terminator pending for the existing `rdln` wrapper, truncates at the limit, and stores the length last; `strstore` (TPSRC2 `strstore` + TPSRC8 `asnvar`) copies `min(len(t), size(s)-1)` characters, never writes the source, and leaves the destination tail past the new length untouched. The milestone also exposed a pre-existing frame-layout bug: a local `string` was packed with the *integer* rule, so its characters grew UP over the saved BP at `[BP]` and the return address at `[BP+2]` — `readln` into such a local then ran the string's own bytes as its return address. Locals are now packed top-anchored, which keeps every integer offset byte-for-byte and puts a `string[5]` at `BP-6..BP-1`, inside the frame (38/38 executed, 51/51 entry checks)** |
+| Multi-name declarations, both spellings — `var a, b, c : integer`, `procedure p (x, y : integer ; z : integer)` | `v-TP3-MULTIDECL` | done, **definitions and formal parameters read a comma-separated list of NAMES and then one `: type`, exactly TPSRC7 `rdvarlst`/`rdvloop` (VAR) and `prfcnt`/`ccomma` (parameters), with `;` between parameter groups. The older `name : type, name : type` spelling still parses, because both reach the same symbols; the names are created as placeholders and given the group's one type afterwards. One `GetWord` that did not `Skip` first attached the space after a comma to the next name (`" B"`), which is why the first name worked and the second did not; `t42_multidecl` prints each name's value to prove every one landed on its own slot (43/43 compile, 39/39 executed)** |
+| The 4 KiB code window enforced — TP3 error 98 instead of writing over the data | `v-TP3-CODEWINDOW` | done, **`dataBase` is `rtSz + 1000H`, so code and data share one 64 KiB `.COM` segment and code may not reach it. `Ebyte` now refuses past `dataBase` (guarded by `imgReady`, since `dataBase` is 0 until `Compile` measures it) and reports TP3's error 98, "Memory overflow" — the same number `DefVar` already uses when `dc` overflows. `t43_codewindow` emits more than 4 KiB of inline literal bytes and must report `ERR 98`; disabling the check turns the fixture green-as-`OK`, which is the bug (a silently corrupt image), so the matrix row is the proof (44/44 compile)** |
 
 Every row that names a tag has one, and every tag points at a commit on
 `master`; verified by diffing the rows against `git tag -l`, which is how
@@ -72,7 +74,7 @@ gm2 -fiso -fuse-module-list=modules.lst -o tpshell \
         Shell.mod Compiler.mod Term.o TextBuf.o Posix.o Editor.o   # phase B2
 ```
 
-Current clean build: `make clean && make` → rc=0, `tpshell` **191072 bytes**.
+Current clean build: `make clean && make` → rc=0, `tpshell` **191104 bytes**.
 The one diagnostic is `./Compiler.mod: ParseExpr: too many errors in pass 3`,
 which is the expected phase-1 rollup that the recipe tolerates — not a real
 error. `shell/Makefile` is the single authoritative build recipe.
@@ -124,11 +126,11 @@ needing a squint. It was checked for vacuousness by reverting the string
 scanner fix: the suite went red with exit 1, and came back green only when the
 fix was restored.
 
-**38 of 42 fixtures compile clean**, up from 1 (the empty program) when the
+**39 of 44 fixtures compile clean**, up from 1 (the empty program) when the
 direct harness was first built.
 
 ```
-compile matrix: 42 passed, 0 failed (of 42)
+compile matrix: 44 passed, 0 failed (of 44)
 ```
 
 Compiling: `t01` minimal · `t04` var+assign+`writeln` · `t06` two args ·
@@ -149,7 +151,9 @@ arguments are computed in various positions, each one keeping its own value. ·
 `t39` `s : string [10]`, assigned a literal and written — globally and from
 inside a procedure, so both address paths run ·
 `t40` `readln` into a global *and* a local `string` ·
-`t41` `s := t` between two strings, with the truncation both ways.
+`t41` `s := t` between two strings, with the truncation both ways ·
+`t42` `var a, b, c : integer` and `procedure p (x, y : integer ; z : integer)`,
+each name landing on its own slot.
 
 Failing, all deliberately: `t14` `array [1..5] of integer` at its point of use
 and `t25` a string literal assigned to an *integer* (`i := 'ab'`, the reverse
@@ -157,11 +161,13 @@ direction is still unimplemented) both → `ENoLib`
 (102), the original's "not implemented" path; `t38` `readln (b)` of a
 `boolean` → error 66, TP3's "I/O not allowed here" (TPSRC8 `prderrtp`), which
 refuses the whole class because the runtime has no boolean reader at all; and
+`t43_codewindow` a program whose code exceeds the 4 KiB window → error 98,
+"Memory overflow", rather than a corrupt image; and
 `uierror` is a deliberate syntax error (41) used by the UI test.
 
-`run_com_tests.sh` additionally links **all 38 that compile** to a real `.COM`
+`run_com_tests.sh` additionally links **all 39 that compile** to a real `.COM`
 and re-verifies the bytes with an independent Python checker that *measures*
-the layout instead of restating it: `independent .COM check: 38 checked, 0
+the layout instead of restating it: `independent .COM check: 39 checked, 0
 failed`. That last part was itself a bug fix — see "the two restated
 constants" below.
 
@@ -205,7 +211,7 @@ proves each one can go red.
 | `check_runtime.py` + `runtime.golden` | the built runtime's code region (585 bytes, code 0..584) sweeps cleanly through FCML, every entry and all branch targets land on an instruction boundary, and the whole disassembly is byte-for-byte the committed golden | whether the golden is *right* |
 | `check_framedisp.py` | `[BP+off]` uses disp8 iff `off <= 127`, for locals (negative) and far parameters (>127) | which of the two encodings was chosen, if the other also works |
 | `run_com_exec.py` | the **emitted image executes** and prints exactly the expected bytes | semantics the fixture never exercises |
-| `run_exec86.py` | the same image runs in a **second, independent 8086** and agrees with qemu byte for byte, 38/38 | the `.out` file it shares with `run_com_exec.py` — a wrong expectation fails both at once |
+| `run_exec86.py` | the same image runs in a **second, independent 8086** and agrees with qemu byte for byte, 39/39 | the `.out` file it shares with `run_com_exec.py` — a wrong expectation fails both at once |
 | `rt_exec.py` | the **runtime entries themselves** are called directly, one qemu boot per call, 50 cases plus a pre-flight, 51/51 — plus a `check_bp_contract` pre-flight over the built blob: an entry that borrows BP must open with `55 8B EC` (exact) and contain a `5D` (a screen, because `5D` is also a displacement byte) | whether the *caller's* half of a contract is right, where the only witness is a case that happens to make two calls in a row |
 
 Two of these deserve the detail, because the reason they exist is the reason
@@ -278,7 +284,7 @@ and `tests/check_comimage.py` asserting there is only one copy of each.
 ### Execution under qemu — `tests/run_com_exec.py`
 
 The check that cannot be written as a byte comparison, so also the one that
-finds the most: 38 fixtures are compiled to `.COM`, put on a floppy, booted,
+finds the most: 39 fixtures are compiled to `.COM`, put on a floppy, booted,
 and their serial output compared to a committed `.out` file **exactly** — CRLF
 included — plus the exit code passed to `INT 21h AH=4Ch`.
 
@@ -338,7 +344,7 @@ compiler: **does the 8086 have this instruction at all?**
             value conditions  : = x6  <> x2  < x5  >= x3  <= x2  > x16
             branch conditions : IF / REPEAT x9  CASE x2  FOR downto x1  FOR to x3
             runtime: 616 bytes, 330 swept, 0 0F-prefixed
-            program code: 38 of 38 fixtures swept end to end, 4315 bytes
+            program code: 39 of 39 fixtures swept end to end, 4603 bytes
             t33_cmpops: 13 comparisons matched against their source operators, in order
             clause H: 8 of 8 fixtures matched the branch conditions read off their source
 ```
@@ -581,7 +587,7 @@ checker.
 compile to `.COM` images, are booted on a floppy by a 512-byte hand-assembled
 boot sector, and are compared **byte for byte** against the exact output the
 fixture demands — `tests/run_com_exec.py`, wired into `run_all.sh`, 21/21 at
-that tag and 38/38 now.
+that tag and 39/39 now.
 
 Everything below is about establishing what *can* be believed, because the
 first attempt at this used an emulator that was wrong, and a wrong oracle is
@@ -1166,9 +1172,11 @@ Details that are deliberate, not incidental:
   prove the *path* exists (compile → poke → run → report → no file written),
   which it does with eight assertions. The other 37 are covered by
   `run_exec86.py`, which calls the same interpreter directly.
-- **Every fixture that compiles is now also run.** 38 of 42 execute; the 4 that
+- **Every fixture that compiles is now also run.** 39 of 44 execute; the 5 that
   do not are `t14` and `t25` (`ENoLib`, by design), `t38` (a boolean read,
-  refused with TP3's error 66, by design) and `uierror` (a deliberate syntax
+  refused with TP3's error 66, by design), `t43` (a program whose code exceeds
+  the 4 KiB window, refused with error 98, by design) and `uierror` (a
+  deliberate syntax
   error). The gap this replaces was nine fixtures that compiled and were
   *never executed* — `t08` const, `t09` if/then/else, `t10` while, `t11` for/to,
   `t12` repeat/until, `t13` procedure + value param, `t15` label + goto,
@@ -1990,18 +1998,19 @@ independently-scanned inventory at all.
    RESUME-TP3.md §3.11), range/index checks (`TU_RANGE_CHECK`,
    `TU_INDEX_CHECK`), typed constants (RESUME-TP3.md §3.14), `array` at its
    point of use (`t14`), `case` with subrange labels.
-3. Make the 4 KiB code window an enforced limit rather than a documented one:
-   report an error when `pc` reaches `dc`, instead of writing over the data.
-4. Both spellings of a multi-name declaration. `var i, c : integer;` is
-   error 1 at the comma and needs two `var` lines; `procedure f (a : integer;
-   b : integer)` is error 1 at the semicolon and needs a comma. Neither is
-   wrong Pascal, so a program that compiles under one compiler may not under
-   another. A parameter may also not shadow a global (`DupTest` rejects any
-   name `Search` finds at any level), which Pascal allows.
-5. Harden the program-header parameter loop against non-advancing input
+3. Harden the program-header parameter loop against non-advancing input
    (`program p(1;)`) with a `BOOLEAN` flag — **not** `EXIT`, which ICEs gm2.
-6. FreeDOS (`freedos.qcow2`, FD14-LiveCD) is still untried. Now that `R` runs
+4. FreeDOS (`freedos.qcow2`, FD14-LiveCD) is still untried. Now that `R` runs
    the image in-process, a real DOS is no longer needed for any claim above —
    but it is still the only way to get a third opinion on the `INT 21h` shim,
    and `Exec86` deliberately implements only the three functions the runtime
    calls.
+
+The 4 KiB code window is no longer a documented limitation but an enforced
+one (`v-TP3-CODEWINDOW`): `Ebyte` reports TP3's error 98 when the code
+reaches `dataBase`, and `t43_codewindow` is the fixture that makes it red
+(the run below it, `t43` not in `run_com_exec`).
+
+Multi-name declarations both ways are `v-TP3-MULTIDECL`: `var a, b, c :
+integer` and `procedure p (x, y : integer ; z : integer)` parse as TPSRC7
+does, and the older `name : type, name : type` spelling still parses.

+ 49 - 6
TP3-COMPILER.md

@@ -106,6 +106,46 @@ Three related defects fixed alongside:
   is attached to, so `Compound` then demanded a `;` that does not exist in
   `1: x := 1`.  `Statmnt` now parses the labelled statement.
 
+## Multi-name declarations, both spellings (the other `Skip` bug)
+
+TP3 collects a whole comma-separated list of names and then reads one `: type`
+for the list: `rdvarlst`/`rdvloop` for `VAR` (TPSRC7 ~1267, `CALL ccomma / JZ
+rdvloop`), `prfcnt`/`ccomma` for formal parameters (TPSRC7 ~891-925).  Groups
+are separated by `;`.  So both
+
+```
+var a, b, c : integer ;
+procedure p (x, y : integer ; z : integer) ;
+```
+
+are legal, and the older this-compiler-only spelling `a : integer, b : integer`
+is *not* what TP3 accepts -- but it is accepted here too, because both reach
+the same symbols and refusing it would turn a working program red for no
+behavioural reason.  `DefVar` now creates a placeholder symbol per name
+(`NewSym (nm, KVar, TNone, ...)`) and patches the group's one `cls`/`size`/
+`elem` into every one after `ParseType`; `ProcFunc` does the same and advances
+`parmOff` once per name.
+
+The bug this exposed is the same class as the one above: `GetWord` does **not**
+`Skip` the blanks first (it reads `CurCh` as the first character), and after
+`MatchDelim (',')` the cursor sits on the space after the comma.  The first
+name of a `VAR` group parsed (its `Skip` came from the group header) and every
+later name silently became `" B"`; `t42_multidecl` prints each name's value and
+is the fixture that made it red.
+
+## The 4 KiB code window is enforced
+
+`dataBase` is `rtSz + 1000H`: code and data share one 64 KiB `.COM` segment,
+so the program's code may not grow into the data area.  `Ebyte` used to check
+only `MaxCode` (24000), so a program whose code passed `dataBase` wrote over
+its own globals and produced a plausible, wrong image with no diagnostic.
+`Ebyte` now reports TP3's error 98 ("Memory overflow") when `pc >= dataBase`,
+gated on a new `imgReady` flag because `dataBase` is still 0 until `Compile`
+measures the runtime and lays out the data area.  `t43_codewindow` emits more
+than 4 KiB of inline string bytes and must report `ERR 98`; with the gate
+disabled the same fixture compiles `OK` -- the corrupt image -- which is the
+proof the test is not vacuous.
+
 ## Tests
 
 Several harnesses live in `shell/tests/` -- in the repo on purpose, because
@@ -141,7 +181,9 @@ use) and `t25` (a string literal assigned to an integer, `i := 'ab'`) → `ENoLi
 (102) -- the original's own "not implemented" path, and pinned on
 purpose so that implementing them is a deliberate change to a test rather than
 an accident; `t38` (`readln (b)` of a `boolean`) → error 66, TP3's "I/O not
-allowed here", for the same reason; and `uierror` is a deliberate syntax error
+allowed here", for the same reason; `t43_codewindow` emits more than the 4 KiB
+between the runtime and the data base → error 98, "Memory overflow", rather
+than a silently corrupt image; and `uierror` is a deliberate syntax error
 used by `uitest.py`.  (`t39_strvar` is the counter-example: a *string*
 variable, `s : string [10]`, assigned a literal and written -- the first
 declared type wider than a word that compiles, and run under both oracles.)
@@ -154,13 +196,13 @@ byte is a perfectly well-formed instruction that does something else, so no
 amount of inspecting the compiler's intent will catch it.  This harness closes
 that gap the only way it can be closed -- by running the thing.
 
-For each of 38 fixtures it compiles a `.COM`, writes a 1.44 MB floppy with the
+For each of 39 fixtures it compiles a `.COM`, writes a 1.44 MB floppy with the
 image in it, boots `qemu-system-i386 -fda disk.img -serial out.txt`, and
 compares `out.txt` to the committed `tests/fixtures/tNN.out` **exactly** --
 CRLF included -- plus the exit code the program hands to `INT 21h AH=4Ch`.
 
 ```
-cd shell && python3 tests/run_com_exec.py            # all 38
+cd shell && python3 tests/run_com_exec.py            # all 39
 cd shell && python3 tests/run_com_exec.py --show     # print the serial file
 cd shell && python3 tests/run_com_exec.py --rebless  # rewrite the .out files
 ```
@@ -189,9 +231,10 @@ partial read or a wrong sector count would have been visible, this was not.
 window at `C000h`; and the copy is executed code, so it is the last thing
 that touches the image and no BIOS call follows it.
 
-**38 of 42 fixtures execute.**  4 do not: `t14` and `t25` (`ENoLib`, by
-design), `t38` (a boolean read, refused with TP3's error 66, by design) and
-`uierror` (a deliberate syntax error).  The nine control-flow fixtures (`t08`
+**39 of 44 fixtures execute.**  5 do not: `t14` and `t25` (`ENoLib`, by
+design), `t38` (a boolean read, refused with TP3's error 66, by design),
+`t43` (a program whose code exceeds the 4 KiB window, refused with error 98,
+by design) and `uierror` (a deliberate syntax error).  The nine control-flow fixtures (`t08`
 const, `t09` if, `t10` while, `t11` for, `t12` repeat, `t13` procedure, `t15`
 label, `t27` five locals, `t28` 70 parameters) used to compile without a
 `.out` and were never run at all; they now print and are covered, which closed

+ 109 - 38
shell/Compiler.mod

@@ -243,6 +243,12 @@ VAR
       prepended. *)
    rtSz, dataBase : CARDINAL ;
 
+   (* TRUE once Compile has computed rtSz/dataBase, i.e. once the code window
+      has been measured.  Ebyte refuses to write past dataBase (the 4 KiB code
+      window) and needs to know the window is real before it can: dataBase is
+      0 until it is set, and `pc >= 0' would abort on the very first byte. *)
+   imgReady : BOOLEAN ;
+
    (* Image offset of the entry jump's rel16 operand, patched at the end of
       Compile.  The jump is at image offset 0, so its displacement is simply
       the program code's end - 3. *)
@@ -424,7 +430,13 @@ END OK ;
 
 PROCEDURE Ebyte (b : BYTE) ;
 BEGIN
-   IF pc >= MaxCode THEN
+   IF (pc >= MaxCode) OR (imgReady AND (pc >= dataBase)) THEN
+      (* The image buffer bound, and the code window.  dataBase is a fixed
+         4 KiB above the end of the runtime, so a program whose code reaches
+         it has grown into its own data area - the two cannot share a 64 KiB
+         .COM segment.  Reported as TP3's error 98 (memory overflow) rather
+         than a silent truncation; see the layout note in Compile and
+         SUMMARY.md "Known limit". *)
       Err (EMemOvf)
    ELSE
       cbuf [pc] := b ;
@@ -3035,6 +3047,7 @@ VAR nm : ARRAY [0..MaxName] OF CHAR ;
     tok : CARDINAL ;
     v : LONGINT ;
     off : CARDINAL ;
+    first, i : CARDINAL ;
 BEGIN
    LOOP
       Skip () ;                (* after the VAR keyword: skip blanks *)
@@ -3042,15 +3055,32 @@ BEGIN
          Err (EUnknown) ;
          RETURN
       END ;
+      (* A group is `name (, name)* : type' - TPSRC7 rdvarlst/rdvloop collects
+         the whole comma-separated list of names FIRST (`CALL ccomma / JZ
+         rdvloop'), then reads one `: type' with ecolon/rdvartp, then vardef2
+         gives every name in the list that one type.  The names are created as
+         placeholders here and filled in once the type is known, which is the
+         same shape as TP3 reserving a symtab slot per name and storing the
+         type pointer into each afterwards. *)
+      first := symTop ;
       LOOP
+         Skip () ;             (* GetWord does not skip blanks, and the cursor
+                                  sits on the space after the ',' *)
+         IF NOT Alpha (CurCh ()) THEN
+            Err (EUnknown) ;
+            RETURN
+         END ;
          GetWord () ;
          SaveWord (nm) ;
          DupTest (nm) ;
-         IF NOT MatchDelim (':') THEN
-            Err (ENoSemi)
-         END ;
-         ParseType (cls, size, elem) ;
-         off := 0 ;
+         DropC (NewSym (nm, KVar, TNone, 0, 0, 0, 0, lexnest # 0)) ;
+         IF NOT MatchDelim (',') THEN
+            EXIT
+         END
+      END ;
+      ExpectDelim (':', ENoSemi) ;
+      ParseType (cls, size, elem) ;
+      IF OK () THEN
          IF lexnest = 0 THEN
             IF (size > 2) AND (cls # TString) THEN
                (* Still the catch-all for a type whose storage this compiler does
@@ -3068,14 +3098,27 @@ BEGIN
                   RdIntConst (v) ;
                   off := W16 (v)
                ELSE
-                  Err (EUnknown)
+                  Err (EUnknown) ;
+                  off := 0
+               END ;
+               (* TP3's absolute path (vdstore) leaves the offset fixed, so
+                  every name in the group aliases the same address. *)
+               i := first ;
+               WHILE i < symTop DO
+                  symtab [i].cls := cls ; symtab [i].size := size ;
+                  symtab [i].elem := elem ; symtab [i].off := off ;
+                  INC (i)
                END
             ELSE
-               off := dc ;
-               dc := dc + size
+               i := first ;
+               WHILE i < symTop DO
+                  symtab [i].cls := cls ; symtab [i].size := size ;
+                  symtab [i].elem := elem ; symtab [i].off := dc ;
+                  dc := dc + size ;
+                  INC (i)
+               END
             END ;
-            DropC (NewSym (nm, KVar, cls, size, elem, off, 0, FALSE)) ;
-            varspc := varspc + size
+            varspc := varspc + size * (symTop - first)
          ELSE
             IF (size > 2) AND (cls # TString) THEN
                Err (ENoLib) ;
@@ -3092,23 +3135,33 @@ BEGIN
                pre-decrement rule produced, while a string[5] local (size 6) is
                at -6..-1 instead of the old -2..+3 - which straddled the saved
                BP at [BP] and the return address at [BP+2] and made `readln(s)'
-               into a local store characters over its own return address. *)
-            DropC (NewSym (nm, KVar, cls, size, elem,
-                           (locFree - size + 1) MOD 10000H, 0, TRUE)) ;
-            locFree := (locFree - size) MOD 10000H ;
-            locBytes := locBytes + size
-         END ;
-         IF NOT MatchDelim (',') THEN
-            EXIT
+               into a local store characters over its own return address.
+               Each name in the group gets its own slot. *)
+            i := first ;
+            WHILE i < symTop DO
+               symtab [i].cls := cls ; symtab [i].size := size ;
+               symtab [i].elem := elem ;
+               symtab [i].off := (locFree - size + 1) MOD 10000H ;
+               locFree := (locFree - size) MOD 10000H ;
+               INC (i)
+            END ;
+            locBytes := locBytes + size * (symTop - first)
          END
       END ;
-      IF NOT MatchDelim (';') THEN
+      IF MatchDelim (',') THEN
+         (* The older `name : type, name : type' spelling: another group
+            follows without a semicolon.  TP3's grammar is names-then-one-type
+            with ',' inside a group and ';' between groups (TPSRC7 rdvarlst /
+            rdvloop then ecolon/rdvartp), and both spellings reach the same
+            symbols, so both separators are accepted here. *)
+      ELSIF MatchDelim (';') THEN
+         PeekKw (tok) ;
+         IF tok # TkNone THEN
+            RETURN
+         END
+      ELSE
          Err (ENoSemi) ;
          RETURN
-      END ;
-      PeekKw (tok) ;
-      IF tok # TkNone THEN
-         RETURN
       END
    END
 END DefVar ;
@@ -3203,7 +3256,7 @@ VAR nm : ARRAY [0..MaxName] OF CHAR ;
     saveNest, saveLoc, saveRes, saveF : CARDINAL ;
     saveLB, savePO : CARDINAL ;
     nestMark : CARDINAL ;             (* symTop just inside this procedure *)
-    i : CARDINAL ;
+    i, first : CARDINAL ;
 BEGIN
    isFunc := curIsFunc ;
    Skip () ;                   (* after the PROCEDURE/FUNCTION keyword *)
@@ -3246,26 +3299,43 @@ BEGIN
                (* VAR parameter recorded as value in this milestone *)
                DropB (MatchKey (tok))
             END ;
-            Skip () ;             (* blanks before the parameter name *)
-            IF NOT Alpha (CurCh ()) THEN
-               Err (EUnknown) ;
-               RETURN
+            first := symTop ;
+            LOOP
+               Skip () ;          (* blanks before the parameter name *)
+               IF NOT Alpha (CurCh ()) THEN
+                  Err (EUnknown) ;
+                  RETURN
+               END ;
+               GetWord () ;
+               SaveWord (parmNm) ;
+               DupTest (parmNm) ;
+               DropC (NewSym (parmNm, KVar, TNone, 0, 0, 0, 0, TRUE)) ;
+               IF NOT MatchDelim (',') THEN
+                  EXIT
+               END
             END ;
-            GetWord () ;
-            SaveWord (parmNm) ;
-            DupTest (parmNm) ;
             ExpectDelim (':', ENoSemi) ;   (* formal is 'name : type' *)
             ParseType (cls, size, elem) ;
             IF size > 2 THEN
                Err (ENoLib) ;
                RETURN
             END ;
-            DropC (NewSym (parmNm, KVar, cls, size, elem, parmOff, 0, TRUE)) ;
-            parmOff := parmOff + 2 ;
-            IF NOT MatchDelim (',') THEN
-               IF MatchDelim (')') THEN
-                  EXIT
-               END ;
+            i := first ;
+            WHILE i < symTop DO
+               symtab [i].cls := cls ; symtab [i].size := size ;
+               symtab [i].elem := elem ;
+               symtab [i].off := parmOff ;
+               parmOff := parmOff + 2 ;
+               INC (i)
+            END ;
+            IF MatchDelim (')') THEN
+               EXIT
+            END ;
+            IF MatchDelim (';') THEN
+               (* next group *)
+            ELSIF MatchDelim (',') THEN
+               (* the older `name : type, name : type' spelling *)
+            ELSE
                Err (ENoSemi) ;
                EXIT
             END
@@ -3542,6 +3612,7 @@ BEGIN
    rtSz := rt + EntSize ;
    dataBase := rtSz + 1000H ;
    dc := dataBase ;
+   imgReady := TRUE ;
    strTop := 0 ;
    strCnt := 0 ;
    rdStrX := 0 ;

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

@@ -290,4 +290,20 @@ t38_readbool	ERR	66	659
 t39_strvar	OK	217	15
 t40_readln	OK	163	15
 t41_strassign	OK	263	51
+#   t42_multidecl   multi-name declarations in both spellings: `var a, b, c :
+#                   integer', `procedure add3 (x, y, z : integer)' and
+#                   `procedure mix (u, v : integer ; w : integer)' (TP3's
+#                   grammar: names joined by ',', groups by ';'), plus the
+#                   older `p : integer, q : integer' form.  data=12 is the four
+#                   2-byte globals a,b,c,d plus the two @@T temporaries; the
+#                   .out proves each name landed on its own slot.
+t42_multidecl	OK	304	12
 uierror	ERR	41	331
+#   t43_codewindow  a program whose code exceeds the 4 KiB window between the
+#                   end of the runtime and the data base (runtime + 1000h).  It
+#                   must be REPORTED as TP3 error 98 (memory overflow); before
+#                   Ebyte was bounded by dataBase it overran its own data and
+#                   produced a corrupt image with no diagnostic.  Each of the 20
+#                   writeln cases below embeds 240 bytes inline (wrtinl), so the
+#                   overflow lands inside one of them at pos 4978.
+t43_codewindow	ERR	98	4978

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

@@ -0,0 +1,4 @@
+6
+9
+1 2 3
+123

+ 43 - 0
shell/tests/fixtures/t42_multidecl.pas

@@ -0,0 +1,43 @@
+program t42;
+(* Multi-name declarations, in both spellings TP3 accepts.
+
+   TP3's grammar (TPSRC7) collects a comma-separated list of NAMES, then reads
+   ONE `: type' for the whole list (rdvarlst/rdvloop for VAR, prfcnt/ccomma for
+   parameters), and separates parameter groups with ';'.  So all three of
+
+      var a, b, c : integer ;        (names joined by ',')
+      procedure p (x, y : integer)   (names joined by ',')
+      procedure q (x : integer ; y : integer)   (groups joined by ';')
+
+   are legal, and this fixture uses each.  The older `name : type, name : type'
+   spelling is exercised too, so it keeps working.  Every variable and every
+   parameter must still land on the same slot layout as if written one per
+   line, which is what the printed values check. *)
+var
+  a, b, c : integer ;
+  d : integer ;
+procedure add3 (x, y, z : integer) ;
+begin
+  d := x + y + z
+end ;
+procedure add2 (p : integer, q : integer) ;
+begin
+  d := d + p + q
+end ;
+procedure mix (u, v : integer ; w : integer) ;
+begin
+  d := u * 100 + v * 10 + w
+end ;
+begin
+  a := 1 ;
+  b := 2 ;
+  c := 3 ;
+  add3 (a, b, c) ;
+  writeln (d) ;
+  d := 0 ;
+  add2 (4, 5) ;
+  writeln (d) ;
+  writeln (a, ' ', b, ' ', c) ;
+  mix (1, 2, 3) ;
+  writeln (d)
+end.

+ 31 - 0
shell/tests/fixtures/t43_codewindow.pas

@@ -0,0 +1,31 @@
+program t43;
+(* Code-window overflow.  TP3-comp places the data area a fixed 4 KiB above the
+   end of the runtime, so the program code and the program's data share one
+   64 KiB .COM segment and the code may NOT reach the data base.  This program
+   emits more than 4 KiB of code - each string case below is written inline
+   into the code stream by wrtinl (TPSRC4 xwrtstr's literal path) - and the
+   compiler must REPORT that (TP3 error 98, "Memory overflow") rather than
+   write over the data area.  Before the limit was enforced this fixture
+   overran dataBase silently and the image was corrupt. *)
+begin
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+  writeln ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') ;
+end.