TSTK P2.6

Developer(s): Bob Mitchell
Date: 1986
Type: Program
Platform(s): TS 2068
Tags: Utility

TS Toolkit P2.6 is a comprehensive BASIC programming utility for the Timex Sinclair 2068 that provides 26 tools accessible via single-key menu selection. The toolkit loads as a machine code extension residing at address 59696, activated via `RANDOMIZE USR 60000`, and takes advantage of Interrupt Mode 2 (IM2) for background operation — allowing features like Autoline, Trace, and Disable NEW to remain active while the user works in BASIC. Its feature set covers program editing (Copy Lines, Delete Lines, Move Lines, Merge Lines, Line Sort, Renumber), code analysis (Search & List, Locate Token, Trace, List Variables, Display Memory, Hex & Dec converter), source management (REM Create, REM Delete, Alter Program, Compactor, Upper/Lower Case conversion), and asset utilities (UDG Designer, Bytes to DATA, UDGs to DATA). The Uncorrupt option and Disable NEW command provide safeguards against accidental program loss, while the Autoline feature streamlines program entry by automatically incrementing line numbers.


Overview

TS Toolkit P2.6 (version 2.6C) is a machine code utility suite designed to augment the built-in BASIC environment of the Timex Sinclair 2068. It installs itself into high memory and hooks into the system using Interrupt Mode 2 (IM2), enabling several of its features to operate continuously in the background without disrupting normal BASIC operation.

Installation and Activation

The toolkit loads as a named bytes block (tstk p2.6C) and provides two entry points:

  • RANDOMIZE USR 60000 — initial activation after loading
  • RANDOMIZE USR 59696 — re-entry after issuing a NEW command

The IM2 interrupt hook allows features such as Autoline, Trace, and Disable NEW to persist across BASIC operations, since they are serviced during normal interrupt cycles rather than requiring explicit invocation each time.

Full Feature Set (A–Z Menu)

KeyFunctionCategory
AAlter ProgramEditing
BBytes to DATAAsset Conversion
CCopy LinesEditing
DDelete LinesEditing
EREM CreateSource Management
FREM DeleteSource Management
GUDG DesignerAsset Creation
HHex & DecUtilities
IInformationSystem Info
JMerge LinesEditing
KUpper CaseText Processing
LLower CaseText Processing
MMove LinesEditing
NAutoline OnEntry Aid (IM2)
OLocate TokenAnalysis
PCompactorOptimization
QDisplay MemoryAnalysis
RRenumberEditing
SSearch & ListAnalysis
TTrace OnDebugging (IM2)
UUDGs to DATAAsset Conversion
VList VariablesAnalysis
WDisable NEWProtection (IM2)
XN/Q/T/W OffControl
YUncorruptRecovery
ZLine SortEditing

Notable Technical Features

  • IM2 Background Services: Autoline (N), Trace (T), Disable NEW (W), and Display Memory (Q) leverage Interrupt Mode 2 to operate transparently alongside BASIC execution.
  • Compactor (P): Removes redundant spaces and optimizes BASIC source to reduce program size and improve execution speed.
  • Uncorrupt (Y): Attempts to repair a corrupted BASIC program area, potentially recovering work that would otherwise be lost.
  • Disable NEW (W): Intercepts the NEW command via the IM2 hook to prevent accidental erasure of the BASIC program in memory.
  • UDG Designer (G) and UDGs to DATA (U): Provide an integrated workflow for creating and exporting user-defined graphics directly from the toolkit.
  • Bytes to DATA (B): Converts binary memory blocks into BASIC DATA statements, useful for embedding machine code routines within BASIC programs.
  • Line Sort (Z): Sorts BASIC program lines, a feature useful for organizing merged or otherwise disordered source code.

Historical Context

BASIC toolkits of this type were a popular category of utility software for Sinclair-compatible machines throughout the mid-1980s. The TS 2068’s extended memory map and support for IM2 interrupts gave toolkit authors more flexibility than was available on the base Spectrum hardware. Providing 26 distinct tools mapped to the 26 letter keys was a common and ergonomic design choice for menu-driven utilities of this era, minimizing keystrokes while making all functions easy to remember.

Image Gallery

Source Code

;=====================================================================
; TS 2068 TOOLKIT  —  Bob Mitchell, 1985
; Annotated disassembly of the machine-code block at $E930-$FF57
;=====================================================================
;
; WHAT IT IS
;   A menu-driven BASIC programmer's toolkit for the Timex/Sinclair
;   2068.  26 commands (A-Z) covering renumber, move/copy/delete line
;   blocks, search & replace, REM creation for machine code, a UDG
;   designer, a line sorter, memory/trace displays and a NEW-key lock.
;   The author's name appears at $F113 ("BOB MITCHELL 1985") and is
;   printed as message 51 on the bottom line of the menu.
;
; LOAD ADDRESS
;   ORG $E930.  The code runs above RAMTOP, so the host BASIC must
;   CLEAR 59695 ($E92F) or lower before loading.  Nothing is
;   relocatable — every internal reference is absolute.
;
; ENTRY POINTS
;   $E930  IM2_INIT   build the IM2 vector table, set I.  Call once
;                     after loading (e.g. RANDOMIZE USR 59696).
;   $EA60  MAIN       the toolkit itself (RANDOMIZE USR 60000).
;
; MEMORY USED OUTSIDE THE CODE BLOCK
;   $E800-$E900  257-byte IM2 vector table, every byte = $E9
;   $E9E9-$E9EB  JP $FEFF   (the actual interrupt service routine)
;
;---------------------------------------------------------------------
; A NOTE ON THE ORIGINAL DISASSEMBLY
;---------------------------------------------------------------------
;   Roughly half of this program is *inline data* following CALL
;   instructions — the routines at $ED8F/$ED92/$ED97 pop their own
;   return address and interpret the bytes that follow as a display
;   /prompt script.  A plain linear disassembler therefore runs off
;   the rails at every command handler and mis-types the following
;   code as data (and the data as code).  Every such block is marked
;   below with the pseudo-op DEFB and decoded in comments.
;
;---------------------------------------------------------------------
; THE INLINE SCRIPT LANGUAGE  (interpreted by $ED99)
;---------------------------------------------------------------------
;   Registers carried through a script:  HL = "current value",
;   DE = script pointer.
;
;   $00        end of script.  Print "\r\r EXECUTE? ", read a key;
;              "Y" prints "Y\r\r" and execution resumes at the byte
;              after the $00; anything else restores SP from
;              SAVED_SP and drops back to the main menu.
;   $01-$0F    printable control character (mainly $0D = newline)
;   $10        print "#" then HL as four hex digits
;   $11 nn     input a string (max 33 chars) into buffer 0 (nn=$00)
;              or buffer 1 (nn<>$00)
;   $12 nn     print string buffer 0 / 1
;   $13        input a number into HL.  Decimal, or "#hhhh" for hex
;   $14        print HL as decimal, leading zeros suppressed
;   $15        error 4 "Zero not Allowed" if HL = 0
;   $16        error 1 "Range Error"        if P_INCR  > 100
;   $17        error 3 "Invalid Line Number" if P_DEST or P_FINISH
;                                              > 9996
;   $18        error 2 "Numbers Reversed"   if P_START > P_FINISH
;   $19        end of script — resume execution at the next byte,
;              with stream 2 (upper screen) selected
;   $1A-$1F    store HL into parameter slot 0..5
;              ($ECCB P_START, $ECCD P_FINISH, $ECCF P_DEST,
;               $ECD1 P_INCR, $ECD3 P_WORK1, $ECD5 P_WORK2)
;   $20-$7E    printable ASCII
;   $7F        print " ©"  (only when the channel is 'S' or 'P')
;   $80-$BF    print " " + MSGTAB message (b AND $3F) + ": "
;   $C0-$FF    print " " + MSGTAB message (b AND $3F)
;
;---------------------------------------------------------------------
; MSGTAB word list (base $EFFA, index = byte AND $3F)
;---------------------------------------------------------------------
;    0 ADDRESS      1 START       2 FINISH      3 1st
;    4 UNAFFECTED   5 DESTINATION 6 INCREMENT   7 LINE
;    8 NUMBER       9 DELETE     10 MOVE       11 COPY
;   12 RENUMBER    13 BLOCK      14 OF         15 PROGRAM
;   16 MACHINE     17 CODE       18 UDGs       19 TO
;   20 DATA        21 SEARCH     22 LIST       23 REPLACE
;   24 OLD         25 NEW        26 STRING     27 REM
;   28 UPPER       29 lower      30 CASE       31 DEC
;   32 HEX         33 MERGE      34 ? (0=no)   35 CREATE
;   36 SYMBOL      37 BYTES      38 PER        39 bytes
;   40 STATUS      41 REPORT     42 MEMORY     43 LEFT
;   44 LENGTH      45 VARIABLE   46 FILE       47 USER RAM
;   48 INTERRUPTS  49 NORMAL     50 DIVERTED   51 BOB MITCHELL 1985
;   52 DESIGN      53 CHARACTER  54 USE        55 COMPACT
;   56 COLUMN      57 I/O (on/off)             58 IN QUOTES? (Y/N)
;   59 AUTO        60 ABCDEFGH   61 RESTART    62 <CR><CR>
;   63 (c)
;
;   Note: the ROM's PUTMES skips one terminator before message 0, so
;   the byte AT the table base ($EFFA = $8D) is a dummy terminator and
;   message 0 is the first string after it.  Verified two ways: $EFEB
;   +0 gives " EXECUTE? " and $F17B/$F17E give "HEX"/"DEC"+"IMAL".
;
;---------------------------------------------------------------------
; ROM ENTRY POINTS USED  (all verified against the TS2068 HOME ROM)
;---------------------------------------------------------------------
PARP    EQU $03F3   ; tone.  DE = cycles-1, HL = period
PUTMES  EQU $073F   ; print message A from table at DE
K_CLS   EQU $08A6   ; clear screen
CLLHS   EQU $08A9   ; clear/attr left-hand side
EDIT_K  EQU $0A82   ; the line editor
DELSYM  EQU $0B7E   ; delete the single byte at (HL)
PUTDIG  EQU $11EA   ; send decimal digit A to current channel
SELECT  EQU $1230   ; select stream A
SEL_HL  EQU $1248   ; select the channel whose address is in HL
INSERT  EQU $12BB   ; make BC bytes of room at (HL)
X_CALC  EQU $134E   ; STKBOT = WORKSP, reset fp stack
PUT_SR  EQU $15A1   ; ">" / current-line marker
OUTNUM  EQU $1676   ; decimal digit of HL, BC = -power of ten
SKIPNUM EQU $1602   ; if A = $0E skip the 5-byte number, reload A
FIND_L  EQU $16D6   ; find line >= HL; DE = previous line
RECLEN  EQU $1720   ; DE = next record, BC = length
DEL_DE  EQU $174D   ; delete the bytes between HL and DE
DELREC  EQU $1750   ; delete BC bytes at (HL)
PUT_BC  EQU $1788   ; print BC as a decimal line number
CHK_SZ  EQU $1FBB   ; error 4 unless BC bytes are free below RAMTOP
PAUSE   EQU $1FF2   ; HALT / decrement BC / test KEYHIT
PO_STR  EQU $21DB   ; print BC characters from (DE)
ALPHAQ  EQU $304B   ; CY set if A is a letter
STKUSN  EQU $3059   ; stack the number at CH_ADD
DIGITQ  EQU $30D9   ; NC if A is '0'-'9'
MULT    EQU $3468   ; HL = HL * DE
;
; System variables (identical to the ZX Spectrum in this range)
LASTK   EQU $5C08
FLAGS   EQU $5C3B
TVFLAG  EQU $5C3C
PPC     EQU $5C45
EPPC    EQU $5C49
VARS    EQU $5C4B
CURCHL  EQU $5C51
PROG    EQU $5C53
ELINE   EQU $5C59
KCUR    EQU $5C5B
CH_ADD  EQU $5C5D
WORKSP  EQU $5C61
STKBOT  EQU $5C63
STKEND  EQU $5C65
FLAGX   EQU $5C71
UDG     EQU $5C7B
ECHOE   EQU $5C82
SCRCT   EQU $5C8C
RAMTOP  EQU $5CB2

        ORG $E930

;=====================================================================
; IM2_INIT  ($E930)
; Build the interrupt-mode-2 vector table and point I at it.
; Does NOT enable IM2 — commands N/Q/T/W do that via SET_IM2.
;=====================================================================
IM2_INIT:
$E930  C5           PUSH BC
$E931  D5           PUSH DE
$E932  E5           PUSH HL
$E933  F5           PUSH AF
$E934  21 00 E8     LD HL,$E800        ; vector table base (I = $E8)
$E937  06 00        LD B,$00           ; 256 iterations
IM2_FILL:
$E939  36 E9        LD (HL),$E9        ; every entry byte = $E9 ...
$E93B  23           INC HL
$E93C  10 FB        DJNZ IM2_FILL
$E93E  36 E9        LD (HL),$E9        ; ... plus the 257th byte at $E900
                                       ; so any data-bus value yields $E9E9
$E940  3E C3        LD A,$C3           ; plant JP $FEFF at $E9E9
$E942  32 E9 E9     LD ($E9E9),A
$E945  21 FF FE     LD HL,$FEFF        ; = ISR
$E948  22 EA E9     LD ($E9EA),HL
$E94B  3E E8        LD A,$E8
$E94D  ED 47        LD I,A
$E94F  F1           POP AF
$E950  E1           POP HL
$E951  D1           POP DE
$E952  C1           POP BC
$E953  C9           RET

;---------------------------------------------------------------------
; $E954-$E9E8   149 spare bytes (all $00)
;---------------------------------------------------------------------
$E954  DEFS 149,$00

;---------------------------------------------------------------------
; $E9E9  the IM2 vector target itself.  Written by IM2_INIT, but the
;        loaded image already contains the finished instruction.
;---------------------------------------------------------------------
$E9E9  C3 FF FE     JP ISR

;---------------------------------------------------------------------
; $E9EC-$EA5F   116 spare bytes (all $00)
;---------------------------------------------------------------------
$E9EC  DEFS 116,$00

;=====================================================================
; MAIN  ($EA60)  —  the menu
; Stacks its own address so every command RETurns here, and records SP
; so an aborted command can unwind (see EF67/ABORT).
;=====================================================================
MAIN:
$EA60  21 60 EA     LD HL,MAIN
$EA63  E5           PUSH HL            ; command handlers RET to MAIN
$EA64  ED 73 C9 EC  LD (SAVED_SP),SP   ; unwind point for "not Y" / errors
$EA68  21 46 EF     LD HL,$EF46        ; HL -> "CALL REPORT / DEFB 0"
                                       ; ("Task Complete" trampoline, used
                                       ;  as the value seed for scripts)
$EA6B  E5           PUSH HL
$EA6C  CD 8F ED     CALL SCRIPT_CLS    ; clear screen, run the script below
;---------------------------------------------------------------------
; Inline script: the menu page.  16 rows of two 16-column halves.
;---------------------------------------------------------------------
$EA6F  DEFB $0D
$EA70  DEFM "        TS 2068  TOOLKIT"
$EA88  DEFB $0D,$0D
$EA8A  DEFM "A Alter Program N Autoline On   "
$EAAA  DEFM "B Bytes to DATA O Locate Token  "
$EACA  DEFM "C Copy Lines    P Compactor     "
$EAEA  DEFM "D Delete lines  Q Display Memory"
$EB0A  DEFM "E REM Create    R Renumber      "
$EB2A  DEFM "F REM Delete    S Search & List "
$EB4A  DEFM "G UDG Designer  T Trace on      "
$EB6A  DEFM "H Hex & Dec     U UDGs to DATA  "
$EB8A  DEFM "I Information   V List Variables"
$EBAA  DEFM "J Merge Lines   W Disable NEW   "
$EBCA  DEFM "K Upper Case    X N/Q/T/W off   "
$EBEA  DEFM "L Lower Case    Y Uncorrupt     "
$EC0A  DEFM "M Move Lines    Z Line Sort     "
$EC2A  DEFB $0D,$0D
$EC2C  DEFM " PRESS A KEY FOLLOWED BY ENTER,"
$EC4B  DEFB $0D
$EC4C  DEFM " or just ENTER anytime for menu"
$EC6B  DEFB $FE               ; MSG 62 = <CR><CR>
$EC6C  DEFB $7F               ; " ©"
$EC6D  DEFB $F3               ; MSG 51 = "BOB MITCHELL 1985"
$EC6E  DEFB $0D
$EC6F  DEFB $19               ; end of script; resume below on stream 2

;---------------------------------------------------------------------
; Read the command letter and vector through CMDTAB.
;---------------------------------------------------------------------
GETCMD:
$EC70  01 02 00     LD BC,$0002        ; accept up to 2 characters
$EC73  CD F7 EE     CALL INPUT         ; A = first char (ENTER alone aborts)
$EC76  CD 4B 30     CALL ALPHAQ
$EC79  30 F5        JR NC,GETCMD       ; not a letter — ask again
$EC7B  E6 DF        AND $DF            ; force upper case
$EC7D  D6 41        SUB 'A'
$EC7F  87           ADD A,A            ; *2
$EC80  21 1D ED     LD HL,CMDTAB
$EC83  5F           LD E,A
$EC84  16 00        LD D,$00
$EC86  19           ADD HL,DE
$EC87  5E           LD E,(HL)
$EC88  23           INC HL
$EC89  56           LD D,(HL)
$EC8A  D5           PUSH DE
$EC8B  C9           RET                ; jump to the handler
                                       ; (which RETs to MAIN)

;=====================================================================
; PRHEX4  ($EC8C)  print "#" followed by HL in hex
;=====================================================================
PRHEX4:
$EC8C  3E 23        LD A,'#'
$EC8E  D7           RST $10
$EC8F  7C           LD A,H
$EC90  CD 9E EC     CALL PRHIGH
$EC93  7C           LD A,H
$EC94  CD A2 EC     CALL PRLOW
$EC97  7D           LD A,L
$EC98  CD 9E EC     CALL PRHIGH
$EC9B  7D           LD A,L
$EC9C  18 04        JR PRLOW
PRHIGH:
$EC9E  1F           RRA                ; high nibble -> low
$EC9F  1F           RRA
$ECA0  1F           RRA
$ECA1  1F           RRA
PRLOW:
$ECA2  E6 0F        AND $0F
$ECA4  FE 0A        CP $0A
$ECA6  38 02        JR C,PRNIB
$ECA8  C6 07        ADD A,$07          ; 'A'-'F'
PRNIB:
$ECAA  C3 EA 11     JP PUTDIG          ; adds $30 and prints

;=====================================================================
; PRDEC  ($ECAD)  print HL as decimal, leading zeros suppressed
; E = $FF is the ROM's "still suppressing" flag for OUTNUM.
;=====================================================================
PRDEC:
$ECAD  1E FF        LD E,$FF
$ECAF  01 F0 D8     LD BC,-10000
$ECB2  CD 76 16     CALL OUTNUM
$ECB5  01 18 FC     LD BC,-1000
$ECB8  CD 76 16     CALL OUTNUM
$ECBB  01 9C FF     LD BC,-100
$ECBE  CD 76 16     CALL OUTNUM
$ECC1  0E F6        LD C,$F6           ; BC = -10  (B is still $FF)
$ECC3  CD 76 16     CALL OUTNUM
$ECC6  7D           LD A,L             ; units
$ECC7  18 E1        JR PRNIB

;=====================================================================
; WORKSPACE  ($ECC9-$ED1C)
; The values below are whatever the last session left behind; they are
; the program's only mutable storage.
;=====================================================================
SAVED_SP:  $ECC9  DEFW $61E2   ; SP on entry to MAIN — unwind target
P_START:   $ECCB  DEFW $0001   ; script slot $1A
P_FINISH:  $ECCD  DEFW $0FA0   ; script slot $1B
P_DEST:    $ECCF  DEFW $0FA0   ; script slot $1C
P_INCR:    $ECD1  DEFW $000A   ; script slot $1D
P_WORK1:   $ECD3  DEFW $7747   ; script slot $1E
P_WORK2:   $ECD5  DEFW $0006   ; script slot $1F
STR0_LEN:  $ECD7  DEFW $0001   ; string buffer 0 (script codes $11/$12 nn=0)
STR0:      $ECD9  DEFB $4E,$00,$38,$30   ; "N" + stale bytes from last run
           $ECDD  DEFS 29,$00            ; STR0 is 33 bytes: $ECD9-$ECF9
STR1_LEN:  $ECFA  DEFW $0000   ; string buffer 1 (script codes $11/$12 nn<>0)
STR1:      $ECFC  DEFS 33,$00  ; $ECFC-$ED1C, ends exactly at CMDTAB

