; MULTISEARCH - ZX BASIC SEARCH & REPLACE tool
; Copyright 1985,1993 Simon N Goodwin.
; Relocatable Z80 code, 225 bytes long
;
         LFMT  64,96,3
         LPRT  1,27,91,50,119
;
; The above assembler directives for the ZA80 printer/list
; file select Elite pitch on the HP Deskjet 500 printer.
;
; Based on my article in YOUR SPECTRUM #12, pages 47-53
; Test file for ZA80 cross assembler, version 1.06
; This file may be freely distributed only as part of
; the SPECULATOR Amiga Spectrum emulator release.
;
;--------------------------------------------------------
;
; USAGE: Set S$ to the string you want to find (1-255
; bytes) and R$ to the replacement string (0-255 bytes)
; Call the start of the code (e.g. with PRINT USR 23296
; if loaded in the printer buffer) to replace all cases
; of the text in S$ in the program with the text in R$.
; For example:
;
;   10 LET S$="S$="
;   20 LET R$="X$="
;   30 LOAD "MULTISEARCH" CODE 23296
;   40 RANDOMIZE USR 23296
;
; When you run this, line 10 will be changed to read:
;
;   10 LET X$="X$="
;
; NOTES: MULTISEARCH does not replace the floating-point
; form of numbers unless you specify it in both strings. The
; YS article explains how to add the f.p. form to the digits.
;
; The changed lines are not error-checked so you may get a
; 'nonsense in BASIC' or other error reports at run-time if
; the altered line is not correct ZX BASIC.
;
; REPORTS:
;
; BREAK IN PROGRAM   User pressed SPACE while searching. Some
;                    changes may not have been made.
;
; NONSENSE IN BASIC  The altered program does not pass the ZX
;                    BASIC syntax checker. Change it back, or
;                    reload the original version!
;
; OK                 The requested changes have been made.
;
; OUT OF MEMORY      the replacement is longer than the
;                    pattern and MULTISEARCH ran out of memory
;                    before it reached the end of the program.

; PARAMETER ERROR    Search string is null, or either string
;                    is more than 255 characters long.
;
; VARIABLE NOT FOUND Either S$ or R$ is not assigned.
;
; The value returned by the USR call is not meaningful.
;
;--------------------------------------------------------
;
; Spectrum ZX BASIC system variable addresses
;
PROG:    EQU   23635       ; Points to start of program
VARS:    EQU   23627       ; Points to start of variables
;
; Temporary variables, allocated in ZX BASIC's MEM area
;
R_LEN:   EQU   23726       ; Pointer to replacement
L_LEN:   EQU   23724       ; Pointer to line length word
;
; 16K ZX ROM routine addresses
;
; SHRINK and EXPAND allocate or deallocate BC bytes at HL
; If space is tight EXPAND may return an 'out of memory' error
;
SHRINK:  EQU   19E8H       ; Deallocate BASIC space
EXPAND:  EQU   1655H       ; Allocate more BASIC space
F_VAR:   EQU   19B8H       ; Find next variable
;
; Byte constants
;
CODE:    EQU   3           ; ZX file type
NUMBER:  EQU   14          ; Code prefix of imbedded numbers
ENTER:   EQU   13          ; ASCII code of line end character
T_END:   EQU   128         ; Marker at end of variable table
;
; Amiga Speculator header - N.B. word values are Big Endian
;
HEADER:  DB    "ZX82"      ; Header signature
         DB    CODE,0      ; File type, uncompressed
         DB    0,LAST-FIND_S  ; Length - high byte (0) first
         DB    FIND_S/256,FIND_S & 255  ; Start (relocatable)
         DW    80H         ; BASIC variable offset (unused)
;
         ORG   23296       ; Start in the printer buffer
;
FIND_S:  LD    HL,(VARS)   ; Find search string S$
NEXT1:   LD    A,(HL)
         CP    "S"         ; Capital S signifies S$
         JR    Z,GOT_S
         CP    T_END
         JR    Z,ERROR
         CALL  F_VAR       ; Find the next variable
         EX    DE,HL
         JR    NEXT1
;
ERROR:   RST   8
         DB    1           ; Variable not found
;
LEN_ERR: RST   8
         DB    25          ; Parameter error (wrong string length)
