diff --git a/annotated/README.md b/annotated/README.md new file mode 100644 index 0000000..6a508a8 --- /dev/null +++ b/annotated/README.md @@ -0,0 +1,240 @@ +# Annotated SAM Coupé ROM 3.0 source + +A parallel copy of the ROM source, documented as a modern codebase would be: file-level overviews, per-routine +contracts (entry, exit, registers used, notes), inline explanation of non-obvious code, and named constants in place +of magic numbers. + +**The annotated source is intended to assemble to a byte-identical ROM image.** Nothing here changes behaviour. + +> [!WARNING] +> This annotation was produced with AI assistance and has not been assembled on real hardware (see +> [Verification](#verification) for what *has* been checked). Treat the commentary as a well-evidenced reading of the +> code, not as the author's own documentation. + +## Status + +**Complete.** All 39 source files have been converted, plus the new `equates.asm`. The annotated tree assembles to +an image byte-identical to the one built from the original source. + +| File | Lines (orig → annotated) | Contents | +|---|---|---| +| `samrom.asm` | 49 → 73 | Build glue; include order changed (see below) | +| `equates.asm` | — → 670 | **New file**: named constants | +| `vars.asm` | 527 → 655 | System variable map; adds legacy token aliases | +| `main.asm` | 552 → 872 | Restarts, interrupt entries, public jump table, inter-ROM linkage | +| `lookvar.asm` | 328 → 372 | Variable lookup; documents both variable storage layouts | +| `misc1.asm` | 507 → 623 | Streams and channels, colour state, READ, POKE, the 0-512K address model | +| `editor.asm` | 648 → 786 | The line editor, channel R, DEF KEY expansion | +| `list.asm` | 657 → 800 | AUTOLIST, LIST, CLS, PRINT, listing cursor movement | +| `mainlp.asm` | 775 → 862 | Interpreter main loop, syntax pass, line insertion, error handling | +| `eval.asm` | 894 → 1004 | Expression evaluator, operator priorities, literal conversion | +| `do.asm` | 823 → 967 | DO/LOOP, IF/ELSE, FOR/NEXT, BASIC stack, line finder | +| `grabput.asm` | 472 → 543 | GRAB and PUT block graphics, FARLDIR cross-page block move | +| `tadjm.asm` | 907 → 1114 | Calculator stack, program searcher, MAKEROOM/RECLAIM, page arithmetic, tape edges | +| `assign.asm` | 1004 → 1156 | Assignment, array indexing, string slicing, DIM | +| `fn.asm` | 995 → 1097 | DEF FN/FN, DEF PROC/PROC, LOCAL, the call-buffer compile pass | +| `nparpro.asm` | 772 → 872 | PROC parameter binding by value and by REF, RESTORE, local teardown | +| `misc2.asm` | 997 → 1137 | RST 8 dispatch and DOS hand-off, ROM1-to-RAM stubs, LET, RUN/CLEAR, syntax helpers | +| `endprint.asm` | 988 → 1160 | Per-mode character rendering, screen and pixel addressing, the fixed routines | +| `graph0.asm` | 529 → 642 | CIRCLE and DRAW: midpoint circle, Bresenham line, off-screen checking | +| `graph1.asm` | 313 → 380 | PLOT and the per-mode, per-`OVER` pixel plot routines dispatched through IY | +| `graph2.asm` | 1093 → 1333 | BLITZ, flood FILL and its check screen, coordinate ranges and offsets, the graphics recorder | +| `roll.asm` | 847 → 1140 | ROLL and SCROLL, the editor's window scroller, mode 0 scan stepping | +| `fpcmain.asm` | 766 → 844 | Floating-point calculator: opcode table, fetch-execute loop, stack primitives | +| `mult.asm` | 827 → 987 | Multiply, divide, add, subtract; integer fast paths and the 32-bit mantissa loops | +| `transend.asm` | 407 → 521 | SIN, COS, TAN, EXP, LN, ATN, ASN, ACS and `^`, as calculator bytecode | +| `printfp.asm` | 564 → 682 | Number to decimal string: shift-and-DAA integer conversion, ×10 fraction conversion | +| `miscx2.asm` | 517 → 589 | DEF KEYCODE, DEF FN, **the tokeniser**, MERGE | +| `miscx1.asm` | 782 → 923 | The RAM-executed routines: RENUMBER, GET, DELETE, KEYIN, POP, INPUT | +| `misc31.asm` | 695 → 812 | CALL, cold-start RAM sizing and initialisation, NEW, font unpacking, PALETTE | +| `misc32.asm` | 653 → 811 | BEEP and the sound effects, colour items, BORDER, WINDOW, RANDOMIZE | +| `scrsel2.asm` | 663 → 815 | GOTO/GOSUB, the keyword matcher, MODE and CSIZE, AUTO, SOUND, the disk boot loader | +| `scrfn.asm` | 774 → 918 | COPY, `SCREEN$`, listing output, the pretty-listing indent machinery | +| `tapex.asm` | 567 → 720 | Tape and network block transfer: pulse encoding, leader detection, bit timing | +| `using.asm` | 842 → 997 | Pointer adjustment after memory moves, printer output, heap, INSTR, LENGTH, `STRING$`, curved DRAW | +| `tprint.asm` | 832 → 1000 | The print path: ASCII, tokens, UDGs, control codes, scrolling | +| `rom1fns.asm` | 877 → 1193 | VAL, RND, ATTR, POINT, the string producers, SQR/ABS/SGN/INT/TRUNCATE, UDG | +| `scrsel1.asm` | 878 → 1125 | Screens and streams, page allocation, the interrupt handler, the keyboard scanner | +| `tapemn.asm` | 929 → 1134 | SAVE/LOAD/VERIFY/MERGE: header building, matching and reconciliation | +| `text.asm` | 1406 → 1589 | Messages and their compression, the keyword table, dispatch tables, key map, character set | +| `romtest.asm` | 110 → 170 | Standalone: compare the assembled image against the live ROM | + +`annotated/samrom.asm` includes every file in the directory, so the tree builds on its own; the check script still +overlays it onto the originals so the two images can be compared side by side. + +## Conventions + +### File header + +Each file opens with a banner giving its purpose, the routines it contains grouped by theme, and any structural +constraint a reader needs up front (paging assumptions, why a body is copied to RAM before execution, and so on). + +### Routine header + +```asm +; --------------------------------------------------------------------------------------------------------------------- +; NAME -- one-line summary +; +; Longer explanation of the algorithm and why it is written this way. +; +; Entry: A = ... +; HL = ... +; Exit: BC = ... +; CY set if ... +; Notes: ... +; --------------------------------------------------------------------------------------------------------------------- +``` + +Entry/Exit are stated wherever the original comments or the call sites make the contract determinable. Where a routine +has several entry points, each is documented at its label. + +### Layout + +* Labels in column 0, mnemonics in column 13, comments aligned at column 41 where practical. +* Maximum line length 120 characters. +* Section banners use `=` rules, routine headers `-` rules. + +### Naming + +New constants are prefixed so they can never collide with the original symbols, none of which contain an underscore: + +| Prefix | Meaning | Example | +|---|---|---| +| `ERR_` | Error/report code passed after `RST &08` | `ERR_NONSENSE` (29) | +| `HOOK_` | DOS hook code | `HOOK_AUTOLOAD` (136) | +| `TOK_` | Single-byte BASIC keyword token | `TOK_REM` (&B7) | +| `FN_` | Function code following the `&FF` prefix | `FN_SIN` (&53) | +| `F...` | Bit mask within a named flag byte | `FFLAGRUN`, `FTVAUTOLIST` | +| `LMPR`/`VID` | Paging and video port bit values | `LMPRROM1`, `VIDMODE` | +| `CC_` | Print control code | `CC_AT` (22) | +| `FT_` | Save/load file type | `FT_BASIC` (16) | +| `RDIR`/`RS` | ROLL/SCROLL direction and mode | `RDIRDOWN` (4), `RSROLL` (&FF) | +| `SCLIST` | Field within a screen list entry | `SCLISTPAGE` (%00011111) | +| `KB` | Keyboard queue and matrix geometry | `KBQMASK` (7), `KBSHCTRL` (3) | +| `BLZ` | BLITZ command code | `BLZCIRCLE` (3) | +| `SKIP...` | Opcode literal used as a "jump over the next instruction" | `SKIP1CP` (&FE) | + +The handful of constants the original source already named (`SAVETOK`, `THENTOK`, `LRPORT`, …) keep those names. +Token names are retained in `vars.asm` as aliases of the systematic `TOK_`/`FN_` definitions, so both spellings +resolve to the same value and older listings stay readable. + +### Include order + +`annotated/samrom.asm` pulls `equates.asm` and `vars.asm` in **before** `main.asm`, where the original included +`vars.asm` second. Both files contain nothing but `EQU` definitions and emit no bytes, so the image is unaffected — +and every module can now refer to ports, tokens and system variables by name rather than by literal. + +## Verification + +### Confirmed by assembly + +pyz80 1.3.0 builds the annotated tree to an image **byte-identical** to the original. `check.sh` performs the whole +comparison: + +```sh +python -m pip install pyz80 +annotated/check.sh +``` + +```text +samrom.asm (40 annotated files): BYTE-IDENTICAL +romtest.asm: BYTE-IDENTICAL + +verify_annotated.py: FAILURES: 0 +``` + +It builds `samrom.asm` from the repository root and from `annotated/`, compares the binaries, does the same for +`romtest.asm` — which `samrom.asm` does not include — and then runs the static checker, which reports *which line* +differs when something does. Exit status is 0 only if every comparison matched, so it drops straight into a hook or +a CI step. Run it after every edit. + +Options: `--overlay` copies `annotated/*.asm` over a copy of the originals and builds that instead, which is what +made it possible to verify one file at a time during the conversion and is still the right mode for a partial +tree; `--no-verify` skips the static checker; `--keep` leaves the build directory behind. + +The script finds pyz80 itself, including in the per-user scripts directory Windows installs it into. Note that the +pip package provides a console script called `pyz80`, **not** `pyz80.py`; the repository's `Makefile` and +`make.bat` still invoke the older script name. + +The annotated tree is self-contained, so the default mode is a genuine standalone build. Both the reordered +includes and the forward-referenced constants assemble cleanly, so the two open questions noted below are now +closed. + +Note that the pip package installs a `pyz80` console script, **not** `pyz80.py`; the repository's `Makefile` and +`make.bat` still invoke the older script name. + +`romtest.asm` is not reachable from `samrom.asm`, so it is checked by building it separately in both trees and +comparing the two images the same way. + +### Pre-existing source/image discrepancy + +The assembled original differs from `roms/ROM30` in exactly three bytes, at ROM1 &F5F8-&F5FA (file offset 30200, in +the copyright banner at `UMVAL` = &F5DD): the source spells the +copyright banner `MILES GORDON TECHNOLOGY PLC` where the shipped image has `plc`. This predates the annotation and +is unaffected by it — but it does mean the source is not quite a byte-exact reconstruction of the official image, +despite what `ReadMe.txt` implies. + +### Static equivalence check + +`verify_annotated.py` checks equivalence directly from the text, which is useful for spotting mistakes without a +full build and for pinpointing *which line* differs when a build does fail: + +1. Strip comments and blank lines. +2. Build a symbol table from every `EQU` in both trees and resolve each to an integer. +3. Normalise each code line: canonicalise numeric literals (`&FF`, `%11111111`, `255`, `"A"`) to decimal, substitute + resolved symbols for their values, constant-fold arithmetic operands, and standardise whitespace and case. +4. Compare the resulting instruction streams line for line. + +If the streams match, the two files must assemble identically — no annotation or renaming can have altered the +emitted bytes. Memory indirection is distinguished from arithmetic so `LD HL,(CHAD)` is never confused with a folded +constant; folded values are compared modulo 65536 so `-SCANBYTESM23` and `&FF80` are recognised as the same word; +and includes of files that emit nothing are ignored so the reordering above is not flagged. + +```sh +python annotated/verify_annotated.py . annotated +``` + +```text + -- equates.asm: no original (new file, skipped) + OK main.asm (386 code lines) + OK roll.asm (623 code lines) + ... + OK vars.asm (0 code lines) + +FAILURES: 0 +``` + +Two parsing bugs in the checker had to be fixed before it agreed with the assembler: an apostrophe was treated as a +string quote even in `EX AF,AF'`, which swallowed the following comment, and the numeric-literal pattern matched +*inside* identifiers, so `SKIP1LDH` was read as `SKIP1L` plus the suffix-hex literal `DH`. Both produced false +mismatches on files the assembler had already confirmed byte-identical. + +The check has teeth: it was validated by seeding a one-byte change (`LD A,&FE` → `LD A,&FD`), which it located +immediately, and it caught a genuine error during the conversion of `main.asm`, where the `MODE` range check's +`LD DE,&0400+34` had been rewritten with the wrong error constant (34 is `ERR_BADMODE`, not `ERR_IOOR`). + +A separate cross-check confirms all 508 symbols defined in the original tree resolve to identical values in the +annotated tree. + +### What it does not prove + +* That the ROM behaves correctly on hardware. A byte-identical image cannot behave differently, but neither the + original nor the annotated build has been run on a real machine as part of this work. `romtest.asm` is the tool + for that check: it compares the assembled image against the live ROM from within a running machine. +* That the commentary is correct. The build proves the *code* is unchanged, not that the explanations of it are + right — see the warning at the top. + +## Related documentation + +Prose documentation of the same material lives in [`../docs`](../docs): + +* [source-files.md](../docs/source-files.md) — per-file routine reference +* [constants.md](../docs/constants.md) — every `EQU` in the original source +* [memory-map.md](../docs/memory-map.md) — system page and BASIC area layout +* [machine-code-interface.md](../docs/machine-code-interface.md) — jump table, calculator, `CALL`/`USR` +* [extending-basic.md](../docs/extending-basic.md) — the interpreter's extension hooks, with worked examples +* [dos-and-extensions.md](../docs/dos-and-extensions.md) — what SAMDOS 2, MasterDOS and MasterBASIC add on top +* [tokenized-program-format.md](../docs/tokenized-program-format.md) — tokeniser and stored program format +* [file-formats.md](../docs/file-formats.md) — saved file header and layout +* [font-rendering.md](../docs/font-rendering.md) — character cell geometry +* [hudg.md](../docs/hudg.md) — character set pointers diff --git a/annotated/assign.asm b/annotated/assign.asm new file mode 100644 index 0000000..51ea119 --- /dev/null +++ b/annotated/assign.asm @@ -0,0 +1,1172 @@ +; ===================================================================================================================== +; ASSIGN.ASM -- Assignment, array indexing, string slicing and DIM +; ===================================================================================================================== +; +; Everything that writes to a variable, and everything that works out where in a variable to write. +; +; THE DESTINATION DESCRIPTOR +; -------------------------- +; Before any assignment, SYNTAX1 (or SYNTAX4 for a FOR variable) looks the name up and records what it found: +; +; DEST/DESTP the value's address -- or, for a variable that does not exist yet, the place where its record +; should be linked in +; STRLEN the destination length for an existing string, or the type byte otherwise +; FLAGX bit 0 set when the variable is new +; DFTFB zero when an existing numeric holds "minus zero", which DEFAULT treats as not existing +; +; ASSIGN then acts on that description. Numerics either overwrite five bytes or create a new chain entry; strings +; are more involved, because their length can change. +; +; STRINGS OF FIXED AND VARIABLE LENGTH +; ------------------------------------ +; A slice, or an element of a string array, has a fixed size: the value is copied in, truncated or space-padded to +; fit. A simple string can change size, so assignment creates a whole new record at the end of the string area and +; then deletes the old one. Doing it in that order is what makes LET a$ = a$ + "x" work. +; +; ===================================================================================================================== + + +; --------------------------------------------------------------------------------------------------------------------- +; VALFET1 / VALFET2 -- evaluate a value and assign it +; +; Entry: VALFET1 with the destination already assessed; VALFET2 with the expected type in A. +; Exit: The value is stored, or the types did not match and ERR_NONSENSE was raised. +; --------------------------------------------------------------------------------------------------------------------- + +VALFET1: LD A,(FLAGS) + +VALFET2: PUSH AF + CALL SCANNING + LD A,(FLAGS) + LD D,A + POP AF + XOR D + AND FFLAGNUM + JP NZ,NONSENSE ; A string cannot be assigned to a number, or the reverse + + LD A,D + RLA + RET NC ; Syntax check only + + +; --------------------------------------------------------------------------------------------------------------------- +; ASSIGN -- store the calculator stack top into the described destination +; --------------------------------------------------------------------------------------------------------------------- + +ASSIGN: CALL ASSISR + JP SELCHADP ; The store may have changed the page + + +; --------------------------------------------------------------------------------------------------------------------- +; CGXRG -- halve or double the X range pseudo-variable +; +; Called when FATPIX or MODE changes the pixel width, so that graphics coordinates keep referring to the same place. +; +; Entry: HL -> the XRG value, A = 0 to double or non-zero to halve. +; --------------------------------------------------------------------------------------------------------------------- + +CGXRG: PUSH HL + CALL HLTOFPCS + LD B,A + + DB CALC ; XRG + DB ONELIT + DB 2 ; XRG, 2 + DB STKBREG ; XRG, 2, flag + DB JPFALSE ; Zero means double, so jump to the multiply + DB 4 + + DB DIVN ; XRG / 2 + DB JUMP + DB 2 + + DB MULT ; XRG * 2 + DB EXIT + + POP DE + JR ASENV ; Drop the result and copy it back into the variable + + +; --------------------------------------------------------------------------------------------------------------------- +; CRTVAR35 / CRTVAR4 -- create a numeric variable from a name already in the buffer +; +; Entry: CRTVAR35 from PROC parameter processing; CRTVAR4 (also used by MERGE and SETUPVARS) with A = the type byte. +; The value is on the calculator stack and the name is at FIRLET. +; --------------------------------------------------------------------------------------------------------------------- + +CRTVAR35: LD A,(TLBYTE) + +CRTVAR4: LD C,A + LD HL,FLAGS + SET 6,(HL) ; FFLAGNUM + + CALL NUMLOOK + CALL SYN14C ; Build the destination descriptor from the search result + + +; --------------------------------------------------------------------------------------------------------------------- +; ASSISR -- the assignment proper +; +; Entry: DEST, DESTP, STRLEN and FLAGX describe the destination; the value is on the calculator stack. +; --------------------------------------------------------------------------------------------------------------------- + +ASSISR: CALL ADDRDEST ; For a new numeric this is the previous link; for an existing one the + ; value; for a new string the area terminator; for an existing string its + ; first character + LD A,(FLAGS) + ADD A,A + LD A,(FLAGX) + JP P,ASSTR ; A string + + EX DE,HL + RRA + JR C,ASNN ; FFLXNEWVAR: the numeric does not exist yet + + +; --------------------------------------------------------------------------------------------------------------------- +; ASENV -- overwrite an existing numeric +; +; Entry: DE -> the five bytes in the variables area. +; --------------------------------------------------------------------------------------------------------------------- + +ASENV: CALL FDELETE ; Drop the value, leaving HL pointing at it + +LDI5: LD BC,NUMVALSIZE + LDIR + RET + + +; --------------------------------------------------------------------------------------------------------------------- +; ASNN -- create a new numeric variable +; +; Entry: DE -> the low byte of the last link in this letter's chain. +; Notes: The new record goes at NUMEND, so the link is set to the displacement from itself to there. The link +; cannot be written until the space is known to exist, hence the two-stage approach. +; --------------------------------------------------------------------------------------------------------------------- + +ASNN: LD A,(DESTP) + AND LMPRPAGE + LD C,A + LD HL,(NUMEND) + LD A,(NUMENDP) + SUB C + JR Z,ANSP ; The usual case: both are in the same page + + LD BC,&4000 + +ANSPL: ADD HL,BC ; Bring NUMEND into the same frame of reference as the link, so the + DEC A ; subtraction below is meaningful. Wrapping past 64K does not matter, + JR NZ,ANSPL ; since a chain never spans that much. + + AND A + +ANSP: SBC HL,DE ; Displacement from the link's low byte to the first free byte + DEC HL ; ... measured from its high byte, as the link format requires + PUSH HL ; Keep it: the link cannot be written until the space is secured + + LD A,(NUMENDP) + LD C,A + LD HL,(SAVARS) + LD A,(SAVARSP) + CP C ; The string area is always the higher of the two + JR Z,ABSP ; Unusually, both are in the same page + + SET 6,H ; One page apart, so add 16K to compare them + +ABSP: LD BC,(NUMEND) + SBC HL,BC + EX DE,HL ; DE = the free gap, HL = the link address + LD A,D + AND A + JR NZ,ANOK ; At least 256 bytes free + + LD A,E + CP 60 + JR NC,ANOK ; At least 60, which is the largest a numeric record can be + + CALL ADDRSAV ; Not enough: open more space before the string area + CALL DECPTR + LD BC,&0200 + CALL MAKEROOM + CALL ADDRDEST ; The move may have shifted the link, so fetch it again + +; --- ANOK: there is room, so commit --- + +ANOK: POP DE + LD (HL),E ; Point the previous last variable of this letter at the new one + INC HL + LD (HL),D + CALL ADDRNE ; HL -> NUMEND, where the record goes + LD A,(TLBYTE+33) + LD (HL),A ; Type and name length + INC HL + LD B,&FF + LD (HL),B + INC HL + LD (HL),B ; A link of &FFFF: this is now the last of its letter + INC HL + EX DE,HL + LD HL,FIRLET+34 ; The second letter of the name + AND TLNAMELEN + JR Z,ASNCL ; A one-letter name + + LD C,A + INC B ; BC = the rest of the name + LDIR + +ASNCL: CALL ASENV ; Copy the value in + + +; --------------------------------------------------------------------------------------------------------------------- +; NELOAD -- set NUMEND past a newly created variable +; --------------------------------------------------------------------------------------------------------------------- + +NELOAD: LD (NUMEND),DE + BIT 6,D + RET Z ; Still inside the window + + RES 6,D ; Crossed 16K, so advance the page + LD A,(NUMENDP) + INC A + LD (NUMENDP),A + JR NELOAD + + +; ===================================================================================================================== +; ASSTR -- assign to a string +; ===================================================================================================================== + +ASSTR: RRA + JP C,ASNST ; FFLXNEWVAR: create it + +; --- An existing string --- + + LD BC,(STRLEN) ; The destination length + LD A,(DESTP) + RLA + JR C,ASDEL ; Bit 7 set: a simple string, so replace the whole record + +; --- A slice or array element: fixed size, so copy in place --- + + LD A,B + OR C + RET Z ; A zero-length destination, as in LET a$(4 TO 3) = "test" + + PUSH HL ; The destination + PUSH BC ; Its size + CALL STKFETCH ; ADE = the source, BC = its length + POP HL ; The destination size + SBC HL,BC ; NC here + JR NC,AES1 ; The source is no longer than the destination + + ADD HL,BC ; It is longer, so truncate it + LD B,H + LD C,L + LD HL,0 ; ... and no padding is needed + +AES1: EX (SP),HL ; Stack the padding count, recover the destination + EX DE,HL ; DE = destination, HL = source + EX AF,AF' + CALL SPLITBC + IN A,(251) + LD C,A ; CDE = the destination + EX AF,AF' ; AHL = the source + CALL FARLDIR + POP BC ; The padding count + LD A,B + OR C + RET Z + +; --- Space-pad the remainder of the destination --- + + LD A,(TEMPB2) + CALL TSURPG ; The page the copy finished in + LD HL,(TEMPW1) ; ... and the address just past it + XOR A + CP C ; NC only when C is zero + ADC A,B ; A = B, or B+1 when C is non-zero: whole passes of the loop below + LD B,C + LD C,A + LD A," " + +ASPSL: LD (HL),A + INC HL + DJNZ ASPSL + + DEC C + RET Z + + CALL CHKHL ; Keep the pointer inside the window + JR ASPSL + + +; --------------------------------------------------------------------------------------------------------------------- +; RECORD -- RECORD TO a$ | RECORD STOP +; +; Arms stream 16 so that printing to it appends to the named string, and makes graphics commands append BLITZ +; records to it as well. +; --------------------------------------------------------------------------------------------------------------------- + +RECORD: CP TOK_STOP + JR NZ,RECORD2 + + XOR A + LD (GRARF),A ; Graphics recording off + RST &20 + RET + +RECORD2: CP TOTOK + JR NZ,RCNONS + + RST &20 + CALL LVFLAGS + JP M,NONSENSE ; A numeric target makes no sense + + JR C,RECORD3 ; Running + + BIT 6,C + RET Z ; Syntax check: a simple string is acceptable + +RCNONS: RST &08 + DB ERR_NONSENSE + +RECORD3: EX AF,AF' + CALL NZ,ASDEL2 ; Delete any existing variable of that name + + LD DE,STRM16NM + CALL SCOPN1 ; Record the name where the stream 16 driver can find it. + ; This may copy 12 bytes and touch GRARF, which is harmless since GRARF is + ; set immediately afterwards. + LD A,D + LD (GRARF),A ; Graphics recording on + CALL SCOPNM ; Exits with BC = 0 + JR ASNS1 ; Create the variable as a null string + + +; --------------------------------------------------------------------------------------------------------------------- +; ASDEL -- assign a simple string, then delete the previous version +; +; The new value is built first, so that LET a$ = a$ and LET a$ = a$ + "x" both work. +; --------------------------------------------------------------------------------------------------------------------- + +ASDEL: CALL ASNST ; Create the new record at the end of the string area + CALL ADDRDEST ; HL -> the text of the old one + LD DE,-STRHDRSIZE + ADD HL,DE ; -> its type byte + CALL CHKPTR + JR ASDEL3 + + +; --------------------------------------------------------------------------------------------------------------------- +; ASDL1 / ASDEL2 / ASDEL3 -- delete a string or array record +; +; Entry: ASDL1 with A = the page; ASDEL2 with STRLOCN pointing at it and its page mapped; +; ASDEL3 with HL pointing at it. +; --------------------------------------------------------------------------------------------------------------------- + +ASDL1: CALL SELURPG + +ASDEL2: LD HL,(STRLOCN) + +ASDEL3: PUSH HL + LD BC,STRHDRSIZE-3 + ADD HL,BC ; -> the length in pages + CALL ADD14 + LD B,H + LD C,L + POP HL + JP RECL2BIG ; Remove the data and its 14-byte header together + + +; --------------------------------------------------------------------------------------------------------------------- +; ADD14 -- read a record's data length and add the header size +; +; Entry: HL -> the length in pages. +; Exit: AHL = the total record length. +; --------------------------------------------------------------------------------------------------------------------- + +ADD14: LD A,(HL) + INC HL + LD E,(HL) + INC HL + LD D,(HL) + EX DE,HL ; AHL = the data length + LD BC,STRHDRSIZE + ADD HL,BC + BIT 6,H + RET Z + + INC A + RET + + +; --------------------------------------------------------------------------------------------------------------------- +; ASNST -- create a new string record at the end of the string area +; +; Entry: The value is on the calculator stack; the name is at TLBYTE+33. +; Notes: A source that lies above WKEND -- a temporary in workspace -- must not be auto-adjusted when MAKEROOM +; moves memory, because it sits above the change point and would be adjusted wrongly. Such a source is +; parked in FIRST/LAST instead of XPTR. +; --------------------------------------------------------------------------------------------------------------------- + +ASNST: CALL STKFETCH ; A = page, DE = start, BC = length + + AND LMPRPAGE + LD H,A + LD A,(WKENDP) + LD L,A + LD A,H + CP L + JR C,ASNS1 ; The source page is below WKEND's + + JR NZ,ASNS0 ; ... or above it + + LD HL,(WKEND) + SBC HL,DE + JR NC,ASNS1 ; Same page, and the source is at or below WKEND + +ASNS0: LD (FIRST),DE ; Above WKEND: remember it somewhere that is not auto-adjusted + LD (LAST),A + LD A,&FF ; Signal that XPTR is not in use + +; --- ASNS1: also entered by RECORD, to create a null string --- + +ASNS1: PUSH AF + PUSH BC ; The text length + LD (XPTR),DE ; Park the source where MAKEROOM will adjust it if it moves + LD (XPTRP),A + LD A,STRHDRSIZE ; Type byte, 10 name characters and the 3-byte length + ADD A,C + LD C,A + JR NC,ASNS2 + + INC B + JP Z,STLERR ; The whole record must fit in 65535 bytes + +ASNS2: LD A,B + RLCA + RLCA + AND &03 ; A = whole 16K pages, BC = the remainder + CALL SAROOM ; Open the space and write the type byte and name + POP BC ; The text length + CALL MBC ; Write the length into the record + IN A,(251) + LD C,A ; CDE = the destination + POP AF + INC A + LD A,(XPTRP) + LD HL,(XPTR) + JR NZ,ASNS3 ; XPTR was in use, so it holds the adjusted source + + LD A,(LAST) ; Otherwise the source was parked in FIRST/LAST + LD HL,(FIRST) + +ASNS3: LD (XPTR+1),A ; The page has bit 7 clear, which cancels the '?' marker + JP FARLDIR + + +; ===================================================================================================================== +; Assessing a destination +; ===================================================================================================================== + +; --------------------------------------------------------------------------------------------------------------------- +; SYNTAX4 -- assess a FOR control variable +; +; A FOR variable must be a simple numeric. Any ordinary variables of the same name are marked unused, so that the +; FOR record replaces them rather than shadowing them. +; --------------------------------------------------------------------------------------------------------------------- + +SYNTAX4: CALL LVFLAGS + JP P,NONSENSE ; A string + + BIT 5,C + JP NZ,NONSENSE ; A numeric array + + JR NC,SYNT41 ; Syntax check + + EX AF,AF' + +SYN42: JR Z,SYN14C ; It does not exist yet + + BIT 6,C ; TLFORVAR: it is already a FOR variable, so reuse it + JR NZ,SYN14C + + PUSH IX + POP HL ; -> the link + SET 5,(IX-1) ; Mark the ordinary variable unused + CALL NVMLP ; Keep searching the chain + JR SYN42 ; ... and mark every copy + +SYNT41: EX AF,AF' + + JR SYN14C + + +; --------------------------------------------------------------------------------------------------------------------- +; SSYNTAX1 / SYNTAX1 -- assess a destination for LET, READ or INPUT +; --------------------------------------------------------------------------------------------------------------------- + +SSYNTAX1: RST &20 + +SYNTAX1: CALL LOOKVARS + + +; --------------------------------------------------------------------------------------------------------------------- +; SYN14C / SYN1PP -- build the destination descriptor from a search result +; +; Entry: DE -> the value if found; C = the type byte, from the variables area if found or the wanted one if not. +; Exit: DEST, DESTP, STRLEN, FLAGX and DFTFB all set. +; --------------------------------------------------------------------------------------------------------------------- + +SYN14C: EX DE,HL + +; --- SYN1PP: entered from PROC parameter processing --- + +SYN1PP: LD HL,FLAGX + LD (HL),0 ; Assume the variable exists + JR NZ,TSYNT12 ; It does, or this is a syntax check + + INC (HL) ; FFLXNEWVAR + + LD A,C + AND TLARRAY + JR Z,TSYNT14 ; A simple string or number: DESTP bit 7 stays clear + +VNFERR: RST &08 ; An undimensioned array, or a slice of a string that does not exist + DB ERR_NOTFOUND + +; --- The variable exists, or this is a syntax check --- + +TSYNT12: LD A,(FLAGS) + ADD A,A + JP P,TSYNT13 ; A string + + BIT 5,C + JR Z,TSYNT14 ; A simple number + +TSYNT13: CALL STKVAR ; Index the array or stack the string details + LD A,(FLAGS) + ADD A,A + JP M,TSYNT15 ; A numeric array element: HL -> it, its page mapped + + CALL C,STKFETCH ; A string: DE = start, BC = length, A = page with bit 7 set when the old + EX DE,HL ; copy must be deleted, as in LET a$ = "ss" + JR TSYN16 + +TSYNT14: EX DE,HL + +; --- Record the result. For numerics and new variables STRLEN holds the type byte and junk; for existing strings +; --- and arrays it holds the length. + +TSYNT15: IN A,(251) + AND LMPRPAGE + +TSYN16: LD (STRLEN),BC + LD (DEST),HL + LD (DESTP),A + LD B,(HL) + INC HL + LD A,(HL) + INC A + OR B + INC HL + OR (HL) + INC HL + OR (HL) ; Zero only when the value is 00 FF 00 00, i.e. minus zero + LD (DFTFB),A ; Meaningless for a variable that does not exist + + +; --------------------------------------------------------------------------------------------------------------------- +; SCOPNM / SCOPN1 / SCOPN2 -- copy the name to a second buffer +; +; The evaluator will reuse TLBYTE and FIRLET, so the name of the destination is kept at TLBYTE+33. +; --------------------------------------------------------------------------------------------------------------------- + +SCOPNM: LD DE,TLBYTE+33 + +; --- SCOPN1: entered by RECORD TO, which copies to STRM16NM instead --- + +SCOPN1: LD HL,TLBYTE + LD A,(HL) + AND TLNAMELEN ; Name length minus one for a numeric, the true length otherwise + ADD A,2 ; Allow for the type byte and, for a numeric, the first letter + LD C,A + +; --- SCOPN2: entered by the LENGTH function --- + +SCOPN2: LD B,0 + LDIR + JP SELCHADP + + +; ===================================================================================================================== +; STKVAR -- index an array, or stack a string's parameters +; ===================================================================================================================== +; +; Entry: DE -> the length in pages if running, C = the type byte, CY if running. +; CHAD points past the '$' or '(' unless an error follows. +; Exit: For a numeric array, HL -> the element. For a string, its parameters are on the calculator stack. +; --------------------------------------------------------------------------------------------------------------------- + +STKVAR: EX DE,HL + +STKVAR2: JR C,SVRUNT ; Running + +; --- Syntax check: verify the subscript or slicer syntax only --- + + BIT 6,C + JR NZ,SVSSL ; A string array or a sliced string + + BIT 5,C + RET Z ; A simple unsliced string: nothing to check + + DB SKIP1CP ; A numeric array: fall into the subscript check + +SVDSL: RST &20 + +; --- SVDSK: check "n,n,...,n)". Also called by DIM. --- + +SVDSK: CALL EXPT1NUM + CP "," + JR Z,SVDSL + +SVIBH: JP INSISCBRK + + +; --------------------------------------------------------------------------------------------------------------------- +; SVSSL -- check string array or slicer syntax +; +; Accepts "()", "(n,x TO y)", "(n,n, TO y)", "(n,n,y TO)" and the like. +; --------------------------------------------------------------------------------------------------------------------- + +SVSSL: RST &18 + CP ")" + JR Z,SVSL3 ; "()" is allowed + + DB SKIP1CP + +SVSSLP: RST &20 + + CP TOTOK + JR Z,SVSL2 ; A slicer with no first number + + CALL EXPT1NUM + CP "," + JR Z,SVSSLP + + CP TOTOK + JR Z,SVSL2 + + CALL INSISCBRK + JR SLPXHP + +SVSL2: RST &20 ; Skip TO + CP ")" + JR Z,SVSL3 ; "x TO )" + + CALL EX1NUMCB ; "x TO n)" + DB SKIP1CP + +SVSL3: RST &20 + +SLPXHP: JP SLLPEX ; EXPT1NUM left the type as numeric; restore "string" + + +; ===================================================================================================================== +; SVRUNT -- run-time array indexing and string parameter stacking +; ===================================================================================================================== + +SVRUNT: LD A,C + AND TLARRAY + JR NZ,SVARRAYS + +; --- A simple string: convert the stored pages-plus-remainder length to a plain 16-bit one --- + + LD A,(HL) ; Pages, 0-3 + INC HL + LD C,(HL) + INC HL + RRCA + RRCA ; The page count becomes the top two bits + OR (HL) + LD B,A ; BC = the length + LD D,&80 ; Bit 7: delete the old copy after assignment + +SVSIMPLE: EX DE,HL + INC DE ; -> the text + IN A,(251) + BIT 6,D + JR Z,SVSS2 ; The text is in the window + + RES 6,D ; A string starting near &BFFF leaves the pointer in section D + INC A + +SVSS2: AND LMPRPAGE + OR H ; Bit 7 set for a simple string (delete the old copy), clear for a + ; one-dimensional string array element (overwrite in place) + CALL STKSTORE ; FFLAGNUM is already correct + CALL SELCHADP + LD A,(TLBYTE) + BIT 6,A + RET Z ; No bracket followed the name, so no slicing + + JP SLCL2 + + +; --------------------------------------------------------------------------------------------------------------------- +; SVARRAYS -- index an array +; +; The element offset is built up as total = (...(s1 * d2 + s2) * d3 + ...) + sn, computed on the calculator stack, +; then multiplied by the element size and added to the data start. +; --------------------------------------------------------------------------------------------------------------------- + +SVARRAYS: INC HL + INC HL + INC HL + LD B,(HL) ; The number of dimensions + BIT 5,C + JR NZ,SVCDIS ; A numeric array + + DJNZ SVCKS ; A multi-dimensional string array + + LD D,B ; One dimension: D = 0, so bit 7 stays clear meaning "overwrite" + INC HL + LD C,(HL) + INC HL + LD B,(HL) ; BC = the single dimension's length + JR SVSIMPLE ; Treat it exactly like a simple string + +SVCKS: LD A,(TLBYTE) ; A multi-dimensional string array must be referred to with a slicer + AND TLSTRARRAY + JR Z,SWERHP + +SVCDIS: IN A,(URPORT) + PUSH AF ; The page holding the dimension list + PUSH BC ; B = the dimension count, excluding the last for a string array + INC HL + PUSH HL + XOR A + CALL STACKA ; The running total starts at zero + POP HL + POP BC + +SVLOOP: POP AF + PUSH AF + OUT (URPORT),A ; Map the dimension list + PUSH BC + LD C,(HL) + INC HL + LD B,(HL) ; BC = this dimension's size, the subscript limit + INC HL + PUSH HL + PUSH BC + CALL SELCHADP ; Map the program, to evaluate the subscript + CALL STACKBC ; Stack the dimension size + POP BC + CALL GETSUBS ; The subscript, checked against the limit and decremented + +SWERHP: JP NC,SWER2 ; Out of range + + CALL STACKHL + + DB CALC ; total, size, subscript + DB SWOP13 ; subscript, size, total + DB MULT + DB ADDN ; total * size + subscript + DB EXIT + + POP DE ; The dimension list pointer + POP BC ; The dimension counter + RST &18 + DEC B + JR Z,SVEXLP ; All dimensions done + + CP "," + JR NZ,SWER2 ; More dimensions require more subscripts + + RST &20 + EX DE,HL + JR SVLOOP + + +; --------------------------------------------------------------------------------------------------------------------- +; SVEXLP -- all subscripts consumed; compute the element address +; +; Entry: The array's page is on the stack, DE -> the data start. +; --------------------------------------------------------------------------------------------------------------------- + +SVEXLP: BIT 5,C + JR NZ,SVNUMER + +; --- A string array: the last dimension is the element length, and a slicer may follow --- + + POP AF + OUT (URPORT),A + EX DE,HL + LD C,(HL) + INC HL + LD B,(HL) + INC HL + EX DE,HL ; DE -> the data start + PUSH BC ; The element length + CALL SVSR ; AHL = the element's address + POP BC + EX DE,HL + CALL STKST0 ; Stack it; bit 7 clear means "do not erase the original" + CALL SELCHADP + RST &18 + CP ")" + JR Z,SVDIM ; No slicer, as in a$(3) + + CP "," + JR Z,SLCL ; A slicer follows, as in a$(3,2 TO 5) + +SWER2: RST &08 + DB ERR_SUBSCRIPT + +SVDIM: RST &20 + CP "(" + JR NZ,SLLPEX ; Nothing more + +SLCL: RST &20 ; Skip the '(' or ',' + +SLCL2: CALL SLICING + JR SVDIM + +SLLPEX: LD HL,FLAGS + RES 6,(HL) ; FFLAGNUM clear: the result is a string + RET + + +; --- A numeric array: five bytes per element --- + +SVNUMER: CP ")" + JR NZ,SWER2 + + RST &20 + LD BC,NUMVALSIZE + POP AF ; The page + CALL SVSR + JP TSURPG + + +; --------------------------------------------------------------------------------------------------------------------- +; SVSR -- turn an element index into an address +; +; Entry: BC = the element size, DE -> the data start, A = its page, the index on the calculator stack. +; Exit: AHL = the element's address. +; --------------------------------------------------------------------------------------------------------------------- + +SVSR: PUSH AF + PUSH DE + CALL STACKBC + + DB CALC ; total, element size + DB MULT ; the byte offset + DB EXIT + + CALL UNSTLEN ; AHL = it, in page form + POP DE + POP BC + LD C,B ; CDE = the data start + BIT 6,D + JR Z,SVSR2 + + INC C ; ADDAHLCDE ignores bit 6, so account for it here + +SVSR2: JP ADDAHLCDE + + +; ===================================================================================================================== +; SLICING -- evaluate a string slicer +; ===================================================================================================================== +; +; Forms: (), (n), (a TO b), (a TO), ( TO b). An empty result is produced for a reversed range such as (5 TO 2), +; but a genuinely out-of-range value is an error. +; --------------------------------------------------------------------------------------------------------------------- + +SLICING: CALL RUNFLG + CALL C,STKFETCH ; ADE = start, BC = length + + PUSH AF ; The page + RST &18 + POP HL ; H = the page + CP ")" + JR Z,SLSTORE ; "()" means the whole string + + LD (TEMPB2),A ; Non-zero: no subscript error yet + PUSH DE ; The string start + PUSH HL ; H = its page + LD DE,0 ; The default first position + CP TOTOK + JR Z,SLSEC ; "( TO x)" + + CALL GETSUBS ; The first number, checked against the length and decremented + EX DE,HL + RST &18 + CP TOTOK + JR Z,SLSEC + + CP ")" + +NONSH: JR NZ,SWER2 + + LD H,D ; "(n)" means a single character + LD L,E + JR SLDEF + +SLSEC: RST &20 + CP ")" + LD H,B + LD L,C + DEC HL ; Default the second number to the last position + JR Z,SLDEF ; "(x TO )" or "( TO )" + + PUSH DE + CALL GETSUBS ; The second number + POP DE + JR C,SLSE2 ; In range, or a syntax check + + LD A,H + OR L + JR Z,SLND ; "(2 TO 0)" is an empty string, not an error + +SLSE2: PUSH HL + RST &18 + POP HL + CP ")" + JR NZ,NONSH + +SLDEF: SBC HL,DE ; Second minus first; NC here + LD BC,0 ; A reversed range gives an empty string + JR C,SLNUL + + LD A,(TEMPB2) + AND A + JP Z,SWER2 ; A subscript really was out of range + + INC HL + +SLND: LD B,H + LD C,L ; BC = the slice length + +SLNUL: POP AF + POP HL ; AHL = the string start + ADD HL,DE ; ... plus the first position + CALL C,PGOA + BIT 6,H + JR Z,SLDF2 + + RES 6,H + INC A + +SLDF2: EX DE,HL ; ADE = the slice start, BC = its length + LD H,A + +SLSTORE: LD A,(FLAGS) + AND &BF + LD (FLAGS),A ; FFLAGNUM clear: a string + RLA + RET NC ; Syntax check + + LD A,H + JP STKST0 ; Stack it, with bit 7 clear: do not delete the original + + +; ===================================================================================================================== +; DIM -- DIM name(d1,...,dn) +; ===================================================================================================================== +; +; Deletes any existing variable of the same name, then builds a record whose data area holds the dimension count, +; the dimension sizes, and the elements, all cleared. +; --------------------------------------------------------------------------------------------------------------------- + +DIM: CALL LOOKVARS + IN A,(URPORT) ; The page of the existing variable, if there is one + EX AF,AF' ; NZ if it was found + PUSH BC + CALL SCOPNM ; Copy the name where the evaluator cannot disturb it + POP BC + LD A,(TLBYTE) ; The requested type, which may differ from what was found + AND TLARRAY + JP Z,NONSENSE ; DIM needs a bracket + + CALL RUNFLG + JR C,DIMRUN + + CALL SVDSK ; Syntax check: "n,n,...,n)" + +DIM2: CP "," + RET NZ + + RST &20 + JR DIM ; DIM a(8),b(6,5),a$(2) and so on + +DIMRUN: EX AF,AF' + PUSH BC + CALL NZ,ASDL1 ; Delete the existing array + + CALL SELCHADP + POP BC + BIT 5,C + LD BC,1 ; String elements are one byte each + JR Z,DIM4 + + LD C,NUMVALSIZE ; Numeric elements are five + +DIM4: CALL STACKBC ; The running product; B is still zero, so it doubles as the dimension count + DB SKIP1CP + +DIMSZLP: RST &20 ; Skip the comma + + CALL GETSUBS ; The dimension size, returned decremented + INC HL + PUSH HL ; Keep it on the machine stack, behind the counter + PUSH BC + CALL STACKHL + + DB CALC + DB MULT ; 5 * d1 * d2 ..., or 1 * d1 ... + DB EXIT + + POP BC + INC B ; One more dimension + RST &18 + CP "," + JR Z,DIMSZLP + + CALL INSISCBRK + + PUSH BC ; B = the dimension count + LD L,B + LD H,0 + ADD HL,HL ; Two bytes per dimension size ... + INC HL ; ... plus one for the count itself + CALL STACKHL + + DB CALC ; element bytes, dimension info bytes + DB SWOP ; info, elements + DB DUP ; info, elements, elements + DB SWOP13 ; elements, elements, info + DB ADDN ; elements, elements + info + DB DUP + DB ONELIT + DB STRHDRSIZE + DB ADDN ; elements, elements + info, elements + info + 14 + DB EXIT + + CALL UNSTLEN ; ABC = the total record size + CALL SAROOM ; Open it and write the type byte and name; DE -> just past the name + PUSH DE + CALL UNSTLEN ; The size excluding the 14-byte header + EX DE,HL + POP HL + LD (HL),A ; Pages + INC HL + LD (HL),E + INC HL + LD (HL),D ; ... and the remainder + INC HL + POP AF + LD (HL),A ; The dimension count + LD E,A + LD D,0 + ADD HL,DE + ADD HL,DE ; -> the high byte of the last dimension size + LD D,H + LD E,L + +DIMENTLP: POP BC ; The sizes come off the stack in reverse order + LD (HL),B + DEC HL + LD (HL),C + DEC HL + DEC A + JR NZ,DIMENTLP + + PUSH DE + CALL UNSTLEN ; The element area size + CALL AHLNORM ; ... as a 19-bit value + POP DE + EX DE,HL + INC HL ; -> the first byte to clear + LD B,E ; B counts bytes within a 256-byte block + LD E,D + LD D,A ; DE counts the blocks + LD A,B + AND A + JR Z,DIMNAC + + INC DE ; A partial final block, so one more pass. DE is never zero. + +DIMNAC: LD A,(TLBYTE+33) + AND TLSTRARRAY + LD C," " ; String arrays are cleared to spaces + JR NZ,GARC + + LD C,A ; Numeric arrays to zero + +GARC: CALL CHKHL + +DIMCLP: LD (HL),C + INC HL + DJNZ DIMCLP + + DEC DE + LD A,D + OR E + JR NZ,GARC + + CALL SELCHADP + RST &18 + JP DIM2 + + +; --------------------------------------------------------------------------------------------------------------------- +; SAROOM -- open a record at the end of the string area and write its name +; +; Entry: A = pages, BC = the remainder of the total size. +; Exit: DE -> just past the 11-byte type and name field. +; --------------------------------------------------------------------------------------------------------------------- + +SAROOM: PUSH AF + CALL ADDRELND ; The end of the string area is one below the edit line + POP AF + CALL MKRBIG + EX DE,HL + LD HL,TLBYTE+33 + LD BC,11 ; The type byte and ten name characters + LDIR + RET + + +; --------------------------------------------------------------------------------------------------------------------- +; GETSUBS -- evaluate a subscript +; +; Entry: CHAD at the expression, BC = the limit. +; Exit: HL = the value minus one, so the range becomes 0 to limit-1. BC and DE unchanged. +; CY if it was in range. A subscript of zero is always an error. +; --------------------------------------------------------------------------------------------------------------------- + +GETSUBS: PUSH BC + CALL EXPT1NUM + JR C,GTSBC + + POP BC + RET ; Syntax check: a number is all that is required + +; --- GTSBC: run time --- + +GTSBC: CALL GETINT + POP BC ; The limit + OR H + JR Z,SWSIG ; Subscript zero + + DEC HL + SBC HL,BC ; A limit of &FFFF permits 1 to &FFFF + ADD HL,BC + RET C + +SWSIG: XOR A + LD (TEMPB2),A ; Record that a subscript was out of range + RET diff --git a/annotated/check.sh b/annotated/check.sh new file mode 100644 index 0000000..6cbeba2 --- /dev/null +++ b/annotated/check.sh @@ -0,0 +1,145 @@ +#!/bin/bash +# +# check.sh -- prove that the annotated source assembles to the same ROM image as the original. +# +# Builds samrom.asm from the repository root and from annotated/, and compares the two binaries byte for byte. +# romtest.asm is not reachable from samrom.asm, so it is built and compared separately. Finally the static +# equivalence checker is run, which reports *which line* differs when something does. +# +# Usage: annotated/check.sh [options] +# +# --overlay copy annotated/*.asm over a copy of the originals and build that, instead of building +# annotated/ directly. Equivalent while every file is converted; useful if you are working on a +# partial conversion, where the annotated directory does not stand alone. +# --no-verify skip verify_annotated.py (the binary comparison alone is the authoritative check) +# --keep leave the build directory in place and print its path +# +# Exit status is 0 only if every comparison matched. + +set -u + +overlay=0 +verify=1 +keep=0 + +for arg in "$@"; do + case "$arg" in + --overlay) overlay=1 ;; + --no-verify) verify=0 ;; + --keep) keep=1 ;; + -h|--help) sed -n '3,17p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) echo "check.sh: unknown option '$arg'" >&2; exit 2 ;; + esac +done + +here=$(cd "$(dirname "$0")" && pwd) +root=$(cd "$here/.." && pwd) + +# --------------------------------------------------------------------------------------------------------------------- +# Locate pyz80. The pip package installs a console script called "pyz80" (not "pyz80.py", which is what the +# repository's Makefile and make.bat still invoke). On Windows it lands in the per-user scripts directory, which is +# often not on PATH. +# --------------------------------------------------------------------------------------------------------------------- + +if ! command -v pyz80 >/dev/null 2>&1; then + for finder in "sysconfig.get_path('scripts','nt_user')" "sysconfig.get_path('scripts')"; do + dir=$(python -c "import sysconfig;print($finder)" 2>/dev/null) || continue + # python may report a Windows path; make it usable from this shell. + case "$dir" in + [A-Za-z]:\\*) dir="/$(echo "${dir:0:1}" | tr 'A-Z' 'a-z')/$(echo "${dir:3}" | tr '\\' '/')" ;; + esac + if [ -x "$dir/pyz80" ] || [ -f "$dir/pyz80" ]; then + PATH="$PATH:$dir" + export PATH + break + fi + done +fi + +if ! command -v pyz80 >/dev/null 2>&1; then + echo "check.sh: pyz80 not found. Install it with: python -m pip install pyz80" >&2 + exit 2 +fi + +# --------------------------------------------------------------------------------------------------------------------- + +work=$(mktemp -d) || exit 2 +if [ "$keep" -eq 0 ]; then + trap 'rm -rf "$work"' EXIT +fi + +status=0 + +# build