;=====================================================================
; CMDTAB  ($ED1D)  26 handler addresses, indexed by (letter-'A')*2
;=====================================================================
CMDTAB:
$ED1D  AD F1        DEFW $F1AD   ; A  Alter Program   (search & replace)
$ED1F  EE F6        DEFW $F6EE   ; B  Bytes to DATA
$ED21  9A F5        DEFW $F59A   ; C  Copy Lines
$ED23  10 F6        DEFW $F610   ; D  Delete lines
$ED25  3F F6        DEFW $F63F   ; E  REM Create
$ED27  EF F9        DEFW $F9EF   ; F  REM Delete
$ED29  81 FA        DEFW $FA81   ; G  UDG Designer
$ED2B  77 F1        DEFW $F177   ; H  Hex & Dec
$ED2D  25 F8        DEFW $F825   ; I  Information
$ED2F  A4 FC        DEFW $FCA4   ; J  Merge Lines
$ED31  F7 FC        DEFW $FCF7   ; K  Upper Case
$ED33  03 FD        DEFW $FD03   ; L  Lower Case
$ED35  24 F3        DEFW $F324   ; M  Move Lines
$ED37  74 FD        DEFW $FD74   ; N  Autoline On
$ED39  91 FE        DEFW $FE91   ; O  Locate Token
$ED3B  3C FC        DEFW $FC3C   ; P  Compactor
$ED3D  EB FD        DEFW $FDEB   ; Q  Display Memory
$ED3F  5C F3        DEFW $F35C   ; R  Renumber
$ED41  85 F9        DEFW $F985   ; S  Search & List
$ED43  F7 FD        DEFW $FDF7   ; T  Trace on
$ED45  B0 F6        DEFW $F6B0   ; U  UDGs to DATA
$ED47  DE F8        DEFW $F8DE   ; V  List Variables
$ED49  3E FF        DEFW $FF3E   ; W  Disable NEW
$ED4B  35 FF        DEFW $FF35   ; X  N/Q/T/W off
$ED4D  52 F4        DEFW $F452   ; Y  Uncorrupt   (= rebuild line lengths)
$ED4F  19 F5        DEFW $F519   ; Z  Line Sort

;=====================================================================
; PRCHAR  ($ED51)  print one script byte
;   < $7F   straight to the print routine
;   = $7F   " ©" — but only on a screen ('S') or printer ('P') channel
;   > $7F   " " + MSGTAB[A AND $3F], plus ": " when bit 6 is clear
;=====================================================================
PRCHAR:
$ED51  FE 7F        CP $7F
$ED53  28 04        JR Z,PRCOPY
$ED55  30 1A        JR NC,PRMSG
$ED57  D7           RST $10
$ED58  C9           RET
PRCOPY:
$ED59  4F           LD C,A
$ED5A  2A 51 5C     LD HL,(CURCHL)
$ED5D  23           INC HL             ; +4 = the channel's letter
$ED5E  23           INC HL
$ED5F  23           INC HL
$ED60  23           INC HL
$ED61  7E           LD A,(HL)
$ED62  FE 53        CP 'S'
$ED64  28 05        JR Z,PRCOPY2
$ED66  FE 50        CP 'P'
$ED68  79           LD A,C
$ED69  20 07        JR NZ,PRMSG2       ; other channel: treat $7F as a message
PRCOPY2:
$ED6B  3E 20        LD A,' '
$ED6D  D7           RST $10
$ED6E  79           LD A,C
$ED6F  D7           RST $10            ; the (c) symbol
$ED70  C9           RET
PRMSG:
$ED71  4F           LD C,A
PRMSG2:
$ED72  E6 3F        AND $3F            ; message number
$ED74  47           LD B,A
$ED75  C5           PUSH BC
$ED76  3E 20        LD A,' '
$ED78  D7           RST $10
$ED79  78           LD A,B
$ED7A  11 FA EF     LD DE,MSGTAB
$ED7D  FD CB 01 C6  SET 0,(IY+$01)     ; FLAGS bit 0 - leading space handling
$ED81  CD 3F 07     CALL PUTMES
$ED84  C1           POP BC
$ED85  CB 71        BIT 6,C
$ED87  C0           RET NZ             ; $C0-$FF: no colon
$ED88  3E 3A        LD A,':'
$ED8A  D7           RST $10
$ED8B  3E 20        LD A,' '
$ED8D  D7           RST $10
$ED8E  C9           RET

;=====================================================================
; THE SCRIPT ENGINE
;   SCRIPT_CLS ($ED8F)  clear screen, select stream $FE, run script
;   SCRIPT_FE  ($ED92)  select stream $FE, run script
;   SCRIPT     ($ED97)  run the script that follows the CALL
; None of these return to the instruction after the CALL: the return
; address IS the script, and control resumes at the $19 / $00 byte.
;=====================================================================
SCRIPT_CLS:
$ED8F  CD A6 08     CALL K_CLS
SCRIPT_FE:
$ED92  3E FE        LD A,$FE           ; stream $FE = whole screen, no scroll
$ED94  CD 30 12     CALL SELECT
SCRIPT:
$ED97  D1           POP DE             ; DE = address of the script bytes
$ED98  1B           DEC DE
SCR_LOOP:
$ED99  13           INC DE
$ED9A  1A           LD A,(DE)
$ED9B  A7           AND A
$ED9C  CA 34 EE     JP Z,SCR_END       ; $00 - "EXECUTE?"
$ED9F  FE 20        CP $20
$EDA1  30 43        JR NC,SCR_PRINT    ; >= $20 - printable / message
$EDA3  FE 10        CP $10
$EDA5  38 3F        JR C,SCR_PRINT     ; $01-$0F - control character
$EDA7  28 2E        JR Z,SCR_HEX       ; $10
$EDA9  FE 11        CP $11
$EDAB  28 52        JR Z,SCR_INSTR     ; $11
$EDAD  FE 12        CP $12
$EDAF  28 63        JR Z,SCR_OUTSTR    ; $12
$EDB1  FE 13        CP $13
$EDB3  28 3A        JR Z,SCR_INNUM     ; $13
$EDB5  FE 14        CP $14
$EDB7  28 3D        JR Z,SCR_OUTNUM    ; $14
$EDB9  FE 15        CP $15
$EDBB  28 21        JR Z,SCR_NZ        ; $15
$EDBD  FE 19        CP $19
$EDBF  CA 57 EE     JP Z,SCR_RESUME    ; $19
$EDC2  38 67        JR C,SCR_CHECK     ; $16/$17/$18 - validators
;   $1A-$1F: store HL into parameter slot A-$1A
$EDC4  D6 1A        SUB $1A
$EDC6  87           ADD A,A
$EDC7  4F           LD C,A
$EDC8  06 00        LD B,$00
$EDCA  D5           PUSH DE            ; save the script pointer
$EDCB  EB           EX DE,HL           ; DE = value
$EDCC  21 CB EC     LD HL,P_START
$EDCF  09           ADD HL,BC
$EDD0  73           LD (HL),E
$EDD1  23           INC HL
$EDD2  72           LD (HL),D
$EDD3  EB           EX DE,HL           ; HL = value again
$EDD4  D1           POP DE
$EDD5  18 C2        JR SCR_LOOP
SCR_HEX:
$EDD7  D5           PUSH DE
$EDD8  CD 8C EC     CALL PRHEX4
$EDDB  D1           POP DE
$EDDC  18 BB        JR SCR_LOOP
SCR_NZ:
$EDDE  7C           LD A,H
$EDDF  B5           OR L
$EDE0  20 B7        JR NZ,SCR_LOOP
$EDE2  CD 4A EF     CALL REPORT
$EDE5  04           DEFB 4             ; "Zero not Allowed"
SCR_PRINT:
$EDE6  D5           PUSH DE
$EDE7  E5           PUSH HL
$EDE8  CD 51 ED     CALL PRCHAR
$EDEB  E1           POP HL
$EDEC  D1           POP DE
$EDED  18 AA        JR SCR_LOOP
SCR_INNUM:
$EDEF  D5           PUSH DE
$EDF0  CD A9 EE     CALL INNUM
$EDF3  D1           POP DE
$EDF4  18 A3        JR SCR_LOOP
SCR_OUTNUM:
$EDF6  D5           PUSH DE
$EDF7  E5           PUSH HL
$EDF8  CD AD EC     CALL PRDEC
$EDFB  E1           POP HL
$EDFC  D1           POP DE
$EDFD  18 9A        JR SCR_LOOP
SCR_INSTR:
$EDFF  13           INC DE             ; operand byte selects the buffer
$EE00  1A           LD A,(DE)
$EE01  D5           PUSH DE
$EE02  A7           AND A
$EE03  21 D7 EC     LD HL,STR0_LEN
$EE06  28 03        JR Z,SCR_IS2
$EE08  21 FA EC     LD HL,STR1_LEN
SCR_IS2:
$EE0B  01 21 00     LD BC,$0021        ; 33 characters maximum
$EE0E  CD 9A EE     CALL INSTR
$EE11  D1           POP DE
SCR_BACK:
$EE12  18 85        JR SCR_LOOP
SCR_OUTSTR:
$EE14  13           INC DE
$EE15  1A           LD A,(DE)
$EE16  D5           PUSH DE
$EE17  A7           AND A
$EE18  21 D7 EC     LD HL,STR0_LEN
$EE1B  28 03        JR Z,SCR_OS2
$EE1D  21 FA EC     LD HL,STR1_LEN
SCR_OS2:
$EE20  4E           LD C,(HL)
$EE21  23           INC HL
$EE22  46           LD B,(HL)
$EE23  23           INC HL
$EE24  EB           EX DE,HL
$EE25  CD DB 21     CALL PO_STR        ; print BC chars from (DE)
$EE28  D1           POP DE
$EE29  18 E7        JR SCR_BACK
SCR_CHECK:
$EE2B  D6 17        SUB $17            ; $16->-1  $17->0  $18->+1
$EE2D  D5           PUSH DE
$EE2E  CD 61 EE     CALL VALIDATE
$EE31  D1           POP DE
$EE32  18 DE        JR SCR_BACK

;---------------------------------------------------------------------
; SCR_END ($EE34)  script byte $00 — the "EXECUTE?" confirmation
;---------------------------------------------------------------------
SCR_END:
$EE34  D5           PUSH DE            ; DE -> byte after the $00
$EE35  E5           PUSH HL
$EE36  11 EB EF     LD DE,ASKTAB
$EE39  D5           PUSH DE
$EE3A  AF           XOR A
$EE3B  CD 3F 07     CALL PUTMES        ; "\r\r EXECUTE? "
$EE3E  01 02 00     LD BC,$0002
$EE41  CD F7 EE     CALL INPUT
$EE44  E6 DF        AND $DF
$EE46  FE 59        CP 'Y'
$EE48  28 05        JR Z,SCR_GO
$EE4A  ED 7B C9 EC  LD SP,(SAVED_SP)   ; abandon — unwind to MAIN
$EE4E  C9           RET
SCR_GO:
$EE4F  D1           POP DE
$EE50  3E 01        LD A,$01
$EE52  CD 3F 07     CALL PUTMES        ; "Y\r\r"
$EE55  E1           POP HL
$EE56  D1           POP DE
;---------------------------------------------------------------------
; SCR_RESUME ($EE57)  script byte $19 — leave the script, run on
;---------------------------------------------------------------------
SCR_RESUME:
$EE57  13           INC DE
$EE58  D5           PUSH DE            ; the resume address
$EE59  E5           PUSH HL
$EE5A  3E 02        LD A,$02
$EE5C  CD 30 12     CALL SELECT        ; back to stream 2 (upper screen)
$EE5F  E1           POP HL
$EE60  C9           RET                ; -> the byte after the script

;=====================================================================
; VALIDATE  ($EE61)  A = -1 / 0 / +1 selects the test
;=====================================================================
VALIDATE:
$EE61  28 20        JR Z,V_DEST        ; script $17
$EE63  30 0F        JR NC,V_ORDER      ; script $18
;   script $16 - increment must be 1..100
$EE65  AF           XOR A
$EE66  11 64 00     LD DE,100
$EE69  2A D1 EC     LD HL,(P_INCR)
$EE6C  37           SCF
$EE6D  ED 52        SBC HL,DE          ; HL - 101
$EE6F  D8           RET C
$EE70  CD 4A EF     CALL REPORT
$EE73  01           DEFB 1             ; "Range Error"
V_ORDER:
$EE74  2A CB EC     LD HL,(P_START)
$EE77  ED 5B CD EC  LD DE,(P_FINISH)
$EE7B  37           SCF
$EE7C  ED 52        SBC HL,DE          ; start - finish - 1
$EE7E  D8           RET C
$EE7F  CD 4A EF     CALL REPORT
$EE82  02           DEFB 2             ; "Numbers Reversed"
V_DEST:
$EE83  21 0C 27     LD HL,9996
$EE86  ED 5B CF EC  LD DE,(P_DEST)
$EE8A  ED 52        SBC HL,DE
$EE8C  38 08        JR C,V_BAD
$EE8E  19           ADD HL,DE          ; HL = 9996 again
$EE8F  ED 5B CD EC  LD DE,(P_FINISH)
$EE93  ED 52        SBC HL,DE
$EE95  D0           RET NC
V_BAD:
$EE96  CD 4A EF     CALL REPORT
$EE99  03           DEFB 3             ; "Invalid Line Number"

;=====================================================================
; INSTR  ($EE9A)  read up to BC characters into the buffer at (HL)
;=====================================================================
INSTR:
$EE9A  E5           PUSH HL
$EE9B  CD F7 EE     CALL INPUT         ; BC = length, DE = text
$EE9E  E1           POP HL
$EE9F  71           LD (HL),C
$EEA0  23           INC HL
$EEA1  70           LD (HL),B
$EEA2  23           INC HL
$EEA3  EB           EX DE,HL
$EEA4  ED B0        LDIR
$EEA6  AF           XOR A
$EEA7  12           LD (DE),A          ; NUL-terminate
$EEA8  C9           RET

;=====================================================================
; INNUM  ($EEA9)  read a number into HL.  "#" prefix = hexadecimal.
;=====================================================================
INNUM:
$EEA9  01 06 00     LD BC,$0006        ; up to 6 characters
$EEAC  CD F7 EE     CALL INPUT
$EEAF  21 00 00     LD HL,$0000
$EEB2  FE 23        CP '#'
$EEB4  28 1C        JR Z,IN_HEX
IN_DEC:
$EEB6  FE 0D        CP $0D
$EEB8  C8           RET Z
$EEB9  44           LD B,H             ; HL = HL*10 + digit
$EEBA  4D           LD C,L
$EEBB  29           ADD HL,HL
$EEBC  29           ADD HL,HL
$EEBD  09           ADD HL,BC
$EEBE  29           ADD HL,HL
$EEBF  38 0F        JR C,IN_AGAIN      ; overflow -> ask again
$EEC1  CD D9 30     CALL DIGITQ
$EEC4  38 0A        JR C,IN_AGAIN      ; not a digit -> ask again
$EEC6  D6 30        SUB '0'
$EEC8  06 00        LD B,$00
$EECA  4F           LD C,A
$EECB  13           INC DE
$EECC  1A           LD A,(DE)
$EECD  09           ADD HL,BC
$EECE  30 E6        JR NC,IN_DEC
IN_AGAIN:
$EED0  18 D7        JR INNUM
IN_HEX:
$EED2  13           INC DE
$EED3  1A           LD A,(DE)
$EED4  FE 0D        CP $0D
$EED6  C8           RET Z
$EED7  29           ADD HL,HL          ; HL = HL*16 + nibble
$EED8  29           ADD HL,HL
$EED9  29           ADD HL,HL
$EEDA  29           ADD HL,HL
$EEDB  CD D9 30     CALL DIGITQ
$EEDE  DC EB EE     CALL C,HEXLET      ; not 0-9: try A-F
$EEE1  38 ED        JR C,IN_AGAIN
$EEE3  D6 30        SUB '0'
$EEE5  06 00        LD B,$00
$EEE7  4F           LD C,A
$EEE8  09           ADD HL,BC
$EEE9  18 E7        JR IN_HEX
HEXLET:
$EEEB  E6 DF        AND $DF
$EEED  D6 41        SUB 'A'
$EEEF  D8           RET C
$EEF0  FE 06        CP $06
$EEF2  3F           CCF
$EEF3  D8           RET C
$EEF4  C6 3A        ADD A,$3A          ; map A-F to '0'+10..'0'+15
$EEF6  C9           RET

;=====================================================================
; INPUT  ($EEF7)  read a line using the ROM editor
; Entry: BC = maximum length accepted.
; Exit:  BC = length, DE = text, A = first character.
;        An empty line (just ENTER) unwinds to MAIN.
;        An over-long line loops back and asks again.
;=====================================================================
INPUT:
$EEF7  C5           PUSH BC
$EEF8  2A 51 5C     LD HL,(CURCHL)
$EEFB  E5           PUSH HL            ; remember the caller's channel
$EEFC  AF           XOR A
$EEFD  CD 30 12     CALL SELECT        ; stream 0 (lower screen)
$EF00  CD A9 08     CALL CLLHS
$EF03  FD CB 02 DE  SET 3,(IY+$02)     ; TVFLAG - input on lower screen
$EF07  CD 4E 13     CALL X_CALC
$EF0A  21 71 5C     LD HL,FLAGX
$EF0D  CB EE        SET 5,(HL)         ; INPUT mode
$EF0F  CB FE        SET 7,(HL)
$EF11  E5           PUSH HL
$EF12  01 01 00     LD BC,$0001
$EF15  F7           RST $30            ; BC-SPACES: one byte of workspace
$EF16  36 0D        LD (HL),$0D
$EF18  22 5B 5C     LD (KCUR),HL
$EF1B  CD 82 0A     CALL EDIT_K        ; hand over to the ROM editor
$EF1E  CD A9 08     CALL CLLHS
$EF21  E1           POP HL
$EF22  CB AE        RES 5,(HL)
$EF24  CB BE        RES 7,(HL)
$EF26  E1           POP HL
$EF27  CD 48 12     CALL SEL_HL        ; restore the caller's channel
$EF2A  FD 36 22 00  LD (IY+$22),$00    ; zero KCUR high byte - no cursor
$EF2E  2A 63 5C     LD HL,(STKBOT)
$EF31  ED 5B 61 5C  LD DE,(WORKSP)
$EF35  37           SCF
$EF36  ED 52        SBC HL,DE          ; HL = length typed - 1
$EF38  C1           POP BC             ; BC = maximum allowed
$EF39  ED 42        SBC HL,BC
$EF3B  30 BA        JR NC,INPUT        ; too long: start over
$EF3D  09           ADD HL,BC
$EF3E  44           LD B,H
$EF3F  4D           LD C,L             ; BC = actual length
$EF40  78           LD A,B
$EF41  B1           OR C
$EF42  1A           LD A,(DE)          ; A = first character
$EF43  C0           RET NZ
$EF44  18 21        JR ABORT           ; empty line -> back to the menu

;---------------------------------------------------------------------
; $EF46  "Task Complete" trampoline.  MAIN pushes this address so a
;        handler that just RETs lands here and reports success.
;---------------------------------------------------------------------
$EF46  CD 4A EF     CALL REPORT
$EF49  00           DEFB 0             ; "Task Complete"

;=====================================================================
; REPORT  ($EF4A)  print report message n, beep twice, return to MAIN
; Called as:   CALL REPORT
;              DEFB n
; The DEFB is read via the return address; REPORT never comes back.
;=====================================================================
REPORT:
$EF4A  CD A9 08     CALL CLLHS
$EF4D  E1           POP HL             ; HL -> the DEFB
$EF4E  7E           LD A,(HL)
$EF4F  11 6B EF     LD DE,ERRTAB
$EF52  CD 3F 07     CALL PUTMES
$EF55  21 84 01     LD HL,$0184        ; period
$EF58  11 0A 02     LD DE,$020A        ; cycles-1
$EF5B  CD F3 03     CALL PARP          ; beep
$EF5E  01 01 00     LD BC,$0001
$EF61  CD F2 1F     CALL PAUSE
$EF64  CD F2 1F     CALL PAUSE
ABORT:
$EF67  ED 7B C9 EC  LD SP,(SAVED_SP)
$EF6B  C9           RET                ; -> MAIN
;                   ^ this RET opcode ($C9) doubles as the dummy
;                     terminator that starts ERRTAB.

;=====================================================================
; ERRTAB  ($EF6B)  report messages, index 0-7
;=====================================================================
ERRTAB:
$EF6B  DEFB $C9                        ; dummy terminator
$EF6C  DEFM "Task Complete"            ; 0  -> "Task Completed"
$EF79  DEFB $E4                        ;   'd'+$80
$EF7A  DEFM "Range Erro"               ; 1
$EF84  DEFB $F2                        ;   'r'+$80
$EF85  DEFM "Numbers Reverse"          ; 2
$EF94  DEFB $E4
$EF95  DEFM "Invalid Line Numbe"       ; 3
$EFA7  DEFB $F2
$EFA8  DEFM "Zero not Allowe"          ; 4
$EFB7  DEFB $E4
$EFB8  DEFM "Lines would Overla"       ; 5
$EFCA  DEFB $F0                        ;   'p'+$80
$EFCB  DEFM "No Room at Destinatio"    ; 6
$EFE0  DEFB $EE                        ;   'n'+$80
$EFE1  DEFM "Zero Block"               ; 7

