|
|
@@ -36,13 +36,147 @@ whole-program pass 3 silently caps identifier/error counts — see
|
|
|
4. **`Compile` error-return params** aligned to `Compiler.def`
|
|
|
(`errNo`/`errPos`, was `eNo`/`ePos`) — gm2 ISO rejects a disparate name
|
|
|
in the proper procedure.
|
|
|
+5. **`HALT` -> `HALT (0)`** in `Shell.mod`. A bare `HALT` (no operand)
|
|
|
+ aborts under `gm2 -fiso`: a minimal repro exits 134 (SIGABRT) with a
|
|
|
+ bare `HALT` but 0 with `HALT (0)`. Quitting the shell died on SIGABRT.
|
|
|
+6. **Module-level `errNo` renamed `errNum`.** `Compile`'s formal `errNo`
|
|
|
+ shadowed the module variable, so `errNo := errNo` was a *self*-assignment
|
|
|
+ and every error code reached the caller as 0.
|
|
|
|
|
|
The compiler's whole-program 8086-emit, TEXT-equivalent, pastres and
|
|
|
-errexit flow are unchanged (mirrors TP3 TPSRC file, ConvertTP3).
|
|
|
+errexit flow are otherwise unchanged (mirrors TP3 TPSRC file, ConvertTP3).
|
|
|
+
|
|
|
+## Lexer/parser bug class: "Alpha test without Skip"
|
|
|
+
|
|
|
+The single largest source of wrong behaviour. `Skip ()` is what folds
|
|
|
+blanks, `{ }` and `(* *)` comments, and `CR`; neither `PeekKw` nor
|
|
|
+`MatchKey` leaves the cursor past it (`PeekKw` save/restores `srcPos`,
|
|
|
+`MatchKey` stops right after the word it matched). So a routine entered
|
|
|
+*immediately after a keyword* sees the CR/blank that follows that keyword,
|
|
|
+and any
|
|
|
+
|
|
|
+```
|
|
|
+IF NOT Alpha (CurCh ()) THEN Err (EUnknown) ; RETURN
|
|
|
+```
|
|
|
+
|
|
|
+fires with `EUnknown` (41) pointing at a blank. That is why every program
|
|
|
+used to fail on `program`'s program name, and why `writeln(...)` failed on
|
|
|
+its `(`.
|
|
|
+
|
|
|
+Fixed by adding the missing `Skip ()` at each entry point (it is idempotent,
|
|
|
+so it is safe even where a `Skip` already ran):
|
|
|
+
|
|
|
+| routine | entered after |
|
|
|
+|---|---|
|
|
|
+| program header | `PROGRAM` |
|
|
|
+| `DefVar` | `VAR` |
|
|
|
+| `DefConst` | `CONST` |
|
|
|
+| `DefType` | `TYPE` |
|
|
|
+| `ParseType` | `:` / `=` |
|
|
|
+| `ProcFunc` (name) | `PROCEDURE` / `FUNCTION` |
|
|
|
+| `ProcFunc` (parameter name) | `(`, or a var-parameter's `VAR` |
|
|
|
+| `Statmnt`, `TkFor` branch | `FOR` |
|
|
|
+| `Statmnt`, `TkGoto` branch | `GOTO` |
|
|
|
+
|
|
|
+`DefLabelPart`, `MatchKey`, `MatchDelim` and `MatchAssign` already had theirs.
|
|
|
+
|
|
|
+Three related defects fixed alongside:
|
|
|
+
|
|
|
+- `ProcFunc`'s parameter loop used `MatchKey (tok) AND (tok = TkVar)` to
|
|
|
+ detect a var-parameter. `MatchKey` **consumes** the word it reads, so
|
|
|
+ using it as a lookahead ate the parameter's own name. Replaced with
|
|
|
+ `PeekKw` (non-consuming), consuming with `DropB (MatchKey (tok))` only on
|
|
|
+ a `VAR` hit.
|
|
|
+- `ProcFunc` called `ParseType` straight after the parameter name without
|
|
|
+ consuming the `:` of `name : type`; added `ExpectDelim (':', ENoSemi)`.
|
|
|
+- `ParseLabelStmt` consumed `n :` but never parsed the statement the label
|
|
|
+ is attached to, so `Compound` then demanded a `;` that does not exist in
|
|
|
+ `1: x := 1`. `Statmnt` now parses the labelled statement.
|
|
|
|
|
|
## Tests
|
|
|
-- `/tmp/tp_iso_mods.sh` two-phase build → `tpshell` 120528B ELF, runs,
|
|
|
- redraws, ESC quits. `make test` and editor/editor-round-trips covered in
|
|
|
- the earlier TP3-EDITOR-PSEUDOCODE suite.
|
|
|
|
|
|
-*Generated: 2026-09-22*
|
|
|
+Two harnesses live in `shell/tests/` -- in the repo on purpose, because
|
|
|
+`/tmp` is wiped between sessions and an earlier /tmp-only harness was lost
|
|
|
+with it.
|
|
|
+
|
|
|
+### `run_compile_tests.sh` -- compiler front end, no pty, instant
|
|
|
+
|
|
|
+`tests/CompileTest.mod` links `Compiler` + `TextBuf` + `Posix` and reads
|
|
|
+fixture paths from **stdin**, one per line. For each it does exactly what
|
|
|
+the shell's `LoadWorkFile` does (LF -> CR normalisation, `^Z` ends the
|
|
|
+text), calls `Compile`, and prints a one-line verdict plus a source excerpt
|
|
|
+with a caret under the error position. It rebuilds `Compiler.o` when the
|
|
|
+source is newer, so an edit is picked up automatically.
|
|
|
+
|
|
|
+```
|
|
|
+cd shell && tests/run_compile_tests.sh # all fixtures
|
|
|
+cd shell && tests/run_compile_tests.sh /some/dir # another fixture set
|
|
|
+```
|
|
|
+
|
|
|
+Current matrix (15 fixtures) -- **8 compile, up from 1**:
|
|
|
+
|
|
|
+| fixture | result |
|
|
|
+|---|---|
|
|
|
+| `t01_minimal` `program t01; begin end.` | OK code=26 |
|
|
|
+| `t08_const` const decls, `'A'` char const | OK code=32 |
|
|
|
+| `t09_if` if/then/else | OK code=64 |
|
|
|
+| `t10_while` while | OK code=72 |
|
|
|
+| `t11_for` for/to | OK code=66 |
|
|
|
+| `t12_repeat` repeat/until | OK code=69 |
|
|
|
+| `t13_proc` procedure + value parameter | OK code=50 |
|
|
|
+| `t15_label` label + goto | OK code=35 |
|
|
|
+| `t02`, `t03`, `t04`, `t05`, `t06`, `t07` (any use of `writeln`) | ERROR 41 |
|
|
|
+| `t14_types` `array [1..5] of integer` | ERROR 102 (`ENoLib`) |
|
|
|
+
|
|
|
+### `uitest.py` -- shell/editor behaviour that only exists interactively
|
|
|
+
|
|
|
+Drives a real pty (`ptyharness.py` supplies read-until-quiet, key sending
|
|
|
+and a small VT100 emulator) and asserts the TP3 **compile-error jump**:
|
|
|
+`W` load -> `C` compile -> `ESC` -> the editor opens with the cursor exactly
|
|
|
+on the error position -> `Ctrl-K D` back to the menu -> `Q` exit 0.
|
|
|
+10/10 pass on `t02_writeln.pas`, and the reported error is 41 -- i.e. the
|
|
|
+`errNo` self-assignment fix is now proven through the real UI.
|
|
|
+
|
|
|
+The jump mirrors original TP3 `kcwait` + `editor2`
|
|
|
+(`Resources/turbopascal3source/TP3/TPSRC5:333-336` and `:919`):
|
|
|
+`waitesc; BX:=txerrpos; DEC BX; JMP editor2`, where `editor2` then does
|
|
|
+`ADD BX,txbeg; INC BX` -- the `DEC`/`INC` cancel, so the net position is
|
|
|
+`txbeg+txerrpos` = our 0-based `errPos`. It is armed as a sticky position
|
|
|
+(`Editor.GotoOffset`) instead of by changing `Run`'s signature, so
|
|
|
+`Editor.def` stays additive.
|
|
|
+
|
|
|
+Note `LoadWorkFile` ends with a `Pause`, so a driver must send one filler
|
|
|
+key after the path; skipping it desynchronises every later keypress.
|
|
|
+
|
|
|
+## Known gap: no standard procedure library
|
|
|
+
|
|
|
+`Inittur` defines six predefined *types* (INTEGER/BYTE/CHAR/BOOLEAN/REAL/
|
|
|
+STRING), TRUE/FALSE and two temporaries -- and **zero procedures**. So
|
|
|
+`WRITELN` is never in the symbol table, the `ELSE` branch of `Statmnt` fails
|
|
|
+its `Search`, and every program that prints anything dies with `EUnknown`
|
|
|
+(41) on the `(` after the call name. That is why 6 of the 15 fixtures still
|
|
|
+fail, and it is the next milestone. `EmCallMost` already has the right
|
|
|
+shape: a `KProc` symbol with `defnd := TRUE` and `goPos` = runtime entry
|
|
|
+emits a direct near call. The runtime entries `TU_InitMem=8H`,
|
|
|
+`TU_ProgEnd=10H`, `TU_StackChk=18H` are image-base offsets, i.e. the runtime
|
|
|
+blob is meant to be prepended at link time by the not-yet-written linker.
|
|
|
+`CmdRun`, the interpreter, is likewise still a stub.
|
|
|
+
|
|
|
+`ENoLib` (102) is the deliberate "not implemented yet" path for
|
|
|
+real/set/record/file/string and for any type wider than 2 bytes.
|
|
|
+
|
|
|
+## gm2 pitfall: `EXIT` inside the program-header `WHILE` crashes pass 3
|
|
|
+
|
|
|
+Adding an `EXIT` to the program-header parameter loop (to guard against
|
|
|
+non-advancing input on malformed input such as `program p(1;)`) ICEs gm2:
|
|
|
+
|
|
|
+```
|
|
|
+internal compiler error: Abandon
|
|
|
+ ... ExitStatement -> PopExit -> M2StackWord_PopWord -> invalidloc
|
|
|
+```
|
|
|
+
|
|
|
+Extracting the loop into its own procedure did **not** help. So the header
|
|
|
+fix is deliberately exactly one added `Skip ()` and no `EXIT`. If that loop
|
|
|
+must be hardened, use a `BOOLEAN` "advanced" flag, never `EXIT`.
|
|
|
+
|
|
|
+*Generated: 2026-09-26*
|