|
|
@@ -0,0 +1,181 @@
|
|
|
+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.
|