;=====================================================================
; ASKTAB  ($EFEB)  the two-entry confirmation table
;   0 = "\r\r EXECUTE? "      1 = "Y\r\r"
; Entry 1's terminator ($8D) is also the dummy terminator of MSGTAB.
;=====================================================================
ASKTAB:
$EFEB  DEFB $A1                        ; dummy terminator
$EFEC  DEFB $0D,$0D
$EFEE  DEFM " EXECUTE?"                ; 0
$EFF7  DEFB $A0                        ;   ' '+$80
$EFF8  DEFB $59,$0D                    ; 1  "Y",CR
MSGTAB:
$EFFA  DEFB $8D                        ;   CR+$80  / MSGTAB dummy terminator

;=====================================================================
; MSGTAB body ($EFFB-$F176).  64 words, see the index in the header.
; Each message ends with its last character + $80.
;=====================================================================
$EFFB  DEFM "ADDRES"      : DEFB $D3   ;  0 ADDRESS
$F002  DEFM "STAR"        : DEFB $D4   ;  1 START
$F007  DEFM "FINIS"       : DEFB $C8   ;  2 FINISH
$F00D  DEFM "1s"          : DEFB $F4   ;  3 1st
$F010  DEFM "UNAFFECTE"   : DEFB $C4   ;  4 UNAFFECTED
$F01A  DEFM "DESTINATIO"  : DEFB $CE   ;  5 DESTINATION
$F025  DEFM "INCREMEN"    : DEFB $D4   ;  6 INCREMENT
$F02E  DEFM "LIN"         : DEFB $C5   ;  7 LINE
$F032  DEFM "NUMBE"       : DEFB $D2   ;  8 NUMBER
$F038  DEFM "DELET"       : DEFB $C5   ;  9 DELETE
$F03E  DEFM "MOV"         : DEFB $C5   ; 10 MOVE
$F042  DEFM "COP"         : DEFB $D9   ; 11 COPY
$F046  DEFM "RENUMBE"     : DEFB $D2   ; 12 RENUMBER
$F04E  DEFM "BLOC"        : DEFB $CB   ; 13 BLOCK
$F053  DEFM "O"           : DEFB $C6   ; 14 OF
$F055  DEFM "PROGRA"      : DEFB $CD   ; 15 PROGRAM
$F05C  DEFM "MACHIN"      : DEFB $C5   ; 16 MACHINE
$F063  DEFM "COD"         : DEFB $C5   ; 17 CODE
$F067  DEFM "UDG"         : DEFB $F3   ; 18 UDGs
$F06B  DEFM "T"           : DEFB $CF   ; 19 TO
$F06D  DEFM "DAT"         : DEFB $C1   ; 20 DATA
$F071  DEFM "SEARC"       : DEFB $C8   ; 21 SEARCH
$F077  DEFM "LIS"         : DEFB $D4   ; 22 LIST
$F07B  DEFM "REPLAC"      : DEFB $C5   ; 23 REPLACE
$F082  DEFM "OL"          : DEFB $C4   ; 24 OLD
$F085  DEFM "NE"          : DEFB $D7   ; 25 NEW
$F088  DEFM "STRIN"       : DEFB $C7   ; 26 STRING
$F08E  DEFM "RE"          : DEFB $CD   ; 27 REM
$F091  DEFM "UPPE"        : DEFB $D2   ; 28 UPPER
$F096  DEFM "lowe"        : DEFB $F2   ; 29 lower
$F09B  DEFM "CAS"         : DEFB $C5   ; 30 CASE
$F09F  DEFM "DE"          : DEFB $C3   ; 31 DEC
$F0A2  DEFM "HE"          : DEFB $D8   ; 32 HEX
$F0A5  DEFM "MERG"        : DEFB $C5   ; 33 MERGE
$F0AA  DEFM "? (0=no"     : DEFB $A9   ; 34 ? (0=no)
$F0B2  DEFM "CREAT"       : DEFB $C5   ; 35 CREATE
$F0B8  DEFM "SYMBO"       : DEFB $CC   ; 36 SYMBOL
$F0BE  DEFM "BYTE"        : DEFB $D3   ; 37 BYTES
$F0C3  DEFM "PE"          : DEFB $D2   ; 38 PER
$F0C6  DEFM "byte"        : DEFB $F3   ; 39 bytes
$F0CB  DEFM "STATU"       : DEFB $D3   ; 40 STATUS
$F0D1  DEFM "REPOR"       : DEFB $D4   ; 41 REPORT
$F0D7  DEFM "MEMOR"       : DEFB $D9   ; 42 MEMORY
$F0DD  DEFM "LEF"         : DEFB $D4   ; 43 LEFT
$F0E1  DEFM "LENGT"       : DEFB $C8   ; 44 LENGTH
$F0E7  DEFM "VARIABL"     : DEFB $C5   ; 45 VARIABLE
$F0EF  DEFM "FIL"         : DEFB $C5   ; 46 FILE
$F0F3  DEFM "USER RA"     : DEFB $CD   ; 47 USER RAM
$F0FB  DEFM "INTERRUPT"   : DEFB $D3   ; 48 INTERRUPTS
$F105  DEFM "NORMA"       : DEFB $CC   ; 49 NORMAL
$F10B  DEFM "DIVERTE"     : DEFB $C4   ; 50 DIVERTED
$F113  DEFM "BOB MITCHELL 198" : DEFB $B5   ; 51 BOB MITCHELL 1985
$F124  DEFM "DESIG"       : DEFB $CE   ; 52 DESIGN
$F12A  DEFM "CHARACTE"    : DEFB $D2   ; 53 CHARACTER
$F133  DEFM "US"          : DEFB $C5   ; 54 USE
$F136  DEFM "COMPAC"      : DEFB $D4   ; 55 COMPACT
$F13D  DEFM "COLUM"       : DEFB $CE   ; 56 COLUMN
$F143  DEFM "I/O (on/off" : DEFB $A9   ; 57 I/O (on/off)
$F14F  DEFM "IN QUOTES? (Y/N" : DEFB $A9 ; 58 IN QUOTES? (Y/N)
$F15F  DEFM "AUT"         : DEFB $CF   ; 59 AUTO
$F163  DEFM "ABCDEFG"     : DEFB $C8   ; 60 ABCDEFGH
$F16B  DEFM "RESTAR"      : DEFB $D4   ; 61 RESTART
$F172  DEFB $0D,$8D                    ; 62 CR CR
$F174  DEFM "(c"          : DEFB $A9   ; 63 (c)

;=====================================================================
; COMMAND H  —  Hex & Dec   ($F177)
; Just an information page; the real work is INNUM's "#" prefix.
;=====================================================================
CMD_H:
$F177  CD 8F ED     CALL SCRIPT_CLS
$F17A  DEFB $0D
$F17B  DEFB $E0                        ; MSG 32 "HEX"
$F17C  DEFM " &"
$F17E  DEFB $DF                        ; MSG 31 "DEC"
$F17F  DEFM "IMAL"
$F183  DEFB $FE                        ; MSG 62 <CR><CR>
$F184  DEFM " Precede"
$F18C  DEFB $E0                        ; MSG 32 "HEX"
$F18D  DEFM " nos with ""#""."
$F19B  DEFB $19                        ; resume below
$F19C  CD 97 ED     CALL SCRIPT        ; wait-for-key script
$F19F  DEFB $13                        ;   input a number
$F1A0  DEFB $FE                        ;   <CR><CR>
$F1A1  DEFB $9F                        ;   MSG 31 "DEC" + ": "
$F1A2  DEFB $14                        ;   print it in decimal
$F1A3  DEFB $06
$F1A4  DEFB $A0                        ;   MSG 32 "HEX" + ": "
$F1A5  DEFB $10                        ;   print it in hex
$F1A6  DEFB $19                        ; resume below
$F1A7  FD 36 52 FF  LD (IY+$52),$FF    ; SCRCT - suppress "scroll?"
$F1AB  18 EF        JR $F19C           ; loop for another number

;=====================================================================
; COMMAND A  —  Alter Program  ($F1AD)   search & replace
;=====================================================================
CMD_A:
$F1AD  CD 8F ED     CALL SCRIPT_CLS
$F1B0  DEFB $0D
$F1B1  DEFB $D5                        ; MSG 21 "SEARCH"
$F1B2  DEFM " &"
$F1B4  DEFB $D7                        ; MSG 23 "REPLACE"
$F1B5  DEFB $FE                        ; <CR><CR>
$F1B6  DEFB $C1,$80                    ; " START ADDRESS: "
$F1B8  DEFB $13,$1A,$14                ; input -> P_START, echo
$F1BB  DEFB $FE
$F1BC  DEFB $C2,$80                    ; " FINISH ADDRESS: "
$F1BE  DEFB $13,$1B,$1C,$14            ; input -> P_FINISH and P_DEST
$F1C2  DEFB $FE
$F1C3  DEFB $D8,$9A                    ; " OLD STRING: "
$F1C5  DEFB $11,$00,$12,$00            ; input/echo buffer 0
$F1C9  DEFB $FE
$F1CA  DEFB $D9,$9A                    ; " NEW STRING: "
$F1CC  DEFB $11,$01,$12,$01            ; input/echo buffer 1
$F1D0  DEFB $FE
$F1D1  DEFB $D6,$A2                    ; " LIST ? (0=no): "
$F1D3  DEFB $13,$1D,$14                ; input -> P_INCR (used as a flag)
$F1D6  DEFB $16,$17,$18                ; validate
$F1D9  DEFB $00                        ; "EXECUTE?"
;---------------------------------------------------------------------
; Walk the program from P_START to P_FINISH replacing STR0 with STR1.
;---------------------------------------------------------------------
$F1DA  CD 95 F2     CALL EXPAND        ; resolve "&&&" number escapes
$F1DD  2A CB EC     LD HL,(P_START)
$F1E0  CD D6 16     CALL FIND_L
$F1E3  18 03        JR AL_LINE
AL_NEXT:
$F1E5  2A D5 EC     LD HL,(P_WORK2)    ; address of the next line
AL_LINE:
$F1E8  22 D3 EC     LD (P_WORK1),HL    ; address of this line
$F1EB  CD 20 17     CALL RECLEN
$F1EE  ED 53 D5 EC  LD (P_WORK2),DE    ; remember where the next one starts
$F1F2  56           LD D,(HL)          ; line number (big-endian)
$F1F3  23           INC HL
$F1F4  5E           LD E,(HL)
$F1F5  23           INC HL
$F1F6  E5           PUSH HL
$F1F7  2A CD EC     LD HL,(P_FINISH)
$F1FA  A7           AND A
$F1FB  ED 52        SBC HL,DE
AL_DONE:
$F1FD  E1           POP HL
$F1FE  D8           RET C              ; beyond the last wanted line
$F1FF  4E           LD C,(HL)          ; BC = line length
$F200  23           INC HL
$F201  46           LD B,(HL)
AL_TEXT:
$F202  23           INC HL             ; HL -> the line text
AL_SCAN:
$F203  11 D9 EC     LD DE,STR0
AL_CMP1:
$F206  1A           LD A,(DE)
$F207  BE           CP (HL)
$F208  28 14        JR Z,AL_CMPN       ; first character matches
AL_STEP:
$F20A  7E           LD A,(HL)
$F20B  CD 02 16     CALL SKIPNUM       ; $0E -> skip the 5-byte value
$F20E  20 06        JR NZ,AL_STEP2
$F210  2B           DEC HL             ; SKIPNUM moved HL on by 6
$F211  0B           DEC BC
$F212  0B           DEC BC
$F213  0B           DEC BC
$F214  0B           DEC BC
$F215  0B           DEC BC
AL_STEP2:
$F216  23           INC HL
$F217  0B           DEC BC
$F218  78           LD A,B
$F219  B1           OR C
$F21A  28 C9        JR Z,AL_NEXT       ; end of line
$F21C  18 E8        JR AL_CMP1
AL_CMPN:
$F21E  C5           PUSH BC
$F21F  DD E1        POP IX             ; IX = bytes left in this line
$F221  37           SCF
$F222  E5           PUSH HL
$F223  2A D7 EC     LD HL,(STR0_LEN)
$F226  ED 42        SBC HL,BC          ; does the search string still fit?
$F228  E1           POP HL
$F229  30 BA        JR NC,AL_NEXT
$F22B  E5           PUSH HL
$F22C  ED 4B D7 EC  LD BC,(STR0_LEN)
AL_CMP2:
$F230  1A           LD A,(DE)
$F231  BE           CP (HL)
$F232  13           INC DE
$F233  23           INC HL
$F234  0B           DEC BC
$F235  28 07        JR Z,AL_HIT
$F237  E1           POP HL             ; mismatch - restore and step on
$F238  DD E5        PUSH IX
$F23A  C1           POP BC
$F23B  0B           DEC BC
$F23C  18 C4        JR AL_TEXT
AL_HIT:
$F23E  78           LD A,B
$F23F  B1           OR C
$F240  20 EE        JR NZ,AL_CMP2      ; keep comparing
;   Found.  Swap STR0 for STR1 in place.
$F242  ED 4B D7 EC  LD BC,(STR0_LEN)
$F246  2A FA EC     LD HL,(STR1_LEN)
$F249  A7           AND A
$F24A  ED 42        SBC HL,BC          ; HL = growth (may be negative)
$F24C  C5           PUSH BC
$F24D  E5           PUSH HL
$F24E  44           LD B,H
$F24F  4D           LD C,L
$F250  D4 BB 1F     CALL NC,CHK_SZ     ; growing: is there room?
$F253  E1           POP HL
$F254  C1           POP BC
$F255  E3           EX (SP),HL
$F256  CD 50 17     CALL DELREC        ; remove the old text
$F259  E5           PUSH HL
$F25A  ED 4B FA EC  LD BC,(STR1_LEN)
$F25E  C5           PUSH BC
$F25F  CD BB 12     CALL INSERT        ; open a gap for the new text
$F262  C1           POP BC
$F263  D1           POP DE
$F264  21 FC EC     LD HL,STR1
$F267  ED B0        LDIR
$F269  C1           POP BC
$F26A  D5           PUSH DE
$F26B  2A D5 EC     LD HL,(P_WORK2)    ; fix up "next line" pointer
$F26E  09           ADD HL,BC
$F26F  22 D5 EC     LD (P_WORK2),HL
$F272  A7           AND A
$F273  ED 52        SBC HL,DE
$F275  E5           PUSH HL
$F276  2A D3 EC     LD HL,(P_WORK1)    ; fix up this line's length word
$F279  23           INC HL
$F27A  23           INC HL
$F27B  5E           LD E,(HL)
$F27C  23           INC HL
$F27D  56           LD D,(HL)
$F27E  EB           EX DE,HL
$F27F  09           ADD HL,BC
$F280  EB           EX DE,HL
$F281  72           LD (HL),D
$F282  2B           DEC HL
$F283  73           LD (HL),E
$F284  2B           DEC HL
$F285  2B           DEC HL
$F286  3A D1 EC     LD A,(P_INCR)      ; the LIST flag
$F289  A7           AND A
$F28A  28 04        JR Z,AL_ON
$F28C  CD A1 15     CALL PUT_SR        ; echo the altered line
$F28F  D7           RST $10
AL_ON:
$F290  C1           POP BC
$F291  E1           POP HL
$F292  C3 03 F2     JP AL_SCAN         ; keep searching the same line

;=====================================================================
; EXPAND  ($F295)
; Both search strings may contain a "&&&<number>&&&" escape.  BASIC
; keeps numeric constants as ASCII digits followed by $0E and a 5-byte
; binary value, so a plain text search would never match one.  This
; routine evaluates the digits with the ROM number parser and rewrites
; the escape as digits + $0E + the five bytes off the calculator stack.
;=====================================================================
EXPAND:
$F295  ED 4B D7 EC  LD BC,(STR0_LEN)
$F299  11 D9 EC     LD DE,STR0
$F29C  CD B6 F2     CALL FIND_AMP
$F29F  DC D3 F2     CALL C,DO_AMP
$F2A2  38 F1        JR C,EXPAND        ; another escape in buffer 0?
EXPAND1:
$F2A4  ED 4B FA EC  LD BC,(STR1_LEN)
$F2A8  11 FC EC     LD DE,STR1
$F2AB  CD B6 F2     CALL FIND_AMP
$F2AE  DC D3 F2     CALL C,DO_AMP
$F2B1  38 F1        JR C,EXPAND1
$F2B3  C9           RET
FIND_AMP1:
$F2B4  54           LD D,H
$F2B5  5D           LD E,L
FIND_AMP:
$F2B6  1A           LD A,(DE)
$F2B7  FE 26        CP '&'
$F2B9  28 07        JR Z,FA_RUN
FA_NEXT:
$F2BB  13           INC DE
$F2BC  0B           DEC BC
$F2BD  78           LD A,B
$F2BE  B1           OR C
$F2BF  C8           RET Z              ; NC = no escape found
$F2C0  18 F4        JR FIND_AMP
FA_RUN:
$F2C2  62           LD H,D             ; need three in a row
$F2C3  6B           LD L,E
$F2C4  23           INC HL
$F2C5  7E           LD A,(HL)
$F2C6  FE 26        CP '&'
$F2C8  20 F1        JR NZ,FA_NEXT
$F2CA  23           INC HL
$F2CB  7E           LD A,(HL)
$F2CC  FE 26        CP '&'
$F2CE  20 EB        JR NZ,FA_NEXT
$F2D0  23           INC HL             ; HL -> first char after "&&&"
$F2D1  37           SCF
$F2D2  C9           RET
DO_AMP:
$F2D3  D5           PUSH DE
$F2D4  E5           PUSH HL
$F2D5  E5           PUSH HL
$F2D6  2A 5D 5C     LD HL,(CH_ADD)
$F2D9  E3           EX (SP),HL         ; stack the real CH_ADD
$F2DA  22 5D 5C     LD (CH_ADD),HL     ; parse from inside the string
$F2DD  DF           RST $18            ; GET-CHAR
$F2DE  CD D9 30     CALL DIGITQ
$F2E1  30 0C        JR NC,DA_NUM
$F2E3  FE C4        CP $C4             ; BIN token
$F2E5  28 08        JR Z,DA_NUM
$F2E7  E1           POP HL             ; not a number - leave it alone
$F2E8  22 5D 5C     LD (CH_ADD),HL
$F2EB  E1           POP HL
$F2EC  E1           POP HL
$F2ED  A7           AND A
$F2EE  C9           RET
DA_NUM:
$F2EF  CD 59 30     CALL STKUSN        ; stack the value
$F2F2  DF           RST $18
$F2F3  01 01 00     LD BC,$0001
$F2F6  CD B4 F2     CALL FIND_AMP1     ; closing "&&&"?
$F2F9  E1           POP HL
$F2FA  22 5D 5C     LD (CH_ADD),HL     ; restore CH_ADD
$F2FD  E1           POP HL
$F2FE  38 06        JR C,DA_SUB
$F300  EF           RST $28            ; calculator ...
$F301  DEFB $02                        ;   delete
$F302  DEFB $38                        ;   end-calc
$F303  D1           POP DE
$F304  A7           AND A
$F305  C9           RET
DA_SUB:
$F306  EB           EX DE,HL
$F307  A7           AND A
$F308  ED 52        SBC HL,DE
$F30A  EB           EX DE,HL
$F30B  42           LD B,D
$F30C  4B           LD C,E
$F30D  D1           POP DE
$F30E  ED B0        LDIR               ; close up over the "&&&"
$F310  3E 0E        LD A,$0E           ; BASIC's number marker
$F312  12           LD (DE),A
$F313  13           INC DE
$F314  2A 65 5C     LD HL,(STKEND)     ; take the 5-byte value back
$F317  01 05 00     LD BC,$0005
$F31A  A7           AND A
$F31B  ED 42        SBC HL,BC
$F31D  22 65 5C     LD (STKEND),HL
$F320  ED B0        LDIR
$F322  37           SCF
$F323  C9           RET

;=====================================================================
; COMMAND M  —  Move Lines  ($F324)
;=====================================================================
CMD_M:
$F324  CD 8F ED     CALL SCRIPT_CLS
$F327  DEFB $0D
$F328  DEFB $CA                        ; MSG 10 "MOVE"
$F329  DEFB $19                        ; resume below
$F32A  CD 39 F3     CALL ASK_BLOCK     ; the four standard prompts
$F32D  CD 44 F5     CALL CHK_ROOM      ; will it fit at the destination?
$F330  CD 6D F3     CALL RENUM_BLK     ; give the block its new numbers
$F333  CD BB F3     CALL FIX_REFS      ; repoint GO TO / GO SUB / ...
$F336  C3 19 F5     JP CMD_Z           ; sort the lines into place

