v-TP3-DECL-WINDOW-STRIDX — Session summary ========================================== Tag: v-TP3-DECL-WINDOW-STRIDX Commit: (the commit that adds this file) Covers: v-TP3-MULTIDECL, v-TP3-CODEWINDOW, v-TP3-STRINDEX This is the resume point for the session that did "Next steps" items 3 and 4 in full and the first slice of item 1. It is written so the next session can pick up without re-deriving anything: every claim below is pinned by a fixture in `shell/tests/fixtures/expected.tsv` and by the gate suite. What changed ------------ 1. Multi-name declarations, both spellings (`v-TP3-MULTIDECL`). TP3 reads a whole comma-separated list of NAMES and then ONE `: type` for the list, groups separated by `;`: TPSRC7:744 vardef TPSRC7:1267-1275 rdvarlst / rdvloop (`CALL ccomma / JZ rdvloop', then ecolon/rdvartp) TPSRC7:891-925 prfcnt / ccomma (formal parameters) So `var a, b, c : integer ;` and `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. Both are accepted here now, because both reach the same symbols. `DefVar` creates a placeholder 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 names-then-type order is what makes the placeholder-then-patch shape necessary: the type is not known when the names are read. 2. The 4 KiB code window is enforced (`v-TP3-CODEWINDOW`). `dataBase` is `rtSz + 1000H`: code and data share one 64 KiB `.COM` segment, so program code may not grow into the data area. `Ebyte` checked only `MaxCode` (24000), so code past `dataBase` wrote over the globals and produced a plausible, wrong image with no diagnostic. `Ebyte` now reports TP3's error 98 ("Memory overflow" = `EMemOvf`, the number `DefVar` already uses when `dc` overflows), gated on a new `imgReady` flag because `dataBase` is 0 until `Compile` measures the runtime and lays out the data area. 3. String subscripts `s[i]`, constant index (`v-TP3-STRINDEX`). A `string [n]` is a length byte at `s[0]` followed by the characters, so `s[i]` is the single byte at `off+i`. `ParseSub` folds a constant index into `boff` (the field an array already uses) and drops `r.cls` to `TChar`; then `LoadAtom` loads one byte (`A0 off` / `8A 46 disp`) and zeroes `AH`, `Assignment` stores one byte when the symbol is `TString` but `r.cls` is `TChar`, `IoCall` already classified on `arg.cls` so `writeln (s[i])` picked `wrchar`, and the reader now classifies `read (s[i])` the same way and pushes `off+boff`. Only a constant index is accepted; a variable index is `ENoLib`, exactly as it already was for an array. No new runtime entry: a constant index is pure compile-time offset arithmetic, so the runtime image is unchanged (616 bytes, 18 entries, 51 branches). Bugs found and fixed -------------------- 1. **`GetWord` does not `Skip`, so every name after the first in a multi-name VAR group was wrong.** After `MatchDelim (',')` the cursor sits on the space, and `GetWord` reads `CurCh` as the first character, so the second name became `" B"` (leading space). The first name parsed (its `Skip` came from the group header) which is why "one name works, two do not". `t42` is red before the added `Skip`, green after. Same class as the nine `Skip` sites fixed in `v-TP3-PARSER-FIXES`. 2. **`ParseAtom` clobbered every subscript.** Its variable arm ended with ParseVar (r) ; r.boff := 0 ; (* discards the subscript ParseSub just folded in *) `ParseVar` already sets `boff := 0` before calling `ParseSub`, so the reset was pure loss: `s[i]` addressed `off+0`, the length byte, while still reporting itself as a char. Latent for a long time because an `array` cannot be declared yet, so no fixture had ever exercised the fold. `t44` reads the length byte with the reset restored and the right bytes without it. 3. **The 4 KiB window was documented but not enforced.** Not a crash but a silent corruption: an over-long program overwrote its own data and linked to a well-formed image with no error. This is the shape of failure the project treats as a bug. `t43_codewindow` is red-as-`OK` (i.e. compiles wrongly) with the gate disabled. Scope decisions and findings ---------------------------- - The declaration change is a strict SUPERSET of TP3: it accepts TP3's `;`-separated groups AND the older `,`-between-decls spelling. Refusing the latter would turn working programs red for no behavioural reason, and both reach the same symbols. - A `GetWord` after a `,`/`;` must be preceded by `Skip ()`. This is the same trap as the existing "Alpha test without Skip" class and is worth remembering for any new comma-loop. - The string subscript uses `r.cls = TChar` while the SYMBOL's class stays `TString` as the discriminator between "the element" and "the whole string". That is the only thing that lets `s := t`, `writeln (s)` and `s[i]` share one symbol without a new field on `ERes`. - Constant index only. A variable index needs the runtime address arithmetic TP3 does in `indstr`/`ialoop` (TPSRC9), and is the natural next step. - `tpshell` clean-build size is unchanged at 191104 bytes across items 3+4 and the string index. This is real, not a stale build: the two `tpshell` binaries differ by md5 and `Compiler.o` grows 75064 -> 75568 bytes, but the linked size lands on the same boundary. `make` was verified to rebuild `Compiler.o` before linking. Fixtures added -------------- t42_multidecl OK 304 12 both declaration spellings t43_codewindow ERR 98 4978 code past dataBase, TP3 error 98 t44_strindex OK 258 16 s[i] read + write, global + local t45_strindex_read OK 134 15 readln (s[i]) (input: 'X' then 'Y') Next work --------- Item 1 (the rest of the string language), in the order that keeps each step self-contained: a. **Variable index `s [i]`.** TP3 computes the address at run time: `indstr` / `ialoop` / `ianopop` (TPSRC9:543 `xindchk`). A clean shape for this compiler is `LEA BX,[base]` (new `EmLeaBx`; the address forms already exist as `EmPushVarAddr`'s three encodings, minus the push), then `LoadAtom (index)` into AX (which must not touch BX), `ADD BX,AX` (`01 C3'), then `MOV AL,[BX]` / `MOV [BX],AL`. `ParseSub` must park the index `ERes` on `r` until `LoadAtom`/`Assignment` runs, so `ERes` needs somewhere to keep a nested index (a side array indexed by a small handle, or the kind/imm/idx/boff of the index copied across). b. **String comparison `= <> < <= > >=`** (`cmpstr`, TPSRC2:890-924, and `vcompare`, TPSRC9:133). A new runtime entry taking two addresses, leaving flags set, is enough because `EmSetcc` reads live flags; the open question is the SIGNEDNESS and the prefix rule (is "ab" < "abc"?) — read it off `cmpstr` before trusting the `SETL`/`SETG` opcodes. This adds a runtime entry, so `runtime.golden`, `check_runtime.py`, `rt_exec.py` (next selector is 18) and the branch count all move with it. c. **Concatenation `+`** (`xconcat`, TPSRC2; `addstr` -> `xconcat`, TPSRC9:194). Hardest: TP3 pushes strings inline on the stack, this model addresses them, so a result needs a scratch/result string and a length rule (TP3 caps at 255). Item 2 (the large one) is untouched: nested procedures / recursion, `var` parameters (the `SEG:OFF` push, RESUME-TP3.md 3.11), range/index checks (`TU_RANGE_CHECK`, `TU_INDEX_CHECK`), typed constants (3.14), `array` at its point of use (t14), `case` with subrange labels. Also still open from before this session: d. Harden the program-header parameter loop against non-advancing input (`program p(1;)`) with a `BOOLEAN` flag — NOT `EXIT`, which ICEs gm2. e. FreeDOS (`freedos.qcow2`, FD14-LiveCD) is still untried; it is the only third opinion on the `INT 21h` shim. How to verify this session's work --------------------------------- cd shell && make clean && make # tpshell 191104 bytes, rc=0 cd shell && tests/run_all.sh # expect: OVERALL: ALL PASS At this tag the suite reports: compile matrix: 46 passed, 0 failed (of 46) independent .COM check: 41 checked, 0 failed execution: 41 passed, 0 failed (of 41) exec86: 41 passed, 0 failed (of 41), cross-checked against qemu runtime entries: 51 passed, 0 failed (of 51) check_8086: 41 of 41 fixtures swept end to end, 4963 bytes runtime: 616 bytes, 18 entries, 51 branches OVERALL: ALL PASS Each new behaviour was proven red before green: the multiply-name read (`t42`) fails without the `Skip`, the subscript read (`t44`) reads the length byte without the `ParseAtom` fix, and the code-window report (`t43`) turns into a silent `OK` with the `Ebyte` gate disabled.