;
; Now HL points at the string name S$, check its length
;
GOT_S:   INC   HL
         LD    A,(HL)      ; Check it is not null
         OR    A
         JR    Z,LEN_ERR
         INC   HL
         LD    A,(HL)      ; and not more than 255 bytes long
         OR    A
         JR    NZ,LEN_ERR
         INC   HL
         PUSH  HL
         POP   IX          ; Now IX -> text of S$
;
FINDR:   LD    HL,(VARS)   ; Find search string S$
NEXT2:   LD    A,(HL)
         CP    "R"         ; Capital R signifies R$
         JR    Z,GOT_R
         CP    T_END       ; Any more variables?
         JR    Z,ERROR
         CALL  F_VAR       ; Find the next one
         EX    DE,HL
         JR    NEXT2
;
GOT_R:   INC   HL
         LD    (R_LEN),HL
         INC   HL
         LD    A,(HL)      ; Not more than 255 bytes
         OR    A
         JR    NZ,LEN_ERR
;
; Prepare to search program
;
         LD    DE,(PROG)
         DEC   DE          ; Dummy line-end
;
; Main search loop; first find line-length
;
LINE:    INC   DE          ; Skip past line end marker
         INC   DE          ; Skip past line number
         INC   DE
         LD    (L_LEN),DE
         INC   DE
         INC   DE
FIND:    PUSH  DE
         LD    B,(IX-2)    ; Pattern length in B
         PUSH  IX
         POP   HL
MATCH:   LD    A,(DE)
         CP    (HL)
         JR    NZ,GO_ON
         INC   HL
         INC   DE
         DJNZ  MATCH
;
; Match found, work out delta length
;
         LD    HL,(R_LEN)
         LD    A,(HL)
         SUB   (IX-2)      ; A is extra space needed
         JR    Z,LEN_OK
         JR    NC,ADD_A
;
; Discard 256-A bytes from program
;
         NEG
         LD    C,A
         LD    HL,(L_LEN)
         LD    E,(HL)
         INC   HL
         LD    D,(HL)
         EX    DE,HL
         OR    A
         SBC   HL,BC       ; New line length
         EX    DE,HL
         LD    (HL),D
         DEC   HL
         LD    (HL),E
;
; Adjust pointers to R$ and S$
;
         PUSH  IX
         POP   HL
         SBC   HL,BC
         PUSH  HL
         POP   IX
         LD    HL,(R_LEN)
         SBC   HL,BC
         LD    (R_LEN),HL
         POP   HL
         PUSH  HL          ; Shrink from start
         CALL  SHRINK
         JR    LEN_OK
;
; Extended jumps to ensure relocatability
;
FINDX:   JR    FIND
LINEX:   JR    LINE
;
; Add A bytes to program
;
ADD_A:   LD    C,A
         PUSH  DE
         LD    HL,(L_LEN)
         LD    E,(HL)
         INC   HL
         LD    D,(HL)
         EX    DE,HL
         ADD   HL,BC       ; New line length
         EX    DE,HL
         LD    (HL),D
         DEC   HL
         LD    (HL),E
;
; Adjust pointers to R$ and S$
;
         ADD   IX,BC
         LD    HL,(R_LEN)
         ADD   HL,BC
         LD    (R_LEN),HL
         POP   HL
         CALL  EXPAND
;
; Copy new data to the space in the program
;
LEN_OK:  POP   DE
         LD    HL,(R_LEN)
         LD    B,0
         LD    C,(HL)
         LD    A,C
         OR    A           ; Check if replacement is null
         JR    Z,NEXT
         INC   HL          ; Bump HL past length word
         INC   HL
         LDIR
         JR    NEXT        ; Search on from (DE)
;
; Check the next position
;
GO_ON:   POP   DE
         INC   DE
NEXT:    LD    A,127       ; Poll last keyboard row
         IN    A,(254)     ; Via the ZX82 ULA port
         RRA
         JR    C,CONTINUE
         RST   8
         DB    20
;
CONTINUE: LD   HL,(VARS)
         OR    A           ; Ensure carry is clear
         SBC   HL,DE
         RET   C           ; Return at end of program
         LD    A,(DE)
         CP    ENTER       ; End of line?
         JR    Z,LINEX
         CP    NUMBER
         JR    NZ,FINDX    ; Keep looking
         LD    HL,6
         ADD   HL,DE       ; Skip marker & 5 binary bytes
         EX    DE,HL
         JR    CONTINUE
;
LAST:    END