;=====================================================================
; ASK_BLOCK  ($F339)  the prompt script shared by M and R
;=====================================================================
ASK_BLOCK:
$F339  CD 92 ED     CALL SCRIPT_FE
$F33C  DEFB $CD,$CE,$CF              ; " BLOCK OF PROGRAM"
$F33F  DEFB $FE
$F340  DEFB $C1,$80                  ; " START ADDRESS: "
$F342  DEFB $13,$1A,$14              ;   -> P_START
$F345  DEFB $FE
$F346  DEFB $C3,$C4,$80              ; " 1st UNAFFECTED ADDRESS: "
$F349  DEFB $13,$1B,$14              ;   -> P_FINISH
$F34C  DEFB $FE
$F34D  DEFB $85                      ; " DESTINATION: "
$F34E  DEFB $13,$1C,$14              ;   -> P_DEST
$F351  DEFB $FE
$F352  DEFB $18,$17                  ; start<=finish, dest/finish<=9996
$F354  DEFB $86                      ; " INCREMENT: "
$F355  DEFB $13,$1D,$14              ;   -> P_INCR
$F358  DEFB $15,$16                  ; non-zero, <=100
$F35A  DEFB $00                      ; "EXECUTE?"
$F35B  C9           RET

;=====================================================================
; COMMAND R  —  Renumber  ($F35C)
;=====================================================================
CMD_R:
$F35C  CD 8F ED     CALL SCRIPT_CLS
$F35F  DEFB $0D
$F360  DEFB $CC                        ; MSG 12 "RENUMBER"
$F361  DEFB $19
$F362  CD 39 F3     CALL ASK_BLOCK
$F365  CD 6D F3     CALL RENUM_BLK
$F368  CD F6 F4     CALL CHK_ORDER     ; would the result overlap?
$F36B  18 4E        JR FIX_REFS

;=====================================================================
; RENUM_BLK  ($F36D)
; Pass 1.  Every line's 2-byte LENGTH field is used as scratch to hold
; the line's OLD number while the number field takes the new one.  The
; length field can be sacrificed because the walker (NEXT_LINE) finds
; line ends by scanning for $0D, not by using the length.  Pass 3
; (REBUILD, $F452) puts the lengths back.
;=====================================================================
RENUM_BLK:
$F36D  2A CB EC     LD HL,(P_START)
$F370  CD D6 16     CALL FIND_L
$F373  E5           PUSH HL            ; first line of the block
$F374  2A CD EC     LD HL,(P_FINISH)
$F377  CD D6 16     CALL FIND_L
$F37A  13           INC DE
$F37B  ED 53 D3 EC  LD (P_WORK1),DE    ; one past the end of the block
$F37F  ED 5B CF EC  LD DE,(P_DEST)     ; the first new line number
$F383  2A 53 5C     LD HL,(PROG)
;   Lines before the block: copy the old number into the length field
;   so that pass 2 can look every old number up.
RB_PRE:
$F386  CD CF F4     CALL IN_PROG
$F389  30 0D        JR NC,RB_BLOCK
$F38B  46           LD B,(HL)
$F38C  23           INC HL
$F38D  4E           LD C,(HL)
$F38E  23           INC HL
$F38F  71           LD (HL),C
$F390  23           INC HL
$F391  70           LD (HL),B
$F392  23           INC HL
$F393  CD C4 F4     CALL NEXT_LINE
$F396  18 EE        JR RB_PRE
RB_BLOCK:
$F398  E1           POP HL
;   Lines inside the block: swap in the new number, stash the old one.
RENUM_RUN:
$F399  E5           PUSH HL
$F39A  D5           PUSH DE
$F39B  ED 5B D3 EC  LD DE,(P_WORK1)
$F39F  A7           AND A
$F3A0  ED 52        SBC HL,DE
$F3A2  D1           POP DE
$F3A3  E1           POP HL
$F3A4  D0           RET NC             ; past the end of the block
$F3A5  46           LD B,(HL)          ; B = old high byte
$F3A6  72           LD (HL),D          ;     new high byte
$F3A7  23           INC HL
$F3A8  4E           LD C,(HL)          ; C = old low byte
$F3A9  73           LD (HL),E          ;     new low byte
$F3AA  23           INC HL
$F3AB  71           LD (HL),C          ; length field := old number
$F3AC  23           INC HL
$F3AD  70           LD (HL),B
$F3AE  23           INC HL
$F3AF  E5           PUSH HL
$F3B0  2A D1 EC     LD HL,(P_INCR)
$F3B3  19           ADD HL,DE
$F3B4  EB           EX DE,HL           ; next new number
$F3B5  E1           POP HL
$F3B6  CD C4 F4     CALL NEXT_LINE
$F3B9  18 DE        JR RENUM_RUN

;=====================================================================
; FIX_REFS  ($F3BB)
; Pass 2.  Find every line-number reference (GO TO, GO SUB, RUN, LIST,
; LLIST, RESTORE, LINE) and rewrite the digits to the new number.
;=====================================================================
FIX_REFS:
$F3BB  3E FF        LD A,$FF
$F3BD  CD 30 12     CALL SELECT        ; stream $FF - edit-buffer channel
$F3C0  2A 53 5C     LD HL,(PROG)
$F3C3  23           INC HL             ; skip the first line's header
$F3C4  23           INC HL
$F3C5  23           INC HL
$F3C6  23           INC HL
FR_LOOP:
$F3C7  CD 6E F4     CALL FIND_KEYWD
$F3CA  D2 52 F4     JP NC,REBUILD      ; end of program - pass 3
$F3CD  54           LD D,H             ; DE = first digit
$F3CE  5D           LD E,L
$F3CF  06 00        LD B,$00           ; B = digit count
FR_DIGITS:
$F3D1  04           INC B
$F3D2  23           INC HL
$F3D3  7E           LD A,(HL)
$F3D4  FE 2E        CP '.'
$F3D6  20 03        JR NZ,FR_TEST      ; a decimal point - not a line number
FR_SKIP:
$F3D8  EB           EX DE,HL
$F3D9  18 EC        JR FR_LOOP
FR_TEST:
$F3DB  FE 0E        CP $0E             ; number marker ends the digits
$F3DD  20 F2        JR NZ,FR_DIGITS
$F3DF  23           INC HL             ; step over the 5-byte value
$F3E0  23           INC HL
$F3E1  23           INC HL
$F3E2  23           INC HL
$F3E3  23           INC HL
$F3E4  23           INC HL
$F3E5  7E           LD A,(HL)
$F3E6  FE 3A        CP ':'
$F3E8  28 04        JR Z,FR_OK
$F3EA  FE 0D        CP $0D
$F3EC  20 EA        JR NZ,FR_SKIP      ; something follows - an expression
FR_OK:
$F3EE  78           LD A,B
$F3EF  FE 05        CP $05
$F3F1  30 E5        JR NC,FR_SKIP      ; more than 4 digits - not a line no.
$F3F3  D5           PUSH DE
$F3F4  21 00 00     LD HL,$0000
FR_VAL:
$F3F7  1A           LD A,(DE)          ; HL = value of the digit string
$F3F8  FE 0E        CP $0E
$F3FA  28 0F        JR Z,FR_FIND
$F3FC  D6 30        SUB '0'
$F3FE  4D           LD C,L
$F3FF  44           LD B,H
$F400  29           ADD HL,HL
$F401  29           ADD HL,HL
$F402  09           ADD HL,BC
$F403  29           ADD HL,HL          ; *10
$F404  4F           LD C,A
$F405  06 00        LD B,$00
$F407  09           ADD HL,BC
$F408  13           INC DE
$F409  18 EC        JR FR_VAL
;   Look the old number up in the stashed length fields.
FR_FIND:
$F40B  4D           LD C,L
$F40C  44           LD B,H
$F40D  2A 53 5C     LD HL,(PROG)
FR_SEEK:
$F410  23           INC HL
$F411  23           INC HL             ; HL -> stashed old number
$F412  CD CF F4     CALL IN_PROG
$F415  38 03        JR C,FR_CMP
$F417  E1           POP HL             ; not found - leave the text alone
FR_ON:
$F418  18 AD        JR FR_LOOP
FR_CMP:
$F41A  23           INC HL
$F41B  7E           LD A,(HL)
$F41C  B8           CP B
$F41D  30 07        JR NC,FR_MAYBE
FR_NEXT:
$F41F  23           INC HL
$F420  23           INC HL
$F421  CD C4 F4     CALL NEXT_LINE
$F424  18 EA        JR FR_SEEK
FR_MAYBE:
$F426  2B           DEC HL
$F427  20 04        JR NZ,FR_GOT
$F429  7E           LD A,(HL)
$F42A  B9           CP C
$F42B  38 F2        JR C,FR_NEXT
FR_GOT:
$F42D  2B           DEC HL
$F42E  4E           LD C,(HL)          ; BC = the NEW number
$F42F  2B           DEC HL
$F430  46           LD B,(HL)
$F431  E1           POP HL             ; HL -> the old digits
$F432  E5           PUSH HL
$F433  EB           EX DE,HL
$F434  C5           PUSH BC
$F435  CD 4D 17     CALL DEL_DE        ; delete the old digit string
$F438  C1           POP BC
$F439  22 5B 5C     LD (KCUR),HL       ; stream $FF writes at KCUR
$F43C  C5           PUSH BC
$F43D  CD 88 17     CALL PUT_BC        ; write the new digits
$F440  C1           POP BC
$F441  2A 5B 5C     LD HL,(KCUR)
$F444  AF           XOR A
$F445  23           INC HL             ; rebuild the hidden 5-byte value
$F446  77           LD (HL),A
$F447  23           INC HL
$F448  77           LD (HL),A
$F449  23           INC HL
$F44A  71           LD (HL),C
$F44B  23           INC HL
$F44C  70           LD (HL),B
$F44D  23           INC HL
$F44E  77           LD (HL),A
$F44F  E1           POP HL
$F450  18 C6        JR FR_ON

;=====================================================================
; REBUILD  ($F452)  = COMMAND Y "Uncorrupt"
; Pass 3.  Recompute every line's length word by measuring the gap to
; the next line.  Run on its own it repairs a program whose length
; fields have been corrupted.
;=====================================================================
CMD_Y:
REBUILD:
$F452  2A 53 5C     LD HL,(PROG)
RB_LOOP:
$F455  23           INC HL
$F456  23           INC HL
$F457  CD CF F4     CALL IN_PROG
$F45A  D0           RET NC
$F45B  54           LD D,H
$F45C  5D           LD E,L
$F45D  23           INC HL
$F45E  23           INC HL
$F45F  CD C4 F4     CALL NEXT_LINE
$F462  E5           PUSH HL
$F463  37           SCF
$F464  ED 52        SBC HL,DE
$F466  2B           DEC HL
$F467  EB           EX DE,HL
$F468  73           LD (HL),E
$F469  23           INC HL
$F46A  72           LD (HL),D
$F46B  E1           POP HL
$F46C  18 E7        JR RB_LOOP

;=====================================================================
; FIND_KEYWD  ($F46E)
; Scan the program from HL for a keyword that takes a line number,
; followed by a digit.  Returns CY with HL on the first digit; NC at
; the end of the program.  Skips REM bodies, quoted strings and the
; 5-byte binary form of every constant.
;=====================================================================
FIND_KEYWD:
$F46E  7E           LD A,(HL)
$F46F  CD CF F4     CALL IN_PROG
$F472  D0           RET NC
$F473  FE EA        CP $EA             ; REM
$F475  20 0D        JR NZ,FK_QUOTE
FK_REM:
$F477  23           INC HL             ; skip to end of line
$F478  7E           LD A,(HL)
$F479  FE 0D        CP $0D
$F47B  20 FA        JR NZ,FK_REM
FK_HDR:
$F47D  23           INC HL             ; step over the next line's header
$F47E  23           INC HL
$F47F  23           INC HL
$F480  23           INC HL
$F481  23           INC HL
$F482  18 EA        JR FIND_KEYWD
FK_QUOTE:
$F484  FE 22        CP '"'
$F486  20 09        JR NZ,FK_TOKEN
FK_STR:
$F488  23           INC HL
$F489  7E           LD A,(HL)
$F48A  FE 22        CP '"'
$F48C  20 FA        JR NZ,FK_STR
$F48E  23           INC HL
$F48F  18 DD        JR FIND_KEYWD
FK_TOKEN:
$F491  FE 0D        CP $0D
$F493  28 E8        JR Z,FK_HDR
$F495  CD 02 16     CALL SKIPNUM
$F498  28 D4        JR Z,FIND_KEYWD
$F49A  FE ED        CP $ED             ; GO SUB
$F49C  28 1B        JR Z,FK_DIGIT
$F49E  FE EC        CP $EC             ; GO TO
$F4A0  28 17        JR Z,FK_DIGIT
$F4A2  FE F7        CP $F7             ; RUN
$F4A4  28 13        JR Z,FK_DIGIT
$F4A6  FE F0        CP $F0             ; LIST
$F4A8  28 0F        JR Z,FK_DIGIT
$F4AA  FE E5        CP $E5             ; RESTORE
$F4AC  28 0B        JR Z,FK_DIGIT
$F4AE  FE E1        CP $E1             ; LLIST
$F4B0  28 07        JR Z,FK_DIGIT
$F4B2  FE CA        CP $CA             ; LINE
$F4B4  28 03        JR Z,FK_DIGIT
$F4B6  23           INC HL
$F4B7  18 B5        JR FIND_KEYWD
FK_DIGIT:
$F4B9  23           INC HL
$F4BA  7E           LD A,(HL)
$F4BB  FE 30        CP '0'
$F4BD  38 AF        JR C,FIND_KEYWD    ; keyword not followed by a digit
$F4BF  FE 3A        CP '9'+1
$F4C1  30 AB        JR NC,FIND_KEYWD
$F4C3  C9           RET                ; CY set, HL on the first digit

;=====================================================================
; NEXT_LINE  ($F4C4)  advance HL past the next $0D, skipping the
; 5-byte binary form of any embedded constant.
;=====================================================================
NEXT_LINE:
$F4C4  7E           LD A,(HL)
NL_LOOP:
$F4C5  CD 02 16     CALL SKIPNUM
$F4C8  28 FB        JR Z,NL_LOOP
$F4CA  FE 0D        CP $0D
$F4CC  23           INC HL
$F4CD  20 F5        JR NZ,NEXT_LINE

;=====================================================================
; IN_PROG  ($F4CF)  CY if HL is still below VARS (i.e. inside the
; program area).  Preserves everything.
;=====================================================================
IN_PROG:
$F4CF  E5           PUSH HL
$F4D0  D5           PUSH DE
$F4D1  ED 5B 4B 5C  LD DE,(VARS)
$F4D5  A7           AND A
$F4D6  ED 52        SBC HL,DE
$F4D8  D1           POP DE
$F4D9  E1           POP HL
$F4DA  C9           RET

;=====================================================================
; FIND_UNSORTED  ($F4DB)
; Walk the program looking for a line whose number is not greater than
; its predecessor's.  C = $00 tests "prev >= cur", C = $FF tests
; "prev > cur".  Returns NC with HL on the offender, CY if the whole
; program is in ascending order.
;=====================================================================
FIND_UNSORTED:
$F4DB  2A 53 5C     LD HL,(PROG)
$F4DE  11 00 00     LD DE,$0000        ; DE = previous line number
FU_LOOP:
$F4E1  D5           PUSH DE
$F4E2  56           LD D,(HL)
$F4E3  23           INC HL
$F4E4  5E           LD E,(HL)
$F4E5  23           INC HL             ; DE = this line number
$F4E6  E3           EX (SP),HL
$F4E7  79           LD A,C
$F4E8  87           ADD A,A            ; carry := bit 7 of C
$F4E9  ED 52        SBC HL,DE
$F4EB  E1           POP HL
$F4EC  D0           RET NC             ; out of order - HL -> length word
$F4ED  23           INC HL
$F4EE  23           INC HL
$F4EF  CD C4 F4     CALL NEXT_LINE
$F4F2  38 ED        JR C,FU_LOOP
$F4F4  37           SCF
$F4F5  C9           RET                ; all in order

;=====================================================================
; CHK_ORDER  ($F4F6)  used by R
; If the renumber left any line out of order, undo it (swap the old
; numbers back out of the length fields), rebuild the lengths and
; report "Lines would Overlap".
;=====================================================================
CHK_ORDER:
$F4F6  AF           XOR A
$F4F7  4F           LD C,A
$F4F8  CD DB F4     CALL FIND_UNSORTED
$F4FB  D8           RET C              ; fine - carry on
$F4FC  2A 53 5C     LD HL,(PROG)
CO_UNDO:
$F4FF  23           INC HL
$F500  23           INC HL
$F501  4E           LD C,(HL)          ; old number out of the length field
$F502  23           INC HL
$F503  46           LD B,(HL)
$F504  2B           DEC HL
$F505  2B           DEC HL
$F506  2B           DEC HL
$F507  70           LD (HL),B          ; put it back in the number field
$F508  23           INC HL
$F509  71           LD (HL),C
$F50A  23           INC HL
$F50B  23           INC HL
$F50C  23           INC HL
$F50D  CD C4 F4     CALL NEXT_LINE
$F510  38 ED        JR C,CO_UNDO
$F512  CD 52 F4     CALL REBUILD
$F515  CD 4A EF     CALL REPORT
$F518  05           DEFB 5             ; "Lines would Overlap"

;=====================================================================
; COMMAND Z  —  Line Sort  ($F519)
; Repeatedly take the first out-of-order line and move it, whole, to
; where FIND_L says it belongs.  Also the tail of M (Move Lines).
;=====================================================================
CMD_Z:
$F519  0E FF        LD C,$FF
$F51B  CD DB F4     CALL FIND_UNSORTED
$F51E  D8           RET C              ; sorted
$F51F  4E           LD C,(HL)          ; BC = line body length
$F520  23           INC HL
$F521  46           LD B,(HL)
$F522  2B           DEC HL
$F523  2B           DEC HL
$F524  2B           DEC HL             ; HL -> start of the line
$F525  03           INC BC             ; + the 4 header bytes
$F526  03           INC BC
$F527  03           INC BC
$F528  03           INC BC
$F529  E5           PUSH HL
$F52A  C5           PUSH BC
$F52B  EB           EX DE,HL
$F52C  CD D6 16     CALL FIND_L        ; where does it belong?
$F52F  C1           POP BC
$F530  E5           PUSH HL
$F531  C5           PUSH BC
$F532  CD BB 12     CALL INSERT        ; open a gap there
$F535  C1           POP BC
$F536  D1           POP DE
$F537  E1           POP HL
$F538  09           ADD HL,BC          ; the source moved up by BC
$F539  E5           PUSH HL
$F53A  C5           PUSH BC
$F53B  ED B0        LDIR               ; copy the line into the gap
$F53D  C1           POP BC
$F53E  E1           POP HL
$F53F  CD 50 17     CALL DELREC        ; remove the original
$F542  18 D5        JR CMD_Z

;=====================================================================
; CHK_ROOM  ($F544)  used by M and C
; Count the lines in the block, multiply by the increment and make
; sure the renumbered block will fit in the gap at the destination.
;=====================================================================
CHK_ROOM:
$F544  2A CB EC     LD HL,(P_START)
$F547  CD D6 16     CALL FIND_L
$F54A  11 00 00     LD DE,$0000        ; DE = line count
CR_COUNT:
$F54D  46           LD B,(HL)
$F54E  23           INC HL
$F54F  4E           LD C,(HL)
$F550  23           INC HL
$F551  E5           PUSH HL
$F552  2A CD EC     LD HL,(P_FINISH)
$F555  A7           AND A
$F556  ED 42        SBC HL,BC
$F558  E1           POP HL
$F559  38 0A        JR C,CR_SIZE
$F55B  28 08        JR Z,CR_SIZE
$F55D  13           INC DE
$F55E  23           INC HL
$F55F  23           INC HL
$F560  CD C4 F4     CALL NEXT_LINE
$F563  18 E8        JR CR_COUNT
CR_SIZE:
$F565  2A D1 EC     LD HL,(P_INCR)
$F568  CD 68 34     CALL MULT          ; HL = lines * increment
$F56B  38 16        JR C,CR_BAD        ; overflow
CR_FIT:
$F56D  E5           PUSH HL
$F56E  2A CF EC     LD HL,(P_DEST)
$F571  E5           PUSH HL
$F572  CD D6 16     CALL FIND_L
$F575  28 0C        JR Z,CR_BAD        ; that line already exists
$F577  56           LD D,(HL)
$F578  23           INC HL
$F579  5E           LD E,(HL)          ; DE = the next existing number
$F57A  E1           POP HL
$F57B  EB           EX DE,HL
$F57C  A7           AND A
$F57D  ED 52        SBC HL,DE          ; gap available
$F57F  C1           POP BC
$F580  ED 42        SBC HL,BC          ; gap needed
$F582  D0           RET NC
CR_BAD:
$F583  CD 4A EF     CALL REPORT
$F586  06           DEFB 6             ; "No Room at Destination"

