Status: compiler-core (Compiler.mod) + shell compiled clean with
gm2 -fiso -Wall, and the ISO shell tpshell builds and runs (ESC-key
driven, TP3-style DOS screen redraws). This is the working implementation
companion — the human overview is TP3-COMPILER.md history summary and
RESUME-TP3.md.
pass-3 "too many errors / too many identifiers" cap on compiler-sized whole-program programs)
gm2 -fiso -c Compiler.mod # phase A: small per-module pass
gm2 -fiso -fgen-module-list=modules.lst -o /dev/null \
Compiler.mod Term.o TextBuf.o Posix.o Editor.o # phase B1: build list
gm2 -fiso -fuse-module-list=modules.lst -o tpshell \
Shell.mod Compiler.mod Term.o TextBuf.o Posix.o Editor.o # phase B2: link
(Equivalent make target tpshell in shell/Makefile.) The single gm2 -o
whole-program pass 3 silently caps identifier/error counts — see
build_tpshell.sh.)
Compiler.mod (all ISO-legal, gm2-verifiable)CHAR is
not the ZType; ch = 09H → ORD (ch) = 09H (and likewise 0DH/0AH/0CH).-Wno-): added DropCh/DropB/DropC discard helpers
and wrapped ~39 statement call sites of GetCh, MatchKey, NewSym,
EmCall, EmJmpNear (no output reground clutter; TP3 TP5 cascadepos
waitkey semantics preserved via the editor's ESC wait).AND/OR/NOT on W16-folded CARDINAL (lines ~1233/1246/1454) are
illegal on ZType in gm2 → added BitAnd/BitOr/BitNot (BITSET */+/-
fold, VAL-projected back to LONGINT) and rewrote the operands.Compile error-return params aligned to Compiler.def
(errNo/errPos, was eNo/ePos) — gm2 ISO rejects a disparate name
in the proper procedure.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.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 otherwise unchanged (mirrors TP3 TPSRC file, ConvertTP3).
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.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, instanttests/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 interactivelyDrives 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.
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.
EXIT inside the program-header WHILE crashes pass 3Adding 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