|
@@ -21,6 +21,7 @@ manual, not guessed.
|
|
|
| Linker: real DOS `.COM` writer + independent byte checker | `v-TP3-COM-IMAGE` | done, executed |
|
|
| Linker: real DOS `.COM` writer + independent byte checker | `v-TP3-COM-IMAGE` | done, executed |
|
|
|
| Measured encodings: ModR/M table, runtime audit, golden disassembly, `[BP+off]` | `v-TP3-MEASURED-EMITTERS` | done, executed |
|
|
| Measured encodings: ModR/M table, runtime audit, golden disassembly, `[BP+off]` | `v-TP3-MEASURED-EMITTERS` | done, executed |
|
|
|
| Execution: a boot sector, qemu, and a claim about behaviour | `v-TP3-EXECUTION` | done, **21/21 fixtures run, exact output** |
|
|
| Execution: a boot sector, qemu, and a claim about behaviour | `v-TP3-EXECUTION` | done, **21/21 fixtures run, exact output** |
|
|
|
|
|
+| Execute the nine fixtures that only compiled | `v-TP3-DEAD-FIXTURES` | done, **30/30 run; four operator/scoping bugs found** |
|
|
|
| Runtime entries under qemu, and the register contract stated | `v-TP3-BP-CONTRACT` | done, **36/36 entry checks; `wrchar`/`wrbool` no longer destroy BP** |
|
|
| Runtime entries under qemu, and the register contract stated | `v-TP3-BP-CONTRACT` | done, **36/36 entry checks; `wrchar`/`wrbool` no longer destroy BP** |
|
|
|
| `CmdRun` (the `R` key), in-process 8086 interpreter | — | **not started** |
|
|
| `CmdRun` (the `R` key), in-process 8086 interpreter | — | **not started** |
|
|
|
|
|
|
|
@@ -71,7 +72,7 @@ verdict plus a source excerpt with a caret at `errPos`. No pty, instant.
|
|
|
```
|
|
```
|
|
|
cd shell && tests/run_compile_tests.sh # all fixtures
|
|
cd shell && tests/run_compile_tests.sh # all fixtures
|
|
|
cd shell && tests/run_compile_tests.sh /some/dir # another fixture set
|
|
cd shell && tests/run_compile_tests.sh /some/dir # another fixture set
|
|
|
-printf '@dump\ntests/fixtures/t19_int1.pas\n' | ./compiletest # hex-dump the image
|
|
|
|
|
|
|
+printf '@dump \ntests/fixtures/t19_int1.pas\n' | ./compiletest # hex-dump (trailing space! see TP3-COMPILER.md)
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
**The matrix asserts; it does not just count.** `tests/fixtures/expected.tsv`
|
|
**The matrix asserts; it does not just count.** `tests/fixtures/expected.tsv`
|
|
@@ -209,12 +210,12 @@ by building it and requiring the check to stay green.
|
|
|
### Execution under qemu — `tests/run_com_exec.py`
|
|
### Execution under qemu — `tests/run_com_exec.py`
|
|
|
|
|
|
|
|
The sixth check is the one that cannot be written as a byte comparison, so it
|
|
The sixth check is the one that cannot be written as a byte comparison, so it
|
|
|
-is also the one that finds the most: 21 fixtures are compiled to `.COM`, put on
|
|
|
|
|
|
|
+is also the one that finds the most: 30 fixtures are compiled to `.COM`, put on
|
|
|
a floppy, booted, and their serial output compared to a committed `.out` file
|
|
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`.
|
|
**exactly** — CRLF included — plus the exit code passed to `INT 21h AH=4Ch`.
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
-execution: 21 passed, 0 failed (of 21)
|
|
|
|
|
|
|
+execution: 30 passed, 0 failed (of 30)
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
The two fixtures it found nothing in are the interesting ones: `t31_procparam`
|
|
The two fixtures it found nothing in are the interesting ones: `t31_procparam`
|
|
@@ -278,7 +279,8 @@ checker.
|
|
|
**It is now closed, and the milestone is `v-TP3-EXECUTION`.** 21 fixtures
|
|
**It is now closed, and the milestone is `v-TP3-EXECUTION`.** 21 fixtures
|
|
|
compile to `.COM` images, are booted on a floppy by a 512-byte hand-assembled
|
|
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
|
|
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.
|
|
|
|
|
|
|
+fixture demands — `tests/run_com_exec.py`, wired into `run_all.sh`, 21/21 at
|
|
|
|
|
+that tag and 30/30 now.
|
|
|
|
|
|
|
|
Everything below is about establishing what *can* be believed, because the
|
|
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
|
|
first attempt at this used an emulator that was wrong, and a wrong oracle is
|
|
@@ -750,22 +752,58 @@ Details that are deliberate, not incidental:
|
|
|
## Honest limitations
|
|
## Honest limitations
|
|
|
|
|
|
|
|
- **`CmdRun` — the `R` key — is still a stub.** The compiler's *output* now
|
|
- **`CmdRun` — the `R` key — is still a stub.** The compiler's *output* now
|
|
|
- executes (21 fixtures, exact output, exit codes), but the shell cannot run a
|
|
|
|
|
|
|
+ executes (30 fixtures, exact output, exit codes), but the shell cannot run a
|
|
|
`.COM` in place. The linker writes a real `.COM` and the boot sector runs one
|
|
`.COM` in place. The linker writes a real `.COM` and the boot sector runs one
|
|
|
under qemu; nothing in the host program yet interprets 8086 code. So the
|
|
under qemu; nothing in the host program yet interprets 8086 code. So the
|
|
|
user's route to seeing output is "compile, then run under qemu", not "press
|
|
user's route to seeing output is "compile, then run under qemu", not "press
|
|
|
`R`". TP3's `R` runs in the same 64 KB with no DOS loader, which is why this
|
|
`R`". TP3's `R` runs in the same 64 KB with no DOS loader, which is why this
|
|
|
is an interpreter and not a `system()` call.
|
|
is an interpreter and not a `system()` call.
|
|
|
-- **21 of 33 fixtures execute.** 3 do not compile (`t14` and `t25` by design
|
|
|
|
|
- as `ENoLib`, `uierror` deliberately), leaving **9 that compile and are never
|
|
|
|
|
- run**: `t08` const · `t09` if/then/else · `t10` while · `t11` for/to ·
|
|
|
|
|
- `t12` repeat/until · `t13` procedure + value param · `t15` label + goto ·
|
|
|
|
|
- `t27` five locals · `t28` the 70-parameter declaration. Those are not
|
|
|
|
|
- incidental omissions — they are the *control-flow* fixtures, and `t27`'s
|
|
|
|
|
- five locals are precisely the `[BP+off]` paths this project got wrong twice.
|
|
|
|
|
- They have byte-level checks and no behavioural check at all. Writing nine
|
|
|
|
|
- `.out` files is cheap and is the highest-value step after `rt_exec.py`; the
|
|
|
|
|
- byte counts in `expected.tsv` will then have a behavioural counterpart.
|
|
|
|
|
|
|
+- **Every fixture that compiles is now also run.** 30 of 33 execute; the 3 that
|
|
|
|
|
+ do not are `t14` and `t25` (`ENoLib`, 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,
|
|
|
|
|
+ `t27` five locals, `t28` the 70-parameter declaration — and all nine
|
|
|
|
|
+ **contained no `write`/`writeln` at all**. They assigned to a variable and
|
|
|
|
|
+ fell off the end, so the only thing an empty `.out` could have asserted was
|
|
|
|
|
+ "did not crash". That is why `t12`'s loop ran exactly once for the whole life
|
|
|
|
|
+ of the project with nothing to see it. The nine now print, each `.out` is
|
|
|
|
|
+ derived by hand from the Pascal rather than from the machine, and
|
|
|
|
|
+ `nonvacuity.sh` mutates each of the four bugs back in and requires the
|
|
|
|
|
+ execution check to go red. Finding them cost four fixes; see the milestone
|
|
|
|
|
+ section.
|
|
|
|
|
+- **A call takes at most 16 arguments, and says "compiler overflow" if you
|
|
|
|
|
+ exceed it.** `args` is `ARRAY [0..15] OF ERes` in the three call parsers, and
|
|
|
|
|
+ the guard raises `ECompOvf` = 99. So `far (1, 2, ... , 70)` is rejected with
|
|
|
|
|
+ error 99, whose text in TP3 means the compiler's own table overflowed — an
|
|
|
|
|
+ error about the compiler, for a program that merely has a long argument
|
|
|
|
|
+ list. This is why `t28_farparam` can *declare* 70 parameters (which is what
|
|
|
|
|
+ puts `p63` at `[BP+128]` and exercises the disp16 encoding) but can only
|
|
|
|
|
+ *pass* 16, and why its body reads `p63`/`p70` into a variable whose value is
|
|
|
|
|
+ deliberately absent from the `.out`: those two slots hold stack garbage, and a
|
|
|
|
|
+ fixture that printed them would be testing the harness, not the compiler.
|
|
|
|
|
+- **Parameters are separated by `,` and only by `,`.** `procedure two (a : integer ; b : integer)`
|
|
|
|
|
+ is error 1 at the semicolon; `procedure two (a : integer, b : integer)`
|
|
|
|
|
+ compiles. The reverse holds for variable declarations — `var a, b : integer`
|
|
|
|
|
+ is error 1 at the comma and needs two `var` lines. Inconsistent, and neither
|
|
|
|
|
+ spelling is wrong Pascal, so a program that compiles under one compiler may
|
|
|
|
|
+ not under another.
|
|
|
|
|
+- **A parameter may not shadow a global.** `DefProc` calls `DupTest` on every
|
|
|
|
|
+ parameter name, and `DupTest` reports error 41 for any name `Search` finds —
|
|
|
|
|
+ including one declared at an outer level. `procedure bump (x : integer)` with
|
|
|
|
|
+ a global `x` is rejected. Pascal allows the shadow and the inner one wins.
|
|
|
|
|
+ `HideLocals` fixed siblings colliding with each other; this is the
|
|
|
|
|
+ parent-vs-child direction of the same question and is untouched.
|
|
|
|
|
+- **The branch and compare lowering is 386 code on an 8086 target.** `EmJcc`
|
|
|
|
|
+ emits `0F 8x rel16` and `EmSetcc` emits `0F 9x` (SETcc). Neither exists on
|
|
|
|
|
+ an 8086. TP3 uses `JZ rel8` over a 3-byte `EJMP`, with `excond` materialising
|
|
|
|
|
+ the *negated* boolean (`TPSRC8` ~244-300). Every relational operator and every
|
|
|
|
|
+ conditional branch in the compiler is affected, so this is not one call site
|
|
|
|
|
+ but the whole idiom. **No test can see it**: qemu-i386 defaults to a
|
|
|
|
|
+ post-386 CPU, so every image in `run_com_exec.py` runs correctly on hardware
|
|
|
|
|
+ that did not exist when TP3 shipped. Catching it needs `-cpu 8086` (untried) or
|
|
|
|
|
+ a rewrite to the `excond` idiom plus a short/near branch policy — a milestone
|
|
|
|
|
+ with a wide golden blast radius, deliberately not folded into this one.
|
|
|
- **The runtime's entries are now each called directly, and two of its
|
|
- **The runtime's entries are now each called directly, and two of its
|
|
|
invariants are stated rather than implied.** 436 bytes, 14 entries, 100
|
|
invariants are stated rather than implied.** 436 bytes, 14 entries, 100
|
|
|
emitter helpers decoded against their own names across both modules, the
|
|
emitter helpers decoded against their own names across both modules, the
|
|
@@ -773,15 +811,16 @@ Details that are deliberate, not incidental:
|
|
|
boundaries — and 35 cases that call the entries one at a time under qemu
|
|
boundaries — and 35 cases that call the entries one at a time under qemu
|
|
|
(`rt_exec.py`, 36/36). What is *not* covered is what a direct call cannot
|
|
(`rt_exec.py`, 36/36). What is *not* covered is what a direct call cannot
|
|
|
see: `wrtinl` is checked, but only from the harness's side of the calling
|
|
see: `wrtinl` is checked, but only from the harness's side of the calling
|
|
|
- contract, and the nine fixtures that are compiled but never executed
|
|
|
|
|
- (above) are still reached only through the 21 that are.
|
|
|
|
|
|
|
+ contract. (The nine fixtures that compiled but were never executed used to sit
|
|
|
|
|
+ here as an extra gap; they are now run, so every fixture that reaches the
|
|
|
|
|
+ runtime at all also reaches it *through generated code*.)
|
|
|
- **The pushback slot's address is a moving target.** It sits at
|
|
- **The pushback slot's address is a moving target.** It sits at
|
|
|
`rtSz + LoadBias + dataAt + D_PUSH`, so every runtime growth moves it, and
|
|
`rtSz + LoadBias + dataAt + D_PUSH`, so every runtime growth moves it, and
|
|
|
every address derived from it must be recomputed. It is computed, not
|
|
every address derived from it must be recomputed. It is computed, not
|
|
|
hard-coded — but the two `RT_SZ` constants that *were* hard-coded and had
|
|
hard-coded — but the two `RT_SZ` constants that *were* hard-coded and had
|
|
|
drifted (see the execution section) are the precedent for why this one gets
|
|
drifted (see the execution section) are the precedent for why this one gets
|
|
|
stated every time.
|
|
stated every time.
|
|
|
-- **21 fixtures is a small sample of Pascal.** They cover `var`, `const`,
|
|
|
|
|
|
|
+- **30 fixtures is a small sample of Pascal.** They cover `var`, `const`,
|
|
|
`if`, `while`, `for`, `repeat`, `case` over scalars, procedures with value
|
|
`if`, `while`, `for`, `repeat`, `case` over scalars, procedures with value
|
|
|
parameters, `goto`/`label`, string literals and `readln`. They do **not**
|
|
parameters, `goto`/`label`, string literals and `readln`. They do **not**
|
|
|
cover nested procedures, recursion, `var` parameters, `with`, records, sets,
|
|
cover nested procedures, recursion, `var` parameters, `with`, records, sets,
|
|
@@ -1058,9 +1097,69 @@ Eleven more, and the two worst in the project are here.
|
|
|
(ADDRESS) vs `LdBxVx` (CONTENTS) vs `StVxBx`; `MovAlBl` (`8A C3`, register)
|
|
(ADDRESS) vs `LdBxVx` (CONTENTS) vs `StVxBx`; `MovAlBl` (`8A C3`, register)
|
|
|
vs `LdAlBx` (`8A 07`, memory); `MovAh0` → `{0xB4}` vs `MovAl0` → `{0xB0}`.
|
|
vs `LdAlBx` (`8A 07`, memory); `MovAh0` → `{0xB4}` vs `MovAl0` → `{0xB0}`.
|
|
|
|
|
|
|
|
|
|
+### Then nine fixtures were made to print, and four more appeared
|
|
|
|
|
+
|
|
|
|
|
+Twenty-seven bugs, and every one of them had been found by looking at bytes,
|
|
|
|
|
+decoding them, or reading them. The remaining fixtures — nine of them, the
|
|
|
|
|
+control-flow ones — had never been executed, because each one **assigned to a
|
|
|
|
|
+variable and fell off the end**: no `write`, no `writeln`, nothing to observe.
|
|
|
|
|
+An empty `.out` would have asserted only "did not crash", which is a property of
|
|
|
|
|
+the runtime, not of the operator under test. `t12_repeat` could have been
|
|
|
|
|
+compiling `repeat…until` as a single-pass loop for the entire life of the
|
|
|
|
|
+project and no check would have said.
|
|
|
|
|
+
|
|
|
|
|
+Rewriting the nine to print, with each `.out` **derived by hand from the Pascal
|
|
|
|
|
+and not blessed from the machine**, found four more. All four shipped with a
|
|
|
|
|
+green compile matrix, a green `.COM` layout check, a green golden and a green
|
|
|
|
|
+emitter audit.
|
|
|
|
|
+
|
|
|
|
|
+28. **`a * b` emitted `ADD AX,CX`.** `ParseAdd` numbered `+` as `1` and
|
|
|
|
|
+ `ParseMul` numbered `*` as `1` as well, and both pass the bare number to
|
|
|
|
|
+ `BinOpEmit`, which cannot see which precedence level called it. So every
|
|
|
|
|
+ multiplication dispatched to the addition: `a * 2` became `a + 2`, and
|
|
|
|
|
+ `7 * 6` printed `13`. The constant-folding arm of `BinOpEmit` was correct,
|
|
|
|
|
+ which is the only reason anything looked right — `t08_const`'s `n * n` has
|
|
|
|
|
+ two constants, and the one fixture that ever multiplied was multiplying two
|
|
|
|
|
+ constants. Now `OpAdd`/`OpSub`/`OpMul` are named constants, so two levels
|
|
|
|
|
+ cannot collide on a bare `1`, and `t08` was given a variable multiply.
|
|
|
|
|
+
|
|
|
|
|
+29. **`>` and `>=` had their SETcc opcodes swapped.** Op 4 (`>`) emitted `9Dh`
|
|
|
|
|
+ = SETGE and op 5 (`>=`) emitted `9Fh` = SETG, so `a > b` meant `a >= b` and
|
|
|
|
|
+ `a >= b` meant `a > b`. Only the equality boundary could see it: `6>5`,
|
|
|
|
|
+ `4>5`, `5<5`, `4<=5` and `-1>-2` were all already correct, and `5 > 5` and
|
|
|
|
|
+ `5 >= 5` were both wrong. One letter apart in the mnemonic, and the CASE arm
|
|
|
|
|
+ gave no hint which comparison it answered. Every arm now carries its
|
|
|
|
|
+ mnemonic beside the hex.
|
|
|
|
|
+
|
|
|
|
|
+30. **`REPEAT…UNTIL` looped back while the condition was TRUE** — `JNZ` where
|
|
|
|
|
+ the body should be re-entered only when the condition is FALSE. That is
|
|
|
|
|
+ `while`, so the body ran once, the condition was tested, and it stopped.
|
|
|
|
|
+ `i := 0; repeat i := i + 1 until i > 5; writeln (i)` printed **1**; the
|
|
|
|
|
+ hand-derived answer is 6. TP3's own sequence (`TPSRC8` ~244-300) calls
|
|
|
|
|
+ `excond`, which materialises the *negated* boolean, then a fixed `brnchop`
|
|
|
|
|
+ of `JZ`, so a single branch shape serves IF, WHILE and REPEAT alike.
|
|
|
|
|
+
|
|
|
|
|
+31. **Sibling procedures shared one parameter namespace.** A finished
|
|
|
|
|
+ procedure's symbols were left at a `level` that `Search`'s `level <= lexnest`
|
|
|
|
|
+ test still accepted, and every procedure body compiles at the same depth.
|
|
|
|
|
+ So a second `a : integer` was a duplicate (error 41) and an unqualified `a`
|
|
|
|
|
+ inside procedure two silently read procedure one's argument — passing 3
|
|
|
|
|
+ into `one` and then computing `x := a + 1` in `two` printed the wrong
|
|
|
|
|
+ number with no diagnostic at all. `HideLocals` relabels a procedure's own
|
|
|
|
|
+ symbols to `0FFFFH` when it closes, which fails the visibility test at every
|
|
|
|
|
+ depth a later procedure can be at. Relabelled rather than popped, because
|
|
|
|
|
+ `symtab[old].resvar` holds an index and a function's result variable is one
|
|
|
|
|
+ of the entries being hidden.
|
|
|
|
|
+
|
|
|
|
|
+The theme is worth stating because it is the same theme as bug 27: **a name that
|
|
|
|
|
+does not distinguish two things makes the next mistake invisible.** `*` and `+`
|
|
|
|
|
+were both `1`; `>` and `>=` were two hex bytes; two procedures' `a` was one
|
|
|
|
|
+symbol. In each case the fix is to make the distinction part of the name or the
|
|
|
|
|
+surrounding text, not to fix the value and leave the ambiguity in place.
|
|
|
|
|
+
|
|
|
## The bug family, stated once
|
|
## The bug family, stated once
|
|
|
|
|
|
|
|
-Nine of the twenty-seven are the *same* bug in different clothes: **loading the
|
|
|
|
|
|
|
+Nine of the thirty-one are the *same* bug in different clothes: **loading the
|
|
|
address where the value was wanted, or picking the register one byte or one
|
|
address where the value was wanted, or picking the register one byte or one
|
|
|
letter away from the right one.** `EmPushVarAddr` had the right bytes for the
|
|
letter away from the right one.** `EmPushVarAddr` had the right bytes for the
|
|
|
wrong register. `LdAlBx` and `MovAlBl` are one letter apart. `MovAh0` and
|
|
wrong register. `LdAlBx` and `MovAlBl` are one letter apart. `MovAh0` and
|
|
@@ -1137,28 +1236,43 @@ independently-scanned inventory at all.
|
|
|
|
|
|
|
|
## Next steps
|
|
## Next steps
|
|
|
|
|
|
|
|
-1. **`CmdRun`** as an in-process 8086 interpreter — the `R` menu key, and a
|
|
|
|
|
|
|
+1. **8086 branch and compare lowering.** `EmJcc` emits `0F 8x rel16` and
|
|
|
|
|
+ `EmSetcc` emits `0F 9x`; neither instruction exists on an 8086. Every
|
|
|
|
|
+ relational operator and every conditional branch depends on them. TP3's
|
|
|
|
|
+ idiom is `excond` materialising the negated boolean plus `JZ rel8` over a
|
|
|
|
|
+ 3-byte `EJMP` (`TPSRC8` ~244-300), so this needs a boolean-materialisation
|
|
|
|
|
+ strategy and a short/near branch policy, not two opcode substitutions — and
|
|
|
|
|
+ it will move nearly every byte in the golden. **First, try
|
|
|
|
|
+ `qemu-system-i386 -cpu 8086`** on the existing 30 images: if that turns this
|
|
|
|
|
+ class red, it is a one-line addition to `rt_exec.py` and it makes every
|
|
|
|
|
+ later fix provable instead of argued.
|
|
|
|
|
+2. **`CmdRun`** as an in-process 8086 interpreter — the `R` menu key, and a
|
|
|
fallback executor for environments with no DOS. Validate it against qemu on
|
|
fallback executor for environments with no DOS. Validate it against qemu on
|
|
|
the *same images*, so the two oracles check each other. Cross-validation is
|
|
the *same images*, so the two oracles check each other. Cross-validation is
|
|
|
- the point: an interpreter that agrees with qemu on 21 fixtures is far more
|
|
|
|
|
|
|
+ the point: an interpreter that agrees with qemu on 30 fixtures is far more
|
|
|
evidence than either alone.
|
|
evidence than either alone.
|
|
|
-2. **String *variables*** — `s : string`, `s := 'hi'`, `writeln(s)`. The
|
|
|
|
|
|
|
+3. **String *variables*** — `s : string`, `s := 'hi'`, `writeln(s)`. The
|
|
|
encoding blocker is gone (`EmBpDisp`); what is left is a length word, an
|
|
encoding blocker is gone (`EmBpDisp`); what is left is a length word, an
|
|
|
assignment path, and a `WrStr` entry (TPSRC4 `xwrtstr`). `IoCall` currently
|
|
assignment path, and a `WrStr` entry (TPSRC4 `xwrtstr`). `IoCall` currently
|
|
|
refuses with `ENoLib`.
|
|
refuses with `ENoLib`.
|
|
|
-3. Nested procedures / recursion, `var` parameters (the `SEG:OFF` push from
|
|
|
|
|
|
|
+4. Nested procedures / recursion, `var` parameters (the `SEG:OFF` push from
|
|
|
RESUME-TP3.md §3.11), range/index checks (`TU_RANGE_CHECK`,
|
|
RESUME-TP3.md §3.11), range/index checks (`TU_RANGE_CHECK`,
|
|
|
`TU_INDEX_CHECK`), typed constants (RESUME-TP3.md §3.14), `array` at its
|
|
`TU_INDEX_CHECK`), typed constants (RESUME-TP3.md §3.14), `array` at its
|
|
|
point of use (`t14`), `case` with subrange labels.
|
|
point of use (`t14`), `case` with subrange labels.
|
|
|
-4. **`readln` of a `BYTE`** calls `rdint`, which stores 2 bytes and overflows
|
|
|
|
|
|
|
+5. **`readln` of a `BYTE`** calls `rdint`, which stores 2 bytes and overflows
|
|
|
into the next variable. TP3 has a separate `xrdbyte`; a `TU_RdByte` entry is
|
|
into the next variable. TP3 has a separate `xrdbyte`; a `TU_RdByte` entry is
|
|
|
the fix. No fixture exists yet, which is why it has not been done — write
|
|
the fix. No fixture exists yet, which is why it has not been done — write
|
|
|
the fixture first, so the bug is red before the fix.
|
|
the fixture first, so the bug is red before the fix.
|
|
|
-5. Make the 4 KiB code window an enforced limit rather than a documented one:
|
|
|
|
|
|
|
+6. 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.
|
|
report an error when `pc` reaches `dc`, instead of writing over the data.
|
|
|
-6. Comma-separated names: `var i, c : integer;`.
|
|
|
|
|
-7. Harden the program-header parameter loop against non-advancing input
|
|
|
|
|
|
|
+7. 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.
|
|
|
|
|
+8. Harden the program-header parameter loop against non-advancing input
|
|
|
(`program p(1;)`) with a `BOOLEAN` flag — **not** `EXIT`, which ICEs gm2.
|
|
(`program p(1;)`) with a `BOOLEAN` flag — **not** `EXIT`, which ICEs gm2.
|
|
|
-8. FreeDOS (`freedos.qcow2`, FD14-LiveCD) is still untried. Not needed for any
|
|
|
|
|
|
|
+9. FreeDOS (`freedos.qcow2`, FD14-LiveCD) is still untried. Not needed for any
|
|
|
claim above, but it is the only way to get a *real* DOS as a third opinion
|
|
claim above, but it is the only way to get a *real* DOS as a third opinion
|
|
|
on the `INT 21h` shim.
|
|
on the `INT 21h` shim.
|