;=====================================================================
; CHK_OUTSIDE  ($F587)  used by C
; Refuse a destination that falls inside the source block.
;=====================================================================
CHK_OUTSIDE:
$F587  2A CB EC     LD HL,(P_START)
$F58A  ED 5B CF EC  LD DE,(P_DEST)
$F58E  A7           AND A
$F58F  ED 52        SBC HL,DE
$F591  D0           RET NC             ; dest below the block - fine
$F592  2A CD EC     LD HL,(P_FINISH)
$F595  ED 52        SBC HL,DE
$F597  D8           RET C              ; dest above the block - fine
$F598  18 E9        JR CR_BAD

;=====================================================================
; COMMAND C  —  Copy Lines  ($F59A)
; Copies the block, then renumbers *only the copy* by temporarily
; faking PROG and VARS around it so RENUM_RUN/FIX_REFS see nothing
; else.
;=====================================================================
CMD_C:
$F59A  CD 8F ED     CALL SCRIPT_CLS
$F59D  DEFB $0D
$F59E  DEFB $CB                        ; MSG 11 "COPY"
$F59F  DEFB $19
$F5A0  CD 39 F3     CALL ASK_BLOCK
$F5A3  CD 87 F5     CALL CHK_OUTSIDE
$F5A6  CD 44 F5     CALL CHK_ROOM
$F5A9  2A CB EC     LD HL,(P_START)
$F5AC  CD D6 16     CALL FIND_L
$F5AF  E5           PUSH HL
$F5B0  2A CD EC     LD HL,(P_FINISH)
$F5B3  CD D6 16     CALL FIND_L
$F5B6  D1           POP DE
$F5B7  ED 52        SBC HL,DE          ; HL = block length in bytes
$F5B9  28 51        JR Z,CC_ZERO
$F5BB  E5           PUSH HL
$F5BC  D5           PUSH DE
$F5BD  2A CF EC     LD HL,(P_DEST)
$F5C0  CD D6 16     CALL FIND_L
$F5C3  D1           POP DE
$F5C4  ED 52        SBC HL,DE
$F5C6  19           ADD HL,DE
$F5C7  C1           POP BC             ; BC = block length
$F5C8  F5           PUSH AF
$F5C9  C5           PUSH BC
$F5CA  D5           PUSH DE
$F5CB  E5           PUSH HL
$F5CC  CD BB 12     CALL INSERT        ; open the gap
$F5CF  D1           POP DE
$F5D0  E1           POP HL
$F5D1  C1           POP BC
$F5D2  F1           POP AF
$F5D3  30 01        JR NC,CC_COPY
$F5D5  09           ADD HL,BC          ; source moved up
CC_COPY:
$F5D6  E5           PUSH HL
$F5D7  2A 53 5C     LD HL,(PROG)
$F5DA  E3           EX (SP),HL         ; save the real PROG
$F5DB  ED 53 53 5C  LD (PROG),DE       ; PROG := the copy
$F5DF  ED B0        LDIR
$F5E1  2A 4B 5C     LD HL,(VARS)
$F5E4  E5           PUSH HL            ; save the real VARS
$F5E5  D5           PUSH DE
$F5E6  ED 53 4B 5C  LD (VARS),DE       ; VARS := end of the copy
$F5EA  ED 53 D3 EC  LD (P_WORK1),DE
$F5EE  ED 5B CF EC  LD DE,(P_DEST)
$F5F2  2A 53 5C     LD HL,(PROG)
$F5F5  CD 99 F3     CALL RENUM_RUN     ; number the copy
$F5F8  CD BB F3     CALL FIX_REFS      ; and fix its internal jumps
$F5FB  D1           POP DE
$F5FC  2A 4B 5C     LD HL,(VARS)
$F5FF  A7           AND A
$F600  ED 52        SBC HL,DE
$F602  C1           POP BC             ; the real VARS
$F603  09           ADD HL,BC
$F604  22 4B 5C     LD (VARS),HL       ; restore, grown by the copy
$F607  E1           POP HL
$F608  22 53 5C     LD (PROG),HL       ; restore PROG
$F60B  C9           RET
CC_ZERO:
$F60C  CD 4A EF     CALL REPORT
$F60F  07           DEFB 7             ; "Zero Block"

;=====================================================================
; COMMAND D  —  Delete lines  ($F610)
;=====================================================================
CMD_D:
$F610  CD 8F ED     CALL SCRIPT_CLS
$F613  DEFB $0D
$F614  DEFB $C9,$CD,$CE,$CF            ; " DELETE BLOCK OF PROGRAM"
$F618  DEFB $FE
$F619  DEFB $C1,$80,$13,$1A,$14        ; " START ADDRESS: "  -> P_START
$F61E  DEFB $FE
$F61F  DEFB $C2,$80,$13,$1B,$1C,$14    ; " FINISH ADDRESS: " -> P_FINISH,P_DEST
$F625  DEFB $18,$17                    ; validate
$F627  DEFB $00                        ; "EXECUTE?"
$F628  2A CB EC     LD HL,(P_START)
$F62B  CD D6 16     CALL FIND_L
$F62E  E5           PUSH HL
$F62F  2A CD EC     LD HL,(P_FINISH)
$F632  23           INC HL             ; the range is inclusive
$F633  CD D6 16     CALL FIND_L
$F636  D1           POP DE
$F637  ED 52        SBC HL,DE
$F639  28 D1        JR Z,CC_ZERO       ; nothing in range
$F63B  19           ADD HL,DE
$F63C  C3 4D 17     JP DEL_DE

;=====================================================================
; COMMAND E  —  REM Create  ($F63F)
; Builds a REM line of a chosen length filled with a chosen character:
; the standard way of reserving space inside a BASIC program for
; machine code.
;=====================================================================
CMD_E:
$F63F  CD 8F ED     CALL SCRIPT_CLS
$F642  DEFB $0D
$F643  DEFB $E3,$DB,$C7                ; " CREATE REM LINE"
$F646  DEFB $FE
$F647  DEFB $C7,$88                    ; " LINE NUMBER: "
$F649  DEFB $13,$1B,$1C,$14            ;   -> P_FINISH and P_DEST
$F64D  DEFB $17                        ;   <= 9996
$F64E  DEFB $FE
$F64F  DEFB $AC                        ; " LENGTH: "
$F650  DEFB $13,$1D,$14,$15            ;   -> P_INCR, must be non-zero
$F654  DEFB $FE
$F655  DEFB $A4                        ; " SYMBOL: "
$F656  DEFB $11,$00,$12,$00            ;   -> string buffer 0
$F65A  DEFB $00                        ; "EXECUTE?"
$F65B  ED 4B D1 EC  LD BC,(P_INCR)     ; requested body length
$F65F  03           INC BC             ; + REM token
$F660  03           INC BC             ; + CR
$F661  C5           PUSH BC
$F662  03           INC BC             ; + the 4 header bytes
$F663  03           INC BC
$F664  03           INC BC
$F665  03           INC BC
$F666  21 06 00     LD HL,$0006
$F669  ED 42        SBC HL,BC
$F66B  D2 E2 ED     JP NC,$EDE2        ; too short -> "Zero not Allowed"
$F66E  2A CF EC     LD HL,(P_DEST)
$F671  E5           PUSH HL
$F672  C5           PUSH BC
$F673  CD D6 16     CALL FIND_L
$F676  C1           POP BC
$F677  CA 83 F5     JP Z,CR_BAD        ; that line already exists
$F67A  CD BB 12     CALL INSERT
$F67D  23           INC HL
$F67E  D1           POP DE
$F67F  72           LD (HL),D          ; line number, big-endian
$F680  23           INC HL
$F681  73           LD (HL),E
$F682  23           INC HL
$F683  C1           POP BC
$F684  71           LD (HL),C          ; body length, little-endian
$F685  23           INC HL
$F686  70           LD (HL),B
$F687  23           INC HL
$F688  36 EA        LD (HL),$EA        ; REM
$F68A  ED 5B D9 EC  LD DE,(STR0)       ; E = the fill character
$F68E  0B           DEC BC             ; discount the REM token ...
$F68F  0B           DEC BC             ; ... and the closing CR
CE_FILL:
$F690  0B           DEC BC
$F691  23           INC HL
$F692  73           LD (HL),E
$F693  78           LD A,B
$F694  B1           OR C
$F695  20 F9        JR NZ,CE_FILL
$F697  23           INC HL
$F698  36 0D        LD (HL),$0D
$F69A  C9           RET

;---------------------------------------------------------------------
; $F69B-$F6AF   21 spare bytes (all $00)
;---------------------------------------------------------------------
$F69B  DEFS 21,$00

;=====================================================================
; COMMAND U  —  UDGs to DATA  ($F6B0)
; Sets START/FINISH to cover n user-defined graphics (8 bytes each)
; and then falls through into the B command's generator.
;=====================================================================
CMD_U:
$F6B0  CD 8F ED     CALL SCRIPT_CLS
$F6B3  DEFB $0D
$F6B4  DEFB $D2,$D3,$D4                ; " UDGs TO DATA"
$F6B7  DEFB $FE
$F6B8  DEFB $C8,$CE,$92                ; " NUMBER OF UDGs: "
$F6BB  DEFB $13,$1F,$1D,$14,$15        ;   -> P_WORK2 and P_INCR
$F6C0  DEFB $FE
$F6C1  DEFB $C3,$C7,$88                ; " 1st LINE NUMBER: "
$F6C4  DEFB $13,$1B,$1C,$14            ;   -> P_FINISH and P_DEST
$F6C8  DEFB $17,$16                    ; validate
$F6CA  DEFB $19                        ; resume below
$F6CB  2A D5 EC     LD HL,(P_WORK2)
$F6CE  3E 15        LD A,21            ; there are only 21 UDGs
$F6D0  95           SUB L
$F6D1  DA 70 EE     JP C,$EE70         ; more than 21 -> "Range Error"
$F6D4  21 08 00     LD HL,$0008
$F6D7  22 D1 EC     LD (P_INCR),HL     ; 8 bytes per DATA line
$F6DA  2A 7B 5C     LD HL,(UDG)
$F6DD  22 CB EC     LD (P_START),HL
$F6E0  EB           EX DE,HL
$F6E1  2A D5 EC     LD HL,(P_WORK2)
$F6E4  29           ADD HL,HL          ; *8
$F6E5  29           ADD HL,HL
$F6E6  29           ADD HL,HL
$F6E7  19           ADD HL,DE
$F6E8  2B           DEC HL
$F6E9  22 CD EC     LD (P_FINISH),HL
$F6EC  18 32        JR BD_ASK          ; join the B command

;=====================================================================
; COMMAND B  —  Bytes to DATA  ($F6EE)
; Turns a block of memory into a series of DATA lines, either decimal
; or as quoted hex pairs.
;=====================================================================
CMD_B:
$F6EE  CD 8F ED     CALL SCRIPT_CLS
$F6F1  DEFB $0D
$F6F2  DEFB $D0,$D1,$D3,$D4            ; " MACHINE CODE TO DATA"
$F6F6  DEFB $FE
$F6F7  DEFB $C1,$80,$13,$1A,$14        ; " START ADDRESS: "  -> P_START
$F6FC  DEFB $FE
$F6FD  DEFB $C2,$80,$13,$1B,$14        ; " FINISH ADDRESS: " -> P_FINISH
$F702  DEFB $FE
$F703  DEFB $18                        ; start <= finish
$F704  DEFB $C3,$C7,$88                ; " 1st LINE NUMBER: "
$F707  DEFB $13,$1C,$14                ;   -> P_DEST
$F70A  DEFB $FE
$F70B  DEFB $E5,$E6,$87                ; " BYTES PER LINE: "
$F70E  DEFB $13,$1D,$14                ;   -> P_INCR
$F711  DEFB $15,$16                    ; non-zero, <= 100
$F713  DEFB $19                        ; resume below
$F714  2A CF EC     LD HL,(P_DEST)
$F717  11 0C 27     LD DE,9996
$F71A  A7           AND A
$F71B  ED 52        SBC HL,DE
$F71D  D2 96 EE     JP NC,V_BAD        ; "Invalid Line Number"
BD_ASK:
$F720  CD 92 ED     CALL SCRIPT_FE
$F723  DEFB $FE
$F724  DEFB $DF                        ; MSG 31 "DEC"
$F725  DEFM " OR"
$F728  DEFB $A0                        ; MSG 32 "HEX" + ": "
$F729  DEFB $11,$00,$12,$00            ; answer -> string buffer 0
$F72D  DEFB $00                        ; "EXECUTE?"
;   How many DATA lines will there be?
$F72E  2A CD EC     LD HL,(P_FINISH)
$F731  23           INC HL
$F732  ED 5B CB EC  LD DE,(P_START)
$F736  AF           XOR A
$F737  ED 52        SBC HL,DE          ; HL = byte count
$F739  ED 5B D1 EC  LD DE,(P_INCR)     ; bytes per line
$F73D  47           LD B,A
$F73E  4F           LD C,A
BD_COUNT:
$F73F  03           INC BC
$F740  ED 52        SBC HL,DE
$F742  30 FB        JR NC,BD_COUNT
$F744  60           LD H,B
$F745  69           LD L,C             ; HL = number of DATA lines
$F746  CD 6D F5     CALL CR_FIT        ; is there room at the destination?
;   Work out the worst-case text length of one DATA line.
$F749  3A D9 EC     LD A,(STR0)
$F74C  E6 DF        AND $DF
$F74E  2A D1 EC     LD HL,(P_INCR)
$F751  FE 44        CP 'D'
$F753  28 0F        JR Z,BD_DEC
$F755  FE 48        CP 'H'
$F757  28 04        JR Z,BD_HEX
$F759  CD 4A EF     CALL REPORT
$F75C  01           DEFB 1             ; "Range Error"
BD_HEX:
$F75D  11 13 00     LD DE,$0013
$F760  29           ADD HL,HL          ; 2n + 19
$F761  19           ADD HL,DE
$F762  18 0E        JR BD_SIZE
BD_DEC:
$F764  2A D1 EC     LD HL,(P_INCR)
$F767  4D           LD C,L
$F768  44           LD B,H
$F769  29           ADD HL,HL          ; 10n + 5
$F76A  29           ADD HL,HL
$F76B  09           ADD HL,BC
$F76C  29           ADD HL,HL
$F76D  23           INC HL
$F76E  23           INC HL
$F76F  23           INC HL
$F770  23           INC HL
$F771  23           INC HL
BD_SIZE:
$F772  22 D3 EC     LD (P_WORK1),HL    ; bytes needed per line
$F775  2A CF EC     LD HL,(P_DEST)
$F778  CD D6 16     CALL FIND_L
$F77B  22 5B 5C     LD (KCUR),HL       ; stream $FF appends at KCUR
$F77E  3E FF        LD A,$FF
$F780  CD 30 12     CALL SELECT
$F783  ED 5B CB EC  LD DE,(P_START)    ; DE walks the source bytes
BD_LOOP:
$F787  2A CD EC     LD HL,(P_FINISH)
$F78A  A7           AND A
$F78B  ED 52        SBC HL,DE
$F78D  D8           RET C              ; all done
$F78E  CD 9D F7     CALL BD_LINE
$F791  7A           LD A,D
$F792  B3           OR E
$F793  C8           RET Z              ; wrapped past $FFFF
$F794  2A CF EC     LD HL,(P_DEST)
$F797  23           INC HL
$F798  22 CF EC     LD (P_DEST),HL     ; next line number
$F79B  18 EA        JR BD_LOOP

;---------------------------------------------------------------------
; BD_LINE  ($F79D)  emit one complete DATA line at KCUR
;---------------------------------------------------------------------
BD_LINE:
$F79D  ED 4B D3 EC  LD BC,(P_WORK1)
$F7A1  D5           PUSH DE
$F7A2  CD BB 1F     CALL CHK_SZ        ; error 4 if RAM is short
$F7A5  D1           POP DE
$F7A6  2A 5B 5C     LD HL,(KCUR)
$F7A9  E5           PUSH HL            ; remember the line start
$F7AA  2A CF EC     LD HL,(P_DEST)
$F7AD  7C           LD A,H
$F7AE  D7           RST $10            ; line number, big-endian
$F7AF  7D           LD A,L
$F7B0  D7           RST $10
$F7B1  D7           RST $10            ; two placeholder length bytes
$F7B2  D7           RST $10
$F7B3  3E E4        LD A,$E4           ; DATA
$F7B5  D7           RST $10
$F7B6  2A D1 EC     LD HL,(P_INCR)     ; HL counts bytes left on this line
$F7B9  3A D9 EC     LD A,(STR0)
$F7BC  E6 DF        AND $DF
$F7BE  FE 44        CP 'D'
$F7C0  28 30        JR Z,BD_DPUT
;   Hex form: one quoted string of two-digit pairs.
$F7C2  01 00 00     LD BC,$0000
$F7C5  3E 22        LD A,'"'
$F7C7  D7           RST $10
BD_HPUT:
$F7C8  1A           LD A,(DE)
$F7C9  E5           PUSH HL
$F7CA  26 00        LD H,$00
$F7CC  6F           LD L,A
$F7CD  09           ADD HL,BC          ; running checksum
$F7CE  44           LD B,H
$F7CF  4D           LD C,L
$F7D0  E1           POP HL
$F7D1  D5           PUSH DE
$F7D2  F5           PUSH AF
$F7D3  CD 9E EC     CALL PRHIGH
$F7D6  F1           POP AF
$F7D7  CD A2 EC     CALL PRLOW
$F7DA  D1           POP DE
$F7DB  2B           DEC HL
$F7DC  7C           LD A,H
$F7DD  B5           OR L
$F7DE  13           INC DE
$F7DF  28 04        JR Z,BD_HEND
$F7E1  7A           LD A,D
$F7E2  B3           OR E
$F7E3  20 E3        JR NZ,BD_HPUT
BD_HEND:
$F7E5  3E 22        LD A,'"'
$F7E7  D7           RST $10
$F7E8  3E 2C        LD A,','
$F7EA  D7           RST $10
$F7EB  1B           DEC DE
$F7EC  23           INC HL
$F7ED  18 07        JR BD_TAIL
;   Decimal form: comma-separated values.
BD_DSEP:
$F7EF  3E 2C        LD A,','
$F7F1  D7           RST $10
BD_DPUT:
$F7F2  1A           LD A,(DE)
$F7F3  4F           LD C,A
$F7F4  06 00        LD B,$00
BD_TAIL:
$F7F6  C5           PUSH BC
$F7F7  CD C4 FE     CALL PRBC          ; the ASCII digits
$F7FA  C1           POP BC
$F7FB  3E 0E        LD A,$0E           ; then BASIC's hidden binary form
$F7FD  D7           RST $10
$F7FE  AF           XOR A
$F7FF  D7           RST $10            ; exponent 0 = small integer
$F800  AF           XOR A
$F801  D7           RST $10            ; sign
$F802  79           LD A,C
$F803  D7           RST $10            ; low byte
$F804  78           LD A,B
$F805  D7           RST $10            ; high byte
$F806  AF           XOR A
$F807  D7           RST $10
$F808  2B           DEC HL
$F809  7C           LD A,H
$F80A  B5           OR L
$F80B  13           INC DE
$F80C  28 04        JR Z,BD_EOL
$F80E  7A           LD A,D
$F80F  B3           OR E
$F810  20 DD        JR NZ,BD_DSEP
BD_EOL:
$F812  3E 0D        LD A,$0D
$F814  D7           RST $10
$F815  C1           POP BC             ; BC = the line start
$F816  2A 5B 5C     LD HL,(KCUR)
$F819  03           INC BC
$F81A  03           INC BC
$F81B  03           INC BC
$F81C  37           SCF
$F81D  ED 42        SBC HL,BC          ; HL = body length
$F81F  7C           LD A,H
$F820  02           LD (BC),A          ; fill in the placeholders
$F821  0B           DEC BC
$F822  7D           LD A,L
$F823  02           LD (BC),A
$F824  C9           RET

;=====================================================================
; COMMAND I  —  Information  ($F825)
; A status page: free RAM, program length, variables length, user RAM
; and whether the interrupt vector has been diverted.
;=====================================================================
CMD_I:
$F825  FD CB 01 CE  SET 1,(IY+$01)     ; FLAGS bit 1 - suppress leading space
$F829  3E 02        LD A,$02
$F82B  CD 30 12     CALL SELECT
$F82E  FD CB 01 4E  BIT 1,(IY+$01)
$F832  CC A6 08     CALL Z,K_CLS
$F835  3E 02        LD A,$02
$F837  CD 30 12     CALL SELECT
$F83A  CD 97 ED     CALL SCRIPT
$F83D  DEFB $0D,$E8,$E9              ; " STATUS REPORT"
$F840  DEFB $FE,$EA,$AB              ; " MEMORY LEFT: "
$F843  DEFB $19
$F844  2A B2 5C     LD HL,(RAMTOP)
$F847  00           NOP
$F848  ED 5B 59 5C  LD DE,(ELINE)
$F84C  ED 52        SBC HL,DE
$F84E  CD 97 ED     CALL SCRIPT
$F851  DEFB $14,$E7                  ; print it, then " bytes"
$F853  DEFB $FE,$CF,$AC              ; " PROGRAM LENGTH: "
$F856  DEFB $19
$F857  2A 4B 5C     LD HL,(VARS)
$F85A  ED 5B 53 5C  LD DE,(PROG)
$F85E  A7           AND A
$F85F  ED 52        SBC HL,DE
$F861  CD 97 ED     CALL SCRIPT
$F864  DEFB $14,$E7
$F866  DEFB $FE,$ED,$AE              ; " VARIABLE FILE: "
$F869  DEFB $19
$F86A  2A 59 5C     LD HL,(ELINE)
$F86D  ED 5B 4B 5C  LD DE,(VARS)
$F871  A7           AND A
$F872  ED 52        SBC HL,DE
$F874  CD 97 ED     CALL SCRIPT
$F877  DEFB $14,$E7
$F879  DEFB $FE,$AF                  ; " USER RAM: "
$F87B  DEFB $19
$F87C  2A B2 5C     LD HL,(RAMTOP)
$F87F  ED 5B 53 5C  LD DE,(PROG)
$F883  A7           AND A
$F884  ED 52        SBC HL,DE
$F886  CD 97 ED     CALL SCRIPT
$F889  DEFB $14,$E7
$F88B  DEFB $FE,$F0                  ; " INTERRUPTS"
$F88D  DEFB $19
$F88E  ED 57        LD A,I
$F890  FE 3F        CP $3F             ; the ROM's own value
$F892  28 07        JR Z,CI_NORM
$F894  CD 97 ED     CALL SCRIPT
$F897  DEFB $F2                      ; " DIVERTED"
$F898  DEFB $19
$F899  18 05        JR CI_CHAN
CI_NORM:
$F89B  CD 97 ED     CALL SCRIPT
$F89E  DEFB $F1                      ; " NORMAL"
$F89F  DEFB $19
CI_CHAN:
$F8A0  2A 51 5C     LD HL,(CURCHL)
$F8A3  23           INC HL
$F8A4  23           INC HL
$F8A5  23           INC HL
$F8A6  23           INC HL
$F8A7  7E           LD A,(HL)
$F8A8  FE 53        CP 'S'
$F8AA  28 02        JR Z,CI_UDG
$F8AC  FE 50        CP 'P'
CI_UDG:
$F8AE  CC BC F8     CALL Z,UDGRULER    ; only worth it on screen/printer
$F8B1  CD 97 ED     CALL SCRIPT
$F8B4  DEFB $FE,$7F,$F3,$0D          ; " ©BOB MITCHELL 1985"
$F8B8  DEFB $19
$F8B9  C3 70 EC     JP GETCMD          ; straight back to the prompt

;---------------------------------------------------------------------
; UDGRULER  ($F8BC)  the letters A-U over the 21 UDG glyphs
;---------------------------------------------------------------------
UDGRULER:
$F8BC  CD 97 ED     CALL SCRIPT
$F8BF  DEFB $FE,$92,$FE,$20          ; " UDGs: " <CR><CR> " "
$F8C3  DEFB $19
$F8C4  06 15        LD B,21
UR_LET:
$F8C6  3E 56        LD A,'U'+1
$F8C8  90           SUB B              ; 'A'..'U'
$F8C9  D7           RST $10
$F8CA  10 FA        DJNZ UR_LET
$F8CC  3E 0D        LD A,$0D
$F8CE  D7           RST $10
$F8CF  3E 20        LD A,$20
$F8D1  D7           RST $10
$F8D2  06 15        LD B,21
UR_UDG:
$F8D4  3E A5        LD A,$A5
$F8D6  90           SUB B              ; $90..$A4 = the UDG characters
$F8D7  D7           RST $10
$F8D8  10 FA        DJNZ UR_UDG
$F8DA  3E 0D        LD A,$0D
$F8DC  D7           RST $10
$F8DD  C9           RET

;=====================================================================
; COMMAND V  —  List Variables  ($F8DE)
; Walks the variables area printing name and value: numerics through
; the ROM's calculator print routine, strings and arrays by hand.
;=====================================================================
CMD_V:
$F8DE  CD 8F ED     CALL SCRIPT_CLS
$F8E1  DEFB $D6,$ED               ; " LIST VARIABLE"
$F8E3  DEFM "S"
$F8E4  DEFB $FE                   ; <CR><CR>
$F8E5  DEFB $19
$F8E6  2A 4B 5C     LD HL,(VARS)
$F8E9  E5           PUSH HL
CV_NEXT:
$F8EA  E1           POP HL
$F8EB  CD 20 17     CALL RECLEN        ; DE = next variable
$F8EE  D5           PUSH DE
$F8EF  7E           LD A,(HL)
$F8F0  FE 80        CP $80             ; end-of-variables marker
$F8F2  20 03        JR NZ,CV_TYPE
$F8F4  D1           POP DE
$F8F5  18 C2        JR $F8B9           ; -> JP GETCMD
CV_TYPE:
$F8F7  CB 7F        BIT 7,A
$F8F9  20 43        JR NZ,CV_LONG      ; long name / array
$F8FB  CB 6F        BIT 5,A
$F8FD  28 1A        JR Z,CV_STR
;   simple numeric: name "=" value
$F8FF  D7           RST $10
$F900  3E 3D        LD A,'='
$F902  D7           RST $10
$F903  23           INC HL
$F904  ED 5B 65 5C  LD DE,(STKEND)
$F908  01 05 00     LD BC,$0005
$F90B  ED B0        LDIR               ; push the value onto the fp stack
$F90D  ED 53 65 5C  LD (STKEND),DE
$F911  CD A1 31     CALL $31A1         ; OUTPUT - print it
$F914  3E 0D        LD A,$0D
$F916  D7           RST $10
$F917  18 D1        JR CV_NEXT
CV_STR:
$F919  CB EF        SET 5,A            ; restore the letter
$F91B  D7           RST $10
$F91C  3E 24        LD A,'$'
$F91E  D7           RST $10
$F91F  3E 3D        LD A,'='
$F921  D7           RST $10
$F922  3E 22        LD A,'"'
$F924  D7           RST $10
$F925  23           INC HL
$F926  4E           LD C,(HL)          ; BC = string length
$F927  23           INC HL
$F928  46           LD B,(HL)
CV_SLOOP:
$F929  23           INC HL
$F92A  78           LD A,B
$F92B  B1           OR C
$F92C  0B           DEC BC
$F92D  20 05        JR NZ,CV_SCHR
$F92F  3E 22        LD A,'"'
$F931  D7           RST $10
$F932  18 E0        JR $F914           ; CR, then the next variable
CV_SCHR:
$F934  7E           LD A,(HL)
$F935  FE 20        CP $20
$F937  30 02        JR NC,CV_SPUT
$F939  3E 3F        LD A,'?'           ; control codes shown as "?"
CV_SPUT:
$F93B  D7           RST $10
$F93C  18 EB        JR CV_SLOOP
CV_LONG:
$F93E  CB 77        BIT 6,A
$F940  20 11        JR NZ,CV_ARR2      ; array with a $ / long name
$F942  CB 6F        BIT 5,A
$F944  28 15        JR Z,CV_ARRN       ; numeric array
$F946  EE C0        XOR $C0            ; numeric array: recover the letter
$F948  D7           RST $10
$F949  23           INC HL
$F94A  7E           LD A,(HL)
$F94B  CB 7F        BIT 7,A
$F94D  28 F9        JR Z,$F948
$F94F  CB BF        RES 7,A
$F951  18 AC        JR $F8FF
CV_ARR2:
$F953  CB 6F        BIT 5,A
$F955  28 09        JR Z,CV_ARRS2
$F957  E6 5F        AND $5F
$F959  18 A4        JR $F8FF
CV_ARRN:
$F95B  EE E0        XOR $E0
$F95D  D7           RST $10
$F95E  18 06        JR CV_DIMS
CV_ARRS2:
$F960  EE A0        XOR $A0
$F962  D7           RST $10
$F963  3E 24        LD A,'$'
$F965  D7           RST $10
CV_DIMS:
$F966  3E 28        LD A,'('
$F968  D7           RST $10
$F969  23           INC HL
$F96A  23           INC HL
$F96B  23           INC HL
$F96C  46           LD B,(HL)          ; number of dimensions
$F96D  18 03        JR CV_DIM1
CV_DIMN:
$F96F  3E 2C        LD A,','
$F971  D7           RST $10
CV_DIM1:
$F972  23           INC HL
$F973  5E           LD E,(HL)
$F974  23           INC HL
$F975  56           LD D,(HL)
$F976  C5           PUSH BC
$F977  E5           PUSH HL
$F978  EB           EX DE,HL
$F979  CD AD EC     CALL PRDEC         ; print the dimension
$F97C  E1           POP HL
$F97D  C1           POP BC
$F97E  10 EF        DJNZ CV_DIMN
$F980  3E 29        LD A,')'
$F982  D7           RST $10
$F983  18 8F        JR $F914           ; CR, then the next variable

;=====================================================================
; COMMAND S  —  Search & List  ($F985)
; Same scan as command A, but only reports the lines that match.
;=====================================================================
CMD_S:
$F985  CD 8F ED     CALL SCRIPT_CLS
$F988  DEFB $0D
$F989  DEFB $D5,$20,$26,$D6          ; " SEARCH & LIST"
$F98D  DEFB $FE
$F98E  DEFB $C1,$80,$13,$1A,$14      ; " START ADDRESS: "
$F993  DEFB $FE
$F994  DEFB $C2,$80,$13,$1B,$1C,$14  ; " FINISH ADDRESS: "
$F99A  DEFB $FE
$F99B  DEFB $18,$17                  ; validate
$F99D  DEFB $9A                      ; " STRING: "
$F99E  DEFB $11,$00,$12,$00          ;   -> string buffer 0
$F9A2  DEFB $00                      ; "EXECUTE?"
$F9A3  2A CB EC     LD HL,(P_START)
$F9A6  CD D6 16     CALL FIND_L
CS_LINE:
$F9A9  CD 20 17     CALL RECLEN
$F9AC  D5           PUSH DE            ; next line
$F9AD  E5           PUSH HL
$F9AE  56           LD D,(HL)
$F9AF  23           INC HL
$F9B0  5E           LD E,(HL)
$F9B1  23           INC HL             ; DE = line number
$F9B2  E5           PUSH HL
$F9B3  2A CD EC     LD HL,(P_FINISH)
$F9B6  A7           AND A
$F9B7  ED 52        SBC HL,DE
$F9B9  E1           POP HL
$F9BA  30 03        JR NC,CS_SCAN
$F9BC  C1           POP BC             ; past the finish line
$F9BD  C1           POP BC
$F9BE  C9           RET
CS_SCAN:
$F9BF  23           INC HL
$F9C0  23           INC HL             ; HL -> line text
$F9C1  11 D9 EC     LD DE,STR0
$F9C4  ED 4B D7 EC  LD BC,(STR0_LEN)
CS_CMP:
$F9C8  7E           LD A,(HL)
$F9C9  FE 0D        CP $0D
$F9CB  20 07        JR NZ,CS_TEST
$F9CD  E1           POP HL             ; end of line, no match
$F9CE  E1           POP HL
$F9CF  18 D8        JR CS_LINE
CS_RETRY:
$F9D1  E1           POP HL             ; mismatch - step on one character
$F9D2  18 EC        JR CS_SCAN+1
CS_TEST:
$F9D4  E5           PUSH HL
$F9D5  7E           LD A,(HL)
$F9D6  CD 02 16     CALL SKIPNUM
$F9D9  28 FB        JR Z,$F9D6         ; skip the 5-byte number form
$F9DB  1A           LD A,(DE)
$F9DC  BE           CP (HL)
$F9DD  20 F2        JR NZ,CS_RETRY
$F9DF  13           INC DE
$F9E0  23           INC HL
$F9E1  0B           DEC BC
$F9E2  78           LD A,B
$F9E3  B1           OR C
$F9E4  20 EF        JR NZ,$F9D5        ; keep comparing
$F9E6  E1           POP HL             ; matched - list the line
$F9E7  E1           POP HL
$F9E8  CD A1 15     CALL PUT_SR
$F9EB  D7           RST $10
$F9EC  E1           POP HL
$F9ED  18 BA        JR CS_LINE

;=====================================================================
; COMMAND F  —  REM Delete  ($F9EF)
; Removes the machine-code payload from inside a REM line, leaving the
; line itself; the inverse of command E.
;=====================================================================
CMD_F:
$F9EF  CD 8F ED     CALL SCRIPT_CLS
$F9F2  DEFB $0D
$F9F3  DEFB $DB,$C9                  ; " REM DELETE"
$F9F5  DEFB $FE
$F9F6  DEFB $C1,$80,$13,$1A,$14      ; " START ADDRESS: "
$F9FB  DEFB $FE
$F9FC  DEFB $C2,$80,$13,$1B,$1C,$14  ; " FINISH ADDRESS: "
$FA02  DEFB $FE
$FA03  DEFB $18,$17                  ; validate
$FA05  DEFB $00                      ; "EXECUTE?"
$FA06  2A CB EC     LD HL,(P_START)
$FA09  CD D6 16     CALL FIND_L
$FA0C  2B           DEC HL
CF_NEXT:
$FA0D  23           INC HL
CF_LINE:
$FA0E  22 D3 EC     LD (P_WORK1),HL
$FA11  CD 20 17     CALL RECLEN
$FA14  ED 53 D5 EC  LD (P_WORK2),DE
$FA18  56           LD D,(HL)
$FA19  23           INC HL
$FA1A  5E           LD E,(HL)
$FA1B  23           INC HL
$FA1C  E5           PUSH HL
$FA1D  2A CD EC     LD HL,(P_FINISH)
$FA20  A7           AND A
$FA21  ED 52        SBC HL,DE
$FA23  E1           POP HL
$FA24  D8           RET C
$FA25  23           INC HL
CF_SKIP:
$FA26  23           INC HL
$FA27  7E           LD A,(HL)
$FA28  FE 21        CP $21
$FA2A  38 FA        JR C,CF_SKIP       ; skip leading control bytes
$FA2C  FE EA        CP $EA             ; REM as the first statement?
$FA2E  20 0C        JR NZ,CF_COLON
$FA30  ED 5B D3 EC  LD DE,(P_WORK1)
$FA34  2A D5 EC     LD HL,(P_WORK2)
$FA37  CD 4D 17     CALL DEL_DE        ; delete the whole line
$FA3A  18 D2        JR CF_LINE
;   Otherwise look for a ":REM" outside quotes and cut from there.
CF_COLON:
$FA3C  0E 00        LD C,$00           ; C counts quote marks
CF_CHAR:
$FA3E  23           INC HL
$FA3F  7E           LD A,(HL)
CF_NUM:
$FA40  CD 02 16     CALL SKIPNUM
$FA43  28 FB        JR Z,CF_NUM
$FA45  FE 0D        CP $0D
$FA47  28 C4        JR Z,CF_NEXT
$FA49  FE 22        CP '"'
$FA4B  20 01        JR NZ,CF_COL2
$FA4D  0C           INC C
CF_COL2:
$FA4E  FE 3A        CP ':'
$FA50  20 EC        JR NZ,CF_CHAR
$FA52  CB 41        BIT 0,C
$FA54  20 E8        JR NZ,CF_CHAR      ; inside a string - ignore
$FA56  5D           LD E,L
$FA57  54           LD D,H             ; DE = the ":"
CF_SEEK:
$FA58  23           INC HL
$FA59  7E           LD A,(HL)
$FA5A  FE 0D        CP $0D
$FA5C  28 AF        JR Z,CF_NEXT
$FA5E  FE 21        CP $21
$FA60  38 F6        JR C,CF_SEEK
$FA62  FE EA        CP $EA             ; REM
$FA64  EB           EX DE,HL
$FA65  20 D7        JR NZ,CF_CHAR
$FA67  EB           EX DE,HL
$FA68  2A D5 EC     LD HL,(P_WORK2)
$FA6B  2B           DEC HL
$FA6C  CD 4D 17     CALL DEL_DE        ; cut ":REM..." to end of line
$FA6F  ED 5B D3 EC  LD DE,(P_WORK1)
$FA73  13           INC DE
$FA74  13           INC DE
$FA75  13           INC DE
$FA76  A7           AND A
$FA77  ED 52        SBC HL,DE
$FA79  EB           EX DE,HL
$FA7A  72           LD (HL),D          ; rewrite the length word
$FA7B  2B           DEC HL
$FA7C  73           LD (HL),E
$FA7D  2B           DEC HL
$FA7E  2B           DEC HL
$FA7F  18 8D        JR CF_LINE

;=====================================================================
; COMMAND G  —  UDG Designer  ($FA81)
; Edits one user-defined graphic as an 8x8 grid of block characters.
; The grid is drawn at screen address $406C (char row 3, column 12);
; the eight pixel rows are $406C, $416C ... $476C, so INC H steps a
; row.  The editor reads and writes the screen directly and only
; copies the result into the UDG area when you press 9A..9U.
;=====================================================================
CMD_G:
$FA81  CD 8F ED     CALL SCRIPT_CLS
$FA84  DEFB $0D
$FA85  DEFB $F4,$D2                  ; " DESIGN UDGs"
$FA87  DEFB $FE
$FA88  DEFB $B5                      ; " CHARACTER: "
$FA89  DEFB $11,$00,$12,$00          ;   -> string buffer 0
$FA8D  DEFB $00                      ; "EXECUTE?"
$FA8E  3E FE        LD A,$FE
$FA90  CD 30 12     CALL SELECT
$FA93  3E 16        LD A,$16           ; AT
$FA95  D7           RST $10
$FA96  3E 05        LD A,$05           ; row 5
$FA98  D7           RST $10
$FA99  AF           XOR A              ; column 0
$FA9A  D7           RST $10
$FA9B  CD 97 ED     CALL SCRIPT
$FA9E  DEFB $F6,$C7,$F8,$F9          ; " USE LINE COLUMN I/O (on/off)"
$FAA2  DEFB $19
$FAA3  3E FE        LD A,$FE
$FAA5  CD 30 12     CALL SELECT
$FAA8  11 DB FB     LD DE,FUNCTAB
$FAAB  AF           XOR A
$FAAC  CD 3F 07     CALL PUTMES        ; the FUNCTIONS panel
;---------------------------------------------------------------------
; Redraw the grid and wait for a command.
;---------------------------------------------------------------------
CG_DRAW:
$FAAF  CD 66 FB     CALL CG_AT70
$FAB2  06 08        LD B,$08
CG_ROW:
$FAB4  C5           PUSH BC
$FAB5  3E 20        LD A,$20
$FAB7  D7           RST $10
$FAB8  3E 09        LD A,$09
$FABA  90           SUB B
$FABB  5F           LD E,A             ; E = row 1..8
$FABC  C6 30        ADD A,'0'
$FABE  D7           RST $10
$FABF  21 6C 3F     LD HL,$3F6C
$FAC2  7B           LD A,E
$FAC3  84           ADD A,H
$FAC4  67           LD H,A             ; HL = $406C + row*256
$FAC5  4E           LD C,(HL)
$FAC6  06 08        LD B,$08
CG_PIX:
$FAC8  CB 11        RL C
$FACA  3E 20        LD A,$20
$FACC  30 02        JR NC,CG_PUT
$FACE  3E 8F        LD A,$8F           ; solid block graphic
CG_PUT:
$FAD0  D7           RST $10
$FAD1  10 F5        DJNZ CG_PIX
$FAD3  C1           POP BC
$FAD4  7B           LD A,E
$FAD5  C6 30        ADD A,'0'
$FAD7  D7           RST $10
$FAD8  3E 0D        LD A,$0D
$FADA  D7           RST $10
$FADB  10 D7        DJNZ CG_ROW
$FADD  CD 6E FB     CALL CG_ABC
$FAE0  CD 97 ED     CALL SCRIPT
$FAE3  DEFB $0D
$FAE4  DEFM " 0 ="
$FAE8  DEFB $FD                      ; MSG 61 "RESTART"
$FAE9  DEFB $FE,$20
$FAEB  DEFB $19
$FAEC  3E FE        LD A,$FE
$FAEE  CD 30 12     CALL SELECT
$FAF1  CD C4 F8     CALL $F8C4         ; the A-U column letters
CG_CMD:
$FAF4  01 04 00     LD BC,$0004
$FAF7  21 D7 EC     LD HL,STR0_LEN
$FAFA  E5           PUSH HL
$FAFB  CD 9A EE     CALL INSTR
$FAFE  D1           POP DE
$FAFF  13           INC DE
$FB00  13           INC DE
$FB01  1A           LD A,(DE)          ; first character
$FB02  CD D9 30     CALL DIGITQ
$FB05  38 ED        JR C,CG_CMD        ; must start with a digit
$FB07  D6 30        SUB '0'
$FB09  CA 81 FA     JP Z,CMD_G         ; "0" - restart
$FB0C  FE 09        CP $09
$FB0E  28 31        JR Z,CG_FUNC       ; "9" - a function
$FB10  2E 6C        LD L,$6C           ; 1..8 = a pixel row
$FB12  C6 3F        ADD A,$3F
$FB14  67           LD H,A
$FB15  13           INC DE
CG_COL:
$FB16  1A           LD A,(DE)
$FB17  E6 DF        AND $DF
$FB19  FE 41        CP 'A'
$FB1B  38 D7        JR C,CG_CMD
$FB1D  FE 49        CP 'H'+1
$FB1F  30 D3        JR NC,CG_CMD
$FB21  D6 40        SUB '@'            ; 1..8
$FB23  47           LD B,A
$FB24  AF           XOR A
CG_MASK:
$FB25  37           SCF
CG_MSK2:
$FB26  1F           RRA                ; build the single-bit mask
$FB27  10 FD        DJNZ CG_MSK2
$FB29  4F           LD C,A
$FB2A  13           INC DE
$FB2B  1A           LD A,(DE)
$FB2C  E6 DF        AND $DF
$FB2E  FE 49        CP 'I'
$FB30  28 09        JR Z,CG_SET
$FB32  FE 4F        CP 'O'
$FB34  20 BE        JR NZ,CG_CMD
$FB36  79           LD A,C
$FB37  2F           CPL
$FB38  A6           AND (HL)           ; O = pixel off
$FB39  18 02        JR CG_STORE
CG_SET:
$FB3B  79           LD A,C
$FB3C  B6           OR (HL)            ; I = pixel on
CG_STORE:
$FB3D  77           LD (HL),A
$FB3E  C3 AF FA     JP CG_DRAW
;---------------------------------------------------------------------
; 9A..9U  store the grid into UDG n;  9V..9Z  transform it
;---------------------------------------------------------------------
CG_FUNC:
$FB41  13           INC DE
$FB42  1A           LD A,(DE)
$FB43  E6 DF        AND $DF
$FB45  FE 56        CP 'V'
$FB47  30 32        JR NC,CG_XFORM
$FB49  D6 41        SUB 'A'
$FB4B  38 A7        JR C,CG_CMD
$FB4D  87           ADD A,A            ; *8
$FB4E  87           ADD A,A
$FB4F  87           ADD A,A
$FB50  5F           LD E,A
$FB51  16 00        LD D,$00
$FB53  2A 7B 5C     LD HL,(UDG)
$FB56  19           ADD HL,DE
$FB57  EB           EX DE,HL
$FB58  21 6C 40     LD HL,$406C
$FB5B  06 08        LD B,$08
CG_SAVE:
$FB5D  7E           LD A,(HL)
$FB5E  12           LD (DE),A
$FB5F  24           INC H
$FB60  13           INC DE
$FB61  10 FA        DJNZ CG_SAVE
$FB63  C3 81 FA     JP CMD_G
CG_AT70:
$FB66  3E 16        LD A,$16           ; AT 7,0
$FB68  D7           RST $10
$FB69  3E 07        LD A,$07
$FB6B  D7           RST $10
$FB6C  AF           XOR A
$FB6D  D7           RST $10
CG_ABC:
$FB6E  CD 97 ED     CALL SCRIPT
$FB71  DEFB $20,$FC,$0D              ; " ABCDEFGH"
$FB74  DEFB $19
$FB75  3E FE        LD A,$FE
$FB77  CD 30 12     CALL SELECT
$FB7A  C9           RET
CG_XFORM:
$FB7B  21 AF FA     LD HL,CG_DRAW
$FB7E  E5           PUSH HL            ; every transform returns to the redraw
$FB7F  FE 5A        CP 'Z'
$FB81  28 4C        JR Z,CG_RESET
$FB83  D0           RET NC
$FB84  21 6C 40     LD HL,$406C
$FB87  06 08        LD B,$08
$FB89  FE 59        CP 'Y'
$FB8B  28 2B        JR Z,CG_ROT
$FB8D  FE 58        CP 'X'
$FB8F  28 18        JR Z,CG_FLIPV
$FB91  FE 57        CP 'W'
$FB93  28 07        JR Z,CG_FLIPH
CG_INV:                               ; 9V  INVERSE
$FB95  7E           LD A,(HL)
$FB96  2F           CPL
$FB97  77           LD (HL),A
$FB98  24           INC H
$FB99  10 FA        DJNZ CG_INV
$FB9B  C9           RET
CG_FLIPH:                             ; 9W  LEFT-RIGHT
$FB9C  0E 08        LD C,$08
CG_FH2:
$FB9E  CB 16        RL (HL)
$FBA0  1F           RRA                ; shift bits out one way, in the other
$FBA1  0D           DEC C
$FBA2  20 FA        JR NZ,CG_FH2
$FBA4  77           LD (HL),A
$FBA5  24           INC H
$FBA6  10 F4        DJNZ CG_FLIPH
$FBA8  C9           RET
CG_FLIPV:                             ; 9X  UP-DOWN
$FBA9  06 04        LD B,$04
$FBAB  11 6C 47     LD DE,$476C
CG_FV2:
$FBAE  4E           LD C,(HL)
$FBAF  1A           LD A,(DE)
$FBB0  77           LD (HL),A
$FBB1  79           LD A,C
$FBB2  12           LD (DE),A
$FBB3  24           INC H
$FBB4  15           DEC D
$FBB5  10 F7        DJNZ CG_FV2
$FBB7  C9           RET
CG_ROT:                               ; 9Y  ROTATE (transpose)
$FBB8  11 02 48     LD DE,$4802
CG_ROT1:
$FBBB  D5           PUSH DE
$FBBC  0E 08        LD C,$08
CG_ROT2:
$FBBE  1A           LD A,(DE)
$FBBF  87           ADD A,A
$FBC0  CB 1E        RR (HL)
$FBC2  3E 20        LD A,$20
$FBC4  83           ADD A,E
$FBC5  5F           LD E,A
$FBC6  0D           DEC C
$FBC7  20 F5        JR NZ,CG_ROT2
$FBC9  D1           POP DE
$FBCA  1C           INC E
$FBCB  24           INC H
$FBCC  10 ED        DJNZ CG_ROT1
$FBCE  C9           RET
CG_RESET:                             ; 9Z  RESET UDGs
$FBCF  21 08 3E     LD HL,$3E08        ; ROM character set, 'A'
$FBD2  ED 5B 7B 5C  LD DE,(UDG)
$FBD6  01 A8 00     LD BC,$00A8        ; 21 characters * 8 rows
$FBD9  ED B0        LDIR
$FBDB  C9           RET
;                   ^ doubles as FUNCTAB's dummy terminator

;---------------------------------------------------------------------
; FUNCTAB  ($FBDB)  the one-entry table for the FUNCTIONS panel.
; $16 r c = AT, $06 = comma tab.
;---------------------------------------------------------------------
FUNCTAB:
$FBDC  DEFB $16,$07,$10               ; AT 7,16
$FBDF  DEFM "FUNCTIONS"
$FBE8  DEFB $0D,$0D,$06
$FBEB  DEFM "9A-9U ENTER UDG"
$FBFA  DEFB $0D,$0D,$06
$FBFD  DEFM "9V INVERSE"
$FC07  DEFB $0D,$06
$FC09  DEFM "9W LEFT-RIGHT"
$FC16  DEFB $0D,$06
$FC18  DEFM "9X UP-DOWN"
$FC22  DEFB $0D,$06
$FC24  DEFM "9Y ROTATE"
$FC2D  DEFB $0D,$06
$FC2F  DEFM "9Z RESET UDG"
$FC3B  DEFB $D3                       ; 'S'+$80 terminator

;=====================================================================
; COMMAND P  —  Compactor  ($FC3C)
; Strips spaces and control characters that are outside quotes, and
; skips REM lines entirely.
;=====================================================================
CMD_P:
$FC3C  CD 8F ED     CALL SCRIPT_CLS
$FC3F  DEFB $0D
$FC40  DEFB $F7,$CF                  ; " COMPACT PROGRAM"
$FC42  DEFB $FE
$FC43  DEFB $C1,$80,$13,$1A,$14      ; " START ADDRESS: "
$FC48  DEFB $FE
$FC49  DEFB $C2,$80,$13,$1B,$1C,$14  ; " FINISH ADDRESS: "
$FC4F  DEFB $FE
$FC50  DEFB $18,$17                  ; validate
$FC52  DEFB $00                      ; "EXECUTE?"
$FC53  2A CB EC     LD HL,(P_START)
$FC56  CD D6 16     CALL FIND_L
CP_LINE:
$FC59  CD 20 17     CALL RECLEN
$FC5C  46           LD B,(HL)
$FC5D  23           INC HL
$FC5E  4E           LD C,(HL)
$FC5F  23           INC HL             ; BC = line number
$FC60  E5           PUSH HL
$FC61  2A CD EC     LD HL,(P_FINISH)
$FC64  A7           AND A
$FC65  ED 42        SBC HL,BC
$FC67  E1           POP HL
$FC68  D8           RET C              ; past the last wanted line
$FC69  E5           PUSH HL
$FC6A  4E           LD C,(HL)          ; BC = line length
$FC6B  23           INC HL
$FC6C  46           LD B,(HL)
$FC6D  23           INC HL
$FC6E  7E           LD A,(HL)
$FC6F  FE EA        CP $EA             ; a REM line?
$FC71  20 04        JR NZ,CP_START
$FC73  EB           EX DE,HL           ; yes - leave it alone
$FC74  C1           POP BC
$FC75  18 E2        JR CP_LINE
CP_START:
$FC77  1E 00        LD E,$00           ; E bit 0 = inside a string
CP_CHAR:
$FC79  CD 02 16     CALL SKIPNUM
$FC7C  28 FB        JR Z,CP_CHAR
$FC7E  FE 0D        CP $0D
$FC80  20 08        JR NZ,CP_TEST
$FC82  E1           POP HL             ; end of line: rewrite its length
$FC83  71           LD (HL),C
$FC84  23           INC HL
$FC85  70           LD (HL),B
$FC86  23           INC HL
$FC87  09           ADD HL,BC
$FC88  18 CF        JR CP_LINE
CP_TEST:
$FC8A  FE 22        CP '"'
$FC8C  20 01        JR NZ,CP_SP
$FC8E  1C           INC E
CP_SP:
$FC8F  FE 21        CP $21             ; anything below "!" is droppable
$FC91  23           INC HL
$FC92  7E           LD A,(HL)
$FC93  30 E4        JR NC,CP_CHAR
$FC95  CB 43        BIT 0,E
$FC97  20 E0        JR NZ,CP_CHAR      ; inside a string - keep it
$FC99  2B           DEC HL
$FC9A  C5           PUSH BC
$FC9B  CD 7E 0B     CALL DELSYM        ; delete this one byte
$FC9E  C1           POP BC
$FC9F  0B           DEC BC             ; line got one shorter
$FCA0  AF           XOR A
$FCA1  5F           LD E,A
$FCA2  18 EE        JR $FC92

;=====================================================================
; COMMAND J  —  Merge Lines  ($FCA4)
; Joins each line in the range onto the previous one with a ":".
;=====================================================================
CMD_J:
$FCA4  CD 8F ED     CALL SCRIPT_CLS
$FCA7  DEFB $0D
$FCA8  DEFB $E1,$CF,$C7              ; " MERGE PROGRAM LINE"
$FCAB  DEFM "S"
$FCAC  DEFB $FE
$FCAD  DEFB $C1,$80,$13,$1A,$14      ; " START ADDRESS: "
$FCB2  DEFB $FE
$FCB3  DEFB $C2,$80,$13,$1B,$1C,$14  ; " FINISH ADDRESS: "
$FCB9  DEFB $18,$17                  ; validate
$FCBB  DEFB $00                      ; "EXECUTE?"
$FCBC  2A CB EC     LD HL,(P_START)
$FCBF  CD D6 16     CALL FIND_L
$FCC2  23           INC HL
$FCC3  23           INC HL
$FCC4  54           LD D,H
$FCC5  5D           LD E,L             ; DE -> this line's length word
$FCC6  4E           LD C,(HL)
$FCC7  23           INC HL
$FCC8  46           LD B,(HL)
$FCC9  09           ADD HL,BC          ; HL -> the closing $0D
$FCCA  36 3A        LD (HL),':'        ; turn it into a statement separator
$FCCC  E5           PUSH HL
$FCCD  23           INC HL
$FCCE  46           LD B,(HL)          ; next line's number
$FCCF  23           INC HL
$FCD0  4E           LD C,(HL)
$FCD1  23           INC HL
$FCD2  E5           PUSH HL
$FCD3  2A CD EC     LD HL,(P_FINISH)
$FCD6  A7           AND A
$FCD7  ED 42        SBC HL,BC
$FCD9  E1           POP HL
$FCDA  38 17        JR C,CJ_DONE       ; past the range
$FCDC  4E           LD C,(HL)          ; next line's length
$FCDD  23           INC HL
$FCDE  46           LD B,(HL)
$FCDF  E5           PUSH HL
$FCE0  EB           EX DE,HL
$FCE1  5E           LD E,(HL)
$FCE2  23           INC HL
$FCE3  56           LD D,(HL)
$FCE4  EB           EX DE,HL
$FCE5  09           ADD HL,BC          ; combined length
$FCE6  EB           EX DE,HL
$FCE7  72           LD (HL),D
$FCE8  2B           DEC HL
$FCE9  73           LD (HL),E
$FCEA  E1           POP HL
$FCEB  D1           POP DE
$FCEC  23           INC HL
$FCED  13           INC DE
$FCEE  CD 4D 17     CALL DEL_DE        ; remove the second line's header
$FCF1  18 C9        JR CMD_J+$18       ; -> $FCBC, do it again
CJ_DONE:
$FCF3  E1           POP HL
$FCF4  36 0D        LD (HL),$0D        ; restore the final newline
$FCF6  C9           RET

;=====================================================================
; COMMANDS K / L  —  Upper Case / Lower Case  ($FCF7 / $FD03)
; C bit 7 set = force upper case;  C bit 0 set = also inside quotes.
;=====================================================================
CMD_K:
$FCF7  CD 8F ED     CALL SCRIPT_CLS
$FCFA  DEFB $0D
$FCFB  DEFB $DD,$D3,$DC              ; " lower TO UPPER"
$FCFE  DEFB $19
$FCFF  0E 80        LD C,$80
$FD01  18 0A        JR CASE_ASK
CMD_L:
$FD03  CD 8F ED     CALL SCRIPT_CLS
$FD06  DEFB $0D
$FD07  DEFB $DC,$D3,$DD              ; " UPPER TO lower"
$FD0A  DEFB $19
$FD0B  0E 00        LD C,$00
CASE_ASK:
$FD0D  C5           PUSH BC
$FD0E  CD 92 ED     CALL SCRIPT_FE
$FD11  DEFB $DE                      ; " CASE"
$FD12  DEFB $FE
$FD13  DEFB $C1,$80,$13,$1A,$14      ; " START ADDRESS: "
$FD18  DEFB $FE
$FD19  DEFB $C2,$80,$13,$1B,$1C,$14  ; " FINISH ADDRESS: "
$FD1F  DEFB $18,$17
$FD21  DEFB $FE
$FD22  DEFB $BA                      ; " IN QUOTES? (Y/N): "
$FD23  DEFB $11,$00,$12,$00
$FD27  DEFB $00                      ; "EXECUTE?"
$FD28  2A CD EC     LD HL,(P_FINISH)
$FD2B  23           INC HL
$FD2C  CD D6 16     CALL FIND_L
$FD2F  E5           PUSH HL
$FD30  2A CB EC     LD HL,(P_START)
$FD33  CD D6 16     CALL FIND_L
$FD36  D1           POP DE             ; DE = one past the block
$FD37  C1           POP BC             ; C = the direction flag
$FD38  3A D9 EC     LD A,(STR0)
$FD3B  E6 DF        AND $DF
$FD3D  FE 59        CP 'Y'
$FD3F  20 01        JR NZ,CASE_HDR
$FD41  0C           INC C              ; also convert inside strings
CASE_HDR:
$FD42  06 00        LD B,$00           ; B bit 0 = inside a string
$FD44  23           INC HL
$FD45  23           INC HL
$FD46  23           INC HL
$FD47  23           INC HL
CASE_CHAR:
$FD48  23           INC HL
$FD49  A7           AND A
$FD4A  ED 52        SBC HL,DE
$FD4C  D0           RET NC             ; reached the end
$FD4D  19           ADD HL,DE
$FD4E  7E           LD A,(HL)
CASE_NUM:
$FD4F  CD 02 16     CALL SKIPNUM
$FD52  28 FB        JR Z,CASE_NUM
$FD54  FE 0D        CP $0D
$FD56  28 EA        JR Z,CASE_HDR
$FD58  FE 22        CP '"'
$FD5A  20 01        JR NZ,CASE_TEST
$FD5C  04           INC B
CASE_TEST:
$FD5D  CD 4B 30     CALL ALPHAQ
$FD60  30 E6        JR NC,CASE_CHAR    ; not a letter
$FD62  CB 40        BIT 0,B
$FD64  28 04        JR Z,CASE_DO
$FD66  CB 41        BIT 0,C
$FD68  28 DE        JR Z,CASE_CHAR     ; in a string and told not to
CASE_DO:
$FD6A  CB EE        SET 5,(HL)         ; lower case
$FD6C  CB 79        BIT 7,C
$FD6E  28 D8        JR Z,CASE_CHAR
$FD70  CB AE        RES 5,(HL)         ; upper case
$FD72  18 D4        JR CASE_CHAR

;=====================================================================
; COMMAND N  —  Autoline On  ($FD74)
; Installs an interrupt routine that types the next line number for
; you: when the edit line is empty and you press ENTER, it feeds the
; digits of E_PPC+increment into LASTK one per interrupt.
;=====================================================================
CMD_N:
$FD74  CD 8F ED     CALL SCRIPT_CLS
$FD77  DEFB $0D
$FD78  DEFB $FB,$C7,$C8              ; " AUTO LINE NUMBER"
$FD7B  DEFB $FE
$FD7C  DEFB $86,$13,$1D,$14          ; " INCREMENT: "  -> P_INCR
$FD80  DEFB $15,$16                  ; non-zero, <= 100
$FD82  DEFB $00                      ; "EXECUTE?"
$FD83  2A D1 EC     LD HL,(P_INCR)
$FD86  22 4F FF     LD (V_INCR),HL
$FD89  21 8F FD     LD HL,AUTOLINE
$FD8C  C3 41 FF     JP SET_IM2
;---------------------------------------------------------------------
; AUTOLINE  ($FD8F)  called from the ISR, 60 times a second
;---------------------------------------------------------------------
AUTOLINE:
$FD8F  3A 52 FF     LD A,(V_DIGIT)
$FD92  A7           AND A
$FD93  20 18        JR NZ,AL_FEED      ; already part-way through
$FD95  2A 82 5C     LD HL,(ECHOE)
$FD98  3E 20        LD A,$20
$FD9A  BD           CP L
$FD9B  C0           RET NZ             ; edit line not empty
$FD9C  3E 17        LD A,$17
$FD9E  BC           CP H
$FD9F  C0           RET NZ
$FDA0  3A 08 5C     LD A,(LASTK)
$FDA3  FE 0D        CP $0D
$FDA5  C0           RET NZ             ; only after ENTER
$FDA6  3A 3B 5C     LD A,(FLAGS)
$FDA9  87           ADD A,A
$FDAA  D8           RET C
$FDAB  3E 04        LD A,$04           ; four digits to send
AL_FEED:
$FDAD  3D           DEC A
$FDAE  32 52 FF     LD (V_DIGIT),A
$FDB1  2A 49 5C     LD HL,(EPPC)       ; the line the cursor is on
$FDB4  ED 5B 4F FF  LD DE,(V_INCR)
$FDB8  19           ADD HL,DE          ; ... plus the increment
$FDB9  01 18 FC     LD BC,-1000
$FDBC  CD D4 FD     CALL AL_DIGIT
$FDBF  FE 03        CP $03
$FDC1  C8           RET Z
$FDC2  01 9C FF     LD BC,-100
$FDC5  CD D4 FD     CALL AL_DIGIT
$FDC8  FE 02        CP $02
$FDCA  C8           RET Z
$FDCB  0E F6        LD C,$F6           ; BC = -10
$FDCD  CD D4 FD     CALL AL_DIGIT
$FDD0  3D           DEC A
$FDD1  C8           RET Z
$FDD2  0E FF        LD C,$FF           ; BC = -1
AL_DIGIT:
$FDD4  AF           XOR A
AL_DIV:
$FDD5  3C           INC A
$FDD6  09           ADD HL,BC
$FDD7  38 FC        JR C,AL_DIV
$FDD9  ED 42        SBC HL,BC
$FDDB  C6 2F        ADD A,'0'-1
$FDDD  E5           PUSH HL
$FDDE  32 08 5C     LD (LASTK),A       ; pretend the user typed it
$FDE1  21 3B 5C     LD HL,FLAGS
$FDE4  CB EE        SET 5,(HL)         ; "a new key is ready"
$FDE6  E1           POP HL
$FDE7  3A 52 FF     LD A,(V_DIGIT)
$FDEA  C9           RET

;=====================================================================
; COMMAND Q  —  Display Memory  ($FDEB)
; Puts a live "memory left" figure in the top right of the screen.
;=====================================================================
CMD_Q:
$FDEB  CD 8F ED     CALL SCRIPT_CLS
$FDEE  DEFB $0D
$FDEF  DEFB $EA,$EB                  ; " MEMORY LEFT"
$FDF1  DEFB $00                      ; "EXECUTE?"
$FDF2  21 44 FE     LD HL,SHOW_FREE
$FDF5  18 27        JR $FE1E           ; -> JP SET_IM2

;=====================================================================
; COMMAND T  —  Trace on  ($FDF7)
; Shows the line number currently being interpreted, top right.
;=====================================================================
CMD_T:
$FDF7  CD 8F ED     CALL SCRIPT_CLS
$FDFA  DEFB $0D
$FDFB  DEFM " TRACE ON"
$FE04  DEFB $FE
$FE05  DEFM " SPEED: "
$FE0D  DEFB $13,$1D,$14              ;   -> P_INCR
$FE10  DEFB $16                      ; 1..100
$FE11  DEFB $00                      ; "EXECUTE?"
$FE12  3E 65        LD A,101
$FE14  2A D1 EC     LD HL,(P_INCR)
$FE17  95           SUB L
$FE18  32 51 FF     LD (V_DELAY),A     ; delay = 101 - speed
$FE1B  21 21 FE     LD HL,SHOW_LINE
$FE1E  C3 41 FF     JP SET_IM2

;---------------------------------------------------------------------
; SHOW_LINE  ($FE21)  the trace interrupt routine.
; Reads the H/J/K/L/ENTER half-row: ENTER+L holds the display,
; ENTER+K skips the delay.
;---------------------------------------------------------------------
SHOW_LINE:
$FE21  3E BF        LD A,$BF
$FE23  DB FE        IN A,($FE)
$FE25  E6 07        AND $07
$FE27  FE 04        CP $04
$FE29  28 F6        JR Z,SHOW_LINE     ; hold
$FE2B  FE 02        CP $02
$FE2D  28 10        JR Z,SL_NOW        ; no delay
$FE2F  3A 51 FF     LD A,(V_DELAY)
SL_WAIT:
$FE32  01 F4 01     LD BC,500
$FE35  21 00 00     LD HL,$0000
$FE38  54           LD D,H
$FE39  5D           LD E,L
$FE3A  ED B0        LDIR               ; a 500-iteration delay
$FE3C  3D           DEC A
$FE3D  20 F3        JR NZ,SL_WAIT
SL_NOW:
$FE3F  2A 45 5C     LD HL,(PPC)        ; the line being interpreted
$FE42  18 0A        JR PAINT
;---------------------------------------------------------------------
; SHOW_FREE  ($FE44)  the "memory left" interrupt routine
;---------------------------------------------------------------------
SHOW_FREE:
$FE44  A7           AND A
$FE45  2A B2 5C     LD HL,(RAMTOP)
$FE48  ED 5B 59 5C  LD DE,(ELINE)
$FE4C  ED 52        SBC HL,DE
;---------------------------------------------------------------------
; PAINT  ($FE4E)  write HL as five characters straight into the
; display file at $401A (top line, columns 26-30) by copying glyphs
; out of the ROM character set at $3D80 ('0').
;---------------------------------------------------------------------
PAINT:
$FE4E  11 1A 40     LD DE,$401A
$FE51  ED 53 53 FF  LD (V_SCRN),DE
$FE55  01 F0 D8     LD BC,-10000
$FE58  CD 70 FE     CALL PAINT1
$FE5B  01 18 FC     LD BC,-1000
$FE5E  CD 70 FE     CALL PAINT1
$FE61  01 9C FF     LD BC,-100
$FE64  CD 70 FE     CALL PAINT1
$FE67  01 F6 FF     LD BC,-10
$FE6A  CD 70 FE     CALL PAINT1
$FE6D  01 FF FF     LD BC,-1
PAINT1:
$FE70  AF           XOR A
PAINT2:
$FE71  3C           INC A
$FE72  09           ADD HL,BC
$FE73  38 FC        JR C,PAINT2
$FE75  ED 42        SBC HL,BC
$FE77  87           ADD A,A            ; A = digit+1, so *8 ...
$FE78  87           ADD A,A
$FE79  87           ADD A,A
$FE7A  C6 78        ADD A,$78          ; ... + $78 = $80 + 8*digit
$FE7C  5F           LD E,A
$FE7D  16 3D        LD D,$3D           ; DE = $3D80 + 8*digit
$FE7F  E5           PUSH HL
$FE80  2A 53 FF     LD HL,(V_SCRN)
$FE83  23           INC HL
$FE84  22 53 FF     LD (V_SCRN),HL
$FE87  06 08        LD B,$08
PAINT3:
$FE89  1A           LD A,(DE)
$FE8A  77           LD (HL),A
$FE8B  13           INC DE
$FE8C  24           INC H              ; next pixel row of the same cell
$FE8D  10 FA        DJNZ PAINT3
$FE8F  E1           POP HL
$FE90  C9           RET

;=====================================================================
; COMMAND O  —  Locate Token  ($FE91)
; Reports the address of the first symbol of a given BASIC line, in
; both decimal and hex — what you need before POKEing code into a REM.
;=====================================================================
CMD_O:
$FE91  CD 8F ED     CALL SCRIPT_CLS
$FE94  DEFB $0D
$FE95  DEFM " FIND"
$FE9A  DEFB $C0,$CE,$C3,$E4          ; " ADDRESS OF 1st SYMBOL"
$FE9E  DEFB $FE
$FE9F  DEFB $C7,$88                  ; " LINE NUMBER: "
$FEA1  DEFB $13,$1B,$1C,$14          ;   -> P_FINISH and P_DEST
$FEA5  DEFB $17
$FEA6  DEFB $00                      ; "EXECUTE?"
$FEA7  2A CF EC     LD HL,(P_DEST)
$FEAA  CD D6 16     CALL FIND_L
$FEAD  C4 4A EF     CALL NZ,REPORT
$FEB0  03           DEFB 3             ; "Invalid Line Number"
$FEB1  23           INC HL             ; step over the 4-byte header
$FEB2  23           INC HL
$FEB3  23           INC HL
$FEB4  23           INC HL
$FEB5  CD 97 ED     CALL SCRIPT
$FEB8  DEFB $C0                      ; " ADDRESS"
$FEB9  DEFM " IS"
$FEBC  DEFB $FE
$FEBD  DEFB $9F,$14                  ; " DEC: " + the value
$FEBF  DEFB $06                      ; comma tab
$FEC0  DEFB $A0,$10                  ; " HEX: " + "#hhhh"
$FEC2  DEFB $19
$FEC3  C9           RET

;=====================================================================
; PRBC  ($FEC4)  print BC as decimal, preserving HL/DE
;=====================================================================
PRBC:
$FEC4  E5           PUSH HL
$FEC5  D5           PUSH DE
$FEC6  60           LD H,B
$FEC7  69           LD L,C
$FEC8  CD AD EC     CALL PRDEC
$FECB  D1           POP DE
$FECC  E1           POP HL
$FECD  C9           RET

;---------------------------------------------------------------------
; $FECE-$FED7   10 spare bytes (all $00)
;---------------------------------------------------------------------
$FECE  DEFS 10,$00

;=====================================================================
; NUMBER_ALL  ($FED8)
; Rebuilds all line lengths, then renumbers every line 1,2,3...
; No reference to this routine exists anywhere in the image; it looks
; like a leftover, or something meant to be called from BASIC.
;=====================================================================
NUMBER_ALL:
$FED8  CD 52 F4     CALL REBUILD
$FEDB  11 00 00     LD DE,$0000
$FEDE  2A 53 5C     LD HL,(PROG)
NA_LOOP:
$FEE1  13           INC DE
$FEE2  72           LD (HL),D
$FEE3  23           INC HL
$FEE4  73           LD (HL),E
$FEE5  23           INC HL
$FEE6  4E           LD C,(HL)
$FEE7  23           INC HL
$FEE8  46           LD B,(HL)
$FEE9  23           INC HL
$FEEA  09           ADD HL,BC
$FEEB  E5           PUSH HL
$FEEC  ED 4B 4B 5C  LD BC,(VARS)
$FEF0  A7           AND A
$FEF1  ED 42        SBC HL,BC
$FEF3  E1           POP HL
$FEF4  38 EB        JR C,NA_LOOP
$FEF6  C9           RET

;---------------------------------------------------------------------
; $FEF7-$FEFE   8 spare bytes (all $00)
;---------------------------------------------------------------------
$FEF7  DEFS 8,$00

;=====================================================================
; ISR  ($FEFF)  the interrupt service routine reached via $E9E9.
; RST $38 runs the ROM's own maskable-interrupt handler first (FRAMES
; and the keyboard scan), then the toolkit's own work is done with
; interrupts disabled.
;=====================================================================
ISR:
$FEFF  00           NOP
$FF00  00           NOP
$FF01  FF           RST $38            ; ROM keyboard/FRAMES handler
$FF02  F3           DI
$FF03  C5           PUSH BC
$FF04  D5           PUSH DE
$FF05  E5           PUSH HL
$FF06  F5           PUSH AF
$FF07  CD 10 FF     CALL ISR_WORK
$FF0A  F1           POP AF
$FF0B  E1           POP HL
$FF0C  D1           POP DE
$FF0D  C1           POP BC
$FF0E  FB           EI
$FF0F  C9           RET

;---------------------------------------------------------------------
; ISR_WORK  ($FF10)
; 1. the NEW lock: the key decoder produces $E6 (NEW) for "A" in K
;    mode; swap it for $E2 (STOP).  Then, unless SYMBOL SHIFT is being
;    held, throw the keypress away entirely.  Net effect: NEW cannot
;    be typed by accident, and STOP still works the normal way.
; 2. call whatever routine command N/Q/T left in V_USER.
;---------------------------------------------------------------------
ISR_WORK:
$FF10  21 08 5C     LD HL,LASTK
$FF13  3E E6        LD A,$E6           ; NEW
$FF15  BE           CP (HL)
$FF16  20 02        JR NZ,IW_STOP
$FF18  36 E2        LD (HL),$E2        ; -> STOP
IW_STOP:
$FF1A  3E E2        LD A,$E2
$FF1C  BE           CP (HL)
$FF1D  20 0F        JR NZ,IW_USER
$FF1F  3E 7F        LD A,$7F           ; half-row B N M SYMSHIFT SPACE
$FF21  DB FE        IN A,($FE)
$FF23  CB 4F        BIT 1,A            ; SYMBOL SHIFT held?
$FF25  20 07        JR NZ,IW_USER
$FF27  21 3B 5C     LD HL,FLAGS
$FF2A  3E D7        LD A,$D7           ; clear bits 5 and 3
$FF2C  A6           AND (HL)
$FF2D  77           LD (HL),A          ; discard the keypress
IW_USER:
$FF2E  2A 4D FF     LD HL,(V_USER)
$FF31  7C           LD A,H
$FF32  B5           OR L
$FF33  C8           RET Z
$FF34  E9           JP (HL)

;=====================================================================
; COMMAND X  —  N/Q/T/W off  ($FF35)
; Puts the machine back on the ROM's own IM1 interrupt.
;=====================================================================
CMD_X:
$FF35  F3           DI
$FF36  3E 3F        LD A,$3F
$FF38  ED 47        LD I,A
$FF3A  ED 56        IM 1
$FF3C  FB           EI
$FF3D  C9           RET

;=====================================================================
; COMMAND W  —  Disable NEW  ($FF3E)
; Enables IM2 with no user routine, so ISR_WORK's NEW lock is the only
; thing running.
;=====================================================================
CMD_W:
$FF3E  21 00 00     LD HL,$0000
;=====================================================================
; SET_IM2  ($FF41)  install HL as the per-interrupt routine and turn
; interrupt mode 2 on.  IM2_INIT must have run first.
;=====================================================================
SET_IM2:
$FF41  F3           DI
$FF42  22 4D FF     LD (V_USER),HL
$FF45  3E E8        LD A,$E8
$FF47  ED 47        LD I,A
$FF49  ED 5E        IM 2
$FF4B  FB           EI
$FF4C  C9           RET

;=====================================================================
; Interrupt-time variables ($FF4D-$FF57).  Values shown are whatever
; the captured image happened to contain.
;=====================================================================
V_USER:   $FF4D  DEFW $FE44   ; routine called each interrupt (0 = none)
V_INCR:   $FF4F  DEFW $000A   ; N: autoline increment
V_DELAY:  $FF51  DEFB $15     ; T: trace delay, 101 - speed
V_DIGIT:  $FF52  DEFB $00     ; N: digits still to feed
V_SCRN:   $FF53  DEFW $401F   ; Q/T: next display-file cell to paint
          $FF55  DEFB $13,$00,$3E  ; spare / uninitialised
;                          end of image, $FF57

;=====================================================================
; APPENDIX  —  every prompt script rendered, straight from the bytes
; (produced by interpreting the scripts above; verifies the decode)
;=====================================================================
; ==================================================================
; A   ($F1B0)
; ------------------------------------------------------------------
; 
;  SEARCH & REPLACE 

 START ADDRESS: <input num><-P_START><print dec> 

 FINISH ADDRESS: <input num><-P_FINISH><-P_DEST><print dec> 

 OLD STRING: <input str0><echo str0> 

 NEW STRING: <input str1><echo str1> 

 LIST ? (0=no): <input num><-P_INCR><print dec><check 22><check 23><check 24>
; <EXECUTE? y/n>
; ==================================================================
; B   ($F6F1)
; ------------------------------------------------------------------
; 
;  MACHINE CODE TO DATA 

 START ADDRESS: <input num><-P_START><print dec> 

 FINISH ADDRESS: <input num><-P_FINISH><print dec> 

<check 24> 1st LINE NUMBER: <input num><-P_DEST><print dec> 

 BYTES PER LINE: <input num><-P_INCR><print dec><err if 0><check 22>
; <run, resume $F714>
; ==================================================================
; C   ($F59D)
; ------------------------------------------------------------------
; 
;  COPY
; <run, resume $F5A0>
; ==================================================================
; D   ($F613)
; ------------------------------------------------------------------
; 
;  DELETE BLOCK OF PROGRAM 

 START ADDRESS: <input num><-P_START><print dec> 

 FINISH ADDRESS: <input num><-P_FINISH><-P_DEST><print dec><check 24><check 23>
; <EXECUTE? y/n>
; ==================================================================
; E   ($F642)
; ------------------------------------------------------------------
; 
;  CREATE REM LINE 

 LINE NUMBER: <input num><-P_FINISH><-P_DEST><print dec><check 23> 

 LENGTH: <input num><-P_INCR><print dec><err if 0> 

 SYMBOL: <input str0><echo str0>
; <EXECUTE? y/n>
; ==================================================================
; F   ($F9F2)
; ------------------------------------------------------------------
; 
;  REM DELETE 

 START ADDRESS: <input num><-P_START><print dec> 

 FINISH ADDRESS: <input num><-P_FINISH><-P_DEST><print dec> 

<check 24><check 23>
; <EXECUTE? y/n>
; ==================================================================
; G   ($FA84)
; ------------------------------------------------------------------
; 
;  DESIGN UDGs 

 CHARACTER: <input str0><echo str0>
; <EXECUTE? y/n>
; ==================================================================
; H   ($F17A)
; ------------------------------------------------------------------
; 
;  HEX & DECIMAL 

 Precede HEX nos with "#".
; <run, resume $F19C>
; ==================================================================
; I   ($F83D)
; ------------------------------------------------------------------
; 
;  STATUS REPORT 

 MEMORY LEFT: 
; <run, resume $F844>
; ==================================================================
; J   ($FCA7)
; ------------------------------------------------------------------
; 
;  MERGE PROGRAM LINES 

 START ADDRESS: <input num><-P_START><print dec> 

 FINISH ADDRESS: <input num><-P_FINISH><-P_DEST><print dec><check 24><check 23>
; <EXECUTE? y/n>
; ==================================================================
; K   ($FCFA)
; ------------------------------------------------------------------
; 
;  lower TO UPPER
; <run, resume $FCFF>
; ==================================================================
; L   ($FD06)
; ------------------------------------------------------------------
; 
;  UPPER TO lower
; <run, resume $FD0B>
; ==================================================================
; M   ($F327)
; ------------------------------------------------------------------
; 
;  MOVE
; <run, resume $F32A>
; ==================================================================
; N   ($FD77)
; ------------------------------------------------------------------
; 
;  AUTO LINE NUMBER 

 INCREMENT: <input num><-P_INCR><print dec><err if 0><check 22>
; <EXECUTE? y/n>
; ==================================================================
; O   ($FE94)
; ------------------------------------------------------------------
; 
;  FIND ADDRESS OF 1st SYMBOL 

 LINE NUMBER: <input num><-P_FINISH><-P_DEST><print dec><check 23>
; <EXECUTE? y/n>
; ==================================================================
; P   ($FC3F)
; ------------------------------------------------------------------
; 
;  COMPACT PROGRAM 

 START ADDRESS: <input num><-P_START><print dec> 

 FINISH ADDRESS: <input num><-P_FINISH><-P_DEST><print dec> 

<check 24><check 23>
; <EXECUTE? y/n>
; ==================================================================
; Q   ($FDEE)
; ------------------------------------------------------------------
; 
;  MEMORY LEFT
; <EXECUTE? y/n>
; ==================================================================
; R   ($F35F)
; ------------------------------------------------------------------
; 
;  RENUMBER
; <run, resume $F362>
; ==================================================================
; S   ($F988)
; ------------------------------------------------------------------
; 
;  SEARCH & LIST 

 START ADDRESS: <input num><-P_START><print dec> 

 FINISH ADDRESS: <input num><-P_FINISH><-P_DEST><print dec> 

<check 24><check 23> STRING: <input str0><echo str0>
; <EXECUTE? y/n>
; ==================================================================
; T   ($FDFA)
; ------------------------------------------------------------------
; 
;  TRACE ON 

 SPEED: <input num><-P_INCR><print dec><check 22>
; <EXECUTE? y/n>
; ==================================================================
; U   ($F6B3)
; ------------------------------------------------------------------
; 
;  UDGs TO DATA 

 NUMBER OF UDGs: <input num><-P_WORK2><-P_INCR><print dec><err if 0> 

 1st LINE NUMBER: <input num><-P_FINISH><-P_DEST><print dec><check 23><check 22>
; <run, resume $F6CB>
; ==================================================================
; V   ($F8E1)
; ------------------------------------------------------------------
;  LIST VARIABLES 

; <run, resume $F8E6>
; ==================================================================
; shared ASK_BLOCK   ($F33C)
; ------------------------------------------------------------------
;  BLOCK OF PROGRAM 

 START ADDRESS: <input num><-P_START><print dec> 

 1st UNAFFECTED ADDRESS: <input num><-P_FINISH><print dec> 

 DESTINATION: <input num><-P_DEST><print dec> 

<check 24><check 23> INCREMENT: <input num><-P_INCR><print dec><err if 0><check 22>
; <EXECUTE? y/n>
; ==================================================================
; B/U second page   ($F723)
; ------------------------------------------------------------------
;  

 DEC OR HEX: <input str0><echo str0>
; <EXECUTE? y/n>
; ==================================================================
; K/L second page   ($FD11)
; ------------------------------------------------------------------
;  CASE 

 START ADDRESS: <input num><-P_START><print dec> 

 FINISH ADDRESS: <input num><-P_FINISH><-P_DEST><print dec><check 24><check 23> 

 IN QUOTES? (Y/N): <input str0><echo str0>
; <EXECUTE? y/n>
; ==================================================================
; MENU  ($EA6F)
; ------------------------------------------------------------------
; 
;         TS 2068  TOOLKIT
; 
; A Alter Program N Autoline On   B Bytes to DATA O Locate Token  C Copy Lines    P Compactor     D Delete lines  Q Display MemoryE REM Create    R Renumber      F REM Delete    S Search & List G UDG Designer  T Trace on      H Hex & Dec     U UDGs to DATA  I Information   V List VariablesJ Merge Lines   W Disable NEW   K Upper Case    X N/Q/T/W off   L Lower Case    Y Uncorrupt     M Move Lines    Z Line Sort     
; 
;  PRESS A KEY FOLLOWED BY ENTER,
;  or just ENTER anytime for menu 

 (c) BOB MITCHELL 1985
; 
; <run, resume $EC70>

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.