commit 38cf77982271be722123778e303ae9322d2eca1a
parent 59f12078964ac943b79d096a9179078d955c5dd6
Author: Brian Swetland <swetland@frotz.net>
Date: Fri, 19 Aug 2022 17:57:54 -0700
Trim zx81plus.asm down to zx81v2.asm
This contains only the source to recreate the original v2 ROM
and is otherwise tidied up to improve readability.
Diffstat:
A | zx81v2.asm | | | 10192 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 10192 insertions(+), 0 deletions(-)
diff --git a/zx81v2.asm b/zx81v2.asm
@@ -0,0 +1,10192 @@
+; ===========================================================
+; An Assembly Listing of the Operating System of the ZX81 ROM
+; ===========================================================
+
+; 19-DEC-2022
+; -----------
+;
+; << http://github.com/swetland/zx80-rom zx81v2.asm
+;
+; Trimmed the contents of this back to only the parts necessary
+; to build a byte-for-byte identical image to the ZX81 ROM (v2)
+; using the "z80asm" assembler.
+;
+; Attempted to align comments, normalize vertical spacing, and
+; generally tidy things up for maximal readability. The mark_XXXX
+; labels have all been removed.
+;
+; The big advantage vs the 2004 version is that this version
+; includes all the labels and equates from the 09-FEB-2018 "Dual"
+; version, massively improving readability, but discarding the
+; conditional build bits for the SG81 variant
+;
+; Various comments added in the 2018 version are left intact.
+
+; 09-FEB-2018
+; -----------
+;
+; << Found via search on hackaday cdn, cannot find original article or author
+; << https://cdn.hackaday.io/files/289631239152992/ZX81_dual_2018-02-09.htm
+;
+; Merged "Shoulders of Giants" changes (as conditional build options)
+; into the 13-DEC-2004 version, and real labels and equates were added
+; somewhere along the way. A whole additional ROM for a graphics expander
+; board was added as a "bonus" at the end and there was some in-progress
+; work for 40 column PAL displays...
+
+; 13-DEC-2004
+; -----------
+;
+; << Maintained by Geoff Wearmouth and hosted at www.wearmouth.demon.co.uk
+; << Site went offline around the end of 2015
+;
+; Work in progress.
+; This file will cross-assemble an original version of the "Improved"
+; ZX81 ROM.
+; The file can be modified to change the behaviour of the ROM
+; when used in emulators although there is no spare space available.
+;
+; The documentation is incomplete and if you can find a copy
+; of "The Complete Spectrum ROM Disassembly" then many routines
+; such as POINTERS and most of the mathematical routines are
+; similar and often identical.
+;
+; I've used the labels from the above book in this file and also
+; some from the more elusive Complete ZX81 ROM Disassembly
+; by the same publishers, Melbourne House.
+
+
+org 0
+
+CHARS_PER_LINE_WINDOW: equ 32
+CHARS_HORIZONTAL: equ 32
+CHARS_VERTICAL: equ 24
+
+FALSE: equ 0
+
+; define stuff sensibly:
+;
+; I/O locations:
+;
+IO_PORT_TAPE: equ $FF ; write
+IO_PORT_SCREEN: equ $FF ; write
+
+IO_PORT_KEYBOARD_RD: equ $FE ; A0 low
+IO_PORT_NMI_GEN_ON: equ $FE ; A0 low
+IO_PORT_NMI_GEN_OFF: equ $FD ; A1 low
+IO_PORT_PRINTER: equ $FB ; A2 low
+
+;
+; System variables:
+;
+RAMBASE: equ $4000
+ERR_NR: equ $4000 ; The report code. Incremented before printing.
+FLAGS: equ $4001 ; Bit 0: Suppression of leading space.
+ ; Bit 1: Control Flag for the printer.
+ ; Bit 2: Selects K or F mode; or, F or G
+ ; Bit 6: FP no. or string parameters.
+ ; Bit 7: Reset during syntax checking."
+ERR_SP: equ $4002 ; Pointer to the GOSUB stack.
+RAMTOP: equ $4004 ; The top of available RAM, or as specified.
+MODE: equ $4006 ; Holds the code for K or F
+PPC: equ $4007 ; Line number of the current statement.
+PPC_hi: equ PPC+1
+VERSN: equ $4009 ; Marks the start of the RAM that is saved.
+E_PPC: equ $400A ; The BASIC line with the cursor
+D_FILE: equ $400C ; Pointer to Display file
+DF_CC: equ $400E ; Address for PRINT AT position
+VARS: equ $4010 ; Pointer to variable area
+DEST: equ $4012 ; Address of current variable in program area
+E_LINE: equ $4014 ; Pointer to workspace
+E_LINE_hi: equ E_LINE+1
+CH_ADD: equ $4016 ; Pointer for scanning a line, in program or workspace
+X_PTR: equ $4018 ; Pointer to syntax error.
+X_PTR_lo: equ X_PTR
+X_PTR_hi: equ X_PTR+1
+STKBOT: equ $401A ; Pointer to calculator stack bottom.
+STKEND: equ $401C ; Pointer to calculator stack end.
+BERG: equ $401E ; Used for many different counting purposes
+MEM: equ $401F ; Pointer to base of table of fp. nos, either in calc. stack or variable area.
+; ; Unused by ZX BASIC. Or FLAG Y for G007
+DF_SZ: equ $4022 ; Number of lines in the lower screen
+S_TOP: equ $4023 ; Current line number of automatic listing
+LAST_K: equ $4025 ; Last Key pressed
+DEBOUNCE_VAR: equ $4027 ; The de-bounce status
+MARGIN: equ $4028 ; Adjusts for differing TV standards
+NXTLIN: equ $4029 ; Next BASIC line to be interpreted
+OLDPPC: equ $402B ; Last line number, in case needed.
+FLAGX: equ $402D ; Bit 0: Reset indicates an arrayed variable
+ ; Bit 1: Reset indicates a given variable exists
+ ; Bit 5: Set during INPUT mode
+ ; Bit 7: Set when INPUT is to be numeric
+STRLEN: equ $402E ; Length of a string, or a BASIC line
+STRLEN_lo: equ STRLEN ;
+T_ADDR: equ $4030 ; Pointer to parameter table. & distinguishes between PLOT & UNPLOT
+SEED: equ $4032 ; For RANDOM function
+FRAMES: equ $4034 ; Frame counter
+FRAMES_hi: equ FRAMES+1 ;
+COORDS: equ $4036 ; X & Y for PLOT
+COORDS_x: equ COORDS ;
+PR_CC: equ $4038 ; Print buffer counter
+S_POSN: equ $4039 ; Line & Column for PRINT AT
+S_POSN_x: equ $4039 ;
+S_POSN_y: equ $403A ;
+CDFLAG: equ $403B ; Bit 6 = the true fast/slow flag
+ ; Bit 7 = copy of the fast/slow flag. RESET when FAST needed
+PRBUFF: equ $403C ; Printer buffer
+PRBUFF_END: equ $405C ;
+MEM_0_1st: equ $405D ; room for 5 floating point numbers (meme_0 to mem_ 5???)
+; $407B ; unused. Or RESTART to G007
+; $407D ; The BASIC program starts here
+
+; First byte after system variables:
+USER_RAM: equ $407D
+MAX_RAM: equ $7FFF
+
+;===============================
+; ZX81 constants:
+;===============================
+; ZX characters (not the same as ASCII)
+;-------------------------------
+ZX_SPACE: equ $00
+ZX_QUOTE: equ $0B
+ZX_POUND: equ $0C
+ZX_DOLLAR: equ $0D
+ZX_COLON: equ $0E
+ZX_QUERY: equ $0F
+ZX_BRACKET_LEFT: equ $10
+ZX_BRACKET_RIGHT: equ $11
+ZX_GREATER_THAN: equ $12
+ZX_LESS_THAN: equ $13
+ZX_EQUAL: equ $14
+ZX_PLUS: equ $15
+ZX_MINUS: equ $16
+ZX_STAR: equ $17
+ZX_SLASH: equ $18
+ZX_SEMICOLON: equ $19
+ZX_COMMA: equ $1A
+ZX_PERIOD: equ $1B
+ZX_0: equ $1C
+ZX_1: equ $1D
+ZX_2: equ $1E
+ZX_3: equ $1F
+ZX_4: equ $20
+ZX_5: equ $21
+ZX_6: equ $22
+ZX_7: equ $23
+ZX_8: equ $24
+ZX_9: equ $25
+ZX_A: equ $26
+ZX_B: equ $27
+ZX_C: equ $28
+ZX_D: equ $29
+ZX_E: equ $2A
+ZX_F: equ $2B
+ZX_G: equ $2C
+ZX_H: equ $2D
+ZX_I: equ $2E
+ZX_J: equ $2F
+ZX_K: equ $30
+ZX_L: equ $31
+ZX_M: equ $32
+ZX_N: equ $33
+ZX_O: equ $34
+ZX_P: equ $35
+ZX_Q: equ $36
+ZX_R: equ $37
+ZX_S: equ $38
+ZX_T: equ $39
+ZX_U: equ $3A
+ZX_V: equ $3B
+ZX_W: equ $3C
+ZX_X: equ $3D
+ZX_Y: equ $3E
+ZX_Z: equ $3F
+ZX_RND: equ $40
+ZX_INKEY_STR: equ $41
+ZX_PI: equ $42
+;
+; $43 to $6F not used
+;
+ZX_cursor_up: equ $70
+ZX_cursor_down: equ $71
+ZX_cursor_left: equ $72
+ZX_cursor_right: equ $73
+
+ZX_GRAPHICS: equ $74
+ZX_EDIT: equ $75
+ZX_NEWLINE: equ $76
+ZX_RUBOUT: equ $77
+ZX_KL: equ $78
+ZX_FUNCTION: equ $79
+;
+; $7A to $7F not used
+;
+ZX_CURSOR: equ $7F
+;
+; $80 to $BF are inverses of $00 to $3F
+ZX_INV_QUOTE: equ $8B
+ZX_INV_POUND: equ $8C
+ZX_INV_DOLLAR: equ $8D
+ZX_INV_COLON: equ $8E
+ZX_INV_QUERY: equ $8F
+ZX_INV_BRACKET_RIGHT: equ $90
+ZX_INV_BRACKET_LEFT: equ $91
+ZX_INV_GT: equ $92
+
+ZX_INV_PLUS: equ $95
+ZX_INV_MINUS: equ $96
+
+ZX_INV_K: equ $B0
+ZX_INV_S: equ $B8
+
+ZX_DOUBLE_QUOTE: equ $C0
+ZX_AT: equ $C1
+ZX_TAB: equ $C2
+; not used: equ $C3
+ZX_CODE: equ $C4
+ZX_VAL: equ $C5
+ZX_LEN: equ $C6
+ZX_SIN: equ $C7
+ZX_COS: equ $C8
+ZX_TAN: equ $C9
+ZX_ASN: equ $CA
+ZX_ACS: equ $CB
+ZX_ATN: equ $CC
+ZX_LN: equ $CD
+ZX_EXP: equ $CE
+ZX_INT: equ $CF
+
+ZX_SQR: equ $D0
+ZX_SGN: equ $D1
+ZX_ABS: equ $D2
+ZX_PEEK: equ $D3
+ZX_USR: equ $D4
+ZX_STR_STR: equ $D5 ; STR$
+ZX_CHR_STR: equ $D6 ; CHR$
+ZX_NOT: equ $D7
+ZX_POWER: equ $D8
+ZX_OR: equ $D9
+ZX_AND: equ $DA
+ZX_LESS_OR_EQUAL: equ $DB
+ZX_GREATER_OR_EQUAL: equ $DC
+ZX_NOT_EQUAL: equ $DD
+ZX_THEN: equ $DE
+ZX_TO: equ $DF
+
+ZX_STEP: equ $E0
+ZX_LPRINT: equ $E1
+ZX_LLIST: equ $E2
+ZX_STOP: equ $E3
+ZX_SLOW: equ $E4
+ZX_FAST: equ $E5
+ZX_NEW: equ $E6
+ZX_SCROLL: equ $E7
+ZX_CONT: equ $E8
+ZX_DIM: equ $E9
+ZX_REM: equ $EA
+ZX_FOR: equ $EB
+ZX_GOTO: equ $EC
+ZX_GOSUB: equ $ED
+ZX_INPUT: equ $EE
+ZX_LOAD: equ $EF
+
+ZX_LIST: equ $F0
+ZX_LET: equ $F1
+ZX_PAUSE: equ $F2
+ZX_NEXT: equ $F3
+ZX_POKE: equ $F4
+ZX_PRINT: equ $F5
+ZX_PLOT: equ $F6
+ZX_RUN: equ $F7
+ZX_SAVE: equ $F8
+ZX_RAND: equ $F9
+ZX_IF: equ $FA
+ZX_CLS: equ $FB
+ZX_UNPLOT: equ $FC
+ZX_CLEAR: equ $FD
+ZX_RETURN: equ $FE
+ZX_COPY: equ $FF
+
+
+;
+_CLASS_00: equ 0
+_CLASS_01: equ 1
+_CLASS_02: equ 2
+_CLASS_03: equ 3
+_CLASS_04: equ 4
+_CLASS_05: equ 5
+_CLASS_06: equ 6
+
+
+
+; These values taken from BASIC manual
+;
+;
+ERROR_CODE_SUCCESS: equ 0
+ERROR_CODE_CONTROL_VARIABLE: equ 1
+ERROR_CODE_UNDEFINED_VARIABLE: equ 2
+ERROR_CODE_SUBSCRIPT_OUT_OF_RANGE: equ 3
+ERROR_CODE_NOT_ENOUGH_MEMORY: equ 4
+ERROR_CODE_NO_ROOM_ON_SCREEN: equ 5
+ERROR_CODE_ARITHMETIC_OVERFLOW: equ 6
+ERROR_CODE_RETURN_WITHOUT_GOSUB: equ 7
+ERROR_CODE_INPUT_AS_A_COMMAND: equ 8
+ERROR_CODE_STOP: equ 9
+ERROR_CODE_INVALID_ARGUMENT: equ 10
+ERROR_CODE_INTEGER_OUT_OF_RANGE: equ 11
+ERROR_CODE_VAL_STRING_INVALID: equ 12
+ERROR_CODE_BREAK: equ 13
+ERROR_CODE_EMPTY_PROGRAM_NAME: equ 15
+
+;
+; codes for Forth-like calculator
+;
+__jump_true: equ $00
+__exchange: equ $01
+__delete: equ $02
+__subtract: equ $03
+__multiply: equ $04
+__division: equ $05
+__to_power: equ $06
+__or: equ $07
+__boolean_num_and_num: equ $08
+__num_l_eql: equ $09
+__num_gr_eql: equ $0A
+__nums_neql: equ $0B
+__num_grtr: equ $0C
+__num_less: equ $0D
+__nums_eql: equ $0E
+__addition: equ $0F
+__strs_and_num: equ $10
+__str_l_eql: equ $11
+__str_gr_eql: equ $12
+__strs_neql: equ $13
+__str_grtr: equ $14
+__str_less: equ $15
+__strs_eql: equ $16
+__strs_add: equ $17
+__negate: equ $18
+__code: equ $19
+__val: equ $1A
+__len: equ $1B
+__sin: equ $1C
+__cos: equ $1D
+__tan: equ $1E
+__asn: equ $1F
+__acs: equ $20
+__atn: equ $21
+__ln: equ $22
+__exp: equ $23
+__int: equ $24
+__sqr: equ $25
+__sgn: equ $26
+__abs: equ $27
+__peek: equ $28
+__usr_num: equ $29
+__str_dollar: equ $2A
+__chr_dollar: equ $2B
+__not: equ $2C
+__duplicate: equ $2D
+__n_mod_m: equ $2E
+__jump: equ $2F
+__stk_data: equ $30
+__dec_jr_nz: equ $31
+__less_0: equ $32
+__greater_0: equ $33
+__end_calc: equ $34
+__get_argt: equ $35
+__truncate: equ $36
+__fp_calc_2: equ $37
+__e_to_fp: equ $38
+
+;
+; __series_xx: equ $39 : $80__$9F.
+; tells the stack machine to push
+; 0 to 31 floating-point values on the stack.
+;
+__series_06: equ $86
+__series_08: equ $88
+__series_0C: equ $8C
+; __stk_const_xx: equ $3A : $A0__$BF.
+; __st_mem_xx: equ $3B : $C0__$DF.
+; __get_mem_xx: equ $3C : $E0__$FF.
+
+__st_mem_0: equ $C0
+__st_mem_1: equ $C1
+__st_mem_2: equ $C2
+__st_mem_3: equ $C3
+__st_mem_4: equ $C4
+__st_mem_5: equ $C5
+__st_mem_6: equ $C6
+__st_mem_7: equ $C7
+
+__get_mem_0: equ $E0
+__get_mem_1: equ $E1
+__get_mem_2: equ $E2
+__get_mem_3: equ $E3
+__get_mem_4: equ $E4
+
+__stk_zero: equ $A0
+__stk_one: equ $A1
+__stk_half: equ $A2
+__stk_half_pi: equ $A3
+__stk_ten: equ $A4
+
+;*****************************************
+;** Part 1. RESTART ROUTINES AND TABLES **
+;*****************************************
+
+; THE 'START'
+
+; All Z80 chips start at location zero.
+; At start-up the Interrupt Mode is 0, ZX computers use Interrupt Mode 1.
+; Interrupts are disabled .
+
+START:
+ OUT (IO_PORT_NMI_GEN_OFF),A ; Turn off the NMI generator if this ROM is
+ ; running in ZX81 hardware. This does nothing
+ ; if this ROM is running within an upgraded
+ ; ZX80.
+ LD BC,MAX_RAM ; Set BC to the top of possible RAM.
+ ; The higher unpopulated addresses are used for
+ ; video generation.
+ JP RAM_CHECK ; Jump forward to RAM_CHECK.
+
+
+; THE 'ERROR' RESTART
+
+; The error restart deals immediately with an error.
+; ZX computers execute the same code in runtime as when checking syntax.
+; If the error occurred while running a program
+; then a brief report is produced.
+; If the error occurred while entering a BASIC line or in input etc.,
+; then the error marker indicates the exact point at which the error lies.
+
+ERROR_1:
+ LD HL,(CH_ADD) ; fetch character address from CH_ADD.
+ LD (X_PTR),HL ; and set the error pointer X_PTR.
+ JR ERROR_2 ; forward to continue at ERROR_2.
+
+; THE 'PRINT A CHARACTER' RESTART
+
+; This restart prints the character in the accumulator using the alternate
+; register set so there is no requirement to save the main registers.
+; There is sufficient room available to separate a space (zero) from other
+; characters as leading spaces need not be considered with a space.
+
+PRINT_A:
+ AND A ; test for zero - space.
+ JP NZ,PRINT_CH ; jump forward if not to PRINT_CH.
+
+ JP PRINT_SP ; jump forward to PRINT_SP.
+
+ DEFB $FF ; unused location.
+
+; THE 'COLLECT A CHARACTER' RESTART
+
+; The character addressed by the system variable CH_ADD is fetched and if it
+; is a non-space, non-cursor character it is returned else CH_ADD is
+; incremented and the new addressed character tested until it is not a space.
+
+GET_CHAR:
+ LD HL,(CH_ADD) ; set HL to character address CH_ADD.
+ LD A,(HL) ; fetch addressed character to A.
+
+TEST_SP:
+ AND A ; test for space.
+ RET NZ ; return if not a space
+
+ NOP ; else trickle through
+ NOP ; to the next routine.
+
+; THE 'COLLECT NEXT CHARACTER' RESTART
+
+; The character address is incremented and the new addressed character is
+; returned if not a space, or cursor, else the process is repeated.
+
+NEXT_CHAR:
+ CALL CH_ADD_PLUS_1 ; gets next immediate
+ ; character.
+ JR TEST_SP ; back
+
+ DEFB $FF, $FF, $FF ; unused locations.
+
+; THE 'FLOATING POINT CALCULATOR' RESTART
+
+; this restart jumps to the recursive floating-point calculator.
+; the ZX81's internal, FORTH-like, stack-based language.
+;
+; In the five remaining bytes there is, appropriately, enough room for the
+; end-calc literal - the instruction which exits the calculator.
+
+FP_CALC:
+ JP CALCULATE ; jump immediately to the CALCULATE routine.
+
+end_calc:
+ POP AF ; drop the calculator return address RE_ENTRY
+ EXX ; switch to the other set.
+
+ EX (SP),HL ; transfer H'L' to machine stack for the
+ ; return address.
+ ; when exiting recursion then the previous
+ ; pointer is transferred to H'L'.
+
+ EXX ; back to main set.
+ RET ; return.
+
+; THE 'MAKE BC SPACES' RESTART
+
+; This restart is used eight times to create, in workspace, the number of
+; spaces passed in the BC register.
+
+BC_SPACES:
+ PUSH BC ; push number of spaces on stack.
+ LD HL,(E_LINE) ; fetch edit line location from E_LINE.
+ PUSH HL ; save this value on stack.
+ JP RESERVE ; jump forward to continue at RESERVE.
+
+
+_START: equ $00
+_ERROR_1: equ $08
+_PRINT_A: equ $10
+_GET_CHAR: equ $18
+_NEXT_CHAR: equ $20
+_FP_CALC: equ $28
+_BC_SPACES: equ $30
+
+; THE 'INTERRUPT' RESTART
+
+; The Mode 1 Interrupt routine is concerned solely with generating the central
+; television picture.
+; On the ZX81 interrupts are enabled only during the interrupt routine,
+; although the interrupt
+;
+; This Interrupt Service Routine automatically disables interrupts at the
+; outset and the last interrupt in a cascade exits before the interrupts are
+; enabled.
+;
+; There is no DI instruction in the ZX81 ROM.
+;
+; A maskable interrupt is triggered when bit 6 of the Z80's Refresh register
+; changes from set to reset.
+;
+; The Z80 will always be executing a HALT (NEWLINE) when the interrupt occurs.
+; A HALT instruction repeatedly executes NOPS but the seven lower bits
+; of the Refresh register are incremented each time as they are when any
+; simple instruction is executed. (The lower 7 bits are incremented twice for
+; a prefixed instruction)
+;
+; This is controlled by the Sinclair Computer Logic Chip - manufactured from
+; a Ferranti Uncommitted Logic Array.
+;
+; When a Mode 1 Interrupt occurs the Program Counter, which is the address in
+; the upper echo display following the NEWLINE/HALT instruction, goes on the
+; machine stack. 193 interrupts are required to generate the last part of
+; the 56th border line and then the 192 lines of the central TV picture and,
+; although each interrupt interrupts the previous one, there are no stack
+; problems as the 'return address' is discarded each time.
+;
+; The scan line counter in C counts down from 8 to 1 within the generation of
+; each text line. For the first interrupt in a cascade the initial value of
+; C is set to 1 for the last border line.
+; Timing is of the utmost importance as the RH border, horizontal retrace
+; and LH border are mostly generated in the 58 clock cycles this routine
+; takes .
+
+INTERRUPT:
+ DEC C ; (4) decrement C - the scan line counter.
+ JP NZ,SCAN_LINE ; (10/10) JUMP forward if not zero to SCAN_LINE
+
+ POP HL ; (10) point to start of next row in display
+ ; file.
+
+ DEC B ; (4) decrement the row counter. (4)
+ RET Z ; (11/5) return when picture complete to R_IX_1_LAST_NEWLINE
+ ; with interrupts disabled.
+
+ SET 3,C ; (8) Load the scan line counter with eight.
+ ; Note. LD C,$08 is 7 clock cycles which
+ ; is way too fast.
+; ->
+
+WAIT_INT:
+;
+; NB $DD is for 32-column display
+;
+ LD R,A ; (9) Load R with initial rising value $DD.
+
+ EI ; (4) Enable Interrupts. [ R is now $DE ].
+
+ JP (HL) ; (4) jump to the echo display file in upper
+ ; memory and execute characters $00 - $3F
+ ; as NOP instructions. The video hardware
+ ; is able to read these characters and,
+ ; with the I register is able to convert
+ ; the character bitmaps in this ROM into a
+ ; line of bytes. Eventually the NEWLINE/HALT
+ ; will be encountered before R reaches $FF.
+ ; It is however the transition from $FF to
+ ; $80 that triggers the next interrupt.
+ ; [ The Refresh register is now $DF ]
+
+
+SCAN_LINE:
+ POP DE ; (10) discard the address after NEWLINE as the
+ ; same text line has to be done again
+ ; eight times.
+
+ RET Z ; (5) Harmless Nonsensical Timing.
+ ; (condition never met)
+
+ JR WAIT_INT ; (12) back to WAIT_INT
+
+; Note. that a computer with less than 4K or RAM will have a collapsed
+; display file and the above mechanism deals with both types of display.
+;
+; With a full display, the 32 characters in the line are treated as NOPS
+; and the Refresh register rises from $E0 to $FF and, at the next instruction
+; - HALT, the interrupt occurs.
+; With a collapsed display and an initial NEWLINE/HALT, it is the NOPs
+; generated by the HALT that cause the Refresh value to rise from $E0 to $FF,
+; triggering an Interrupt on the next transition.
+; This works happily for all display lines between these extremes and the
+; generation of the 32 character, 1 pixel high, line will always take 128
+; clock cycles.
+
+
+; THE 'INCREMENT CH_ADD' SUBROUTINE
+
+; This is the subroutine that increments the character address system variable
+; and returns if it is not the cursor character. The ZX81 has an actual
+; character at the cursor position rather than a pointer system variable
+; as is the case with prior and subsequent ZX computers.
+
+CH_ADD_PLUS_1:
+ LD HL,(CH_ADD) ; fetch character address to CH_ADD.
+
+TEMP_PTR1:
+ INC HL ; address next immediate location.
+
+TEMP_PTR2:
+ LD (CH_ADD),HL ; update system variable CH_ADD.
+
+ LD A,(HL) ; fetch the character.
+ CP ZX_CURSOR ; compare to cursor character.
+ RET NZ ; return if not the cursor.
+
+ JR TEMP_PTR1 ; back for next character to TEMP_PTR1.
+
+
+; THE 'ERROR_2' BRANCH
+
+; This is a continuation of the error restart.
+; If the error occurred in runtime then the error stack pointer will probably
+; lead to an error report being printed unless it occurred during input.
+; If the error occurred when checking syntax then the error stack pointer
+; will be an editing routine and the position of the error will be shown
+; when the lower screen is reprinted.
+
+ERROR_2:
+ POP HL ; pop the return address which points to the
+ ; DEFB, error code, after the RST 08.
+ LD L,(HL) ; load L with the error code. HL is not needed
+ ; anymore.
+
+ERROR_3:
+ LD (IY+ERR_NR-RAMBASE),L ; place error code in system variable ERR_NR
+ LD SP,(ERR_SP) ; set the stack pointer from ERR_SP
+ CALL SLOW_FAST ; selects slow mode.
+ JP SET_MIN ; exit to address on stack via routine SET_MIN.
+
+ DEFB $FF ; unused.
+
+; THE 'NON MASKABLE INTERRUPT' ROUTINE
+
+; Jim Westwood's technical dodge using Non-Maskable Interrupts solved the
+; flicker problem of the ZX80 and gave the ZX81 a multi-tasking SLOW mode
+; with a steady display. Note that the AF' register is reserved for this
+; function and its interaction with the display routines. When counting
+; TV lines, the NMI makes no use of the main registers.
+; The circuitry for the NMI generator is contained within the SCL (Sinclair
+; Computer Logic) chip.
+; ( It takes 32 clock cycles while incrementing towards zero ).
+
+NMI:
+ EX AF,AF' ; (4) switch in the NMI's copy of the
+ ; accumulator.
+ INC A ; (4) increment.
+ JP M,NMI_RET ; (10/10) jump, if minus, to NMI_RET as this is
+ ; part of a test to see if the NMI
+ ; generation is working or an intermediate
+ ; value for the ascending negated blank
+ ; line counter.
+
+ JR Z,NMI_CONT ; (12) forward to NMI_CONT
+ ; when line count has incremented to zero.
+
+; Note. the synchronizing NMI when A increments from zero to one takes this
+; 7 clock cycle route making 39 clock cycles in all.
+
+NMI_RET:
+ EX AF,AF' ; (4) switch out the incremented line counter
+ ; or test result $80
+ RET ; (10) return to User application for a while.
+
+; This branch is taken when the 55 (or 31) lines have been drawn.
+
+NMI_CONT:
+ EX AF,AF' ; (4) restore the main accumulator.
+
+ PUSH AF ; (11) * Save Main Registers
+ PUSH BC ; (11) **
+ PUSH DE ; (11) ***
+ PUSH HL ; (11) ****
+
+; the next set-up procedure is only really applicable when the top set of
+; blank lines have been generated.
+
+ LD HL,(D_FILE) ; (16) fetch start of Display File from D_FILE
+ ; points to the HALT at beginning.
+ SET 7,H ; (8) point to upper 32K 'echo display file'
+
+ HALT ; (1) HALT synchronizes with NMI.
+ ; Used with special hardware connected to the
+ ; Z80 HALT and WAIT lines to take 1 clock cycle.
+
+
+; the NMI has been generated - start counting.
+; The cathode ray is at the RH side of the TV.
+;
+; First the NMI servicing, similar to CALL = 17 clock cycles.
+; Then the time taken by the NMI for zero-to-one path = 39 cycles
+; The HALT above = 01 cycles.
+; The two instructions below = 19 cycles.
+; The code at R_IX_1 up to and including the CALL = 43 cycles.
+; The Called routine at DISPLAY_5 = 24 cycles.
+; -------------------------------------- ---
+; Total Z80 instructions = 143 cycles.
+;
+; Meanwhile in TV world,
+; Horizontal retrace = 15 cycles.
+; Left blanking border 8 character positions = 32 cycles
+; Generation of 75% scanline from the first NEWLINE = 96 cycles
+; --------------------------------------- ---
+; = 143 cycles
+;
+; Since at the time the first JP (HL) is encountered to execute the echo
+; display another 8 character positions have to be put out, then the
+; Refresh register need to hold $F8. Working back and counteracting
+; the fact that every instruction increments the Refresh register then
+; the value that is loaded into R needs to be $F5. :-)
+;
+;
+ OUT (IO_PORT_NMI_GEN_OFF),A ; (11) Stop the NMI generator.
+
+ JP (IX) ; (8) forward to R_IX_1 (after top) or R_IX_2
+
+; ****************
+; ** KEY TABLES **
+; ****************
+
+; THE 'UNSHIFTED' CHARACTER CODES
+
+K_UNSHIFT:
+ DEFB ZX_Z
+ DEFB ZX_X
+ DEFB ZX_C
+ DEFB ZX_V
+
+ DEFB ZX_A
+ DEFB ZX_S
+ DEFB ZX_D
+ DEFB ZX_F
+ DEFB ZX_G
+
+ DEFB ZX_Q
+ DEFB ZX_W
+ DEFB ZX_E
+ DEFB ZX_R
+ DEFB ZX_T
+
+ DEFB ZX_1
+ DEFB ZX_2
+ DEFB ZX_3
+ DEFB ZX_4
+ DEFB ZX_5
+
+ DEFB ZX_0
+ DEFB ZX_9
+ DEFB ZX_8
+ DEFB ZX_7
+ DEFB ZX_6
+
+ DEFB ZX_P
+ DEFB ZX_O
+ DEFB ZX_I
+ DEFB ZX_U
+ DEFB ZX_Y
+
+ DEFB ZX_NEWLINE
+ DEFB ZX_L
+ DEFB ZX_K
+ DEFB ZX_J
+ DEFB ZX_H
+
+ DEFB ZX_SPACE
+ DEFB ZX_PERIOD
+ DEFB ZX_M
+ DEFB ZX_N
+ DEFB ZX_B
+
+; THE 'SHIFTED' CHARACTER CODES
+
+K_SHIFT:
+ DEFB ZX_COLON ; :
+ DEFB ZX_SEMICOLON ; ;
+ DEFB ZX_QUERY ; ?
+ DEFB ZX_SLASH ; /
+ DEFB ZX_STOP
+ DEFB ZX_LPRINT
+ DEFB ZX_SLOW
+ DEFB ZX_FAST
+ DEFB ZX_LLIST
+ DEFB $C0 ; ""
+ DEFB ZX_OR
+ DEFB ZX_STEP
+ DEFB $DB ; <=
+ DEFB $DD ; <>
+ DEFB ZX_EDIT
+ DEFB ZX_AND
+ DEFB ZX_THEN
+ DEFB ZX_TO
+ DEFB $72 ; cursor-left
+ DEFB ZX_RUBOUT
+ DEFB ZX_GRAPHICS
+ DEFB $73 ; cursor-right
+ DEFB $70 ; cursor-up
+ DEFB $71 ; cursor-down
+ DEFB ZX_QUOTE ; "
+ DEFB $11 ; )
+ DEFB $10 ; (
+ DEFB ZX_DOLLAR ; $
+ DEFB $DC ; >=
+ DEFB ZX_FUNCTION
+ DEFB ZX_EQUAL
+ DEFB ZX_PLUS
+ DEFB ZX_MINUS
+ DEFB ZX_POWER ; **
+ DEFB ZX_POUND ; pound sign
+ DEFB ZX_COMMA ; ,
+ DEFB ZX_GREATER_THAN ; >
+ DEFB ZX_LESS_THAN ; <
+ DEFB ZX_STAR ; *
+
+; THE 'FUNCTION' CHARACTER CODES
+
+K_FUNCT:
+ DEFB ZX_LN
+ DEFB ZX_EXP
+ DEFB ZX_AT
+ DEFB ZX_KL
+ DEFB ZX_ASN
+ DEFB ZX_ACS
+ DEFB ZX_ATN
+ DEFB ZX_SGN
+ DEFB ZX_ABS
+ DEFB ZX_SIN
+ DEFB ZX_COS
+ DEFB ZX_TAN
+ DEFB ZX_INT
+ DEFB ZX_RND
+ DEFB ZX_KL
+ DEFB ZX_KL
+ DEFB ZX_KL
+ DEFB ZX_KL
+ DEFB ZX_KL
+ DEFB ZX_KL
+ DEFB ZX_KL
+ DEFB ZX_KL
+ DEFB ZX_KL
+ DEFB ZX_KL
+ DEFB ZX_TAB
+ DEFB ZX_PEEK
+ DEFB ZX_CODE
+ DEFB ZX_CHR_STR ; CHR$
+ DEFB ZX_STR_STR ; STR$
+ DEFB ZX_KL
+ DEFB ZX_USR
+ DEFB ZX_LEN
+ DEFB ZX_VAL
+ DEFB ZX_SQR
+ DEFB ZX_KL
+ DEFB ZX_KL
+ DEFB ZX_PI
+ DEFB ZX_NOT
+ DEFB ZX_INKEY_STR
+
+; THE 'GRAPHIC' CHARACTER CODES
+
+K_GRAPH:
+ DEFB $08 ; graphic
+ DEFB $0A ; graphic
+ DEFB $09 ; graphic
+ DEFB $8A ; graphic
+ DEFB $89 ; graphic
+
+ DEFB $81 ; graphic
+ DEFB $82 ; graphic
+ DEFB $07 ; graphic
+ DEFB $84 ; graphic
+ DEFB $06 ; graphic
+
+ DEFB $01 ; graphic
+ DEFB $02 ; graphic
+ DEFB $87 ; graphic
+ DEFB $04 ; graphic
+ DEFB $05 ; graphic
+
+ DEFB ZX_RUBOUT
+ DEFB ZX_KL
+ DEFB $85 ; graphic
+ DEFB $03 ; graphic
+ DEFB $83 ; graphic
+
+ DEFB $8B ; graphic
+ DEFB $91 ; inverse )
+ DEFB $90 ; inverse (
+ DEFB $8D ; inverse $
+ DEFB $86 ; graphic
+
+ DEFB ZX_KL
+ DEFB $92 ; inverse >
+ DEFB $95 ; inverse +
+ DEFB $96 ; inverse -
+ DEFB $88 ; graphic
+
+; THE 'TOKEN' TABLES
+
+TOKEN_TABLE:
+ DEFB ZX_QUERY +$80; '?'
+ DEFB ZX_QUOTE, ZX_QUOTE +$80; ""
+ DEFB ZX_A, ZX_T +$80; AT
+ DEFB ZX_T, ZX_A, ZX_B +$80; TAB
+ DEFB ZX_QUERY +$80; '?'
+ DEFB ZX_C, ZX_O, ZX_D, ZX_E +$80; CODE
+ DEFB ZX_V, ZX_A, ZX_L +$80; VAL
+ DEFB ZX_L, ZX_E, ZX_N +$80; LEN
+ DEFB ZX_S, ZX_I, ZX_N +$80; SIN
+ DEFB ZX_C, ZX_O, ZX_S +$80; COS
+ DEFB ZX_T, ZX_A, ZX_N +$80; TAN
+ DEFB ZX_A, ZX_S, ZX_N +$80; ASN
+ DEFB ZX_A, ZX_C, ZX_S +$80; ACS
+ DEFB ZX_A, ZX_T, ZX_N +$80; ATN
+ DEFB ZX_L, ZX_N +$80; LN
+ DEFB ZX_E, ZX_X, ZX_P +$80; EXP
+ DEFB ZX_I, ZX_N, ZX_T +$80; INT
+ DEFB ZX_S, ZX_Q, ZX_R +$80; SQR
+ DEFB ZX_S, ZX_G, ZX_N +$80; SGN
+ DEFB ZX_A, ZX_B, ZX_S +$80; ABS
+ DEFB ZX_P, ZX_E, ZX_E, ZX_K +$80; PEEK
+ DEFB ZX_U, ZX_S, ZX_R +$80; USR
+ DEFB ZX_S, ZX_T, ZX_R, ZX_DOLLAR +$80; STR$
+ DEFB ZX_C, ZX_H, ZX_R, ZX_DOLLAR +$80; CHR$
+ DEFB ZX_N, ZX_O, ZX_T +$80; NOT
+ DEFB ZX_STAR, ZX_STAR +$80; **
+ DEFB ZX_O, ZX_R +$80; OR
+ DEFB ZX_A, ZX_N, ZX_D +$80; AND
+ DEFB ZX_LESS_THAN, ZX_EQUAL +$80; >=
+ DEFB ZX_GREATER_THAN, ZX_EQUAL +$80; <=
+ DEFB ZX_LESS_THAN, ZX_GREATER_THAN +$80; ><
+ DEFB ZX_T, ZX_H, ZX_E, ZX_N +$80; THEN
+ DEFB ZX_T, ZX_O +$80; TO
+ DEFB ZX_S, ZX_T, ZX_E, ZX_P +$80; STEP
+ DEFB ZX_L, ZX_P, ZX_R, ZX_I, ZX_N, ZX_T +$80; LPRINT
+ DEFB ZX_L, ZX_L, ZX_I, ZX_S, ZX_T +$80; LLIST
+ DEFB ZX_S, ZX_T, ZX_O, ZX_P +$80; STOP
+ DEFB ZX_S, ZX_L, ZX_O, ZX_W +$80; SLOW
+ DEFB ZX_F, ZX_A, ZX_S, ZX_T +$80; FAST
+ DEFB ZX_N, ZX_E, ZX_W +$80; NEW
+ DEFB ZX_S, ZX_C, ZX_R, ZX_O, ZX_L, ZX_L +$80; SCROLL
+ DEFB ZX_C, ZX_O, ZX_N, ZX_T +$80; CONT
+ DEFB ZX_D, ZX_I, ZX_M +$80; DIM
+ DEFB ZX_R, ZX_E, ZX_M +$80; REM
+ DEFB ZX_F, ZX_O, ZX_R +$80; FOR
+ DEFB ZX_G, ZX_O, ZX_T, ZX_O +$80; GOTO
+ DEFB ZX_G, ZX_O, ZX_S, ZX_U, ZX_B +$80; GOSUB
+ DEFB ZX_I, ZX_N, ZX_P, ZX_U, ZX_T +$80; INPUT
+ DEFB ZX_L, ZX_O, ZX_A, ZX_D +$80; LOAD
+ DEFB ZX_L, ZX_I, ZX_S, ZX_T +$80; LIST
+ DEFB ZX_L, ZX_E, ZX_T +$80; LET
+ DEFB ZX_P, ZX_A, ZX_U, ZX_S, ZX_E +$80; PAUSE
+ DEFB ZX_N, ZX_E, ZX_X, ZX_T +$80; NEXT
+ DEFB ZX_P, ZX_O, ZX_K, ZX_E +$80; POKE
+ DEFB ZX_P, ZX_R, ZX_I, ZX_N, ZX_T +$80; PRINT
+ DEFB ZX_P, ZX_L, ZX_O, ZX_T +$80; PLOT
+ DEFB ZX_R, ZX_U, ZX_N +$80; RUN
+ DEFB ZX_S, ZX_A, ZX_V, ZX_E +$80; SAVE
+ DEFB ZX_R, ZX_A, ZX_N, ZX_D +$80; RAND
+ DEFB ZX_I, ZX_F +$80; IF
+ DEFB ZX_C, ZX_L, ZX_S +$80; CLS
+ DEFB ZX_U, ZX_N, ZX_P, ZX_L, ZX_O, ZX_T +$80; UNPLOT
+ DEFB ZX_C, ZX_L, ZX_E, ZX_A, ZX_R +$80; CLEAR
+ DEFB ZX_R, ZX_E, ZX_T, ZX_U, ZX_R, ZX_N +$80; RETURN
+ DEFB ZX_C, ZX_O, ZX_P, ZX_Y +$80; COPY
+ DEFB ZX_R, ZX_N, ZX_D +$80; RND
+ DEFB ZX_I, ZX_N, ZX_K, ZX_E, ZX_Y, ZX_DOLLAR +$80; INKEY$
+ DEFB ZX_P, ZX_I +$80; PI
+
+; THE 'LOAD_SAVE UPDATE' ROUTINE
+
+LOAD_SAVE:
+ INC HL ;
+ EX DE,HL ;
+ LD HL,(E_LINE) ; system variable edit line E_LINE.
+ SCF ; set carry flag
+ SBC HL,DE ;
+ EX DE,HL ;
+ RET NC ; return if more bytes to LOAD_SAVE.
+
+ POP HL ; else drop return address
+
+; THE 'DISPLAY' ROUTINES
+
+SLOW_FAST:
+ LD HL,CDFLAG ; Address the system variable CDFLAG.
+ LD A,(HL) ; Load value to the accumulator.
+ RLA ; rotate bit 6 to position 7.
+ XOR (HL) ; exclusive or with original bit 7.
+ RLA ; rotate result out to carry.
+ RET NC ; return if both bits were the same.
+
+; Now test if this really is a ZX81 or a ZX80 running the upgraded ROM.
+; The standard ZX80 did not have an NMI generator.
+
+ LD A,$7F ; Load accumulator with %011111111
+ EX AF,AF' ; save in AF'
+
+ LD B,17 ; A counter within which an NMI should occur
+ ; if this is a ZX81.
+ OUT (IO_PORT_NMI_GEN_ON),A ; start the NMI generator.
+
+; Note that if this is a ZX81 then the NMI will increment AF'.
+
+LOOP_11:
+
+ DJNZ LOOP_11 ; self loop to give the NMI a chance to kick in.
+ ; = 16*13 clock cycles + 8 = 216 clock cycles.
+
+ OUT (IO_PORT_NMI_GEN_OFF),A ; Turn off the NMI generator.
+ EX AF,AF' ; bring back the AF' value.
+ RLA ; test bit 7.
+ JR NC,NO_SLOW ; forward, if bit 7 is still reset, to NO_SLOW.
+
+; If the AF' was incremented then the NMI generator works and SLOW mode can
+; be set.
+
+ SET 7,(HL) ; Indicate SLOW mode - Compute and Display.
+
+ PUSH AF ; * Save Main Registers
+ PUSH BC ; **
+ PUSH DE ; ***
+ PUSH HL ; ****
+
+ JR DISPLAY_1 ; skip forward - to DISPLAY_1.
+
+NO_SLOW:
+ RES 6,(HL) ; reset bit 6 of CDFLAG.
+ RET ; return.
+
+; THE 'MAIN DISPLAY' LOOP
+
+; This routine is executed once for every frame displayed.
+
+DISPLAY_1:
+
+ LD HL,(FRAMES) ; fetch two-byte system variable FRAMES.
+ DEC HL ; decrement frames counter.
+DISPLAY_P:
+ LD A,$7F ; prepare a mask
+ AND H ; pick up bits 6-0 of H.
+ OR L ; and any bits of L.
+ LD A,H ; reload A with all bits of H for PAUSE test.
+
+; Note both branches must take the same time.
+
+ JR NZ,ANOTHER ; (12/7) forward if bits 14-0 are not zero
+ ; to ANOTHER
+
+ RLA ; (4) test bit 15 of FRAMES.
+ JR OVER_NC ; (12) forward with result to OVER_NC
+
+ANOTHER:
+ LD B,(HL) ; (7) Note. Harmless Nonsensical Timing weight.
+ SCF ; (4) Set Carry Flag.
+
+; Note. the branch to here takes either (12)(7)(4) cyles or (7)(4)(12) cycles.
+
+OVER_NC:
+ LD H,A ; (4) set H to zero
+ LD (FRAMES),HL ; (16) update system variable FRAMES
+ RET NC ; (11/5) return if FRAMES is in use by PAUSE
+ ; command.
+
+DISPLAY_2:
+ CALL KEYBOARD ; gets the key row in H and the column in L.
+ ; Reading the ports also starts
+ ; the TV frame synchronization pulse. (VSYNC)
+
+ LD BC,(LAST_K) ; fetch the last key values
+ LD (LAST_K),HL ; update LAST_K with new values.
+
+ LD A,B ; load A with previous column - will be $FF if
+ ; there was no key.
+ ADD A,2 ; adding two will set carry if no previous key.
+
+ SBC HL,BC ; subtract with the carry the two key values.
+
+; If the same key value has been returned twice then HL will be zero.
+
+ LD A,(DEBOUNCE_VAR)
+ OR H ; and OR with both bytes of the difference
+ OR L ; setting the zero flag for the upcoming branch.
+
+ LD E,B ; transfer the column value to E
+ LD B,11 ; and load B with eleven
+
+ LD HL,CDFLAG ; address system variable CDFLAG
+ RES 0,(HL) ; reset the rightmost bit of CDFLAG
+ JR NZ,NO_KEY ; skip forward if debounce/diff >0 to NO_KEY
+
+ BIT 7,(HL) ; test compute and display bit of CDFLAG
+ SET 0,(HL) ; set the rightmost bit of CDFLAG.
+ RET Z ; return if bit 7 indicated fast mode.
+
+ DEC B ; (4) decrement the counter.
+ NOP ; (4) Timing - 4 clock cycles. ??
+ SCF ; (4) Set Carry Flag
+
+NO_KEY:
+ LD HL,DEBOUNCE_VAR ;
+ CCF ; Complement Carry Flag
+ RL B ; rotate left B picking up carry
+ ; C<-76543210<-C
+
+LOOP_B:
+ DJNZ LOOP_B ; self-loop while B>0 to LOOP_B
+
+ LD B,(HL) ; fetch value of DEBOUNCE_VAR to B
+ LD A,E ; transfer column value
+ CP $FE ;
+ SBC A,A ; A = A-A-C = 0-Carry
+
+; I think this truncating DEBOUNCE_VAR
+; which would explain why the VSYNC time didn't match
+; my calculations that assumed debouncing for 255 loops.
+
+ LD B,$1F ; binary 000 11111
+ OR (HL) ;
+ AND B ; truncate column, 0 to 31
+
+ RRA ;
+ LD (HL),A ;
+
+ OUT (IO_PORT_SCREEN),A ; end the TV frame synchronization pulse.
+
+ LD HL,(D_FILE) ; (12) set HL to the Display File from D_FILE
+ SET 7,H ; (8) set bit 15 to address the echo display.
+
+ CALL DISPLAY_3 ; (17) routine DISPLAY_3 displays the top set
+ ; of blank lines.
+
+; THE 'VIDEO_1' ROUTINE
+
+R_IX_1:
+ LD A,R ; (9) Harmless Nonsensical Timing
+ ; or something very clever?
+ LD BC,25*256+1 ; (10) 25 lines, 1 scanline in first. ($1901)
+
+; 32 characters, use $F5 (i.e. minus 11)
+; 40 characters, use $ED (i.e. minus 19)
+
+ LD A,277-CHARS_PER_LINE_WINDOW ; $F5 for 6.5MHz clocked machines
+ ; (7) This value will be loaded into R and
+ ; ensures that the cycle starts at the right
+ ; part of the display - after last character
+ ; position.
+
+ CALL DISPLAY_5 ; (17) routine DISPLAY_5 completes the current
+ ; blank line and then generates the display of
+ ; the live picture using INT interrupts
+ ; The final interrupt returns to the next
+ ; address.
+R_IX_1_LAST_NEWLINE:
+ DEC HL ; point HL to the last NEWLINE/HALT.
+
+ CALL DISPLAY_3 ; displays the bottom set of blank lines.
+
+R_IX_2:
+ JP DISPLAY_1 ; JUMP back to DISPLAY_1
+
+; THE 'DISPLAY BLANK LINES' ROUTINE
+
+; This subroutine is called twice (see above) to generate first the blank
+; lines at the top of the television display and then the blank lines at the
+; bottom of the display.
+;
+; It is actually pretty bad.
+; PAL or NTSC = 312 or
+; 1 to 5 = 5 long and 5 short sync
+; 6 to 23 = blank
+; 24 to 309 = image
+; 310 to 312 = 6 short sync
+;
+; The ZX80 generates either 62 or 110 blank lines
+;
+; 262 - 31 - 31 = 200
+; 312 - 55 - 55 = 202
+;
+; This does not include 'VSYNC' line periods.
+;
+
+DISPLAY_3:
+ POP IX ; pop the return address to IX register.
+ ; will be either R_IX_1 or R_IX_2 - see above.
+
+ LD C,(IY+MARGIN-RAMBASE) ; load C with value of system constant MARGIN.
+ BIT 7,(IY+CDFLAG-RAMBASE) ; test CDFLAG for compute and display.
+ JR Z,DISPLAY_4 ; forward, with FAST mode, to DISPLAY_4
+
+ LD A,C ; move MARGIN to A - 31d or 55d.
+ NEG ; Negate
+ INC A ;
+ EX AF,AF' ; place negative count of blank lines in A'
+
+ OUT (IO_PORT_NMI_GEN_ON),A ; enable the NMI generator.
+
+ POP HL ; ****
+ POP DE ; ***
+ POP BC ; **
+ POP AF ; * Restore Main Registers
+
+ RET ; return - end of interrupt. Return is to
+ ; user's program - BASIC or machine code.
+ ; which will be interrupted by every NMI.
+
+; THE 'FAST MODE' ROUTINES
+
+DISPLAY_4:
+
+ LD A,284-CHARS_PER_LINE_WINDOW ; $FC for 6.5MHz clocked machines
+ ; (7) load A with first R delay value
+
+ LD B,1 ; (7) one row only.
+
+ CALL DISPLAY_5 ; (17) routine DISPLAY_5
+
+ DEC HL ; (6) point back to the HALT.
+ EX (SP),HL ; (19) Harmless Nonsensical Timing if paired.
+ EX (SP),HL ; (19) Harmless Nonsensical Timing.
+ JP (IX) ; (8) to R_IX_1 or R_IX_2
+
+; THE 'DISPLAY_5' SUBROUTINE
+
+; This subroutine is called from SLOW mode and FAST mode to generate the
+; central TV picture. With SLOW mode the R register is incremented, with
+; each instruction, to $F7 by the time it completes. With fast mode, the
+; final R value will be $FF and an interrupt will occur as soon as the
+; Program Counter reaches the HALT. (24 clock cycles)
+
+DISPLAY_5:
+ LD R,A ; (9) Load R from A. R = slow: $F5 fast: $FC
+
+;; Original, for 32 column display:
+;;
+;; LD A,$DD ; (7) load future R value. $F6 $FD
+;;
+;; For other display widths,
+;; need to count down three instructions then the number of characters
+;;
+ LD A,256-3-CHARS_PER_LINE_WINDOW ; (7) load future R value. $F6 $FD
+
+ EI ; (4) Enable Interrupts $F7 $FE
+
+ JP (HL) ; (4) jump to the echo display. $F8 $FF
+
+; THE 'KEYBOARD SCANNING' SUBROUTINE
+
+; The keyboard is read during the vertical sync interval while no video is
+; being displayed. Reading a port with address bit 0 low i.e. $FE starts the
+; vertical sync pulse.
+
+KEYBOARD:
+ LD HL,$FFFF ; (16) prepare a buffer to take key.
+ LD BC,$FEFE ; (20) set BC to port $FEFE. The B register,
+ ; with its single reset bit also acts as
+ ; an 8-counter.
+ IN A,(C) ; (11) read the port - all 16 bits are put on
+ ; the address bus. Start VSYNC pulse.
+ OR $01 ; (7) set the rightmost bit so as to ignore
+ ; the SHIFT key.
+
+EACH_LINE:
+ OR $E0 ; [7] OR %11100000
+ LD D,A ; [4] transfer to D.
+ CPL ; [4] complement - only bits 4-0 meaningful now.
+ CP 1 ; [7] sets carry if A is zero.
+ SBC A,A ; [4] $FF if $00 else zero.
+ OR B ; [7] $FF or port FE,FD,FB....
+ AND L ; [4] unless more than one key, L will still be
+ ; $FF. if more than one key is pressed then A is
+ ; now invalid.
+ LD L,A ; [4] transfer to L.
+
+; now consider the column identifier.
+
+ LD A,H ; [4] will be $FF if no previous keys.
+ AND D ; [4] 111xxxxx
+ LD H,A ; [4] transfer A to H
+
+; since only one key may be pressed, H will, if valid, be one of
+; 11111110, 11111101, 11111011, 11110111, 11101111
+; reading from the outer column, say Q, to the inner column, say T.
+
+ RLC B ; [8] rotate the 8-counter/port address.
+ ; sets carry if more to do.
+ IN A,(C) ; [10] read another half-row.
+ ; all five bits this time.
+
+ JR C,EACH_LINE ; [12](7) loop back, until done, to EACH_LINE
+
+; The last row read is SHIFT,Z,X,C,V for the second time.
+
+ RRA ; (4) test the shift key - carry will be reset
+ ; if the key is pressed.
+ RL H ; (8) rotate left H picking up the carry giving
+ ; column values -
+ ; $FD, $FB, $F7, $EF, $DF.
+ ; or $FC, $FA, $F6, $EE, $DE if shifted.
+
+; We now have H identifying the column and L identifying the row in the
+; keyboard matrix.
+
+; This is a good time to test if this is an American or British machine.
+; The US machine has an extra diode that causes bit 6 of a byte read from
+; a port to be reset.
+
+ RLA ; (4) compensate for the shift test.
+ RLA ; (4) rotate bit 7 out.
+ RLA ; (4) test bit 6.
+
+ SBC A,A ; (4) $FF or 0 {USA}
+ AND $18 ; (7) 24 or 0
+ ADD A,31 ; (7) 55 or 31
+
+; result is either 31 (USA) or 55 (UK) blank lines above and below the TV
+; picture.
+
+ LD (MARGIN),A ; (13) update system variable MARGIN
+
+ RET ; (10) return
+
+; THE 'SET FAST MODE' SUBROUTINE
+
+SET_FAST:
+ BIT 7,(IY+CDFLAG-RAMBASE)
+ RET Z ;
+
+ HALT ; Wait for Interrupt
+ OUT (IO_PORT_NMI_GEN_OFF),A ;
+ RES 7,(IY+CDFLAG-RAMBASE)
+ RET ; return.
+
+; THE 'REPORT_F'
+
+REPORT_F:
+ RST _ERROR_1
+ DEFB $0E ; Error Report: No Program Name supplied.
+
+
+; THE 'SAVE COMMAND' ROUTINE
+
+SAVE:
+ CALL NAME
+ JR C,REPORT_F ; back with null name
+
+ EX DE,HL ;
+ LD DE,$12CB ; five seconds timing value (4811 decimal)
+
+HEADER:
+ CALL BREAK_1
+ JR NC,BREAK_2
+
+DELAY_1:
+ DJNZ DELAY_1
+
+ DEC DE ;
+ LD A,D ;
+ OR E ;
+ JR NZ,HEADER ; back for delay to HEADER
+
+OUT_NAME:
+ CALL OUT_BYTE
+ BIT 7,(HL) ; test for inverted bit.
+ INC HL ; address next character of name.
+ JR Z,OUT_NAME ; back if not inverted to OUT_NAME
+
+; now start saving the system variables onwards.
+
+ LD HL,VERSN ; set start of area to VERSN thereby
+ ; preserving RAMTOP etc.
+
+OUT_PROG:
+ CALL OUT_BYTE
+
+ CALL LOAD_SAVE ; >>
+ JR OUT_PROG ; loop back
+
+; THE 'OUT_BYTE' SUBROUTINE
+
+; This subroutine outputs a byte a bit at a time to a domestic tape recorder.
+
+OUT_BYTE:
+ LD E,(HL) ; fetch byte to be saved.
+ SCF ; set carry flag - as a marker.
+
+EACH_BIT:
+ RL E ; C < 76543210 < C
+ RET Z ; return when the marker bit has passed
+ ; right through. >>
+
+ SBC A,A ; $FF if set bit or $00 with no carry.
+ AND $05 ; $05 " " " " $00
+ ADD A,$04 ; $09 " " " " $04
+ LD C,A ; transfer timer to C. a set bit has a longer
+ ; pulse than a reset bit.
+
+PULSES:
+ OUT (IO_PORT_TAPE),A ; pulse to cassette.
+ LD B,$23 ; set timing constant
+
+DELAY_2:
+ DJNZ DELAY_2 ; self-loop
+
+ CALL BREAK_1 ; test for BREAK key.
+
+BREAK_2:
+ JR NC,REPORT_D ; forward with break to REPORT_D
+
+ LD B,$1E ; set timing value.
+
+DELAY_3:
+ DJNZ DELAY_3 ; self-loop
+
+ DEC C ; decrement counter
+ JR NZ,PULSES ; loop back
+
+DELAY_4:
+ AND A ; clear carry for next bit test.
+ DJNZ DELAY_4 ; self loop (B is zero - 256)
+
+ JR EACH_BIT ; loop back
+
+; THE 'LOAD COMMAND' ROUTINE
+
+LOAD:
+ CALL NAME
+
+; DE points to start of name in RAM.
+
+ RL D ; pick up carry
+ RRC D ; carry now in bit 7.
+
+LNEXT_PROG:
+ CALL IN_BYTE
+ JR LNEXT_PROG ; loop
+
+; THE 'IN_BYTE' SUBROUTINE
+
+IN_BYTE:
+ LD C,$01 ; prepare an eight counter 00000001.
+
+NEXT_BIT:
+ LD B,$00 ; set counter to 256
+
+BREAK_3:
+ LD A,$7F ; read the keyboard row
+ IN A,(IO_PORT_KEYBOARD_RD) ; with the SPACE key.
+
+ OUT (IO_PORT_SCREEN),A ; output signal to screen.
+
+ RRA ; test for SPACE pressed.
+ JR NC,BREAK_4 ; forward if so to BREAK_4
+
+ RLA ; reverse above rotation
+ RLA ; test tape bit.
+ JR C,GET_BIT ; forward if set to GET_BIT
+
+ DJNZ BREAK_3 ; loop back
+
+ POP AF ; drop the return address.
+ CP D ; ugh.
+
+RESTART:
+ JP NC,INITIAL ; jump forward to INITIAL if D is zero
+ ; to reset the system
+ ; if the tape signal has timed out for example
+ ; if the tape is stopped. Not just a simple
+ ; report as some system variables will have
+ ; been overwritten.
+
+ LD H,D ; else transfer the start of name
+ LD L,E ; to the HL register
+
+IN_NAME:
+ CALL IN_BYTE ; is sort of recursion for name
+ ; part. received byte in C.
+ BIT 7,D ; is name the null string ?
+ LD A,C ; transfer byte to A.
+ JR NZ,MATCHING ; forward with null string
+
+ CP (HL) ; else compare with string in memory.
+ JR NZ,LNEXT_PROG ; back with mis-match
+ ; (seemingly out of subroutine but return
+ ; address has been dropped).
+
+MATCHING:
+ INC HL ; address next character of name
+ RLA ; test for inverted bit.
+ JR NC,IN_NAME ; back if not
+
+; the name has been matched in full.
+; proceed to load the data but first increment the high byte of E_LINE, which
+; is one of the system variables to be loaded in. Since the low byte is loaded
+; before the high byte, it is possible that, at the in-between stage, a false
+; value could cause the load to end prematurely - see LOAD_SAVE check.
+
+ INC (IY+E_LINE_hi-RAMBASE) ; increment E_LINE_hi.
+ LD HL,VERSN ; start loading at VERSN.
+
+IN_PROG:
+ LD D,B ; set D to zero as indicator.
+ CALL IN_BYTE ; loads a byte
+ LD (HL),C ; insert assembled byte in memory.
+ CALL LOAD_SAVE ; >>
+ JR IN_PROG ; loop back
+
+; this branch assembles a full byte before exiting normally
+; from the IN_BYTE subroutine.
+
+GET_BIT:
+ PUSH DE ; save the
+ LD E,$94 ; timing value.
+
+TRAILER:
+ LD B,26 ; counter to twenty six.
+
+COUNTER:
+ DEC E ; decrement the measuring timer.
+ IN A,(IO_PORT_KEYBOARD_RD) ; read the tape input
+ RLA ;
+ BIT 7,E ;
+ LD A,E ;
+ JR C,TRAILER ; loop back with carry to TRAILER
+
+ DJNZ COUNTER
+
+ POP DE ;
+ JR NZ,BIT_DONE
+
+ CP $56 ;
+ JR NC,NEXT_BIT
+
+BIT_DONE:
+ CCF ; complement carry flag
+ RL C ;
+ JR NC,NEXT_BIT
+
+ RET ; return with full byte.
+
+; if break is pressed while loading data then perform a reset.
+; if break pressed while waiting for program on tape then OK to break.
+
+BREAK_4:
+ LD A,D ; transfer indicator to A.
+ AND A ; test for zero.
+ JR Z,RESTART ; back if so
+
+REPORT_D:
+ RST _ERROR_1
+ DEFB $0C ; Error Report: BREAK - CONT repeats
+
+; THE 'PROGRAM NAME' SUBROUTINE
+
+NAME:
+ CALL SCANNING
+ LD A,(FLAGS) ; sv
+ ADD A,A ;
+ JP M,REPORT_C
+
+ POP HL ;
+ RET NC ;
+
+ PUSH HL ;
+ CALL SET_FAST
+ CALL STK_FETCH
+ LD H,D ;
+ LD L,E ;
+ DEC C ;
+ RET M ;
+
+ ADD HL,BC ;
+ SET 7,(HL) ;
+ RET ;
+
+
+; THE 'NEW' COMMAND ROUTINE
+
+NEW:
+ CALL SET_FAST
+ LD BC,(RAMTOP) ; fetch value of system variable RAMTOP
+ DEC BC ; point to last system byte.
+
+
+; THE 'RAM CHECK' ROUTINE
+
+RAM_CHECK:
+ LD H,B ;
+ LD L,C ;
+ LD A,$3F ;
+
+RAM_FILL:
+ LD (HL),$02 ;
+ DEC HL ;
+ CP H ;
+ JR NZ,RAM_FILL
+
+RAM_READ:
+ AND A ;
+ SBC HL,BC ;
+ ADD HL,BC ;
+ INC HL ;
+ JR NC,SET_TOP
+
+ DEC (HL) ;
+ JR Z,SET_TOP
+
+ DEC (HL) ;
+ JR Z,RAM_READ
+
+SET_TOP:
+ LD (RAMTOP),HL ; set system variable RAMTOP to first byte
+ ; above the BASIC system area.
+
+; THE 'INITIALIZATION' ROUTINE
+
+INITIAL:
+ LD HL,(RAMTOP) ; fetch system variable RAMTOP.
+ DEC HL ; point to last system byte.
+ LD (HL),$3E ; make GO SUB end-marker $3E - too high for
+ ; high order byte of line number.
+ ; (was $3F on ZX80)
+ DEC HL ; point to unimportant low-order byte.
+ LD SP,HL ; and initialize the stack-pointer to this
+ ; location.
+ DEC HL ; point to first location on the machine stack
+ DEC HL ; which will be filled by next CALL/PUSH.
+ LD (ERR_SP),HL ; set the error stack pointer ERR_SP to
+ ; the base of the now empty machine stack.
+
+; Now set the I register so that the video hardware knows where to find the
+; character set. This ROM only uses the character set when printing to
+; the ZX Printer. The TV picture is formed by the external video hardware.
+; Consider also, that this 8K ROM can be retro-fitted to the ZX80 instead of
+; its original 4K ROM so the video hardware could be on the ZX80.
+
+ LD A,$1E ; address for this ROM is $1E00.
+ LD I,A ; set I register from A.
+ IM 1 ; select Z80 Interrupt Mode 1.
+
+ LD IY,ERR_NR ; set IY to the start of RAM so that the
+ ; system variables can be indexed.
+
+ LD (IY+CDFLAG-RAMBASE),%01000000
+ ; Bit 6 indicates Compute and Display required.
+
+ LD HL,USER_RAM ; The first location after System Variables -
+ ; 16509 decimal.
+ LD (D_FILE),HL ; set system variable D_FILE to this value.
+ LD B,$19 ; prepare minimal screen of 24 NEWLINEs
+ ; following an initial NEWLINE.
+
+LINE:
+ LD (HL),ZX_NEWLINE ; insert NEWLINE (HALT instruction)
+ INC HL ; point to next location.
+ DJNZ LINE ; loop back for all twenty five to LINE
+
+ LD (VARS),HL ; set system variable VARS to next location
+
+ CALL CLEAR ; sets $80 end-marker and the
+ ; dynamic memory pointers E_LINE, STKBOT and
+ ; STKEND.
+
+N_L_ONLY:
+ CALL CURSOR_IN ; inserts the cursor and
+ ; end-marker in the Edit Line also setting
+ ; size of lower display to two lines.
+
+ CALL SLOW_FAST ; selects COMPUTE and DISPLAY
+
+; THE 'BASIC LISTING' SECTION
+
+UPPER:
+ CALL CLS
+ LD HL,(E_PPC) ; sv
+ LD DE,(S_TOP) ; sv
+ AND A ;
+ SBC HL,DE ;
+ EX DE,HL ;
+ JR NC,ADDR_TOP
+
+ ADD HL,DE ;
+ LD (S_TOP),HL ; sv
+
+ADDR_TOP:
+ CALL LINE_ADDR
+ JR Z,LIST_TOP
+
+ EX DE,HL ;
+
+LIST_TOP:
+ CALL LIST_PROG
+ DEC (IY+BERG-RAMBASE)
+ JR NZ,LOWER
+
+ LD HL,(E_PPC) ; sv
+ CALL LINE_ADDR
+ LD HL,(CH_ADD) ; sv
+ SCF ; Set Carry Flag
+ SBC HL,DE ;
+ LD HL,S_TOP ; sv
+ JR NC,INC_LINE
+
+ EX DE,HL ;
+ LD A,(HL) ;
+ INC HL ;
+ LDI ;
+ LD (DE),A ;
+ JR UPPER
+
+DOWN_KEY:
+ LD HL,E_PPC ; sv
+
+INC_LINE:
+ LD E,(HL) ;
+ INC HL ;
+ LD D,(HL) ;
+ PUSH HL ;
+ EX DE,HL ;
+ INC HL ;
+ CALL LINE_ADDR
+ CALL LINE_NUM
+ POP HL ;
+
+KEY_INPUT:
+ BIT 5,(IY+FLAGX-RAMBASE)
+ JR NZ,LOWER ; forward
+
+ LD (HL),D ;
+ DEC HL ;
+ LD (HL),E ;
+ JR UPPER
+
+
+; THE 'EDIT LINE COPY' SECTION
+
+; This routine sets the edit line to just the cursor when
+; 1) There is not enough memory to edit a BASIC line.
+; 2) The edit key is used during input.
+; The entry point LOWER
+
+EDIT_INP:
+ CALL CURSOR_IN ; sets cursor only edit line.
+
+LOWER:
+ LD HL,(E_LINE) ; fetch edit line start from E_LINE.
+
+EACH_CHAR:
+ LD A,(HL) ; fetch a character from edit line.
+ CP $7E ; compare to the number marker.
+ JR NZ,END_LINE ; forward if not
+
+ LD BC,6 ; else six invisible bytes to be removed.
+ CALL RECLAIM_2
+ JR EACH_CHAR ; back
+
+END_LINE:
+ CP ZX_NEWLINE ;
+ INC HL ;
+ JR NZ,EACH_CHAR
+
+EDIT_LINE:
+ CALL CURSOR ; sets cursor K or L.
+
+EDIT_ROOM:
+ CALL LINE_ENDS
+ LD HL,(E_LINE) ; sv
+ LD (IY+ERR_NR-RAMBASE),$FF
+ CALL COPY_LINE
+ BIT 7,(IY+ERR_NR-RAMBASE)
+ JR NZ,DISPLAY_6
+
+ LD A,(DF_SZ) ;
+ CP CHARS_VERTICAL ; $18 = 24
+ JR NC,DISPLAY_6
+
+ INC A ;
+ LD (DF_SZ),A ;
+ LD B,A ;
+ LD C,1 ;
+ CALL LOC_ADDR
+ LD D,H ;
+ LD E,L ;
+ LD A,(HL) ;
+
+FREE_LINE:
+ DEC HL ;
+ CP (HL) ;
+ JR NZ,FREE_LINE
+
+ INC HL ;
+ EX DE,HL ;
+ LD A,(RAMTOP+1) ; sv RAMTOP_hi
+ CP $4D ;
+ CALL C,RECLAIM_1
+ JR EDIT_ROOM
+
+; THE 'WAIT FOR KEY' SECTION
+
+DISPLAY_6:
+ LD HL,$0000 ;
+ LD (X_PTR),HL ; sv
+
+ LD HL,CDFLAG ; system variable CDFLAG
+ BIT 7,(HL) ;
+ CALL Z,DISPLAY_1
+
+SLOW_DISP:
+ BIT 0,(HL) ;
+ JR Z,SLOW_DISP
+
+ LD BC,(LAST_K) ; sv
+ CALL DEBOUNCE
+ CALL DECODE
+
+ JR NC,LOWER ; back
+
+; THE 'KEYBOARD DECODING' SECTION
+
+; The decoded key value is in E and HL points to the position in the
+; key table. D contains zero.
+
+K_DECODE:
+ LD A,(MODE) ; Fetch value of system variable MODE
+ DEC A ; test the three values together
+
+ JP M,FETCH_2 ; forward, if was zero
+
+ JR NZ,FETCH_1 ; forward, if was 2
+
+; The original value was one and is now zero.
+
+ LD (MODE),A ; update the system variable MODE
+
+ DEC E ; reduce E to range $00 - $7F
+ LD A,E ; place in A
+ SUB 39 ; subtract 39 setting carry if range 00 - 38
+ JR C,FUNC_BASE ; forward, if so
+
+ LD E,A ; else set E to reduced value
+
+FUNC_BASE:
+ LD HL,K_FUNCT ; address of K_FUNCT table for function keys.
+ JR TABLE_ADD ; forward
+
+FETCH_1:
+ LD A,(HL) ;
+ CP ZX_NEWLINE ;
+ JR Z,K_L_KEY
+
+ CP ZX_RND ; $40
+ SET 7,A ;
+ JR C,ENTER
+
+ LD HL,$00C7 ; (expr reqd)
+
+TABLE_ADD:
+ ADD HL,DE ;
+ JR FETCH_3
+
+FETCH_2:
+ LD A,(HL) ;
+ BIT 2,(IY+FLAGS-RAMBASE) ; K or L mode ?
+ JR NZ,TEST_CURS
+
+ ADD A,$C0 ;
+ CP $E6 ;
+ JR NC,TEST_CURS
+
+FETCH_3:
+ LD A,(HL) ;
+
+TEST_CURS:
+ CP $F0 ;
+ JP PE,KEY_SORT
+
+ENTER:
+ LD E,A ;
+ CALL CURSOR
+
+ LD A,E ;
+ CALL ADD_CHAR
+
+BACK_NEXT:
+ JP LOWER ; back
+
+; THE 'ADD CHARACTER' SUBROUTINE
+
+ADD_CHAR:
+ CALL ONE_SPACE
+ LD (DE),A ;
+ RET ;
+
+; THE 'CURSOR KEYS' ROUTINE
+
+K_L_KEY:
+ LD A,ZX_KL ;
+
+KEY_SORT:
+ LD E,A ;
+ LD HL,$0482 ; base address of ED_KEYS (exp reqd)
+ ADD HL,DE ;
+ ADD HL,DE ;
+ LD C,(HL) ;
+ INC HL ;
+ LD B,(HL) ;
+ PUSH BC ;
+
+CURSOR:
+ LD HL,(E_LINE) ; sv
+ BIT 5,(IY+FLAGX-RAMBASE)
+ JR NZ,L_MODE
+
+K_MODE:
+ RES 2,(IY+FLAGS-RAMBASE) ; Signal use K mode
+
+TEST_CHAR:
+ LD A,(HL) ;
+ CP ZX_CURSOR ;
+ RET Z ; return
+
+ INC HL ;
+ CALL NUMBER
+ JR Z,TEST_CHAR
+
+ CP ZX_A ; $26
+ JR C,TEST_CHAR
+
+ CP $DE ; ZX_THEN ??
+ JR Z,K_MODE
+
+L_MODE:
+ SET 2,(IY+FLAGS-RAMBASE) ; Signal use L mode
+ JR TEST_CHAR
+
+; THE 'CLEAR_ONE' SUBROUTINE
+
+CLEAR_ONE:
+ LD BC,$0001 ;
+ JP RECLAIM_2
+
+; THE 'EDITING KEYS' TABLE
+
+ED_KEYS:
+ DEFW UP_KEY
+ DEFW DOWN_KEY
+ DEFW LEFT_KEY
+ DEFW RIGHT_KEY
+ DEFW FUNCTION
+ DEFW EDIT_KEY
+ DEFW N_L_KEY
+ DEFW RUBOUT
+ DEFW FUNCTION
+ DEFW FUNCTION
+
+; THE 'CURSOR LEFT' ROUTINE
+
+LEFT_KEY:
+ CALL LEFT_EDGE
+ LD A,(HL) ;
+ LD (HL),ZX_CURSOR ;
+ INC HL ;
+ JR GET_CODE
+
+; THE 'CURSOR RIGHT' ROUTINE
+
+RIGHT_KEY:
+ INC HL ;
+ LD A,(HL) ;
+ CP ZX_NEWLINE ;
+ JR Z,ENDED_2
+
+ LD (HL),ZX_CURSOR ;
+ DEC HL ;
+
+GET_CODE:
+ LD (HL),A ;
+
+ENDED_1:
+ JR BACK_NEXT
+
+; THE 'RUBOUT' ROUTINE
+
+RUBOUT:
+ CALL LEFT_EDGE
+ CALL CLEAR_ONE
+ JR ENDED_1
+
+; THE 'ED_EDGE' SUBROUTINE
+
+LEFT_EDGE:
+ DEC HL ;
+ LD DE,(E_LINE) ; sv
+ LD A,(DE) ;
+ CP ZX_CURSOR ;
+ RET NZ ;
+ POP DE ;
+
+ENDED_2:
+ JR ENDED_1
+
+; THE 'CURSOR UP' ROUTINE
+
+UP_KEY:
+ LD HL,(E_PPC) ; sv
+ CALL LINE_ADDR
+ EX DE,HL ;
+ CALL LINE_NUM
+ LD HL,E_PPC+1 ; point to system variable E_PPC_hi
+ JP KEY_INPUT ; jump back
+
+; THE 'FUNCTION KEY' ROUTINE
+
+FUNCTION:
+ LD A,E ;
+ AND $07 ;
+ LD (MODE),A ; sv
+ JR ENDED_2 ; back
+
+; THE 'COLLECT LINE NUMBER' SUBROUTINE
+
+ZERO_DE:
+ EX DE,HL ;
+ LD DE,DISPLAY_6 + 1 ; $04C2 - a location addressing two zeros.
+
+LINE_NUM:
+ LD A,(HL) ;
+ AND $C0 ;
+ JR NZ,ZERO_DE
+
+ LD D,(HL) ;
+ INC HL ;
+ LD E,(HL) ;
+ RET ;
+
+; THE 'EDIT KEY' ROUTINE
+
+EDIT_KEY:
+ CALL LINE_ENDS ; clears lower display.
+
+ LD HL,EDIT_INP ; Address: EDIT_INP
+ PUSH HL ; ** is pushed as an error looping address.
+
+ BIT 5,(IY+FLAGX-RAMBASE) ; test FLAGX
+ RET NZ ; indirect jump if in input mode
+ ; to EDIT_INP (begin again).
+
+ LD HL,(E_LINE) ; fetch E_LINE
+ LD (DF_CC),HL ; and use to update the screen cursor DF_CC
+
+; so now RST $10 will print the line numbers to the edit line instead of screen.
+; first make sure that no newline/out of screen can occur while sprinting the
+; line numbers to the edit line.
+
+ ; prepare line 0, column 0.
+
+ LD HL,256*CHARS_VERTICAL + CHARS_HORIZONTAL + 1
+
+ LD (S_POSN),HL ; update S_POSN with these dummy values.
+
+ LD HL,(E_PPC) ; fetch current line from E_PPC may be a
+ ; non-existent line e.g. last line deleted.
+ CALL LINE_ADDR ; gets address or that of
+ ; the following line.
+ CALL LINE_NUM ; gets line number if any in DE
+ ; leaving HL pointing at second low byte.
+
+ LD A,D ; test the line number for zero.
+ OR E ;
+ RET Z ; return if no line number - no program to edit.
+
+ DEC HL ; point to high byte.
+ CALL OUT_NO ; writes number to edit line.
+
+ INC HL ; point to length bytes.
+ LD C,(HL) ; low byte to C.
+ INC HL ;
+ LD B,(HL) ; high byte to B.
+
+ INC HL ; point to first character in line.
+ LD DE,(DF_CC) ; fetch display file cursor DF_CC
+
+ LD A,ZX_CURSOR ; prepare the cursor character.
+ LD (DE),A ; and insert in edit line.
+ INC DE ; increment intended destination.
+
+ PUSH HL ; * save start of BASIC.
+
+ LD HL,29 ; set an overhead of 29 bytes.
+ ADD HL,DE ; add in the address of cursor.
+ ADD HL,BC ; add the length of the line.
+ SBC HL,SP ; subtract the stack pointer.
+
+ POP HL ; * restore pointer to start of BASIC.
+
+ RET NC ; return if not enough room to EDIT_INP EDIT_INP.
+ ; the edit key appears not to work.
+
+ LDIR ; else copy bytes from program to edit line.
+ ; Note. hidden floating point forms are also
+ ; copied to edit line.
+
+ EX DE,HL ; transfer free location pointer to HL
+
+ POP DE ; ** remove address EDIT_INP from stack.
+
+ CALL SET_STK_B ; sets STKEND from HL.
+
+ JR ENDED_2 ; back to ENDED_2 and after 3 more jumps
+ ; to LOWER, LOWER.
+ ; Note. The LOWER routine removes the hidden
+ ; floating-point numbers from the edit line.
+
+; THE 'NEWLINE KEY' ROUTINE
+
+N_L_KEY:
+ CALL LINE_ENDS
+
+ LD HL,LOWER ; prepare address: LOWER
+
+ BIT 5,(IY+FLAGX-RAMBASE)
+ JR NZ,NOW_SCAN
+
+ LD HL,(E_LINE) ; sv
+ LD A,(HL) ;
+ CP $FF ;
+ JR Z,STK_UPPER
+
+ CALL CLEAR_PRB
+ CALL CLS
+
+STK_UPPER:
+ LD HL,UPPER ; Address: UPPER
+
+NOW_SCAN:
+ PUSH HL ; push routine address (LOWER or UPPER).
+ CALL LINE_SCAN
+ POP HL ;
+ CALL CURSOR
+ CALL CLEAR_ONE
+ CALL E_LINE_NUM
+ JR NZ,N_L_INP
+
+ LD A,B ;
+ OR C ;
+ JP NZ,N_L_LINE
+
+ DEC BC ;
+ DEC BC ;
+ LD (PPC),BC ; sv
+ LD (IY+DF_SZ-RAMBASE),2
+ LD DE,(D_FILE) ; sv
+
+ JR TEST_NULL ; forward
+
+N_L_INP:
+ CP ZX_NEWLINE ;
+ JR Z,N_L_NULL
+
+ LD BC,(T_ADDR) ;
+ CALL LOC_ADDR
+ LD DE,(NXTLIN) ;
+ LD (IY+DF_SZ-RAMBASE),2
+
+TEST_NULL:
+ RST _GET_CHAR
+ CP ZX_NEWLINE ;
+
+N_L_NULL:
+ JP Z,N_L_ONLY
+
+ LD (IY+FLAGS-RAMBASE),$80
+ EX DE,HL ;
+
+NEXT_LINE:
+ LD (NXTLIN),HL ;
+ EX DE,HL ;
+ CALL TEMP_PTR2
+ CALL LINE_RUN
+ RES 1,(IY+FLAGS-RAMBASE) ; Signal printer not in use
+ LD A,$C0 ;
+ LD (IY+X_PTR_hi-RAMBASE),A
+ CALL X_TEMP
+ RES 5,(IY+FLAGX-RAMBASE)
+ BIT 7,(IY+ERR_NR-RAMBASE)
+ JR Z,STOP_LINE
+
+ LD HL,(NXTLIN) ;
+ AND (HL) ;
+ JR NZ,STOP_LINE
+
+ LD D,(HL) ;
+ INC HL ;
+ LD E,(HL) ;
+ LD (PPC),DE ;
+ INC HL ;
+ LD E,(HL) ;
+ INC HL ;
+ LD D,(HL) ;
+ INC HL ;
+ EX DE,HL ;
+ ADD HL,DE ;
+ CALL BREAK_1
+ JR C,NEXT_LINE
+
+ LD HL,ERR_NR
+ BIT 7,(HL)
+ JR Z,STOP_LINE
+
+ LD (HL),$0C
+
+STOP_LINE:
+ BIT 7,(IY+PR_CC-RAMBASE)
+ CALL Z,COPY_BUFF
+ LD BC,256*1 + CHARS_HORIZONTAL + 1
+ CALL LOC_ADDR
+ LD A,(ERR_NR)
+ LD BC,(PPC)
+ INC A
+ JR Z,REPORT
+
+ CP $09
+ JR NZ,CONTINUE
+
+ INC BC
+
+CONTINUE:
+ LD (OLDPPC),BC ;
+ JR NZ,REPORT
+
+ DEC BC ;
+
+REPORT:
+ CALL OUT_CODE
+ LD A,ZX_SLASH
+
+ RST _PRINT_A
+ CALL OUT_NUM
+ CALL CURSOR_IN
+ JP DISPLAY_6
+
+N_L_LINE:
+ LD (E_PPC),BC ;
+ LD HL,(CH_ADD) ;
+ EX DE,HL ;
+ LD HL,N_L_ONLY
+ PUSH HL ;
+ LD HL,(STKBOT) ;
+ SBC HL,DE ;
+ PUSH HL ;
+ PUSH BC ;
+ CALL SET_FAST
+ CALL CLS
+ POP HL ;
+ CALL LINE_ADDR
+ JR NZ,COPY_OVER
+
+ CALL NEXT_ONE
+ CALL RECLAIM_2
+
+COPY_OVER:
+ POP BC ;
+ LD A,C ;
+ DEC A ;
+ OR B ;
+ RET Z ;
+
+ PUSH BC ;
+ INC BC ;
+ INC BC ;
+ INC BC ;
+ INC BC ;
+ DEC HL ;
+ CALL MAKE_ROOM
+ CALL SLOW_FAST
+ POP BC ;
+ PUSH BC ;
+ INC DE ;
+ LD HL,(STKBOT) ;
+ DEC HL ;
+ LDDR ; copy bytes
+ LD HL,(E_PPC) ;
+ EX DE,HL ;
+ POP BC ;
+ LD (HL),B ;
+ DEC HL ;
+ LD (HL),C ;
+ DEC HL ;
+ LD (HL),E ;
+ DEC HL ;
+ LD (HL),D ;
+
+ RET ; return.
+
+; THE 'LIST' AND 'LLIST' COMMAND ROUTINES
+
+LLIST:
+ SET 1,(IY+FLAGS-RAMBASE) ; signal printer in use
+
+LIST:
+ CALL FIND_INT
+
+ LD A,B ; fetch high byte of user-supplied line number.
+ AND $3F ; and crudely limit to range 1-16383.
+
+ LD H,A ;
+ LD L,C ;
+ LD (E_PPC),HL ;
+ CALL LINE_ADDR
+
+LIST_PROG:
+ LD E,$00 ;
+
+UNTIL_END:
+ CALL OUT_LINE ; lists one line of BASIC
+ ; making an early return when the screen is
+ ; full or the end of program is reached.
+ JR UNTIL_END ; loop back to UNTIL_END
+
+; THE 'PRINT A BASIC LINE' SUBROUTINE
+
+OUT_LINE:
+ LD BC,(E_PPC) ; sv
+ CALL CP_LINES
+ LD D,$92 ;
+ JR Z,TEST_END
+
+ LD DE,$0000 ;
+ RL E ;
+
+TEST_END:
+ LD (IY+BERG-RAMBASE),E
+ LD A,(HL) ;
+ CP $40 ;
+ POP BC ;
+ RET NC ;
+
+ PUSH BC ;
+ CALL OUT_NO
+ INC HL ;
+ LD A,D ;
+
+ RST _PRINT_A
+ INC HL ;
+ INC HL ;
+
+COPY_LINE:
+ LD (CH_ADD),HL ;
+ SET 0,(IY+FLAGS-RAMBASE) ; Suppress leading space
+
+MORE_LINE:
+ LD BC,(X_PTR) ;
+ LD HL,(CH_ADD) ;
+ AND A ;
+ SBC HL,BC ;
+ JR NZ,TEST_NUM
+ LD A,ZX_INV_S ; $B8 ;
+ RST _PRINT_A
+
+TEST_NUM:
+ LD HL,(CH_ADD) ;
+ LD A,(HL) ;
+ INC HL ;
+ CALL NUMBER
+ LD (CH_ADD),HL ;
+ JR Z,MORE_LINE
+
+ CP ZX_CURSOR ;
+ JR Z,OUT_CURS
+
+ CP ZX_NEWLINE ;
+ JR Z,OUT_CH
+
+ BIT 6,A ;
+ JR Z,NOT_TOKEN
+
+ CALL TOKENS
+ JR MORE_LINE
+
+NOT_TOKEN:
+ RST _PRINT_A
+ JR MORE_LINE
+
+OUT_CURS:
+ LD A,(MODE) ; Fetch value of system variable MODE
+ LD B,$AB ; Prepare an inverse [F] for function cursor.
+
+ AND A ; Test for zero -
+ JR NZ,FLAGS_2 ; forward if not to FLAGS_2
+
+ LD A,(FLAGS) ; Fetch system variable FLAGS.
+ LD B,ZX_INV_K ; Prepare an inverse [K] for keyword cursor.
+
+FLAGS_2:
+ RRA ; 00000?00 -> 000000?0
+ RRA ; 000000?0 -> 0000000?
+ AND $01 ; 0000000? 0000000x
+
+ ADD A,B ; Possibly [F] -> [G] or [K] -> [L]
+
+ CALL PRINT_SP
+ JR MORE_LINE
+
+; THE 'NUMBER' SUBROUTINE
+
+NUMBER:
+ CP $7E ;
+ RET NZ ;
+
+ INC HL ;
+ INC HL ;
+ INC HL ;
+ INC HL ;
+ INC HL ;
+ RET ;
+
+; THE 'KEYBOARD DECODE' SUBROUTINE
+
+DECODE:
+ LD D,0 ;
+ SRA B ; shift bit from B to Carry
+ SBC A,A ; A = 0 - Carry
+ OR $26 ; %00100110
+ LD L,5 ;
+ SUB L ;
+
+KEY_LINE:
+ ADD A,L ;
+ SCF ; Set Carry Flag
+ RR C ;
+ JR C,KEY_LINE
+
+ INC C ;
+ RET NZ ;
+
+ LD C,B ;
+ DEC L ;
+ LD L,1 ;
+ JR NZ,KEY_LINE
+
+ LD HL,$007D ; (expr reqd)
+ LD E,A ;
+ ADD HL,DE ;
+ SCF ; Set Carry Flag
+ RET ;
+
+; THE 'PRINTING' SUBROUTINE
+
+LEAD_SP:
+ LD A,E ;
+ AND A ;
+ RET M ;
+
+ JR PRINT_CH
+
+; HL is typically -10000, -1000, -100, -10
+; and repeatedly subtracted from BC
+; i.e. it print
+
+OUT_DIGIT:
+ XOR A ; assume the digit is zero to begin with
+
+DIGIT_INC:
+ ADD HL,BC ; HL += -ve number
+ INC A ;
+ JR C,DIGIT_INC ; loop
+
+ SBC HL,BC ; undo last iteration
+ DEC A ; undo last iteration
+ JR Z,LEAD_SP ; leading zeros shown as spaces
+
+OUT_CODE:
+ LD E,ZX_0 ; $1C
+ ADD A,E ;
+
+OUT_CH:
+ AND A ;
+ JR Z,PRINT_SP
+
+PRINT_CH:
+ RES 0,(IY+FLAGS-RAMBASE) ; signal leading space permitted
+
+PRINT_SP:
+ EXX ;
+ PUSH HL ;
+ BIT 1,(IY+FLAGS-RAMBASE) ; is printer in use ?
+ JR NZ,LPRINT_A
+
+ CALL ENTER_CH
+ JR PRINT_EXX
+
+LPRINT_A:
+ CALL LPRINT_CH
+
+PRINT_EXX:
+ POP HL ;
+ EXX ;
+ RET ;
+
+ENTER_CH:
+ LD D,A ;
+ LD BC,(S_POSN) ;
+ LD A,C ;
+ CP CHARS_HORIZONTAL+1 ;
+ JR Z,TEST_LOW
+
+TEST_N_L:
+ LD A,ZX_NEWLINE ;
+ CP D ;
+ JR Z,WRITE_N_L
+
+ LD HL,(DF_CC) ;
+ CP (HL) ;
+ LD A,D ;
+ JR NZ,WRITE_CH
+
+ DEC C ;
+ JR NZ,EXPAND_1
+
+ INC HL ;
+ LD (DF_CC),HL ;
+ LD C,CHARS_HORIZONTAL+1 ; $21 = 33 normally
+ DEC B ;
+ LD (S_POSN),BC ;
+
+TEST_LOW:
+ LD A,B ;
+ CP (IY+DF_SZ-RAMBASE)
+ JR Z,REPORT_5
+
+ AND A ;
+ JR NZ,TEST_N_L
+
+REPORT_5:
+ LD L,4 ; 'No more room on screen'
+ JP ERROR_3
+
+EXPAND_1:
+ CALL ONE_SPACE
+ EX DE,HL ;
+
+WRITE_CH:
+ LD (HL),A ;
+ INC HL ;
+ LD (DF_CC),HL ;
+ DEC (IY+S_POSN_x-RAMBASE)
+ RET ;
+
+WRITE_N_L:
+ LD C,CHARS_HORIZONTAL+1 ; $21 = 33
+ DEC B ;
+ SET 0,(IY+FLAGS-RAMBASE) ; Suppress leading space
+ JP LOC_ADDR
+
+; THE 'LPRINT_CH' SUBROUTINE
+
+; This routine sends a character to the ZX-Printer placing the code for the
+; character in the Printer Buffer.
+; Note. PR_CC contains the low byte of the buffer address. The high order byte
+; is always constant.
+
+LPRINT_CH:
+ CP ZX_NEWLINE ; compare to NEWLINE.
+ JR Z,COPY_BUFF ; forward if so
+
+ LD C,A ; take a copy of the character in C.
+ LD A,(PR_CC) ; fetch print location from PR_CC
+ AND $7F ; ignore bit 7 to form true position.
+ CP $5C ; compare to 33rd location
+
+ LD L,A ; form low-order byte.
+ LD H,$40 ; the high-order byte is fixed.
+
+ CALL Z,COPY_BUFF ; to send full buffer to
+ ; the printer if first 32 bytes full.
+ ; (this will reset HL to start.)
+
+ LD (HL),C ; place character at location.
+ INC L ; increment - will not cross a 256 boundary.
+ LD (IY+PR_CC-RAMBASE),L ; update system variable PR_CC
+ ; automatically resetting bit 7 to show that
+ ; the buffer is not empty.
+ RET ; return.
+
+; THE 'COPY' COMMAND ROUTINE
+
+; The full character-mapped screen is copied to the ZX-Printer.
+; All twenty-four text/graphic lines are printed.
+
+COPY:
+;
+ LD D,22 ; prepare to copy twenty four text lines.
+ LD HL,(D_FILE) ; set HL to start of display file from D_FILE.
+ INC HL ;
+ JR COPY_D ; forward
+
+; A single character-mapped printer buffer is copied to the ZX-Printer.
+
+COPY_BUFF:
+ LD D,1 ; prepare to copy a single text line.
+ LD HL,PRBUFF ; set HL to start of printer buffer PRBUFF.
+
+; both paths converge here.
+
+COPY_D:
+ CALL SET_FAST
+
+ PUSH BC ; *** preserve BC throughout.
+ ; a pending character may be present
+ ; in C from LPRINT_CH
+
+COPY_LOOP:
+ PUSH HL ; save first character of line pointer. (*)
+ XOR A ; clear accumulator.
+ LD E,A ; set pixel line count, range 0-7, to zero.
+
+; this inner loop deals with each horizontal pixel line.
+
+COPY_TIME:
+ OUT (IO_PORT_PRINTER),A ; bit 2 reset starts the printer motor
+ ; with an inactive stylus - bit 7 reset.
+ POP HL ; pick up first character of line pointer (*)
+ ; on inner loop.
+
+COPY_BRK:
+ CALL BREAK_1
+ JR C,COPY_CONT ; forward with no keypress to COPY_CONT
+
+; else A will hold 11111111 0
+
+ RRA ; 0111 1111
+ OUT (IO_PORT_PRINTER),A ; stop ZX printer motor, de-activate stylus.
+
+REPORT_D2:
+ RST _ERROR_1
+ DEFB $0C ; Error Report: BREAK - CONT repeats
+
+COPY_CONT:
+ IN A,(IO_PORT_PRINTER) ; read from printer port.
+ ADD A,A ; test bit 6 and 7
+ JP M,COPY_END ; jump forward with no printer to COPY_END
+
+ JR NC,COPY_BRK ; back if stylus not in position to COPY_BRK
+
+ PUSH HL ; save first character of line pointer (*)
+ PUSH DE ; ** preserve character line and pixel line.
+
+ LD A,D ; text line count to A?
+ CP 2 ; sets carry if last line.
+ SBC A,A ; now $FF if last line else zero.
+
+; now cleverly prepare a printer control mask setting bit 2 (later moved to 1)
+; of D to slow printer for the last two pixel lines ( E = 6 and 7)
+
+ AND E ; and with pixel line offset 0-7
+ RLCA ; shift to left.
+ AND E ; and again.
+ LD D,A ; store control mask in D.
+
+COPY_NEXT:
+ LD C,(HL) ; load character from screen or buffer.
+ LD A,C ; save a copy in C for later inverse test.
+ INC HL ; update pointer for next time.
+ CP ZX_NEWLINE ; is character a NEWLINE ?
+ JR Z,COPY_N_L ; forward, if so, to COPY_N_L
+
+ PUSH HL ; * else preserve the character pointer.
+
+ SLA A ; (?) multiply by two
+ ADD A,A ; multiply by four
+ ADD A,A ; multiply by eight
+
+ LD H,$0F ; load H with half the address of character set.
+ RL H ; now $1E or $1F (with carry)
+ ADD A,E ; add byte offset 0-7
+ LD L,A ; now HL addresses character source byte
+
+ RL C ; test character, setting carry if inverse.
+ SBC A,A ; accumulator now $00 if normal, $FF if inverse.
+
+ XOR (HL) ; combine with bit pattern at end or ROM.
+ LD C,A ; transfer the byte to C.
+ LD B,8 ; count eight bits to output.
+
+COPY_BITS:
+ LD A,D ; fetch speed control mask from D.
+ RLC C ; rotate a bit from output byte to carry.
+ RRA ; pick up in bit 7, speed bit to bit 1
+ LD H,A ; store aligned mask in H register.
+
+COPY_WAIT:
+ IN A,(IO_PORT_PRINTER) ; read the printer port
+ RRA ; test for alignment signal from encoder.
+ JR NC,COPY_WAIT ; loop if not present to COPY_WAIT
+
+ LD A,H ; control byte to A.
+ OUT (IO_PORT_PRINTER),A ; and output to printer port.
+ DJNZ COPY_BITS ; loop for all eight bits to COPY_BITS
+
+ POP HL ; * restore character pointer.
+ JR COPY_NEXT ; back for adjacent character line to COPY_NEXT
+
+; A NEWLINE has been encountered either following a text line or as the
+; first character of the screen or printer line.
+
+COPY_N_L:
+ IN A,(IO_PORT_PRINTER) ; read printer port.
+ RRA ; wait for encoder signal.
+ JR NC,COPY_N_L ; loop back if not to COPY_N_L
+
+ LD A,D ; transfer speed mask to A.
+ RRCA ; rotate speed bit to bit 1.
+ ; bit 7, stylus control is reset.
+ OUT (IO_PORT_PRINTER),A ; set the printer speed.
+
+ POP DE ; ** restore character line and pixel line.
+ INC E ; increment pixel line 0-7.
+ BIT 3,E ; test if value eight reached.
+ JR Z,COPY_TIME ; back if not
+
+; eight pixel lines, a text line have been completed.
+
+ POP BC ; lose the now redundant first character
+ ; pointer
+ DEC D ; decrease text line count.
+ JR NZ,COPY_LOOP ; back if not zero
+
+ LD A,$04 ; stop the already slowed printer motor.
+ OUT (IO_PORT_PRINTER),A ; output to printer port.
+
+COPY_END:
+ CALL SLOW_FAST
+ POP BC ; *** restore preserved BC.
+
+; THE 'CLEAR PRINTER BUFFER' SUBROUTINE
+
+; This subroutine sets 32 bytes of the printer buffer to zero (space) and
+; the 33rd character is set to a NEWLINE.
+; This occurs after the printer buffer is sent to the printer but in addition
+; after the 24 lines of the screen are sent to the printer.
+; Note. This is a logic error as the last operation does not involve the
+; buffer at all. Logically one should be able to use
+; 10 LPRINT "HELLO ";
+; 20 COPY
+; 30 LPRINT ; "WORLD"
+; and expect to see the entire greeting emerge from the printer.
+; Surprisingly this logic error was never discovered and although one can argue
+; if the above is a bug, the repetition of this error on the Spectrum was most
+; definitely a bug.
+; Since the printer buffer is fixed at the end of the system variables, and
+; the print position is in the range $3C - $5C, then bit 7 of the system
+; variable is set to show the buffer is empty and automatically reset when
+; the variable is updated with any print position - neat.
+
+CLEAR_PRB:
+ LD HL,PRBUFF_END ; address fixed end of PRBUFF
+ LD (HL),ZX_NEWLINE ; place a newline at last position.
+ LD B,32 ; prepare to blank 32 preceding characters.
+;
+; NB the printer is fixed at 32 characters, maybe it can be tweaked ???
+;
+PRB_BYTES:
+ DEC HL ; decrement address - could be DEC L.
+ LD (HL),0 ; place a zero byte.
+ DJNZ PRB_BYTES ; loop for all thirty-two
+
+ LD A,L ; fetch character print position.
+ SET 7,A ; signal the printer buffer is clear.
+ LD (PR_CC),A ; update one-byte system variable PR_CC
+ RET ; return.
+
+; THE 'PRINT AT' SUBROUTINE
+
+PRINT_AT:
+
+ LD A,CHARS_VERTICAL-1 ; originally 23
+ SUB B ;
+ JR C,WRONG_VAL
+
+TEST_VAL:
+ CP (IY+DF_SZ-RAMBASE)
+ JP C,REPORT_5
+
+ INC A ;
+ LD B,A ;
+ LD A,CHARS_HORIZONTAL-1 ; originally 31
+
+ SUB C ;
+
+WRONG_VAL:
+ JP C,REPORT_B
+
+ ADD A,2 ;
+ LD C,A ;
+
+SET_FIELD:
+ BIT 1,(IY+FLAGS-RAMBASE) ; Is printer in use?
+ JR Z,LOC_ADDR
+
+ LD A,$5D ;
+ SUB C ;
+ LD (PR_CC),A ;
+ RET ;
+
+; THE 'LOCATE ADDRESS' ROUTINE
+
+; I'm guessing this locates the address of a character at X,Y
+; on the screen, with 0,0 being on the bottom left?
+; S_POSN_x: equ $4039
+; S_POSN_y: equ $403A
+; so when BC is stored there, B is Y and C is X
+;
+LOC_ADDR:
+ LD (S_POSN),BC ;
+ LD HL,(VARS) ;
+ LD D,C ;
+ LD A,CHARS_HORIZONTAL+2 ; $22 == 34 originally.
+ SUB C ;
+ LD C,A ;
+ LD A,ZX_NEWLINE ;
+ INC B ;
+
+LOOK_BACK:
+ DEC HL ;
+ CP (HL) ;
+ JR NZ,LOOK_BACK
+
+ DJNZ LOOK_BACK
+
+ INC HL ;
+ CPIR ;
+ DEC HL ;
+ LD (DF_CC),HL ;
+ SCF ; Set Carry Flag
+ RET PO ;
+
+ DEC D ;
+ RET Z ;
+
+ PUSH BC ;
+ CALL MAKE_ROOM
+ POP BC ;
+ LD B,C ;
+ LD H,D ; HL := DE
+ LD L,E ;
+
+EXPAND_2:
+;
+; Writes B spaces to HL--
+;
+ LD (HL),ZX_SPACE ;
+ DEC HL ;
+ DJNZ EXPAND_2
+
+ EX DE,HL ; restore HL
+ INC HL ;
+ LD (DF_CC),HL ;
+ RET ;
+
+; THE 'EXPAND TOKENS' SUBROUTINE
+
+TOKENS:
+ PUSH AF ;
+ CALL TOKEN_ADD
+ JR NC,ALL_CHARS
+
+ BIT 0,(IY+FLAGS-RAMBASE) ; Leading space if set
+ JR NZ,ALL_CHARS
+
+ XOR A ; A = 0 = ZX_SPACE
+
+ RST _PRINT_A
+
+ALL_CHARS:
+ LD A,(BC) ;
+ AND $3F ; truncate to printable values ???
+
+ RST _PRINT_A
+ LD A,(BC) ;
+ INC BC ;
+ ADD A,A ;
+ JR NC,ALL_CHARS
+
+ POP BC ;
+ BIT 7,B ;
+ RET Z ;
+
+ CP ZX_COMMA ; $1A == 26
+ JR Z,TRAIL_SP
+
+ CP ZX_S ; $38 == 56
+ RET C ;
+
+TRAIL_SP:
+ XOR A ;
+ SET 0,(IY+FLAGS-RAMBASE) ; Suppress leading space
+ JP PRINT_SP
+
+TOKEN_ADD:
+ PUSH HL ;
+ LD HL,TOKEN_TABLE
+ BIT 7,A ;
+ JR Z,TEST_HIGH
+
+ AND $3F ;
+
+TEST_HIGH:
+ CP $43 ;
+ JR NC,FOUND
+
+ LD B,A ;
+ INC B ;
+
+WORDS:
+ BIT 7,(HL) ;
+ INC HL ;
+ JR Z,WORDS
+
+ DJNZ WORDS
+
+ BIT 6,A ;
+ JR NZ,COMP_FLAG
+
+ CP $18 ;
+
+COMP_FLAG:
+ CCF ; Complement Carry Flag
+
+FOUND:
+ LD B,H ;
+ LD C,L ;
+ POP HL ;
+ RET NC ;
+
+ LD A,(BC) ;
+ ADD A,$E4 ;
+ RET ;
+
+; THE 'ONE_SPACE' SUBROUTINE
+
+ONE_SPACE:
+ LD BC,$0001 ;
+
+; THE 'MAKE ROOM' SUBROUTINE
+
+MAKE_ROOM:
+ PUSH HL ;
+ CALL TEST_ROOM
+ POP HL ;
+ CALL POINTERS
+ LD HL,(STKEND) ;
+ EX DE,HL ;
+ LDDR ; Copy Bytes
+ RET ;
+
+; THE 'POINTERS' SUBROUTINE
+
+POINTERS:
+ PUSH AF ;
+ PUSH HL ;
+ LD HL,D_FILE ;
+ LD A,$09 ;
+
+NEXT_PTR:
+ LD E,(HL) ;
+ INC HL ;
+ LD D,(HL) ;
+ EX (SP),HL ;
+ AND A ;
+ SBC HL,DE ;
+ ADD HL,DE ;
+ EX (SP),HL ;
+ JR NC,PTR_DONE
+
+ PUSH DE ;
+ EX DE,HL ;
+ ADD HL,BC ;
+ EX DE,HL ;
+ LD (HL),D ;
+ DEC HL ;
+ LD (HL),E ;
+ INC HL ;
+ POP DE ;
+
+PTR_DONE:
+ INC HL ;
+ DEC A ;
+ JR NZ,NEXT_PTR
+
+ EX DE,HL ;
+ POP DE ;
+ POP AF ;
+ AND A ;
+ SBC HL,DE ;
+ LD B,H ;
+ LD C,L ;
+ INC BC ;
+ ADD HL,DE ;
+ EX DE,HL ;
+ RET ;
+
+; THE 'LINE ADDRESS' SUBROUTINE
+
+LINE_ADDR:
+ PUSH HL ;
+ LD HL,USER_RAM ;
+ LD D,H ;
+ LD E,L ;
+
+NEXT_TEST:
+ POP BC ;
+ CALL CP_LINES
+ RET NC ;
+
+ PUSH BC ;
+ CALL NEXT_ONE
+ EX DE,HL ;
+ JR NEXT_TEST
+
+; THE 'COMPARE LINE NUMBERS' SUBROUTINE
+
+CP_LINES:
+ LD A,(HL) ;
+ CP B ;
+ RET NZ ;
+
+ INC HL ;
+ LD A,(HL) ;
+ DEC HL ;
+ CP C ;
+ RET ;
+
+; THE 'NEXT LINE OR VARIABLE' SUBROUTINE
+
+NEXT_ONE:
+ PUSH HL ;
+ LD A,(HL) ;
+ CP $40 ;
+ JR C,LINES
+
+ BIT 5,A ;
+ JR Z,NEXT_0_4 ; skip forward
+
+ ADD A,A ;
+ JP M,NEXT_PLUS_FIVE
+
+ CCF ; Complement Carry Flag
+
+NEXT_PLUS_FIVE:
+ LD BC,$0005 ;
+ JR NC,NEXT_LETT
+
+ LD C,$11 ; 17
+
+NEXT_LETT:
+ RLA ;
+ INC HL ;
+ LD A,(HL) ;
+ JR NC,NEXT_LETT ; loop
+
+ JR NEXT_ADD
+
+LINES:
+ INC HL ;
+
+NEXT_0_4:
+ INC HL ; BC = word at HL++
+ LD C,(HL) ;
+ INC HL ;
+ LD B,(HL) ;
+ INC HL ;
+
+NEXT_ADD:
+ ADD HL,BC ;
+ POP DE ;
+
+; THE 'DIFFERENCE' SUBROUTINE
+
+DIFFER:
+ AND A ;
+ SBC HL,DE ;
+ LD B,H ; BC := (HL-DE)
+ LD C,L ;
+ ADD HL,DE ;
+ EX DE,HL ; DE := old HL ???
+ RET ;
+
+; THE 'LINE_ENDS' SUBROUTINE
+
+LINE_ENDS:
+ LD B,(IY+DF_SZ-RAMBASE)
+ PUSH BC ;
+ CALL B_LINES
+ POP BC ;
+ DEC B ;
+ JR B_LINES
+
+; THE 'CLS' COMMAND ROUTINE
+
+CLS:
+ LD B,CHARS_VERTICAL ; number of lines to clear. $18 = 24 originally.
+
+B_LINES:
+ RES 1,(IY+FLAGS-RAMBASE) ; Signal printer not in use
+ LD C,CHARS_HORIZONTAL+1 ; $21 ; extra 1 is for HALT opcode ?
+ PUSH BC ;
+ CALL LOC_ADDR
+ POP BC ;
+ LD A,(RAMTOP+1) ; is RAMTOP_hi
+ CP $4D ;
+ JR C,COLLAPSED
+
+; If RAMTOP less then 4D00, RAM less than D00 = 3.25 K,
+; uses collapsed display.
+
+ SET 7,(IY+S_POSN_y-RAMBASE)
+
+CLEAR_LOC:
+ XOR A ; prepare a space
+ CALL PRINT_SP ; prints a space
+ LD HL,(S_POSN) ;
+ LD A,L ;
+ OR H ;
+ AND $7E ;
+ JR NZ,CLEAR_LOC
+
+ JP LOC_ADDR
+
+COLLAPSED:
+ LD D,H ; DE := HL
+ LD E,L ;
+ DEC HL ;
+ LD C,B ;
+ LD B,0 ; Will loop 256 times
+ LDIR ; Copy Bytes
+ LD HL,(VARS) ;
+
+; THE 'RECLAIMING' SUBROUTINES
+
+RECLAIM_1:
+ CALL DIFFER
+
+RECLAIM_2:
+ PUSH BC ;
+ LD A,B ;
+ CPL ;
+ LD B,A ;
+ LD A,C ;
+ CPL ;
+ LD C,A ;
+ INC BC ;
+ CALL POINTERS
+ EX DE,HL ;
+ POP HL ;
+ ADD HL,DE ;
+ PUSH DE ;
+ LDIR ; Copy Bytes
+ POP HL ;
+ RET ;
+
+; THE 'E_LINE NUMBER' SUBROUTINE
+
+E_LINE_NUM:
+ LD HL,(E_LINE) ;
+ CALL TEMP_PTR2
+
+ RST _GET_CHAR
+ BIT 5,(IY+FLAGX-RAMBASE)
+ RET NZ ;
+
+ LD HL,MEM_0_1st ;
+ LD (STKEND),HL ;
+ CALL INT_TO_FP
+ CALL FP_TO_BC
+ JR C,NO_NUMBER ; to NO_NUMBER
+
+ LD HL,-10000 ; $D8F0 ; value '-10000'
+ ADD HL,BC ;
+
+NO_NUMBER:
+ JP C,REPORT_C ; to REPORT_C
+
+ CP A ;
+ JP SET_MIN
+
+; THE 'REPORT AND LINE NUMBER' PRINTING SUBROUTINES
+
+OUT_NUM:
+ PUSH DE ;
+ PUSH HL ;
+ XOR A ;
+ BIT 7,B ;
+ JR NZ,UNITS
+
+ LD H,B ; HL := BC
+ LD L,C ;
+ LD E,$FF ;
+ JR THOUSAND
+
+OUT_NO:
+ PUSH DE ;
+ LD D,(HL) ;
+ INC HL ;
+ LD E,(HL) ;
+ PUSH HL ;
+ EX DE,HL ;
+ LD E,ZX_SPACE ; set E to leading space.
+
+THOUSAND:
+ LD BC,-1000 ; $FC18 ;
+ CALL OUT_DIGIT
+ LD BC,-100 ; $FF9C ;
+ CALL OUT_DIGIT
+ LD C,-10 ; $F6 ; B is already FF, so saves a byte.
+ CALL OUT_DIGIT
+ LD A,L ;
+
+UNITS:
+ CALL OUT_CODE
+ POP HL ;
+ POP DE ;
+ RET ;
+
+; THE 'UNSTACK_Z' SUBROUTINE
+
+; This subroutine is used to return early from a routine when checking syntax.
+; On the ZX81 the same routines that execute commands also check the syntax
+; on line entry. This enables precise placement of the error marker in a line
+; that fails syntax.
+; The sequence CALL SYNTAX_Z ; RET Z can be replaced by a call to this routine
+; although it has not replaced every occurrence of the above two instructions.
+; Even on the ZX80 this routine was not fully utilized.
+
+UNSTACK_Z:
+ CALL SYNTAX_Z ; resets the ZERO flag if
+ ; checking syntax.
+ POP HL ; drop the return address.
+ RET Z ; return to previous calling routine if
+ ; checking syntax.
+
+ JP (HL) ; else jump to the continuation address in
+ ; the calling routine as RET would have done.
+
+; THE 'LPRINT' COMMAND ROUTINE
+
+
+LPRINT:
+ SET 1,(IY+FLAGS-RAMBASE) ; Signal printer in use
+
+; THE 'PRINT' COMMAND ROUTINE
+
+PRINT:
+ LD A,(HL) ;
+ CP ZX_NEWLINE ;
+ JP Z,PRINT_END ; to PRINT_END
+
+PRINT_1:
+ SUB ZX_COMMA ; $1A == 26
+ ADC A,$00 ;
+ JR Z,SPACING ; to SPACING
+ ;
+ ; Compare with AT,
+ ; less comma recently subtracted.
+ ;
+ CP ZX_AT-ZX_COMMA ; $A7 == 167
+ JR NZ,NOT_AT ;
+
+
+ RST _NEXT_CHAR
+ CALL CLASS_6
+ CP ZX_COMMA ; $1A = 26
+ JP NZ,REPORT_C ;
+
+ RST _NEXT_CHAR
+ CALL CLASS_6
+ CALL SYNTAX_ON
+
+ RST _FP_CALC ;;
+ DEFB __exchange ;;
+ DEFB __end_calc ;;
+
+ CALL STK_TO_BC
+ CALL PRINT_AT
+ JR PRINT_ON
+
+NOT_AT:
+ CP ZX_TAB-ZX_COMMA ; $A8 == 168
+ JR NZ,NOT_TAB
+
+
+ RST _NEXT_CHAR
+ CALL CLASS_6
+ CALL SYNTAX_ON
+ CALL STK_TO_A
+ JP NZ,REPORT_B
+
+ AND $1F ; truncate to 0 to 31 characters ???
+ LD C,A ;
+ BIT 1,(IY+FLAGS-RAMBASE) ; Is printer in use
+ JR Z,TAB_TEST
+
+ SUB (IY+PR_CC-RAMBASE)
+ SET 7,A ;
+ ADD A,$3C ; 60
+ CALL NC,COPY_BUFF
+
+TAB_TEST:
+ ADD A,(IY+S_POSN_x-RAMBASE) ; screen position X
+ CP CHARS_HORIZONTAL+1 ; 33 (characters horizontal plus newline ???)
+ LD A,(S_POSN_y) ; screen position Y
+ SBC A,1 ;
+ CALL TEST_VAL
+ SET 0,(IY+FLAGS-RAMBASE) ; sv FLAGS - Suppress leading space
+ JR PRINT_ON
+
+NOT_TAB:
+ CALL SCANNING
+ CALL PRINT_STK
+
+PRINT_ON:
+ RST _GET_CHAR
+ SUB ZX_COMMA ; $1A
+ ADC A,0 ;
+ JR Z,SPACING
+
+ CALL CHECK_END
+ JP PRINT_END
+
+SPACING:
+ CALL NC,FIELD
+
+ RST _NEXT_CHAR
+ CP ZX_NEWLINE ;
+ RET Z ;
+
+ JP PRINT_1
+
+SYNTAX_ON:
+ CALL SYNTAX_Z
+ RET NZ ;
+
+ POP HL ;
+ JR PRINT_ON
+
+PRINT_STK:
+ CALL UNSTACK_Z
+ BIT 6,(IY+FLAGS-RAMBASE) ; Numeric or string result?
+ CALL Z,STK_FETCH
+ JR Z,PR_STR_4
+
+ JP PRINT_FP ; jump forward
+
+PR_STR_1:
+ LD A,ZX_QUOTE ; $0B
+
+PR_STR_2:
+ RST _PRINT_A
+
+PR_STR_3:
+ LD DE,(X_PTR) ;
+
+PR_STR_4:
+ LD A,B ;
+ OR C ;
+ DEC BC ;
+ RET Z ;
+
+ LD A,(DE) ;
+ INC DE ;
+ LD (X_PTR),DE ;
+ BIT 6,A ;
+ JR Z,PR_STR_2
+
+ CP $C0 ;
+ JR Z,PR_STR_1
+
+ PUSH BC ;
+ CALL TOKENS
+ POP BC ;
+ JR PR_STR_3
+
+PRINT_END:
+ CALL UNSTACK_Z
+ LD A,ZX_NEWLINE ;
+
+ RST _PRINT_A
+ RET ;
+
+FIELD:
+ CALL UNSTACK_Z
+ SET 0,(IY+FLAGS-RAMBASE) ; Suppress leading space
+ XOR A ;
+
+ RST _PRINT_A
+ LD BC,(S_POSN) ;
+ LD A,C ;
+ BIT 1,(IY+FLAGS-RAMBASE) ; Is printer in use
+ JR Z,CENTRE
+
+ LD A,$5D ;
+ SUB (IY+PR_CC-RAMBASE)
+
+CENTRE:
+ LD C,$11 ;
+ CP C ;
+ JR NC,RIGHT
+
+ LD C,$01 ;
+
+RIGHT:
+ CALL SET_FIELD
+ RET ;
+
+; THE 'PLOT AND UNPLOT' COMMAND ROUTINES
+
+PLOT_UNPLOT:
+;
+; Of the 24 lines, only top 22 ar used for plotting.
+;
+ CALL STK_TO_BC
+ LD (COORDS_x),BC ;
+ LD A,2*(CHARS_VERTICAL-2)-1 ;
+ SUB B ;
+ JP C,REPORT_B
+
+ LD B,A ;
+ LD A,$01 ;
+ SRA B ;
+ JR NC,COLUMNS
+
+ LD A,$04 ;
+
+COLUMNS:
+ SRA C ;
+ JR NC,FIND_ADDR
+
+ RLCA ;
+
+FIND_ADDR:
+ PUSH AF ;
+ CALL PRINT_AT
+ LD A,(HL) ;
+ RLCA ;
+ CP ZX_BRACKET_LEFT ; $10
+ JR NC,TABLE_PTR
+
+ RRCA ;
+ JR NC,SQ_SAVED
+
+ XOR $8F ;
+
+SQ_SAVED:
+ LD B,A ;
+
+TABLE_PTR:
+ LD DE,P_UNPLOT ; Address: P_UNPLOT
+ LD A,(T_ADDR) ; get T_ADDR_lo
+ SUB E ;
+ JP M,PLOT
+
+ POP AF ;
+ CPL ;
+ AND B ;
+ JR UNPLOT
+
+PLOT:
+ POP AF ;
+ OR B ;
+
+UNPLOT:
+ CP 8 ; Only apply to graphic characters (0 to 7)
+ JR C,PLOT_END
+
+ XOR $8F ; binary 1000 1111
+
+PLOT_END:
+ EXX ;
+
+ RST _PRINT_A
+ EXX ;
+ RET ;
+
+; THE 'STACK_TO_BC' SUBROUTINE
+
+STK_TO_BC:
+ CALL STK_TO_A
+ LD B,A ;
+ PUSH BC ;
+ CALL STK_TO_A
+ LD E,C ;
+ POP BC ;
+ LD D,C ;
+ LD C,A ;
+ RET ;
+
+; THE 'STACK_TO_A' SUBROUTINE
+
+STK_TO_A:
+ CALL FP_TO_A
+ JP C,REPORT_B
+
+ LD C,$01 ;
+ RET Z ;
+
+ LD C,$FF ;
+ RET ;
+
+; THE 'SCROLL' SUBROUTINE
+
+SCROLL:
+ LD B,(IY+DF_SZ-RAMBASE)
+ LD C,CHARS_HORIZONTAL+1 ;
+ CALL LOC_ADDR
+ CALL ONE_SPACE
+ LD A,(HL) ;
+ LD (DE),A ;
+ INC (IY+S_POSN_y-RAMBASE)
+ LD HL,(D_FILE) ;
+ INC HL ;
+ LD D,H ;
+ LD E,L ;
+ CPIR ;
+ JP RECLAIM_1
+
+; THE 'SYNTAX' TABLES
+
+; i) The Offset table
+
+offset_t:
+ DEFB P_LPRINT - $ ; 8B offset
+ DEFB P_LLIST - $ ; 8D offset
+ DEFB P_STOP - $ ; 2D offset
+ DEFB P_SLOW - $ ; 7F offset
+ DEFB P_FAST - $ ; 81 offset
+ DEFB P_NEW - $ ; 49 offset
+ DEFB P_SCROLL - $ ; 75 offset
+ DEFB P_CONT - $ ; 5F offset
+ DEFB P_DIM - $ ; 40 offset
+ DEFB P_REM - $ ; 42 offset
+ DEFB P_FOR - $ ; 2B offset
+ DEFB P_GOTO - $ ; 17 offset
+ DEFB P_GOSUB - $ ; 1F offset
+ DEFB P_INPUT - $ ; 37 offset
+ DEFB P_LOAD - $ ; 52 offset
+ DEFB P_LIST - $ ; 45 offset
+ DEFB P_LET - $ ; 0F offset
+ DEFB P_PAUSE - $ ; 6D offset
+ DEFB P_NEXT - $ ; 2B offset
+ DEFB P_POKE - $ ; 44 offset
+ DEFB P_PRINT - $ ; 2D offset
+ DEFB P_PLOT - $ ; 5A offset
+ DEFB P_RUN - $ ; 3B offset
+ DEFB P_SAVE - $ ; 4C offset
+ DEFB P_RAND - $ ; 45 offset
+ DEFB P_IF - $ ; 0D offset
+ DEFB P_CLS - $ ; 52 offset
+ DEFB P_UNPLOT - $ ; 5A offset
+ DEFB P_CLEAR - $ ; 4D offset
+ DEFB P_RETURN - $ ; 15 offset
+ DEFB P_COPY - $ ; 6A offset
+
+; ii) The parameter table.
+
+P_LET:
+ DEFB _CLASS_01 ; A variable is required.
+ DEFB ZX_EQUAL ; Separator: '='
+ DEFB _CLASS_02 ; An expression, numeric or string,
+ ; must follow.
+
+P_GOTO:
+ DEFB _CLASS_06 ; A numeric expression must follow.
+ DEFB _CLASS_00 ; No further operands.
+ DEFW GOTO
+
+P_IF:
+ DEFB _CLASS_06 ; A numeric expression must follow.
+ DEFB ZX_THEN ; Separator: 'THEN'
+ DEFB _CLASS_05 ; Variable syntax checked entirely
+ ; by routine.
+ DEFW IF
+
+P_GOSUB:
+ DEFB _CLASS_06 ; A numeric expression must follow.
+ DEFB _CLASS_00 ; No further operands.
+ DEFW GOSUB
+
+P_STOP:
+ DEFB _CLASS_00 ; No further operands.
+ DEFW STOP
+
+P_RETURN:
+ DEFB _CLASS_00 ; No further operands.
+ DEFW RETURN
+
+P_FOR:
+ DEFB _CLASS_04 ; A single character variable must
+ ; follow.
+ DEFB ZX_EQUAL ; Separator: '='
+ DEFB _CLASS_06 ; A numeric expression must follow.
+ DEFB ZX_TO ; Separator: 'TO'
+ DEFB _CLASS_06 ; A numeric expression must follow.
+ DEFB _CLASS_05 ; Variable syntax checked entirely
+ ; by routine.
+ DEFW FOR
+
+P_NEXT:
+ DEFB _CLASS_04 ; A single character variable must
+ ; follow.
+ DEFB _CLASS_00 ; No further operands.
+ DEFW NEXT
+
+P_PRINT:
+ DEFB _CLASS_05 ; Variable syntax checked entirely
+ ; by routine.
+ DEFW PRINT ; not LPRINT ???
+
+P_INPUT:
+ DEFB _CLASS_01 ; A variable is required.
+ DEFB _CLASS_00 ; No further operands.
+ DEFW INPUT
+
+P_DIM:
+ DEFB _CLASS_05 ; Variable syntax checked entirely
+ ; by routine.
+ DEFW DIM
+
+P_REM:
+ DEFB _CLASS_05 ; Variable syntax checked entirely
+ ; by routine.
+ DEFW REM
+
+P_NEW:
+ DEFB _CLASS_00 ; No further operands.
+ DEFW NEW
+
+P_RUN:
+ DEFB _CLASS_03 ; A numeric expression may follow
+ ; else default to zero.
+ DEFW RUN
+
+P_LIST:
+ DEFB _CLASS_03 ; A numeric expression may follow
+ ; else default to zero.
+ DEFW LIST
+
+P_POKE:
+ DEFB _CLASS_06 ; A numeric expression must follow.
+ DEFB ZX_COMMA ; Separator: ','
+ DEFB _CLASS_06 ; A numeric expression must follow.
+ DEFB _CLASS_00 ; No further operands.
+ DEFW POKE
+
+P_RAND:
+ DEFB _CLASS_03 ; A numeric expression may follow
+ ; else default to zero.
+ DEFW RAND
+
+P_LOAD:
+ DEFB _CLASS_05 ; Variable syntax checked entirely
+ ; by routine.
+ DEFW LOAD
+
+P_SAVE:
+ DEFB _CLASS_05 ; Variable syntax checked entirely
+ ; by routine.
+ DEFW SAVE
+
+P_CONT:
+ DEFB _CLASS_00 ; No further operands.
+ DEFW CONT
+
+P_CLEAR:
+ DEFB _CLASS_00 ; No further operands.
+ DEFW CLEAR
+
+P_CLS:
+ DEFB _CLASS_00 ; No further operands.
+ DEFW CLS
+
+P_PLOT:
+ DEFB _CLASS_06 ; A numeric expression must follow.
+ DEFB ZX_COMMA ; Separator: ','
+ DEFB _CLASS_06 ; A numeric expression must follow.
+ DEFB _CLASS_00 ; No further operands.
+ DEFW PLOT_UNPLOT
+
+P_UNPLOT:
+ DEFB _CLASS_06 ; A numeric expression must follow.
+ DEFB ZX_COMMA ; Separator: ','
+ DEFB _CLASS_06 ; A numeric expression must follow.
+ DEFB _CLASS_00 ; No further operands.
+ DEFW PLOT_UNPLOT
+
+P_SCROLL:
+ DEFB _CLASS_00 ; No further operands.
+ DEFW SCROLL
+
+P_PAUSE:
+ DEFB _CLASS_06 ; A numeric expression must follow.
+ DEFB _CLASS_00 ; No further operands.
+ DEFW PAUSE
+
+P_SLOW:
+ DEFB _CLASS_00 ; No further operands.
+ DEFW SLOW
+
+P_FAST:
+ DEFB _CLASS_00 ; No further operands.
+ DEFW FAST
+
+P_COPY:
+ DEFB _CLASS_00 ; No further operands.
+ DEFW COPY
+
+P_LPRINT:
+ DEFB _CLASS_05 ; Variable syntax checked entirely
+ ; by routine.
+ DEFW LPRINT
+
+P_LLIST:
+ DEFB _CLASS_03 ; A numeric expression may follow
+ ; else default to zero.
+ DEFW LLIST
+
+; THE 'LINE SCANNING' ROUTINE
+
+LINE_SCAN:
+ LD (IY+FLAGS-RAMBASE),1
+ CALL E_LINE_NUM
+
+LINE_RUN:
+ CALL SET_MIN
+ LD HL,ERR_NR ;
+ LD (HL),$FF ;
+ LD HL,FLAGX ;
+ BIT 5,(HL) ;
+ JR Z,LINE_NULL
+
+ CP $E3 ; 'STOP' ?
+ LD A,(HL) ;
+ JP NZ,INPUT_REP
+
+ CALL SYNTAX_Z
+ RET Z ;
+
+ RST _ERROR_1
+ DEFB $0C ; Error Report: BREAK - CONT repeats
+
+; THE 'STOP' COMMAND ROUTINE
+
+STOP:
+ RST _ERROR_1
+ DEFB $08 ; Error Report: STOP statement
+
+; the interpretation of a line continues with a check for just spaces
+; followed by a carriage return.
+; The IF command also branches here with a true value to execute the
+; statement after the THEN but the statement can be null so
+; 10 IF 1 = 1 THEN
+; passes syntax (on all ZX computers).
+
+LINE_NULL:
+ RST _GET_CHAR
+ LD B,$00 ; prepare to index - early.
+ CP ZX_NEWLINE ; compare to NEWLINE.
+ RET Z ; return if so.
+
+ LD C,A ; transfer character to C.
+
+ RST _NEXT_CHAR ; advances.
+ LD A,C ; character to A
+ SUB $E1 ; subtract 'LPRINT' - lowest command.
+ JR C,REPORT_C2 ; forward if less
+
+ LD C,A ; reduced token to C
+ LD HL,offset_t ; set HL to address of offset table.
+ ADD HL,BC ; index into offset table.
+ LD C,(HL) ; fetch offset
+ ADD HL,BC ; index into parameter table.
+ JR GET_PARAM
+
+SCAN_LOOP:
+ LD HL,(T_ADDR) ;
+
+; -> Entry Point to Scanning Loop
+
+GET_PARAM:
+ LD A,(HL) ;
+ INC HL ;
+ LD (T_ADDR),HL ;
+
+ LD BC,SCAN_LOOP
+ PUSH BC ; is pushed on machine stack.
+
+ LD C,A ;
+ CP ZX_QUOTE ; $0B
+ JR NC,SEPARATOR
+
+ LD HL,class_tbl ; class_tbl - the address of the class table.
+ LD B,$00 ;
+ ADD HL,BC ;
+ LD C,(HL) ;
+ ADD HL,BC ;
+ PUSH HL ;
+
+ RST _GET_CHAR
+ RET ; indirect jump to class routine and
+ ; by subsequent RET to SCAN_LOOP.
+
+; THE 'SEPARATOR' ROUTINE
+
+SEPARATOR:
+ RST _GET_CHAR
+ CP C ;
+ JR NZ,REPORT_C2
+ ; 'Nonsense in BASIC'
+
+ RST _NEXT_CHAR
+ RET ; return
+
+; THE 'COMMAND CLASS' TABLE
+
+class_tbl:
+ DEFB CLASS_0 - $ ; 17 offset to; Address: CLASS_0
+ DEFB CLASS_1 - $ ; 25 offset to; Address: CLASS_1
+ DEFB CLASS_2 - $ ; 53 offset to; Address: CLASS_2
+ DEFB CLASS_3 - $ ; 0F offset to; Address: CLASS_3
+ DEFB CLASS_4 - $ ; 6B offset to; Address: CLASS_4
+ DEFB CLASS_5 - $ ; 13 offset to; Address: CLASS_5
+ DEFB CLASS_6 - $ ; 76 offset to; Address: CLASS_6
+
+; THE 'CHECK END' SUBROUTINE
+
+; Check for end of statement and that no spurious characters occur after
+; a correctly parsed statement. Since only one statement is allowed on each
+; line, the only character that may follow a statement is a NEWLINE.
+;
+CHECK_END:
+ CALL SYNTAX_Z
+ RET NZ ; return in runtime.
+
+ POP BC ; else drop return address.
+
+CHECK_2:
+ LD A,(HL) ; fetch character.
+ CP ZX_NEWLINE ; compare to NEWLINE.
+ RET Z ; return if so.
+
+REPORT_C2:
+ JR REPORT_C
+ ; 'Nonsense in BASIC'
+
+; COMMAND CLASSES 03, 00, 05
+
+CLASS_3:
+ CP ZX_NEWLINE ;
+ CALL NUMBER_TO_STK
+
+CLASS_0:
+ CP A ;
+
+CLASS_5:
+ POP BC ;
+ CALL Z,CHECK_END
+ EX DE,HL ;
+ LD HL,(T_ADDR) ;
+ LD C,(HL) ;
+ INC HL ;
+ LD B,(HL) ;
+ EX DE,HL ;
+
+CLASS_END:
+ PUSH BC ;
+ RET ;
+
+; COMMAND CLASSES 01, 02, 04, 06
+
+CLASS_1:
+ CALL LOOK_VARS
+
+CLASS_4_2:
+ LD (IY+FLAGX-RAMBASE),$00
+ JR NC,SET_STK
+
+ SET 1,(IY+FLAGX-RAMBASE)
+ JR NZ,SET_STRLN
+
+REPORT_2:
+ RST _ERROR_1
+ DEFB $01 ; Error Report: Variable not found
+
+SET_STK:
+ CALL Z,STK_VAR
+ BIT 6,(IY+FLAGS-RAMBASE) ; Numeric or string result?
+ JR NZ,SET_STRLN
+
+ XOR A ;
+ CALL SYNTAX_Z
+ CALL NZ,STK_FETCH
+ LD HL,FLAGX ;
+ OR (HL) ;
+ LD (HL),A ;
+ EX DE,HL ;
+
+SET_STRLN:
+ LD (STRLEN),BC ;
+ LD (DEST),HL ;
+
+; THE 'REM' COMMAND ROUTINE
+
+REM:
+ RET ;
+
+CLASS_2:
+ POP BC ;
+ LD A,(FLAGS) ; sv
+
+INPUT_REP:
+ PUSH AF ;
+ CALL SCANNING
+ POP AF ;
+ LD BC,LET ; Address: LET
+ LD D,(IY+FLAGS-RAMBASE)
+ XOR D ;
+ AND $40 ;
+ JR NZ,REPORT_C ; to REPORT_C
+
+ BIT 7,D ;
+ JR NZ,CLASS_END ; to CLASS_END
+
+ JR CHECK_2 ; to CHECK_2
+
+CLASS_4:
+ CALL LOOK_VARS
+ PUSH AF ;
+ LD A,C ;
+ OR $9F ;
+ INC A ;
+ JR NZ,REPORT_C ; to REPORT_C
+
+ POP AF ;
+ JR CLASS_4_2 ; to CLASS_4_2
+
+CLASS_6:
+ CALL SCANNING
+ BIT 6,(IY+FLAGS-RAMBASE) ; Numeric or string result?
+ RET NZ ;
+
+REPORT_C:
+ RST _ERROR_1
+ DEFB $0B ; Error Report: Nonsense in BASIC
+
+; THE 'NUMBER TO STACK' SUBROUTINE
+
+NUMBER_TO_STK:
+ JR NZ,CLASS_6 ; back to CLASS_6 with a non-zero number.
+
+ CALL SYNTAX_Z
+ RET Z ; return if checking syntax.
+
+; in runtime a zero default is placed on the calculator stack.
+
+ RST _FP_CALC ;;
+ DEFB __stk_zero ;;
+ DEFB __end_calc ;;
+
+ RET ; return.
+
+; THE 'SYNTAX_Z' SUBROUTINE
+
+; This routine returns with zero flag set if checking syntax.
+; Calling this routine uses three instruction bytes compared to four if the
+; bit test is implemented inline.
+
+SYNTAX_Z:
+ BIT 7,(IY+FLAGS-RAMBASE) ; checking syntax only?
+ RET ; return.
+
+
+; THE 'IF' COMMAND ROUTINE
+
+; In runtime, the class routines have evaluated the test expression and
+; the result, true or false, is on the stack.
+
+IF:
+ CALL SYNTAX_Z
+ JR Z,IF_END ; forward if checking syntax
+
+; else delete the Boolean value on the calculator stack.
+
+ RST _FP_CALC ;;
+ DEFB __delete ;;
+ DEFB __end_calc ;;
+
+; register DE points to exponent of floating point value.
+
+ LD A,(DE) ; fetch exponent.
+ AND A ; test for zero - FALSE.
+ RET Z ; return if so.
+
+IF_END:
+ JP LINE_NULL ; jump back
+
+; THE 'FOR' COMMAND ROUTINE
+
+FOR:
+ CP ZX_STEP ; is current character 'STEP' ?
+ JR NZ,F_USE_ONE ; forward if not
+
+
+ RST _NEXT_CHAR
+ CALL CLASS_6 ; stacks the number
+ CALL CHECK_END
+ JR F_REORDER ; forward to F_REORDER
+
+F_USE_ONE:
+ CALL CHECK_END
+
+ RST _FP_CALC ;;
+ DEFB __stk_one ;;
+ DEFB __end_calc ;;
+
+F_REORDER:
+ RST _FP_CALC ;; v, l, s.
+ DEFB __st_mem_0 ;; v, l, s.
+ DEFB __delete ;; v, l.
+ DEFB __exchange ;; l, v.
+ DEFB __get_mem_0 ;; l, v, s.
+ DEFB __exchange ;; l, s, v.
+ DEFB __end_calc ;; l, s, v.
+
+ CALL LET
+
+ LD (MEM),HL ; set MEM to address variable.
+ DEC HL ; point to letter.
+ LD A,(HL) ;
+ SET 7,(HL) ;
+ LD BC,$0006 ;
+ ADD HL,BC ;
+ RLCA ;
+ JR C,F_LMT_STP
+
+ SLA C ;
+ CALL MAKE_ROOM
+ INC HL ;
+
+F_LMT_STP:
+ PUSH HL ;
+
+ RST _FP_CALC ;;
+ DEFB __delete ;;
+ DEFB __delete ;;
+ DEFB __end_calc ;;
+
+ POP HL ;
+ EX DE,HL ;
+
+ LD C,$0A ; ten bytes to be moved.
+ LDIR ; copy bytes
+
+ LD HL,(PPC) ; set HL to system variable PPC current line.
+ EX DE,HL ; transfer to DE, variable pointer to HL.
+ INC DE ; loop start will be this line + 1 at least.
+ LD (HL),E ;
+ INC HL ;
+ LD (HL),D ;
+ CALL NEXT_LOOP ; considers an initial pass.
+ RET NC ; return if possible.
+
+; else program continues from point following matching NEXT.
+
+ BIT 7,(IY+PPC_hi-RAMBASE)
+ RET NZ ; return if over 32767 ???
+
+ LD B,(IY+STRLEN_lo-RAMBASE) ; fetch variable name from STRLEN_lo
+ RES 6,B ; make a true letter.
+ LD HL,(NXTLIN) ; set HL from NXTLIN
+
+; now enter a loop to look for matching next.
+
+NXTLIN_NO:
+ LD A,(HL) ; fetch high byte of line number.
+ AND $C0 ; mask off low bits $3F
+ JR NZ,FOR_END ; forward at end of program
+
+ PUSH BC ; save letter
+ CALL NEXT_ONE ; finds next line.
+ POP BC ; restore letter
+
+ INC HL ; step past low byte
+ INC HL ; past the
+ INC HL ; line length.
+ CALL TEMP_PTR1 ; sets CH_ADD
+
+ RST _GET_CHAR
+ CP ZX_NEXT ;
+ EX DE,HL ; next line to HL.
+ JR NZ,NXTLIN_NO ; back with no match
+
+ EX DE,HL ; restore pointer.
+
+ RST _NEXT_CHAR ; advances and gets letter in A.
+ EX DE,HL ; save pointer
+ CP B ; compare to variable name.
+ JR NZ,NXTLIN_NO ; back with mismatch
+
+FOR_END:
+ LD (NXTLIN),HL ; update system variable NXTLIN
+ RET ; return.
+
+
+; THE 'NEXT' COMMAND ROUTINE
+
+NEXT:
+ BIT 1,(IY+FLAGX-RAMBASE)
+ JP NZ,REPORT_2
+
+ LD HL,(DEST)
+ BIT 7,(HL)
+ JR Z,REPORT_1
+
+ INC HL ;
+ LD (MEM),HL ;
+
+ RST _FP_CALC ;;
+ DEFB __get_mem_0 ;;
+ DEFB __get_mem_2 ;;
+ DEFB __addition ;;
+ DEFB __st_mem_0 ;;
+ DEFB __delete ;;
+ DEFB __end_calc ;;
+
+ CALL NEXT_LOOP
+ RET C ;
+
+ LD HL,(MEM) ;
+ LD DE,$000F ;
+ ADD HL,DE ;
+ LD E,(HL) ;
+ INC HL ;
+ LD D,(HL) ;
+ EX DE,HL ;
+ JR GOTO_2
+
+REPORT_1:
+ RST _ERROR_1
+ DEFB $00 ; Error Report: NEXT without FOR
+
+; THE 'NEXT_LOOP' SUBROUTINE
+
+NEXT_LOOP:
+ RST _FP_CALC ;;
+ DEFB __get_mem_1 ;;
+ DEFB __get_mem_0 ;;
+ DEFB __get_mem_2 ;;
+ DEFB __less_0 ;;
+ DEFB __jump_true ;;
+ DEFB LMT_V_VAL - $ ;;
+ DEFB __exchange ;;
+
+LMT_V_VAL:
+ DEFB __subtract ;;
+ DEFB __greater_0 ;;
+ DEFB __jump_true ;;
+ DEFB IMPOSS - $ ;;
+ DEFB __end_calc ;;
+
+ AND A ; clear carry flag
+ RET ; return.
+
+IMPOSS:
+ DEFB __end_calc ;;
+
+ SCF ; set carry flag
+ RET ; return.
+
+; THE 'RAND' COMMAND ROUTINE
+
+; The keyword was 'RANDOMISE' on the ZX80, is 'RAND' here on the ZX81 and
+; becomes 'RANDOMIZE' on the ZX Spectrum.
+; In all invocations the procedure is the same - to set the SEED system variable
+; with a supplied integer value or to use a time-based value if no number, or
+; zero, is supplied.
+
+RAND:
+ CALL FIND_INT
+ LD A,B ; test value
+ OR C ; for zero
+ JR NZ,SET_SEED ; forward if not zero
+
+ LD BC,(FRAMES) ; fetch value of FRAMES system variable.
+
+SET_SEED:
+ LD (SEED),BC ; update the SEED system variable.
+ RET ; return.
+
+
+; THE 'CONT' COMMAND ROUTINE
+
+; Another abbreviated command. ROM space was really tight.
+; CONTINUE at the line number that was set when break was pressed.
+; Sometimes the current line, sometimes the next line.
+
+CONT:
+ LD HL,(OLDPPC) ; set HL from system variable OLDPPC
+ JR GOTO_2 ; forward
+
+
+; THE 'GOTO' COMMAND ROUTINE
+
+; This token also suffered from the shortage of room and there is no space
+; getween GO and TO as there is on the ZX80 and ZX Spectrum. The same also
+; applies to the GOSUB keyword.
+
+GOTO:
+ CALL FIND_INT
+ LD H,B ;
+ LD L,C ;
+
+GOTO_2:
+ LD A,H ;
+ CP $F0 ; ZX_LIST ???
+ JR NC,REPORT_B
+
+ CALL LINE_ADDR
+ LD (NXTLIN),HL ; sv
+ RET ;
+
+; THE 'POKE' COMMAND ROUTINE
+
+POKE:
+ CALL FP_TO_A
+ JR C,REPORT_B ; forward, with overflow
+
+ JR Z,POKE_SAVE ; forward, if positive
+
+ NEG ; negate
+
+POKE_SAVE:
+ PUSH AF ; preserve value.
+ CALL FIND_INT ; gets address in BC
+ ; invoking the error routine with overflow
+ ; or a negative number.
+ POP AF ; restore value.
+
+; Note. the next two instructions are legacy code from the ZX80 and
+; inappropriate here.
+
+ BIT 7,(IY+ERR_NR-RAMBASE) ; test ERR_NR - is it still $FF ?
+ RET Z ; return with error.
+
+ LD (BC),A ; update the address contents.
+ RET ; return.
+
+; THE 'FIND INTEGER' SUBROUTINE
+
+FIND_INT:
+ CALL FP_TO_BC
+ JR C,REPORT_B ; forward with overflow
+
+ RET Z ; return if positive (0-65535).
+
+REPORT_B:
+ RST _ERROR_1
+ DEFB $0A ; Error Report: Integer out of range
+;
+; Seems stupid, $0A is 10 but the ERROR_CODE_INTEGER_OUT_OF_RANGE is 11
+; maybe gets incremented ???
+
+; THE 'RUN' COMMAND ROUTINE
+
+RUN:
+ CALL GOTO
+ JP CLEAR
+
+; THE 'GOSUB' COMMAND ROUTINE
+
+GOSUB:
+ LD HL,(PPC) ;
+ INC HL ;
+ EX (SP),HL ;
+ PUSH HL ;
+ LD (ERR_SP),SP ; set the error stack pointer - ERR_SP
+ CALL GOTO
+ LD BC,6 ;
+
+; THE 'TEST ROOM' SUBROUTINE
+
+; checks there is room for 36 bytes on the stack
+;
+TEST_ROOM:
+ LD HL,(STKEND) ;
+ ADD HL,BC ; HL = STKEND + BC
+ JR C,REPORT_4
+
+ EX DE,HL ; DE = STKEND + BC
+ LD HL,$0024 ; 36 decimal
+ ADD HL,DE ; HL = 36 + STKEND + BC
+ SBC HL,SP ; HL = 36 + STKEND + BC - SP
+ RET C ;
+
+REPORT_4:
+ LD L,3 ;
+ JP ERROR_3
+
+; THE 'RETURN' COMMAND ROUTINE
+
+RETURN:
+ POP HL ;
+ EX (SP),HL ;
+ LD A,H ;
+ CP $3E ;
+ JR Z,REPORT_7
+
+ LD (ERR_SP),SP ;
+ JR GOTO_2 ; back
+
+REPORT_7:
+ EX (SP),HL ;
+ PUSH HL ;
+
+ RST _ERROR_1
+ DEFB 6 ; Error Report: RETURN without GOSUB
+
+; Contradicts BASIC manual:
+; 7 is ERROR_CODE_RETURN_WITHOUT_GOSUB
+; 6 is ERROR_CODE_ARITHMETIC_OVERFLOW
+
+; THE 'INPUT' COMMAND ROUTINE
+
+INPUT:
+ BIT 7,(IY+PPC_hi-RAMBASE)
+ JR NZ,REPORT_8 ; to REPORT_8
+
+ CALL X_TEMP
+ LD HL,FLAGX ;
+ SET 5,(HL) ;
+ RES 6,(HL) ;
+ LD A,(FLAGS) ;
+ AND $40 ; 64
+ LD BC,2 ;
+ JR NZ,PROMPT ; to PROMPT
+
+ LD C,$04 ;
+
+PROMPT:
+ OR (HL) ;
+ LD (HL),A ;
+
+ RST _BC_SPACES
+ LD (HL),ZX_NEWLINE
+ LD A,C ;
+ RRCA ;
+ RRCA ;
+ JR C,ENTER_CUR
+
+ LD A,$0B ; ZX_QUOTE ???
+ LD (DE),A ;
+ DEC HL ;
+ LD (HL),A ;
+
+ENTER_CUR:
+ DEC HL ;
+ LD (HL),ZX_CURSOR ;
+ LD HL,(S_POSN) ;
+ LD (T_ADDR),HL ;
+ POP HL ;
+ JP LOWER
+
+REPORT_8:
+ RST _ERROR_1
+ DEFB 7 ; Error Report: End of file
+
+; THE 'PAUSE' COMMAND ROUTINE
+
+FAST:
+ CALL SET_FAST
+ RES 6,(IY+CDFLAG-RAMBASE)
+ RET ; return.
+
+; THE 'SLOW' COMMAND ROUTINE
+
+SLOW:
+ SET 6,(IY+CDFLAG-RAMBASE)
+ JP SLOW_FAST
+
+; THE 'PAUSE' COMMAND ROUTINE
+
+PAUSE:
+ CALL FIND_INT
+ CALL SET_FAST
+ LD H,B ;
+ LD L,C ;
+ CALL DISPLAY_P
+
+ LD (IY+FRAMES_hi-RAMBASE),$FF
+
+ CALL SLOW_FAST
+ JR DEBOUNCE
+
+; THE 'BREAK' SUBROUTINE
+
+BREAK_1:
+ LD A,$7F ; read port $7FFE - keys B,N,M,.,SPACE.
+ IN A,(IO_PORT_KEYBOARD_RD) ;
+ RRA ; carry will be set if space not pressed.
+
+; THE 'DEBOUNCE' SUBROUTINE
+
+DEBOUNCE:
+ RES 0,(IY+CDFLAG-RAMBASE) ; update
+ LD A,$FF ;
+ LD (DEBOUNCE_VAR),A ; update
+ RET ; return.
+
+; THE 'SCANNING' SUBROUTINE
+
+; This recursive routine is where the ZX81 gets its power.
+; Provided there is enough memory it can evaluate
+; an expression of unlimited complexity.
+; Note. there is no unary plus so, as on the ZX80, PRINT +1 gives a syntax error.
+; PRINT +1 works on the Spectrum but so too does PRINT + "STRING".
+
+SCANNING:
+ RST _GET_CHAR
+ LD B,0 ; set B register to zero.
+ PUSH BC ; stack zero as a priority end-marker.
+
+S_LOOP_1:
+ CP ZX_RND
+ JR NZ,S_TEST_PI ; forward, if not, to S_TEST_PI
+
+; THE 'RND' FUNCTION
+
+RND:
+ CALL SYNTAX_Z
+ JR Z,S_JPI_END ; forward if checking syntax to S_JPI_END
+
+ LD BC,(SEED) ; sv
+ CALL STACK_BC
+
+ RST _FP_CALC ;;
+ DEFB __stk_one ;;
+ DEFB __addition ;;
+ DEFB __stk_data ;;
+ DEFB $37 ;;Exponent: $87, Bytes: 1
+ DEFB $16 ;;(+00,+00,+00)
+ DEFB __multiply ;;
+ DEFB __stk_data ;;
+ DEFB $80 ;;Bytes: 3
+ DEFB $41 ;;Exponent $91
+ DEFB $00,$00,$80 ;;(+00)
+ DEFB __n_mod_m ;;
+ DEFB __delete ;;
+ DEFB __stk_one ;;
+ DEFB __subtract ;;
+ DEFB __duplicate ;;
+ DEFB __end_calc ;;
+
+ CALL FP_TO_BC
+ LD (SEED),BC ; update the SEED system variable.
+ LD A,(HL) ; HL addresses the exponent of the last value.
+ AND A ; test for zero
+ JR Z,S_JPI_END ; forward, if so
+
+ SUB $10 ; else reduce exponent by sixteen
+ LD (HL),A ; thus dividing by 65536 for last value.
+
+S_JPI_END:
+ JR S_PI_END ; forward
+
+S_TEST_PI:
+ CP ZX_PI ; the 'PI' character
+ JR NZ,S_TST_INK ; forward, if not
+
+; THE 'PI' EVALUATION
+
+ CALL SYNTAX_Z
+ JR Z,S_PI_END ; forward if checking syntax
+
+ RST _FP_CALC ;;
+ DEFB __stk_half_pi ;;
+ DEFB __end_calc ;;
+
+ INC (HL) ; double the exponent giving PI on the stack.
+
+S_PI_END:
+ RST _NEXT_CHAR ; advances character pointer.
+
+ JP S_NUMERIC ; jump forward to set the flag
+ ; to signal numeric result before advancing.
+
+S_TST_INK:
+ CP ZX_INKEY_STR ;
+ JR NZ,S_ALPHANUM ; forward, if not
+
+; THE 'INKEY$' EVALUATION
+
+ CALL KEYBOARD
+ LD B,H ;
+ LD C,L ;
+ LD D,C ;
+ INC D ;
+ CALL NZ,DECODE
+ LD A,D ;
+ ADC A,D ;
+ LD B,D ;
+ LD C,A ;
+ EX DE,HL ;
+ JR S_STRING ; forward
+
+S_ALPHANUM:
+ CALL ALPHANUM
+ JR C,S_LTR_DGT ; forward, if alphanumeric
+
+ CP ZX_PERIOD ; is character a '.' ?
+ JP Z,S_DECIMAL ; jump forward if so
+
+ LD BC,$09D8 ; prepare priority 09, operation 'subtract'
+ CP ZX_MINUS ; is character unary minus '-' ?
+ JR Z,S_PUSH_PO ; forward, if so
+
+ CP ZX_BRACKET_LEFT ; is character a '(' ?
+ JR NZ,S_QUOTE ; forward if not
+
+ CALL CH_ADD_PLUS_1 ; advances character pointer.
+
+ CALL SCANNING ; recursively call to evaluate the sub_expression.
+
+ CP ZX_BRACKET_RIGHT; is subsequent character a ')' ?
+ JR NZ,S_RPT_C ; forward if not
+
+
+ CALL CH_ADD_PLUS_1 ; advances.
+ JR S_J_CONT_3 ; relative jump to S_JP_CONT3 and then S_CONT3
+
+; consider a quoted string e.g. PRINT "Hooray!"
+; Note. quotes are not allowed within a string.
+
+S_QUOTE:
+ CP ZX_QUOTE ; is character a quote (") ?
+ JR NZ,S_FUNCTION ; forward, if not
+
+ CALL CH_ADD_PLUS_1 ; advances
+ PUSH HL ; * save start of string.
+ JR S_QUOTE_S ; forward
+
+S_Q_AGAIN:
+ CALL CH_ADD_PLUS_1
+
+S_QUOTE_S:
+ CP ZX_QUOTE ; is character a '"' ?
+ JR NZ,S_Q_NL ; forward if not to S_Q_NL
+
+ POP DE ; * retrieve start of string
+ AND A ; prepare to subtract.
+ SBC HL,DE ; subtract start from current position.
+ LD B,H ; transfer this length
+ LD C,L ; to the BC register pair.
+
+S_STRING:
+ LD HL,FLAGS ; address system variable FLAGS
+ RES 6,(HL) ; signal string result
+ BIT 7,(HL) ; test if checking syntax.
+
+ CALL NZ,STK_STO_STR ; in run-time stacks the
+ ; string descriptor - start DE, length BC.
+
+ RST _NEXT_CHAR ; advances pointer.
+
+S_J_CONT_3:
+ JP S_CONT_3
+
+; A string with no terminating quote has to be considered.
+
+S_Q_NL:
+ CP ZX_NEWLINE
+ JR NZ,S_Q_AGAIN ; loop back if not
+
+S_RPT_C:
+ JP REPORT_C
+
+S_FUNCTION:
+ SUB $C4 ; subtract 'CODE' reducing codes
+ ; CODE thru '<>' to range $00 - $XX
+ JR C,S_RPT_C ; back, if less
+
+; test for NOT the last function in character set.
+
+ LD BC,$04EC ; prepare priority $04, operation 'not'
+ CP $13 ; compare to 'NOT' ( - CODE)
+ JR Z,S_PUSH_PO ; forward, if so
+
+ JR NC,S_RPT_C ; back with anything higher
+
+; else is a function 'CODE' thru 'CHR$'
+
+ LD B,$10 ; priority sixteen binds all functions to
+ ; arguments removing the need for brackets.
+
+ ADD A,$D9 ; add $D9 to give range $D9 thru $EB
+ ; bit 6 is set to show numeric argument.
+ ; bit 7 is set to show numeric result.
+
+; now adjust these default argument/result indicators.
+
+ LD C,A ; save code in C
+
+ CP $DC ; separate 'CODE', 'VAL', 'LEN'
+ JR NC,S_NUMBER_TO_STRING ; skip forward if string operand
+
+ RES 6,C ; signal string operand.
+
+S_NUMBER_TO_STRING:
+ CP $EA ; isolate top of range 'STR$' and 'CHR$'
+ JR C,S_PUSH_PO ; skip forward with others
+
+ RES 7,C ; signal string result.
+
+S_PUSH_PO:
+ PUSH BC ; push the priority/operation
+
+ RST _NEXT_CHAR
+ JP S_LOOP_1 ; jump back
+
+S_LTR_DGT:
+ CP ZX_A ; compare to 'A'.
+ JR C,S_DECIMAL ; forward if less to S_DECIMAL
+
+ CALL LOOK_VARS
+ JP C,REPORT_2 ; back if not found
+ ; a variable is always 'found' when checking
+ ; syntax.
+
+ CALL Z,STK_VAR ; stacks string parameters or
+ ; returns cell location if numeric.
+
+ LD A,(FLAGS) ; fetch FLAGS
+ CP $C0 ; compare to numeric result/numeric operand
+ JR C,S_CONT_2 ; forward if not numeric
+
+ INC HL ; address numeric contents of variable.
+ LD DE,(STKEND) ; set destination to STKEND
+ CALL MOVE_FP ; stacks the five bytes
+ EX DE,HL ; transfer new free location from DE to HL.
+ LD (STKEND),HL ; update STKEND system variable.
+ JR S_CONT_2 ; forward
+
+; The Scanning Decimal routine is invoked when a decimal point or digit is
+; found in the expression.
+; When checking syntax, then the 'hidden floating point' form is placed
+; after the number in the BASIC line.
+; In run-time, the digits are skipped and the floating point number is picked
+; up.
+
+S_DECIMAL:
+ CALL SYNTAX_Z
+ JR NZ,S_STK_DEC ; forward in run-time
+
+ CALL DEC_TO_FP
+
+ RST _GET_CHAR ; advances HL past digits
+ LD BC,$0006 ; six locations are required.
+ CALL MAKE_ROOM
+ INC HL ; point to first new location
+ LD (HL),$7E ; insert the number marker 126 decimal.
+ INC HL ; increment
+ EX DE,HL ; transfer destination to DE.
+ LD HL,(STKEND) ; set HL from STKEND which points to the
+ ; first location after the 'last value'
+ LD C,$05 ; five bytes to move.
+ AND A ; clear carry.
+ SBC HL,BC ; subtract five pointing to 'last value'.
+ LD (STKEND),HL ; update STKEND thereby 'deleting the value.
+
+ LDIR ; copy the five value bytes.
+
+ EX DE,HL ; basic pointer to HL which may be white-space
+ ; following the number.
+ DEC HL ; now points to last of five bytes.
+ CALL TEMP_PTR1 ; advances the character
+ ; address skipping any white-space.
+ JR S_NUMERIC ; forward
+ ; to signal a numeric result.
+
+; In run-time the branch is here when a digit or point is encountered.
+
+S_STK_DEC:
+ RST _NEXT_CHAR
+ CP $7E ; compare to 'number marker'
+ JR NZ,S_STK_DEC ; loop back until found
+ ; skipping all the digits.
+
+ INC HL ; point to first of five hidden bytes.
+ LD DE,(STKEND) ; set destination from STKEND system variable
+ CALL MOVE_FP ; stacks the number.
+ LD (STKEND),DE ; update system variable STKEND.
+ LD (CH_ADD),HL ; update system variable CH_ADD.
+
+S_NUMERIC:
+ SET 6,(IY+FLAGS-RAMBASE) ; Signal numeric result
+
+S_CONT_2:
+ RST _GET_CHAR
+
+S_CONT_3:
+ CP ZX_BRACKET_LEFT ; compare to opening bracket '('
+ JR NZ,S_OPERTR ; forward if not
+
+ BIT 6,(IY+FLAGS-RAMBASE) ; Numeric or string result?
+ JR NZ,S_LOOP ; forward if numeric
+
+; else is a string
+
+ CALL SLICING
+
+ RST _NEXT_CHAR
+ JR S_CONT_3 ; back
+
+; the character is now manipulated to form an equivalent in the table of
+; calculator literals. This is quite cumbersome and in the ZX Spectrum a
+; simple look-up table was introduced at this point.
+
+S_OPERTR:
+ LD BC,$00C3 ; prepare operator 'subtract' as default.
+ ; also set B to zero for later indexing.
+
+ CP ZX_GREATER_THAN ; is character '>' ?
+ JR C,S_LOOP ; forward if less, as
+ ; we have reached end of meaningful expression
+
+ SUB ZX_MINUS ; is character '-' ?
+ JR NC,SUBMLTDIV ; forward with - * / and '**' '<>'
+
+ ADD A,13 ; increase others by thirteen
+ ; $09 '>' thru $0C '+'
+ JR GET_PRIO ; forward
+
+SUBMLTDIV:
+ CP $03 ; isolate $00 '-', $01 '*', $02 '/'
+ JR C,GET_PRIO ; forward if so
+
+; else possibly originally $D8 '**' thru $DD '<>' already reduced by $16
+
+ SUB $C2 ; giving range $00 to $05
+ JR C,S_LOOP ; forward if less
+
+ CP $06 ; test the upper limit for nonsense also
+ JR NC,S_LOOP ; forward if so
+
+ ADD A,$03 ; increase by 3 to give combined operators of
+
+ ; $00 '-'
+ ; $01 '*'
+ ; $02 '/'
+
+ ; $03 '**'
+ ; $04 'OR'
+ ; $05 'AND'
+ ; $06 '<='
+ ; $07 '>='
+ ; $08 '<>'
+
+ ; $09 '>'
+ ; $0A '<'
+ ; $0B '='
+ ; $0C '+'
+
+GET_PRIO:
+ ADD A,C ; add to default operation 'sub' ($C3)
+ LD C,A ; and place in operator byte - C.
+
+ LD HL,tbl_pri - $C3 ; theoretical base of the priorities table.
+ ADD HL,BC ; add C ( B is zero)
+ LD B,(HL) ; pick up the priority in B
+
+S_LOOP:
+ POP DE ; restore previous
+ LD A,D ; load A with priority.
+ CP B ; is present priority higher
+ JR C,S_TIGHTER ; forward if so to S_TIGHTER
+
+ AND A ; are both priorities zero
+ JP Z,GET_CHAR ; exit if zero via GET_CHAR
+
+ PUSH BC ; stack present values
+ PUSH DE ; stack last values
+ CALL SYNTAX_Z
+ JR Z,S_SYNTEST ; forward is checking syntax
+
+ LD A,E ; fetch last operation
+ AND $3F ; mask off the indicator bits to give true
+ ; calculator literal.
+ LD B,A ; place in the B register for BERG
+
+; perform the single operation
+
+ RST _FP_CALC ;;
+ DEFB __fp_calc_2 ;;
+ DEFB __end_calc ;;
+
+ JR S_RUNTEST ; forward
+
+S_SYNTEST:
+ LD A,E ; transfer masked operator to A
+ XOR (IY+FLAGS-RAMBASE) ; XOR with FLAGS like results will reset bit 6
+ AND $40 ; test bit 6
+
+S_RPORT_C:
+ JP NZ,REPORT_C ; back if results do not agree.
+
+; in run-time impose bit 7 of the operator onto bit 6 of the FLAGS
+
+S_RUNTEST:
+ POP DE ; restore last operation.
+ LD HL,FLAGS ; address system variable FLAGS
+ SET 6,(HL) ; presume a numeric result
+ BIT 7,E ; test expected result in operation
+ JR NZ,S_LOOPEND ; forward if numeric
+
+ RES 6,(HL) ; reset to signal string result
+
+S_LOOPEND:
+ POP BC ; restore present values
+ JR S_LOOP ; back
+
+S_TIGHTER:
+ PUSH DE ; push last values and consider these
+
+ LD A,C ; get the present operator.
+ BIT 6,(IY+FLAGS-RAMBASE) ; Numeric or string result?
+ JR NZ,S_NEXT ; forward if numeric to S_NEXT
+
+ AND $3F ; strip indicator bits to give clear literal.
+ ADD A,$08 ; add eight - augmenting numeric to equivalent
+ ; string literals.
+ LD C,A ; place plain literal back in C.
+ CP $10 ; compare to 'AND'
+ JR NZ,S_NOT_AND ; forward if not
+
+ SET 6,C ; set the numeric operand required for 'AND'
+ JR S_NEXT ; forward to S_NEXT
+
+S_NOT_AND:
+ JR C,S_RPORT_C ; back if less than 'AND'
+ ; Nonsense if '-', '*' etc.
+
+ CP __strs_add ; compare to 'strs_add' literal
+ JR Z,S_NEXT ; forward if so signaling string result
+
+ SET 7,C ; set bit to numeric (Boolean) for others.
+
+S_NEXT:
+ PUSH BC ; stack 'present' values
+
+ RST _NEXT_CHAR
+ JP S_LOOP_1 ; jump back
+
+; THE 'TABLE OF PRIORITIES'
+
+tbl_pri:
+ DEFB 6 ; '-'
+ DEFB 8 ; '*'
+ DEFB 8 ; '/'
+ DEFB 10 ; '**'
+ DEFB 2 ; 'OR'
+ DEFB 3 ; 'AND'
+ DEFB 5 ; '<='
+ DEFB 5 ; '>='
+ DEFB 5 ; '<>'
+ DEFB 5 ; '>'
+ DEFB 5 ; '<'
+ DEFB 5 ; '='
+ DEFB 6 ; '+'
+
+; THE 'LOOK_VARS' SUBROUTINE
+
+LOOK_VARS:
+ SET 6,(IY+FLAGS-RAMBASE) ; Signal numeric result
+
+ RST _GET_CHAR
+ CALL ALPHA
+ JP NC,REPORT_C ; to REPORT_C
+
+ PUSH HL ;
+ LD C,A ;
+
+ RST _NEXT_CHAR
+ PUSH HL ;
+ RES 5,C ;
+ CP $10 ; $10
+ JR Z,V_RUN_SYN
+
+ SET 6,C ;
+ CP ZX_DOLLAR ; $0D
+ JR Z,V_STR_VAR ; forward
+
+ SET 5,C ;
+
+V_CHAR:
+ CALL ALPHANUM
+ JR NC,V_RUN_SYN ; forward when not
+
+ RES 6,C ;
+
+ RST _NEXT_CHAR
+ JR V_CHAR ; loop back
+
+V_STR_VAR:
+ RST _NEXT_CHAR
+ RES 6,(IY+FLAGS-RAMBASE) ; Signal string result
+
+V_RUN_SYN:
+ LD B,C ;
+ CALL SYNTAX_Z
+ JR NZ,V_RUN ; forward
+
+ LD A,C ;
+ AND $E0 ;
+ SET 7,A ;
+ LD C,A ;
+ JR V_SYNTAX ; forward
+
+V_RUN:
+ LD HL,(VARS) ; sv
+
+V_EACH:
+ LD A,(HL) ;
+ AND $7F ;
+ JR Z,V_80_BYTE ;
+
+ CP C ;
+ JR NZ,V_NEXT ;
+
+ RLA ;
+ ADD A,A ;
+ JP P,V_FOUND_2
+
+ JR C,V_FOUND_2
+
+ POP DE ;
+ PUSH DE ;
+ PUSH HL ;
+
+V_MATCHES:
+ INC HL ;
+
+V_SPACES:
+ LD A,(DE) ;
+ INC DE ;
+ AND A ;
+ JR Z,V_SPACES ; back
+
+ CP (HL) ;
+ JR Z,V_MATCHES ; back
+
+ OR $80 ;
+ CP (HL) ;
+ JR NZ,V_GET_PTR ; forward
+
+ LD A,(DE) ;
+ CALL ALPHANUM
+ JR NC,V_FOUND_1 ; forward
+
+V_GET_PTR:
+ POP HL ;
+
+V_NEXT:
+ PUSH BC ;
+ CALL NEXT_ONE
+ EX DE,HL ;
+ POP BC ;
+ JR V_EACH ; back
+
+V_80_BYTE:
+ SET 7,B ;
+
+V_SYNTAX:
+ POP DE ;
+
+ RST _GET_CHAR
+ CP $10 ;
+ JR Z,V_PASS ; forward
+
+ SET 5,B ;
+ JR V_END ; forward
+
+V_FOUND_1:
+ POP DE ;
+
+V_FOUND_2:
+ POP DE ;
+ POP DE ;
+ PUSH HL ;
+
+ RST _GET_CHAR
+
+V_PASS:
+ CALL ALPHANUM
+ JR NC,V_END ; forward if not alphanumeric
+
+ RST _NEXT_CHAR
+ JR V_PASS ; back
+
+V_END:
+ POP HL ;
+ RL B ;
+ BIT 6,B ;
+ RET ;
+
+; THE 'STK_VAR' SUBROUTINE
+
+STK_VAR:
+ XOR A ;
+ LD B,A ;
+ BIT 7,C ;
+ JR NZ,SV_COUNT ; forward
+
+ BIT 7,(HL) ;
+ JR NZ,SV_ARRAYS ; forward
+
+ INC A ;
+
+SV_SIMPLE_STR:
+ INC HL ;
+ LD C,(HL) ;
+ INC HL ;
+ LD B,(HL) ;
+ INC HL ;
+ EX DE,HL ;
+ CALL STK_STO_STR
+
+ RST _GET_CHAR
+ JP SV_SLICE_QUERY ; jump forward
+
+SV_ARRAYS:
+ INC HL ;
+ INC HL ;
+ INC HL ;
+ LD B,(HL) ;
+ BIT 6,C ;
+ JR Z,SV_PTR ; forward
+
+ DEC B ;
+ JR Z,SV_SIMPLE_STR ; forward
+
+ EX DE,HL ;
+
+ RST _GET_CHAR
+ CP $10 ;
+ JR NZ,REPORT_3 ; forward
+
+ EX DE,HL ;
+
+SV_PTR:
+ EX DE,HL ;
+ JR SV_COUNT ; forward
+
+SV_COMMA:
+ PUSH HL ;
+
+ RST _GET_CHAR
+ POP HL ;
+ CP ZX_COMMA ; $1A == 26
+ JR Z,SV_LOOP ; forward
+
+ BIT 7,C ;
+ JR Z,REPORT_3 ; forward
+
+ BIT 6,C ;
+ JR NZ,SV_CLOSE ; forward
+
+ CP ZX_BRACKET_RIGHT ; $11
+ JR NZ,SV_RPT_C ; forward
+
+ RST _NEXT_CHAR
+ RET ;
+
+SV_CLOSE:
+ CP ZX_BRACKET_RIGHT ; $11
+ JR Z,SV_DIM ; forward
+
+ CP $DF ;
+ JR NZ,SV_RPT_C ; forward
+
+SV_CH_ADD:
+ RST _GET_CHAR
+ DEC HL ;
+ LD (CH_ADD),HL ; sv
+ JR SV_SLICE ; forward
+
+SV_COUNT:
+ LD HL,$0000 ;
+
+SV_LOOP:
+ PUSH HL ;
+
+ RST _NEXT_CHAR
+ POP HL ;
+ LD A,C ;
+ CP ZX_DOUBLE_QUOTE ;
+ JR NZ,SV_MULT ; forward
+
+ RST _GET_CHAR
+ CP ZX_BRACKET_RIGHT
+ JR Z,SV_DIM ; forward
+
+ CP ZX_TO ;
+ JR Z,SV_CH_ADD ; back
+
+SV_MULT:
+ PUSH BC ;
+ PUSH HL ;
+ CALL DE_DE_PLUS_ONE
+ EX (SP),HL ;
+ EX DE,HL ;
+ CALL INT_EXP1
+ JR C,REPORT_3
+
+ DEC BC ;
+ CALL GET_HL_TIMES_DE
+ ADD HL,BC ;
+ POP DE ;
+ POP BC ;
+ DJNZ SV_COMMA ; loop back
+
+ BIT 7,C ;
+
+SV_RPT_C:
+ JR NZ,SL_RPT_C
+
+ PUSH HL ;
+ BIT 6,C ;
+ JR NZ,SV_ELEM_STR
+
+ LD B,D ;
+ LD C,E ;
+
+ RST _GET_CHAR
+ CP ZX_BRACKET_RIGHT; is character a ')' ?
+ JR Z,SV_NUMBER ; skip forward
+
+REPORT_3:
+ RST _ERROR_1
+ DEFB $02 ; Error Report: Subscript wrong
+
+SV_NUMBER:
+ RST _NEXT_CHAR
+ POP HL ;
+ LD DE,$0005 ;
+ CALL GET_HL_TIMES_DE
+ ADD HL,BC ;
+ RET ; return >>
+
+SV_ELEM_STR:
+ CALL DE_DE_PLUS_ONE
+ EX (SP),HL ;
+ CALL GET_HL_TIMES_DE
+ POP BC ;
+ ADD HL,BC ;
+ INC HL ;
+ LD B,D ;
+ LD C,E ;
+ EX DE,HL ;
+ CALL STK_ST_0
+
+ RST _GET_CHAR
+ CP ZX_BRACKET_RIGHT ; is it ')' ?
+ JR Z,SV_DIM ; forward if so
+
+ CP ZX_COMMA ; $1A == 26 ; is it ',' ?
+ JR NZ,REPORT_3 ; back if not
+
+SV_SLICE:
+ CALL SLICING
+
+SV_DIM:
+ RST _NEXT_CHAR
+
+SV_SLICE_QUERY:
+ CP $10 ;
+ JR Z,SV_SLICE ; back
+
+ RES 6,(IY+FLAGS-RAMBASE) ; Signal string result
+ RET ; return.
+
+; THE 'SLICING' SUBROUTINE
+
+SLICING:
+ CALL SYNTAX_Z
+ CALL NZ,STK_FETCH
+
+ RST _NEXT_CHAR
+ CP ZX_BRACKET_RIGHT; is it ')' ?
+ JR Z,SL_STORE ; forward if so
+
+ PUSH DE ;
+ XOR A ;
+ PUSH AF ;
+ PUSH BC ;
+ LD DE,$0001 ;
+
+ RST _GET_CHAR
+ POP HL ;
+ CP ZX_TO ; is it 'TO' ?
+ JR Z,SL_SECOND ; forward if so
+
+ POP AF ;
+ CALL INT_EXP2
+ PUSH AF ;
+ LD D,B ;
+ LD E,C ;
+ PUSH HL ;
+
+ RST _GET_CHAR
+ POP HL ;
+ CP ZX_TO ; is it 'TO' ?
+ JR Z,SL_SECOND ; forward if so
+
+ CP ZX_BRACKET_RIGHT; $11
+
+SL_RPT_C:
+ JP NZ,REPORT_C
+
+ LD H,D ;
+ LD L,E ;
+ JR SL_DEFINE ; forward
+
+SL_SECOND:
+ PUSH HL ;
+
+ RST _NEXT_CHAR
+ POP HL ;
+ CP ZX_BRACKET_RIGHT; is it ')' ?
+ JR Z,SL_DEFINE ; forward if so
+
+ POP AF ;
+ CALL INT_EXP2
+ PUSH AF ;
+
+ RST _GET_CHAR
+ LD H,B ;
+ LD L,C ;
+ CP ZX_BRACKET_RIGHT; is it ')' ?
+ JR NZ,SL_RPT_C ; back if not
+
+SL_DEFINE:
+ POP AF ;
+ EX (SP),HL ;
+ ADD HL,DE ;
+ DEC HL ;
+ EX (SP),HL ;
+ AND A ;
+ SBC HL,DE ;
+ LD BC,$0000 ;
+ JR C,SL_OVER ; forward
+
+ INC HL ;
+ AND A ;
+ JP M,REPORT_3 ; jump back
+
+ LD B,H ;
+ LD C,L ;
+
+SL_OVER:
+ POP DE ;
+ RES 6,(IY+FLAGS-RAMBASE) ; Signal string result
+
+SL_STORE:
+ CALL SYNTAX_Z
+ RET Z ; return if checking syntax.
+
+; THE 'STK_STORE' SUBROUTINE
+
+STK_ST_0:
+ XOR A ;
+
+STK_STO_STR:
+ PUSH BC ;
+ CALL TEST_5_SP
+ POP BC ;
+ LD HL,(STKEND) ; sv
+ LD (HL),A ;
+ INC HL ;
+ LD (HL),E ;
+ INC HL ;
+ LD (HL),D ;
+ INC HL ;
+ LD (HL),C ;
+ INC HL ;
+ LD (HL),B ;
+ INC HL ;
+ LD (STKEND),HL ; sv
+ RES 6,(IY+FLAGS-RAMBASE) ; Signal string result
+ RET ; return.
+
+; THE 'INT EXP' SUBROUTINES
+
+INT_EXP1:
+ XOR A ;
+
+INT_EXP2:
+ PUSH DE ;
+ PUSH HL ;
+ PUSH AF ;
+ CALL CLASS_6
+ POP AF ;
+ CALL SYNTAX_Z
+ JR Z,I_RESTORE ; forward if checking syntax
+
+ PUSH AF ;
+ CALL FIND_INT
+ POP DE ;
+ LD A,B ;
+ OR C ;
+ SCF ; Set Carry Flag
+ JR Z,I_CARRY ; forward
+
+ POP HL ;
+ PUSH HL ;
+ AND A ;
+ SBC HL,BC ;
+
+I_CARRY:
+ LD A,D ;
+ SBC A,$00 ;
+
+I_RESTORE:
+ POP HL ;
+ POP DE ;
+ RET ;
+
+; THE 'DE,(DE+1)' SUBROUTINE
+
+; INDEX and LOAD Z80 subroutine.
+; This emulates the 6800 processor instruction LDX 1,X which loads a two_byte
+; value from memory into the register indexing it. Often these are hardly worth
+; the bother of writing as subroutines and this one doesn't save any time or
+; memory. The timing and space overheads have to be offset against the ease of
+; writing and the greater program readability from using such toolkit routines.
+
+DE_DE_PLUS_ONE:
+ EX DE,HL ; move index address into HL.
+ INC HL ; increment to address word.
+ LD E,(HL) ; pick up word low_order byte.
+ INC HL ; index high_order byte and
+ LD D,(HL) ; pick it up.
+ RET ; return with DE = word.
+
+; THE 'GET_HL_TIMES_DE' SUBROUTINE
+
+GET_HL_TIMES_DE:
+ CALL SYNTAX_Z
+ RET Z ;
+
+ PUSH BC ;
+ LD B,$10 ;
+ LD A,H ;
+ LD C,L ;
+ LD HL,$0000 ;
+
+HL_LOOP:
+ ADD HL,HL ;
+ JR C,HL_END ; forward with carry
+
+ RL C ;
+ RLA ;
+ JR NC,HL_AGAIN ; forward with no carry
+
+ ADD HL,DE ;
+
+HL_END:
+ JP C,REPORT_4
+
+HL_AGAIN:
+ DJNZ HL_LOOP ; loop back
+
+ POP BC ;
+ RET ; return.
+
+; THE 'LET' SUBROUTINE
+
+LET:
+ LD HL,(DEST)
+ BIT 1,(IY+FLAGX-RAMBASE)
+ JR Z,L_EXISTS ; forward
+
+ LD BC,$0005 ;
+
+L_EACH_CH:
+ INC BC ;
+
+L_NO_SP:
+ INC HL ;
+ LD A,(HL) ;
+ AND A ;
+ JR Z,L_NO_SP ; back
+
+ CALL ALPHANUM
+ JR C,L_EACH_CH ; back
+
+ CP ZX_DOLLAR ; is it '$' ?
+ JP Z,L_NEW_STR ; forward if so
+
+
+ RST _BC_SPACES ; BC_SPACES
+ PUSH DE ;
+ LD HL,(DEST) ;
+ DEC DE ;
+ LD A,C ;
+ SUB $06 ;
+ LD B,A ;
+ LD A,$40 ;
+ JR Z,L_SINGLE
+
+L_CHAR:
+ INC HL ;
+ LD A,(HL) ;
+ AND A ; is it a space ?
+ JR Z,L_CHAR ; back
+
+ INC DE ;
+ LD (DE),A ;
+ DJNZ L_CHAR ; loop back
+
+ OR $80 ;
+ LD (DE),A ;
+ LD A,$80 ;
+
+L_SINGLE:
+ LD HL,(DEST) ;
+ XOR (HL) ;
+ POP HL ;
+ CALL L_FIRST
+
+L_NUMERIC:
+ PUSH HL ;
+
+ RST _FP_CALC ;;
+ DEFB __delete ;;
+ DEFB __end_calc ;;
+
+ POP HL ;
+ LD BC,$0005 ;
+ AND A ;
+ SBC HL,BC ;
+ JR L_ENTER ; forward
+
+L_EXISTS:
+ BIT 6,(IY+FLAGS-RAMBASE) ; Numeric or string result?
+ JR Z,L_DELETE_STR ; forward
+
+ LD DE,$0006 ;
+ ADD HL,DE ;
+ JR L_NUMERIC ; back
+
+L_DELETE_STR:
+ LD HL,(DEST) ;
+ LD BC,(STRLEN) ;
+ BIT 0,(IY+FLAGX-RAMBASE)
+ JR NZ,L_ADD_STR ; forward
+
+ LD A,B ;
+ OR C ;
+ RET Z ;
+
+ PUSH HL ;
+
+ RST _BC_SPACES
+ PUSH DE ;
+ PUSH BC ;
+ LD D,H ;
+ LD E,L ;
+ INC HL ;
+ LD (HL),$00 ;
+ LDDR ; Copy Bytes
+ PUSH HL ;
+ CALL STK_FETCH
+ POP HL ;
+ EX (SP),HL ;
+ AND A ;
+ SBC HL,BC ;
+ ADD HL,BC ;
+ JR NC,L_LENGTH ; forward
+
+ LD B,H ;
+ LD C,L ;
+
+L_LENGTH:
+ EX (SP),HL ;
+ EX DE,HL ;
+ LD A,B ;
+ OR C ;
+ JR Z,L_IN_W_S ; forward if zero
+
+ LDIR ; Copy Bytes
+
+L_IN_W_S:
+ POP BC ;
+ POP DE ;
+ POP HL ;
+
+; THE 'L_ENTER' SUBROUTINE
+;
+; Part of the LET command contains a natural subroutine which is a
+; conditional LDIR. The copy only occurs of BC is non-zero.
+
+L_ENTER:
+ EX DE,HL ;
+ LD A,B ;
+ OR C ;
+ RET Z ;
+
+ PUSH DE ;
+ LDIR ; Copy Bytes
+ POP HL ;
+ RET ; return.
+
+L_ADD_STR:
+ DEC HL ;
+ DEC HL ;
+ DEC HL ;
+ LD A,(HL) ;
+ PUSH HL ;
+ PUSH BC ;
+
+ CALL L_STRING
+
+ POP BC ;
+ POP HL ;
+ INC BC ;
+ INC BC ;
+ INC BC ;
+ JP RECLAIM_2 ; jump back to exit via RECLAIM_2
+
+L_NEW_STR:
+ LD A,$60 ; prepare mask %01100000
+ LD HL,(DEST) ;
+ XOR (HL) ;
+
+; THE 'L_STRING' SUBROUTINE
+
+L_STRING:
+ PUSH AF ;
+ CALL STK_FETCH
+ EX DE,HL ;
+ ADD HL,BC ;
+ PUSH HL ;
+ INC BC ;
+ INC BC ;
+ INC BC ;
+
+ RST _BC_SPACES
+ EX DE,HL ;
+ POP HL ;
+ DEC BC ;
+ DEC BC ;
+ PUSH BC ;
+ LDDR ; Copy Bytes
+ EX DE,HL ;
+ POP BC ;
+ DEC BC ;
+ LD (HL),B ;
+ DEC HL ;
+ LD (HL),C ;
+ POP AF ;
+
+L_FIRST:
+ PUSH AF ;
+ CALL REC_V80
+ POP AF ;
+ DEC HL ;
+ LD (HL),A ;
+ LD HL,(STKBOT) ; sv
+ LD (E_LINE),HL ; sv
+ DEC HL ;
+ LD (HL),$80 ;
+ RET ;
+
+; THE 'STK_FETCH' SUBROUTINE
+
+; This routine fetches a five-byte value from the calculator stack
+; reducing the pointer to the end of the stack by five.
+; For a floating-point number the exponent is in A and the mantissa
+; is the thirty-two bits EDCB.
+; For strings, the start of the string is in DE and the length in BC.
+; A is unused.
+
+STK_FETCH:
+ LD HL,(STKEND) ; load HL from system variable STKEND
+
+ DEC HL ;
+ LD B,(HL) ;
+ DEC HL ;
+ LD C,(HL) ;
+ DEC HL ;
+ LD D,(HL) ;
+ DEC HL ;
+ LD E,(HL) ;
+ DEC HL ;
+ LD A,(HL) ;
+
+ LD (STKEND),HL ; set system variable STKEND to lower value.
+ RET ; return.
+
+; THE 'DIM' COMMAND ROUTINE
+
+; An array is created and initialized to zeros which is also the space
+; character on the ZX81.
+
+DIM:
+ CALL LOOK_VARS
+
+D_RPORT_C:
+ JP NZ,REPORT_C
+
+ CALL SYNTAX_Z
+ JR NZ,D_RUN ; forward
+
+ RES 6,C ;
+ CALL STK_VAR
+ CALL CHECK_END
+
+D_RUN:
+ JR C,D_LETTER ; forward
+
+ PUSH BC ;
+ CALL NEXT_ONE
+ CALL RECLAIM_2
+ POP BC ;
+
+D_LETTER:
+ SET 7,C ;
+ LD B,$00 ;
+ PUSH BC ;
+ LD HL,$0001 ;
+ BIT 6,C ;
+ JR NZ,D_SIZE ; forward
+
+ LD L,$05 ;
+
+D_SIZE:
+ EX DE,HL ;
+
+D_NO_LOOP:
+ RST _NEXT_CHAR
+ LD H,$40 ;
+ CALL INT_EXP1
+ JP C,REPORT_3
+
+ POP HL ;
+ PUSH BC ;
+ INC H ;
+ PUSH HL ;
+ LD H,B ;
+ LD L,C ;
+ CALL GET_HL_TIMES_DE
+ EX DE,HL ;
+
+ RST _GET_CHAR
+ CP ZX_COMMA ; $1A == 26
+ JR Z,D_NO_LOOP ; back
+
+ CP ZX_BRACKET_RIGHT; is it ')' ?
+ JR NZ,D_RPORT_C ; back if not
+
+ RST _NEXT_CHAR
+ POP BC ;
+ LD A,C ;
+ LD L,B ;
+ LD H,$00 ;
+ INC HL ;
+ INC HL ;
+ ADD HL,HL ;
+ ADD HL,DE ;
+ JP C,REPORT_4
+
+ PUSH DE ;
+ PUSH BC ;
+ PUSH HL ;
+ LD B,H ;
+ LD C,L ;
+ LD HL,(E_LINE) ; sv
+ DEC HL ;
+ CALL MAKE_ROOM
+ INC HL ;
+ LD (HL),A ;
+ POP BC ;
+ DEC BC ;
+ DEC BC ;
+ DEC BC ;
+ INC HL ;
+ LD (HL),C ;
+ INC HL ;
+ LD (HL),B ;
+ POP AF ;
+ INC HL ;
+ LD (HL),A ;
+ LD H,D ;
+ LD L,E ;
+ DEC DE ;
+ LD (HL),0 ;
+ POP BC ;
+ LDDR ; Copy Bytes
+
+DIM_SIZES:
+ POP BC ;
+ LD (HL),B ;
+ DEC HL ;
+ LD (HL),C ;
+ DEC HL ;
+ DEC A ;
+ JR NZ,DIM_SIZES ; back
+
+ RET ; return.
+
+; THE 'RESERVE' ROUTINE
+
+RESERVE:
+ LD HL,(STKBOT) ; address STKBOT
+ DEC HL ; now last byte of workspace
+ CALL MAKE_ROOM
+ INC HL ;
+ INC HL ;
+ POP BC ;
+ LD (E_LINE),BC ; sv
+ POP BC ;
+ EX DE,HL ;
+ INC HL ;
+ RET ;
+
+; THE 'CLEAR' COMMAND ROUTINE
+
+CLEAR:
+ LD HL,(VARS) ; sv
+ LD (HL),$80 ;
+ INC HL ;
+ LD (E_LINE),HL ; sv
+
+; THE 'X_TEMP' SUBROUTINE
+
+X_TEMP:
+ LD HL,(E_LINE) ; sv
+
+; THE 'SET_STK' ROUTINES
+
+SET_STK_B:
+ LD (STKBOT),HL ; sv
+
+SET_STK_E:
+ LD (STKEND),HL ; sv
+ RET ;
+
+; THE 'CURSOR_IN' ROUTINE
+
+; This routine is called to set the edit line to the minimum cursor/newline
+; and to set STKEND, the start of free space, at the next position.
+
+CURSOR_IN:
+ LD HL,(E_LINE) ; fetch start of edit line
+ LD (HL),ZX_CURSOR ; insert cursor character
+
+ INC HL ; point to next location.
+ LD (HL),ZX_NEWLINE ; insert NEWLINE character
+ INC HL ; point to next free location.
+
+ LD (IY+DF_SZ-RAMBASE),2 ; set lower screen display file size
+
+ JR SET_STK_B ; exit via SET_STK_B above
+
+; THE 'SET_MIN' SUBROUTINE
+
+SET_MIN:
+ LD HL,$405D ; normal location of calculator's memory area
+ LD (MEM),HL ; update system variable MEM
+ LD HL,(STKBOT) ;
+ JR SET_STK_E ; back
+
+; THE 'RECLAIM THE END_MARKER' ROUTINE
+
+REC_V80:
+ LD DE,(E_LINE) ; sv
+ JP RECLAIM_1
+
+; THE 'ALPHA' SUBROUTINE
+
+ALPHA:
+ CP ZX_A ; $26
+ JR ALPHA_2 ; skip forward
+
+; THE 'ALPHANUM' SUBROUTINE
+
+ALPHANUM:
+ CP ZX_0 ;
+
+ALPHA_2:
+ CCF ; Complement Carry Flag
+ RET NC ;
+
+ CP $40 ;
+ RET ;
+
+; THE 'DECIMAL TO FLOATING POINT' SUBROUTINE
+
+DEC_TO_FP:
+ CALL INT_TO_FP ; gets first part
+ CP ZX_PERIOD ; is character a '.' ?
+ JR NZ,E_FORMAT ; forward if not
+
+ RST _FP_CALC ;;
+ DEFB __stk_one ;;
+ DEFB __st_mem_0 ;;
+ DEFB __delete ;;
+ DEFB __end_calc ;;
+
+NXT_DGT_1:
+ RST _NEXT_CHAR
+ CALL STK_DIGIT
+ JR C,E_FORMAT ; forward
+
+ RST _FP_CALC ;;
+ DEFB __get_mem_0 ;;
+ DEFB __stk_ten ;;
+ DEFB __division ;
+ DEFB $C0 ;;st-mem-0
+ DEFB __multiply ;;
+ DEFB __addition ;;
+ DEFB __end_calc ;;
+
+ JR NXT_DGT_1 ; loop back till exhausted
+
+E_FORMAT:
+ CP ZX_E ; is character 'E' ?
+ RET NZ ; return if not
+
+ LD (IY+MEM_0_1st-RAMBASE),$FF ; initialize sv MEM_0_1st to $FF TRUE
+
+ RST _NEXT_CHAR
+ CP ZX_PLUS ; is character a '+' ?
+ JR Z,SIGN_DONE ; forward if so
+
+ CP ZX_MINUS ; is it a '-' ?
+ JR NZ,ST_E_PART ; forward if not
+
+ INC (IY+MEM_0_1st-RAMBASE) ; sv MEM_0_1st change to FALSE
+
+SIGN_DONE:
+ RST _NEXT_CHAR
+
+ST_E_PART:
+ CALL INT_TO_FP
+
+ RST _FP_CALC ;; m, e.
+ DEFB __get_mem_0 ;; m, e, (1/0) TRUE/FALSE
+ DEFB __jump_true ;;
+ DEFB E_POSTVE - $ ;;
+ DEFB __negate ;; m, _e
+
+E_POSTVE:
+ DEFB __e_to_fp ;; x.
+ DEFB __end_calc ;; x.
+
+ RET ; return.
+
+; THE 'STK_DIGIT' SUBROUTINE
+
+STK_DIGIT:
+ CP ZX_0 ;
+ RET C ;
+
+ CP ZX_A ; $26
+ CCF ; Complement Carry Flag
+ RET C ;
+
+ SUB ZX_0 ;
+
+; THE 'STACK_A' SUBROUTINE
+
+STACK_A:
+ LD C,A ;
+ LD B,0 ;
+
+; THE 'STACK_BC' SUBROUTINE
+
+; The ZX81 does not have an integer number format so the BC register contents
+; must be converted to their full floating-point form.
+
+STACK_BC:
+ LD IY,ERR_NR ; re-initialize the system variables pointer.
+ PUSH BC ; save the integer value.
+
+; now stack zero, five zero bytes as a starting point.
+
+ RST _FP_CALC ;;
+ DEFB __stk_zero ;; 0.
+ DEFB __end_calc ;;
+
+ POP BC ; restore integer value.
+
+ LD (HL),$91 ; place $91 in exponent 65536.
+ ; this is the maximum possible value
+
+ LD A,B ; fetch hi-byte.
+ AND A ; test for zero.
+ JR NZ,STK_BC_2 ; forward if not zero
+
+ LD (HL),A ; else make exponent zero again
+ OR C ; test lo-byte
+ RET Z ; return if BC was zero - done.
+
+; else there has to be a set bit if only the value one.
+
+ LD B,C ; save C in B.
+ LD C,(HL) ; fetch zero to C
+ LD (HL),$89 ; make exponent $89 256.
+
+STK_BC_2:
+ DEC (HL) ; decrement exponent - halving number
+ SLA C ; C<-76543210<-0
+ RL B ; C<-76543210<-C
+ JR NC,STK_BC_2 ; loop back if no carry
+
+ SRL B ; 0->76543210->C
+ RR C ; C->76543210->C
+
+ INC HL ; address first byte of mantissa
+ LD (HL),B ; insert B
+ INC HL ; address second byte of mantissa
+ LD (HL),C ; insert C
+
+ DEC HL ; point to the
+ DEC HL ; exponent again
+ RET ; return.
+
+; THE 'INTEGER TO FLOATING POINT' SUBROUTINE
+
+INT_TO_FP:
+ PUSH AF ;
+
+ RST _FP_CALC ;;
+ DEFB __stk_zero ;;
+ DEFB __end_calc ;;
+
+ POP AF ;
+
+NXT_DGT_2:
+ CALL STK_DIGIT
+ RET C ;
+
+ RST _FP_CALC ;;
+ DEFB __exchange ;;
+ DEFB __stk_ten ;;
+ DEFB __multiply ;;
+ DEFB __addition ;;
+ DEFB __end_calc ;;
+
+ RST _NEXT_CHAR
+ JR NXT_DGT_2
+
+; THE 'E_FORMAT TO FLOATING POINT' SUBROUTINE
+
+; (Offset $38: 'e_to_fp')
+; invoked from DEC_TO_FP and PRINT_FP.
+; e.g. 2.3E4 is 23000.
+; This subroutine evaluates xEm where m is a positive or negative integer.
+; At a simple level x is multiplied by ten for every unit of m.
+; If the decimal exponent m is negative then x is divided by ten for each unit.
+; A short-cut is taken if the exponent is greater than seven and in this
+; case the exponent is reduced by seven and the value is multiplied or divided
+; by ten million.
+; Note. for the ZX Spectrum an even cleverer method was adopted which involved
+; shifting the bits out of the exponent so the result was achieved with six
+; shifts at most. The routine below had to be completely re-written mostly
+; in Z80 machine code.
+; Although no longer operable, the calculator literal was retained for old
+; times sake, the routine being invoked directly from a machine code CALL.
+;
+; On entry in the ZX81, m, the exponent, is the 'last value', and the
+; floating-point decimal mantissa is beneath it.
+
+e_to_fp:
+ RST _FP_CALC ;; x, m.
+ DEFB __duplicate ;; x, m, m.
+ DEFB __less_0 ;; x, m, (1/0).
+ DEFB __st_mem_0 ;; x, m, (1/0).
+ DEFB __delete ;; x, m.
+ DEFB __abs ;; x, +m.
+
+E_LOOP:
+ DEFB __stk_one ;; x, m,1.
+ DEFB __subtract ;; x, m-1.
+ DEFB __duplicate ;; x, m-1,m-1.
+ DEFB __less_0 ;; x, m-1, (1/0).
+ DEFB __jump_true ;; x, m-1.
+ DEFB E_END - $ ;; x, m-1.
+
+ DEFB __duplicate ;; x, m-1, m-1.
+ DEFB __stk_data ;;
+ DEFB $33 ;;Exponent: $83, Bytes: 1
+
+ DEFB $40 ;;(+00,+00,+00) x, m-1, m-1, 6.
+ DEFB __subtract ;; x, m-1, m-7.
+ DEFB __duplicate ;; x, m-1, m-7, m-7.
+ DEFB __less_0 ;; x, m-1, m-7, (1/0).
+ DEFB __jump_true ;; x, m-1, m-7.
+ DEFB E_LOW - $ ;;
+
+; but if exponent m is higher than 7 do a bigger chunk.
+; multiplying (or dividing if negative) by 10 million - 1e7.
+
+ DEFB __exchange ;; x, m-7, m-1.
+ DEFB __delete ;; x, m-7.
+ DEFB __exchange ;; m-7, x.
+ DEFB __stk_data ;;
+ DEFB $80 ;;Bytes: 3
+ DEFB $48 ;;Exponent $98
+ DEFB $18,$96,$80 ;;(+00) m-7, x, 10,000,000 (=f)
+ DEFB __jump ;;
+ DEFB E_CHUNK - $ ;;
+
+E_LOW:
+ DEFB __delete ;; x, m-1.
+ DEFB __exchange ;; m-1, x.
+ DEFB __stk_ten ;; m-1, x, 10 (=f).
+
+E_CHUNK:
+ DEFB __get_mem_0 ;; m-1, x, f, (1/0)
+ DEFB __jump_true ;; m-1, x, f
+ DEFB E_DIVSN - $ ;;
+
+ DEFB __multiply ;; m-1, x*f.
+ DEFB __jump ;;
+ DEFB E_SWAP - $ ;;
+
+E_DIVSN:
+ DEFB __division ;; m-1, x/f (= new x).
+
+E_SWAP:
+ DEFB __exchange ;; x, m-1 (= new m).
+ DEFB __jump ;; x, m.
+ DEFB E_LOOP - $ ;;
+
+E_END:
+ DEFB __delete ;; x. (-1)
+ DEFB __end_calc ;; x.
+
+ RET ; return.
+
+; THE 'FLOATING-POINT TO BC' SUBROUTINE
+
+; The floating-point form on the calculator stack is compressed directly into
+; the BC register rounding up if necessary.
+; Valid range is 0 to 65535.4999
+
+FP_TO_BC:
+ CALL STK_FETCH ; exponent to A
+ ; mantissa to EDCB.
+ AND A ; test for value zero.
+ JR NZ,FPBC_NZRO ; forward if not
+
+; else value is zero
+
+ LD B,A ; zero to B
+ LD C,A ; also to C
+ PUSH AF ; save the flags on machine stack
+ JR FPBC_END ; forward
+
+; EDCB => BCE
+
+FPBC_NZRO:
+ LD B,E ; transfer the mantissa from EDCB
+ LD E,C ; to BCE. Bit 7 of E is the 17th bit which
+ LD C,D ; will be significant for rounding if the
+ ; number is already normalized.
+
+ SUB $91 ; subtract 65536
+ CCF ; complement carry flag
+ BIT 7,B ; test sign bit
+ PUSH AF ; push the result
+
+ SET 7,B ; set the implied bit
+ JR C,FPBC_END ; forward with carry from SUB/CCF
+ ; number is too big.
+
+ INC A ; increment the exponent and
+ NEG ; negate to make range $00 - $0F
+
+ CP $08 ; test if one or two bytes
+ JR C,BIG_INT ; forward with two
+
+ LD E,C ; shift mantissa
+ LD C,B ; 8 places right
+ LD B,$00 ; insert a zero in B
+ SUB $08 ; reduce exponent by eight
+
+BIG_INT:
+ AND A ; test the exponent
+ LD D,A ; save exponent in D.
+
+ LD A,E ; fractional bits to A
+ RLCA ; rotate most significant bit to carry for
+ ; rounding of an already normal number.
+
+ JR Z,EXP_ZERO ; forward if exponent zero
+ ; the number is normalized
+
+FPBC_NORM:
+ SRL B ; 0->76543210->C
+ RR C ; C->76543210->C
+
+ DEC D ; decrement exponent
+
+ JR NZ,FPBC_NORM ; loop back till zero
+
+EXP_ZERO:
+ JR NC,FPBC_END ; forward without carry to NO_ROUND ???
+
+ INC BC ; round up.
+ LD A,B ; test result
+ OR C ; for zero
+ JR NZ,FPBC_END ; forward if not to GRE_ZERO ???
+
+ POP AF ; restore sign flag
+ SCF ; set carry flag to indicate overflow
+ PUSH AF ; save combined flags again
+
+FPBC_END:
+ PUSH BC ; save BC value
+
+; set HL and DE to calculator stack pointers.
+
+ RST _FP_CALC ;;
+ DEFB __end_calc ;;
+
+ POP BC ; restore BC value
+ POP AF ; restore flags
+ LD A,C ; copy low byte to A also.
+ RET ; return
+
+; THE 'FLOATING-POINT TO A' SUBROUTINE
+
+FP_TO_A:
+ CALL FP_TO_BC
+ RET C ;
+
+ PUSH AF ;
+ DEC B ;
+ INC B ;
+ JR Z,FP_A_END ; forward if in range
+
+ POP AF ; fetch result
+ SCF ; set carry flag signaling overflow
+ RET ; return
+
+FP_A_END:
+ POP AF ;
+ RET ;
+
+; THE 'PRINT A FLOATING-POINT NUMBER' SUBROUTINE
+
+; prints 'last value' x on calculator stack.
+; There are a wide variety of formats see Chapter 4.
+; e.g.
+; PI prints as 3.1415927
+; .123 prints as 0.123
+; .0123 prints as .0123
+; 999999999999 prints as 1000000000000
+; 9876543210123 prints as 9876543200000
+
+; Begin by isolating zero and just printing the '0' character
+; for that case. For negative numbers print a leading '-' and
+; then form the absolute value of x.
+
+PRINT_FP:
+ RST _FP_CALC ;; x.
+ DEFB __duplicate ;; x, x.
+ DEFB __less_0 ;; x, (1/0).
+ DEFB __jump_true ;;
+ DEFB PF_NEGTVE - $ ;; x.
+
+ DEFB __duplicate ;; x, x
+ DEFB __greater_0 ;; x, (1/0).
+ DEFB __jump_true ;;
+ DEFB PF_POSTVE - $ ;; x.
+
+ DEFB __delete ;; .
+ DEFB __end_calc ;; .
+
+ LD A,ZX_0 ; load accumulator with character '0'
+
+ RST _PRINT_A
+ RET ; return. >>
+
+PF_NEGTVE:
+ DEFB __abs ;; +x.
+ DEFB __end_calc ;; x.
+
+ LD A,ZX_MINUS ; load accumulator with '-'
+
+ RST _PRINT_A
+
+ RST _FP_CALC ;; x.
+
+PF_POSTVE:
+ DEFB __end_calc ;; x.
+
+; register HL addresses the exponent of the floating-point value.
+; if positive, and point floats to left, then bit 7 is set.
+
+ LD A,(HL) ; pick up the exponent byte
+ CALL STACK_A ; places on calculator stack.
+
+; now calculate roughly the number of digits, n, before the decimal point by
+; subtracting a half from true exponent and multiplying by log to
+; the base 10 of 2.
+; The true number could be one higher than n, the integer result.
+
+ RST _FP_CALC ;; x, e.
+ DEFB __stk_data ;;
+ DEFB $78 ;;Exponent: $88, Bytes: 2
+ DEFB $00,$80 ;;(+00,+00) x, e, 128.5.
+ DEFB __subtract ;; x, e -.5.
+ DEFB __stk_data ;;
+ DEFB $EF ;;Exponent: $7F, Bytes: 4
+ DEFB $1A,$20,$9A,$85 ;; .30103 (log10 2)
+ DEFB __multiply ;; x,
+ DEFB __int ;;
+ DEFB __st_mem_1 ;; x, n.
+
+ DEFB __stk_data ;;
+ DEFB $34 ;;Exponent: $84, Bytes: 1
+ DEFB $00 ;;(+00,+00,+00) x, n, 8.
+
+ DEFB __subtract ;; x, n-8.
+ DEFB __negate ;; x, 8-n.
+ DEFB __e_to_fp ;; x * (10^n)
+
+; finally the 8 or 9 digit decimal is rounded.
+; a ten-digit integer can arise in the case of, say, 999999999.5
+; which gives 1000000000.
+
+ DEFB __stk_half ;;
+ DEFB __addition ;;
+ DEFB __int ;; i.
+ DEFB __end_calc ;;
+
+; If there were 8 digits then final rounding will take place on the calculator
+; stack above and the next two instructions insert a masked zero so that
+; no further rounding occurs. If the result is a 9 digit integer then
+; rounding takes place within the buffer.
+
+ LD HL,$406B ; address system variable MEM_2_5th
+ ; which could be the 'ninth' digit.
+ LD (HL),$90 ; insert the value $90 10010000
+
+; now starting from lowest digit lay down the 8, 9 or 10 digit integer
+; which represents the significant portion of the number
+; e.g. PI will be the nine-digit integer 314159265
+
+ LD B,10 ; count is ten digits.
+
+PF_LOOP:
+ INC HL ; increase pointer
+
+ PUSH HL ; preserve buffer address.
+ PUSH BC ; preserve counter.
+
+ RST _FP_CALC ;; i.
+ DEFB __stk_ten ;; i, 10.
+ DEFB __n_mod_m ;; i mod 10, i/10
+ DEFB __exchange ;; i/10, remainder.
+ DEFB __end_calc ;;
+
+ CALL FP_TO_A ; $00-$09
+
+ OR $90 ; make left hand nibble 9
+
+ POP BC ; restore counter
+ POP HL ; restore buffer address.
+
+ LD (HL),A ; insert masked digit in buffer.
+ DJNZ PF_LOOP ; loop back for all ten
+
+; the most significant digit will be last but if the number is exhausted then
+; the last one or two positions will contain zero ($90).
+
+; e.g. for 'one' we have zero as estimate of leading digits.
+; 1*10^8 100000000 as integer value
+; 90 90 90 90 90 90 90 90 91 90 as buffer mem3/mem4 contents.
+
+ INC HL ; advance pointer to one past buffer
+ LD BC,$0008 ; set C to 8 ( B is already zero )
+ PUSH HL ; save pointer.
+
+PF_NULL:
+ DEC HL ; decrease pointer
+ LD A,(HL) ; fetch masked digit
+ CP $90 ; is it a leading zero ?
+ JR Z,PF_NULL ; loop back if so
+
+; at this point a significant digit has been found. carry is reset.
+
+ SBC HL,BC ; subtract eight from the address.
+ PUSH HL ; ** save this pointer too
+ LD A,(HL) ; fetch addressed byte
+ ADD A,$6B ; add $6B - forcing a round up ripple
+ ; if $95 or over.
+ PUSH AF ; save the carry result.
+
+; now enter a loop to round the number. After rounding has been considered
+; a zero that has arisen from rounding or that was present at that position
+; originally is changed from $90 to $80.
+
+PF_RND_LP:
+ POP AF ; retrieve carry from machine stack.
+ INC HL ; increment address
+ LD A,(HL) ; fetch new byte
+ ADC A,0 ; add in any carry
+
+ DAA ; decimal adjust accumulator
+ ; carry will ripple through the '9'
+
+ PUSH AF ; save carry on machine stack.
+ AND $0F ; isolate character 0 - 9 AND set zero flag
+ ; if zero.
+ LD (HL),A ; place back in location.
+ SET 7,(HL) ; set bit 7 to show printable.
+ ; but not if trailing zero after decimal point.
+ JR Z,PF_RND_LP ; back if a zero
+ ; to consider further rounding and/or trailing
+ ; zero identification.
+
+ POP AF ; balance stack
+ POP HL ; ** retrieve lower pointer
+
+; now insert 6 trailing zeros which are printed if before the decimal point
+; but mark the end of printing if after decimal point.
+; e.g. 9876543210123 is printed as 9876543200000
+; 123.456001 is printed as 123.456
+
+ LD B,6 ; the count is six.
+
+PF_ZERO_6:
+ LD (HL),$80 ; insert a masked zero
+ DEC HL ; decrease pointer.
+ DJNZ PF_ZERO_6 ; loop back for all six
+
+; n-mod-m reduced the number to zero and this is now deleted from the calculator
+; stack before fetching the original estimate of leading digits.
+
+ RST _FP_CALC ;; 0.
+ DEFB __delete ;; .
+ DEFB __get_mem_1 ;; n.
+ DEFB __end_calc ;; n.
+
+ CALL FP_TO_A
+ JR Z,PF_POS ; skip forward if positive
+
+ NEG ; negate makes positive
+
+PF_POS:
+ LD E,A ; transfer count of digits to E
+ INC E ; increment twice
+ INC E ;
+ POP HL ; * retrieve pointer to one past buffer.
+
+GET_FIRST:
+ DEC HL ; decrement address.
+ DEC E ; decrement digit counter.
+ LD A,(HL) ; fetch masked byte.
+ AND $0F ; isolate right-hand nibble.
+ JR Z,GET_FIRST ; back with leading zero
+
+; now determine if E-format printing is needed
+
+ LD A,E ; transfer now accurate number count to A.
+ SUB 5 ; subtract five
+ CP 8 ; compare with 8 as maximum digits is 13.
+ JP P,PF_E_FMT ; forward if positive to PF_E_FMT
+
+ CP $F6 ; test for more than four zeros after point.
+ JP M,PF_E_FMT ; forward if so to PF_E_FMT
+
+ ADD A,6 ; test for zero leading digits, e.g. 0.5
+ JR Z,PF_ZERO_1 ; forward if so to PF_ZERO_1
+
+ JP M,PF_ZEROS ; forward if more than one zero to PF_ZEROS
+
+; else digits before the decimal point are to be printed
+
+ LD B,A ; count of leading characters to B.
+
+PF_NIB_LP:
+ CALL PF_NIBBLE
+ DJNZ PF_NIB_LP ; loop back for counted numbers
+
+ JR PF_DC_OUT ; forward to consider decimal part to PF_DC_OUT
+
+PF_E_FMT:
+ LD B,E ; count to B
+ CALL PF_NIBBLE ; prints one digit.
+ CALL PF_DC_OUT ; considers fractional part.
+
+ LD A,ZX_E ;
+ RST _PRINT_A
+
+ LD A,B ; transfer exponent to A
+ AND A ; test the sign.
+ JP P,PF_E_POS ; forward if positive to PF_E_POS
+
+ NEG ; negate the negative exponent.
+ LD B,A ; save positive exponent in B.
+
+ LD A,ZX_MINUS ;
+ JR PF_E_SIGN ; skip forward to PF_E_SIGN
+
+PF_E_POS:
+ LD A,ZX_PLUS ;
+
+PF_E_SIGN:
+ RST _PRINT_A
+
+; now convert the integer exponent in B to two characters.
+; it will be less than 99.
+
+ LD A,B ; fetch positive exponent.
+ LD B,$FF ; initialize left hand digit to minus one.
+
+PF_E_TENS:
+ INC B ; increment ten count
+ SUB 10 ; subtract ten from exponent
+ JR NC,PF_E_TENS ; loop back if greater than ten
+
+ ADD A,10 ; reverse last subtraction
+ LD C,A ; transfer remainder to C
+
+ LD A,B ; transfer ten value to A.
+ AND A ; test for zero.
+ JR Z,PF_E_LOW ; skip forward if so to PF_E_LOW
+
+ CALL OUT_CODE ; prints as digit '1' - '9'
+
+PF_E_LOW:
+ LD A,C ; low byte to A
+ CALL OUT_CODE ; prints final digit of the
+ ; exponent.
+ RET ; return. >>
+
+
+; THE 'FLOATING POINT PRINT ZEROS' LOOP
+; -------------------------------------
+; This branch deals with zeros after decimal point.
+; e.g. .01 or .0000999
+; Note. that printing to the ZX Printer destroys A and that A should be
+; initialized to '0' at each stage of the loop.
+; Originally LPRINT .00001 printed as .0XYZ1
+
+PF_ZEROS:
+ NEG ; negate makes number positive 1 to 4.
+ LD B,A ; zero count to B.
+
+ LD A,ZX_PERIOD ; prepare character '.'
+ RST _PRINT_A
+
+ LD A,ZX_0 ; prepare a '0'
+PFZROLP:
+ RST _PRINT_A
+ DJNZ PFZROLP ; obsolete loop back to PFZROLP
+
+ JR PF_FRAC_LP ; forward
+
+; there is a need to print a leading zero e.g. 0.1 but not with .01
+
+PF_ZERO_1:
+ LD A,ZX_0 ; prepare character '0'.
+ RST _PRINT_A
+
+; this subroutine considers the decimal point and any trailing digits.
+; if the next character is a marked zero, $80, then nothing more to print.
+
+PF_DC_OUT:
+ DEC (HL) ; decrement addressed character
+ INC (HL) ; increment it again
+ RET PE ; return with overflow (was 128) >>
+ ; as no fractional part
+
+; else there is a fractional part so print the decimal point.
+
+ LD A,ZX_PERIOD ; prepare character '.'
+ RST _PRINT_A
+
+; now enter a loop to print trailing digits
+
+PF_FRAC_LP:
+ DEC (HL) ; test for a marked zero.
+ INC (HL) ;
+ RET PE ; return when digits exhausted >>
+
+ CALL PF_NIBBLE
+ JR PF_FRAC_LP ; back for all fractional digits
+
+; subroutine to print right-hand nibble
+
+PF_NIBBLE:
+ LD A,(HL) ; fetch addressed byte
+ AND $0F ; mask off lower 4 bits
+ CALL OUT_CODE
+ DEC HL ; decrement pointer.
+ RET ; return.
+
+; THE 'PREPARE TO ADD' SUBROUTINE
+
+; This routine is called twice to prepare each floating point number for
+; addition, in situ, on the calculator stack.
+; The exponent is picked up from the first byte which is then cleared to act
+; as a sign byte and accept any overflow.
+; If the exponent is zero then the number is zero and an early return is made.
+; The now redundant sign bit of the mantissa is set and if the number is
+; negative then all five bytes of the number are twos-complemented to prepare
+; the number for addition.
+; On the second invocation the exponent of the first number is in B.
+
+PREP_ADD:
+ LD A,(HL) ; fetch exponent.
+ LD (HL),0 ; make this byte zero to take any overflow and
+ ; default to positive.
+ AND A ; test stored exponent for zero.
+ RET Z ; return with zero flag set if number is zero.
+
+ INC HL ; point to first byte of mantissa.
+ BIT 7,(HL) ; test the sign bit.
+ SET 7,(HL) ; set it to its implied state.
+ DEC HL ; set pointer to first byte again.
+ RET Z ; return if bit indicated number is positive.>>
+
+; if negative then all five bytes are twos complemented starting at LSB.
+
+ PUSH BC ; save B register contents.
+ LD BC,$0005 ; set BC to five.
+ ADD HL,BC ; point to location after 5th byte.
+ LD B,C ; set the B counter to five.
+ LD C,A ; store original exponent in C.
+ SCF ; set carry flag so that one is added.
+
+; now enter a loop to twos_complement the number.
+; The first of the five bytes becomes $FF to denote a negative number.
+
+NEG_BYTE:
+ DEC HL ; point to first or more significant byte.
+ LD A,(HL) ; fetch to accumulator.
+ CPL ; complement.
+ ADC A,0 ; add in initial carry or any subsequent carry.
+ LD (HL),A ; place number back.
+ DJNZ NEG_BYTE ; loop back five times
+
+ LD A,C ; restore the exponent to accumulator.
+ POP BC ; restore B register contents.
+
+ RET ; return.
+
+; THE 'FETCH TWO NUMBERS' SUBROUTINE
+
+; This routine is used by addition, multiplication and division to fetch
+; the two five_byte numbers addressed by HL and DE from the calculator stack
+; into the Z80 registers.
+; The HL register may no longer point to the first of the two numbers.
+; Since the 32-bit addition operation is accomplished using two Z80 16-bit
+; instructions, it is important that the lower two bytes of each mantissa are
+; in one set of registers and the other bytes all in the alternate set.
+;
+; In: HL = highest number, DE= lowest number
+;
+; : alt':
+; :
+; Out:
+; :H,B-C:C,B: num1
+; :L,D-E:D-E: num2
+
+FETCH_TWO:
+ PUSH HL ; save HL
+ PUSH AF ; save A - result sign when used from division.
+
+ LD C,(HL) ;
+ INC HL ;
+ LD B,(HL) ;
+ LD (HL),A ; insert sign when used from multiplication.
+ INC HL ;
+ LD A,C ; m1
+ LD C,(HL) ;
+ PUSH BC ; PUSH m2 m3
+
+ INC HL ;
+ LD C,(HL) ; m4
+ INC HL ;
+ LD B,(HL) ; m5 BC holds m5 m4
+
+ EX DE,HL ; make HL point to start of second number.
+
+ LD D,A ; m1
+ LD E,(HL) ;
+ PUSH DE ; PUSH m1 n1
+
+ INC HL ;
+ LD D,(HL) ;
+ INC HL ;
+ LD E,(HL) ;
+ PUSH DE ; PUSH n2 n3
+
+ EXX ; - - - - - - -
+
+ POP DE ; POP n2 n3
+ POP HL ; POP m1 n1
+ POP BC ; POP m2 m3
+
+ EXX ; - - - - - - -
+
+ INC HL ;
+ LD D,(HL) ;
+ INC HL ;
+ LD E,(HL) ; DE holds n4 n5
+
+ POP AF ; restore saved
+ POP HL ; registers.
+ RET ; return.
+
+; THE 'SHIFT ADDEND' SUBROUTINE
+
+; The accumulator A contains the difference between the two exponents.
+; This is the lowest of the two numbers to be added
+
+SHIFT_FP:
+ AND A ; test difference between exponents.
+ RET Z ; return if zero. both normal.
+
+ CP 33 ; compare with 33 bits.
+ JR NC,ADDEND_0 ; forward if greater than 32
+
+ PUSH BC ; preserve BC - part
+ LD B,A ; shift counter to B.
+
+; Now perform B right shifts on the addend L'D'E'D E
+; to bring it into line with the augend H'B'C'C B
+
+ONE_SHIFT:
+ EXX ; - - -
+ SRA L ; 76543210->C bit 7 unchanged.
+ RR D ; C->76543210->C
+ RR E ; C->76543210->C
+ EXX ; - - -
+ RR D ; C->76543210->C
+ RR E ; C->76543210->C
+ DJNZ ONE_SHIFT ; loop back B times
+
+ POP BC ; restore BC
+ RET NC ; return if last shift produced no carry. >>
+
+; if carry flag was set then accuracy is being lost so round up the addend.
+
+ CALL ADD_BACK
+ RET NZ ; return if not FF 00 00 00 00
+
+; this branch makes all five bytes of the addend zero and is made during
+; addition when the exponents are too far apart for the addend bits to
+; affect the result.
+
+ADDEND_0:
+ EXX ; select alternate set for more significant
+ ; bytes.
+ XOR A ; clear accumulator.
+
+
+; this entry point (from multiplication) sets four of the bytes to zero or if
+; continuing from above, during addition, then all five bytes are set to zero.
+
+ZEROS_4_OR_5:
+ LD L,0 ; set byte 1 to zero.
+ LD D,A ; set byte 2 to A.
+ LD E,L ; set byte 3 to zero.
+ EXX ; select main set
+ LD DE,$0000 ; set lower bytes 4 and 5 to zero.
+ RET ; return.
+
+; THE 'ADD_BACK' SUBROUTINE
+
+; Called from SHIFT_FP above during addition and after normalization from
+; multiplication.
+; This is really a 32_bit increment routine which sets the zero flag according
+; to the 32-bit result.
+; During addition, only negative numbers like FF FF FF FF FF,
+; the twos-complement version of xx 80 00 00 01 say
+; will result in a full ripple FF 00 00 00 00.
+; FF FF FF FF FF when shifted right is unchanged by SHIFT_FP but sets the
+; carry invoking this routine.
+
+ADD_BACK:
+ INC E ;
+ RET NZ ;
+
+ INC D ;
+ RET NZ ;
+
+ EXX ;
+ INC E ;
+ JR NZ,ALL_ADDED ; forward if no overflow
+
+ INC D ;
+
+ALL_ADDED:
+ EXX ;
+ RET ; return with zero flag set for zero mantissa.
+
+; THE 'SUBTRACTION' OPERATION
+
+; just switch the sign of subtrahend and do an add.
+
+SUBTRACT:
+ LD A,(DE) ; fetch exponent byte of second number the
+ ; subtrahend.
+ AND A ; test for zero
+ RET Z ; return if zero - first number is result.
+
+ INC DE ; address the first mantissa byte.
+ LD A,(DE) ; fetch to accumulator.
+ XOR $80 ; toggle the sign bit.
+ LD (DE),A ; place back on calculator stack.
+ DEC DE ; point to exponent byte.
+ ; continue into addition routine.
+
+; THE 'ADDITION' OPERATION
+
+; The addition operation pulls out all the stops and uses most of the Z80's
+; registers to add two floating-point numbers.
+; This is a binary operation and on entry, HL points to the first number
+; and DE to the second.
+
+ADDITION:
+ EXX ; - - -
+ PUSH HL ; save the pointer to the next literal.
+ EXX ; - - -
+
+ PUSH DE ; save pointer to second number
+ PUSH HL ; save pointer to first number - will be the
+ ; result pointer on calculator stack.
+
+ CALL PREP_ADD
+ LD B,A ; save first exponent byte in B.
+ EX DE,HL ; switch number pointers.
+ CALL PREP_ADD
+ LD C,A ; save second exponent byte in C.
+ CP B ; compare the exponent bytes.
+ JR NC,SHIFT_LEN ; forward if second higher
+
+ LD A,B ; else higher exponent to A
+ LD B,C ; lower exponent to B
+ EX DE,HL ; switch the number pointers.
+
+SHIFT_LEN:
+ PUSH AF ; save higher exponent
+ SUB B ; subtract lower exponent
+
+ CALL FETCH_TWO
+ CALL SHIFT_FP
+
+ POP AF ; restore higher exponent.
+ POP HL ; restore result pointer.
+ LD (HL),A ; insert exponent byte.
+ PUSH HL ; save result pointer again.
+
+; now perform the 32-bit addition using two 16-bit Z80 add instructions.
+
+ LD L,B ; transfer low bytes of mantissa individually
+ LD H,C ; to HL register
+
+ ADD HL,DE ; the actual binary addition of lower bytes
+
+; now the two higher byte pairs that are in the alternate register sets.
+
+ EXX ; switch in set
+ EX DE,HL ; transfer high mantissa bytes to HL register.
+
+ ADC HL,BC ; the actual addition of higher bytes with
+ ; any carry from first stage.
+
+ EX DE,HL ; result in DE, sign bytes ($FF or $00) to HL
+
+; now consider the two sign bytes
+
+ LD A,H ; fetch sign byte of num1
+
+ ADC A,L ; add including any carry from mantissa
+ ; addition. 00 or 01 or FE or FF
+
+ LD L,A ; result in L.
+
+; possible outcomes of signs and overflow from mantissa are
+;
+; H + L + carry = L RRA XOR L RRA
+
+; 00 + 00 = 00 00 00
+; 00 + 00 + carry = 01 00 01 carry
+; FF + FF = FE C FF 01 carry
+; FF + FF + carry = FF C FF 00
+; FF + 00 = FF FF 00
+; FF + 00 + carry = 00 C 80 80
+
+ RRA ; C->76543210->C
+ XOR L ; set bit 0 if shifting required.
+
+ EXX ; switch back to main set
+ EX DE,HL ; full mantissa result now in D'E'D E registers.
+ POP HL ; restore pointer to result exponent on
+ ; the calculator stack.
+
+ RRA ; has overflow occurred ?
+ JR NC,TEST_NEG ; skip forward if not
+
+; if the addition of two positive mantissas produced overflow or if the
+; addition of two negative mantissas did not then the result exponent has to
+; be incremented and the mantissa shifted one place to the right.
+
+ LD A,1 ; one shift required.
+ CALL SHIFT_FP ; performs a single shift
+ ; rounding any lost bit
+ INC (HL) ; increment the exponent.
+ JR Z,ADD_REP_6 ; forward to ADD_REP_6 if the exponent
+ ; wraps round from FF to zero as number is too
+ ; big for the system.
+
+; at this stage the exponent on the calculator stack is correct.
+
+TEST_NEG:
+ EXX ; switch in the alternate set.
+ LD A,L ; load result sign to accumulator.
+ AND $80 ; isolate bit 7 from sign byte setting zero
+ ; flag if positive.
+ EXX ; back to main set.
+
+ INC HL ; point to first byte of mantissa
+ LD (HL),A ; insert $00 positive or $80 negative at
+ ; position on calculator stack.
+
+ DEC HL ; point to exponent again.
+ JR Z,GO_NC_MLT ; forward if positive to GO_NC_MLT
+
+; a negative number has to be twos-complemented before being placed on stack.
+
+ LD A,E ; fetch lowest (rightmost) mantissa byte.
+ NEG ; Negate
+ CCF ; Complement Carry Flag
+ LD E,A ; place back in register
+
+ LD A,D ; ditto
+ CPL ;
+ ADC A,0 ;
+ LD D,A ;
+
+ EXX ; switch to higher (leftmost) 16 bits.
+
+ LD A,E ; ditto
+ CPL ;
+ ADC A,0 ;
+ LD E,A ;
+
+ LD A,D ; ditto
+ CPL ;
+ ADC A,0 ;
+ JR NC,END_COMPL ; forward without overflow to END_COMPL
+
+; else entire mantissa is now zero. 00 00 00 00
+
+ RRA ; set mantissa to 80 00 00 00
+ EXX ; switch.
+ INC (HL) ; increment the exponent.
+
+ADD_REP_6:
+ JP Z,REPORT_6 ; jump forward if exponent now zero to REPORT_6
+ ; 'Number too big'
+
+ EXX ; switch back to alternate set.
+
+END_COMPL:
+ LD D,A ; put first byte of mantissa back in DE.
+ EXX ; switch to main set.
+
+GO_NC_MLT:
+ XOR A ; clear carry flag and
+ ; clear accumulator so no extra bits carried
+ ; forward as occurs in multiplication.
+
+ JR TEST_NORM ; forward to common code at TEST_NORM
+ ; but should go straight to NORMALIZE.
+
+; THE 'PREPARE TO MULTIPLY OR DIVIDE' SUBROUTINE
+
+; this routine is called twice from multiplication and twice from division
+; to prepare each of the two numbers for the operation.
+; Initially the accumulator holds zero and after the second invocation bit 7
+; of the accumulator will be the sign bit of the result.
+
+PREP_MULTIPLY_OR_DIVIDE:
+ SCF ; set carry flag to signal number is zero.
+ DEC (HL) ; test exponent
+ INC (HL) ; for zero.
+ RET Z ; return if zero with carry flag set.
+
+ INC HL ; address first mantissa byte.
+ XOR (HL) ; exclusive or the running sign bit.
+ SET 7,(HL) ; set the implied bit.
+ DEC HL ; point to exponent byte.
+ RET ; return.
+
+; THE 'MULTIPLICATION' OPERATION
+
+MULTIPLY:
+ XOR A ; reset bit 7 of running sign flag.
+ CALL PREP_MULTIPLY_OR_DIVIDE
+ RET C ; return if number is zero.
+ ; zero * anything = zero.
+
+ EXX ; - - -
+ PUSH HL ; save pointer to 'next literal'
+ EXX ; - - -
+
+ PUSH DE ; save pointer to second number
+
+ EX DE,HL ; make HL address second number.
+
+ CALL PREP_MULTIPLY_OR_DIVIDE
+
+ EX DE,HL ; HL first number, DE - second number
+ JR C,ZERO_RESULT ; forward with carry to ZERO_RESULT
+ ; anything * zero = zero.
+
+ PUSH HL ; save pointer to first number.
+
+ CALL FETCH_TWO ; fetches two mantissas from
+ ; calc stack to B'C'C,B D'E'D E
+ ; (HL will be overwritten but the result sign
+ ; in A is inserted on the calculator stack)
+
+ LD A,B ; transfer low mantissa byte of first number
+ AND A ; clear carry.
+ SBC HL,HL ; a short form of LD HL,$0000 to take lower
+ ; two bytes of result. (2 program bytes)
+ EXX ; switch in alternate set
+ PUSH HL ; preserve HL
+ SBC HL,HL ; set HL to zero also to take higher two bytes
+ ; of the result and clear carry.
+ EXX ; switch back.
+
+ LD B,33 ; register B can now be used to count 33 shifts.
+ JR STRT_MLT ; forward to loop entry point STRT_MLT
+
+; The multiplication loop is entered at STRT_LOOP.
+
+MLT_LOOP:
+ JR NC,NO_ADD ; forward if no carry
+
+ ; else add in the multiplicand.
+
+ ADD HL,DE ; add the two low bytes to result
+ EXX ; switch to more significant bytes.
+ ADC HL,DE ; add high bytes of multiplicand and any carry.
+ EXX ; switch to main set.
+
+; in either case shift result right into B'C'C A
+
+NO_ADD:
+ EXX ; switch to alternate set
+ RR H ; C > 76543210 > C
+ RR L ; C > 76543210 > C
+ EXX ;
+ RR H ; C > 76543210 > C
+ RR L ; C > 76543210 > C
+
+STRT_MLT:
+ EXX ; switch in alternate set.
+ RR B ; C > 76543210 > C
+ RR C ; C > 76543210 > C
+ EXX ; now main set
+ RR C ; C > 76543210 > C
+ RRA ; C > 76543210 > C
+ DJNZ MLT_LOOP ; loop back 33 timeS
+
+ EX DE,HL ;
+ EXX ;
+ EX DE,HL ;
+ EXX ;
+ POP BC ;
+ POP HL ;
+ LD A,B ;
+ ADD A,C ;
+ JR NZ,MAKE_EXPT ; forward
+
+ AND A ;
+
+MAKE_EXPT:
+ DEC A ;
+ CCF ; Complement Carry Flag
+
+DIVN_EXPT:
+ RLA ;
+ CCF ; Complement Carry Flag
+ RRA ;
+ JP P,OFLW1_CLR
+
+ JR NC,REPORT_6
+
+ AND A ;
+
+OFLW1_CLR:
+ INC A ;
+ JR NZ,OFLW2_CLR
+
+ JR C,OFLW2_CLR
+
+ EXX ;
+ BIT 7,D ;
+ EXX ;
+ JR NZ,REPORT_6
+
+OFLW2_CLR:
+ LD (HL),A ;
+ EXX ;
+ LD A,B ;
+ EXX ;
+
+; addition joins here with carry flag clear.
+
+TEST_NORM:
+ JR NC,NORMALIZE ; forward
+
+ LD A,(HL) ;
+ AND A ;
+
+NEAR_ZERO:
+ LD A,$80 ; prepare to rescue the most significant bit
+ ; of the mantissa if it is set.
+ JR Z,SKIP_ZERO ; skip forward
+
+ZERO_RESULT:
+ XOR A ; make mask byte zero signaling set five
+ ; bytes to zero.
+
+SKIP_ZERO:
+ EXX ; switch in alternate set
+ AND D ; isolate most significant bit (if A is $80).
+
+ CALL ZEROS_4_OR_5 ; sets mantissa without
+ ; affecting any flags.
+
+ RLCA ; test if MSB set. bit 7 goes to bit 0.
+ ; either $00 -> $00 or $80 -> $01
+ LD (HL),A ; make exponent $01 (lowest) or $00 zero
+ JR C,OFLOW_CLR ; forward if first case
+
+ INC HL ; address first mantissa byte on the
+ ; calculator stack.
+ LD (HL),A ; insert a zero for the sign bit.
+ DEC HL ; point to zero exponent
+ JR OFLOW_CLR ; forward
+
+; this branch is common to addition and multiplication with the mantissa
+; result still in registers D'E'D E .
+
+NORMALIZE:
+ LD B,32 ; a maximum of thirty-two left shifts will be
+ ; needed.
+
+SHIFT_ONE:
+ EXX ; address higher 16 bits.
+ BIT 7,D ; test the leftmost bit
+ EXX ; address lower 16 bits.
+
+ JR NZ,NORML_NOW ; forward if leftmost bit was set
+
+ RLCA ; this holds zero from addition, 33rd bit
+ ; from multiplication.
+
+ RL E ; C < 76543210 < C
+ RL D ; C < 76543210 < C
+
+ EXX ; address higher 16 bits.
+
+ RL E ; C < 76543210 < C
+ RL D ; C < 76543210 < C
+
+ EXX ; switch to main set.
+
+ DEC (HL) ; decrement the exponent byte on the calculator
+ ; stack.
+
+ JR Z,NEAR_ZERO ; back if exponent becomes zero
+ ; it's just possible that the last rotation
+ ; set bit 7 of D. We shall see.
+
+ DJNZ SHIFT_ONE ; loop back
+
+; if thirty-two left shifts were performed without setting the most significant
+; bit then the result is zero.
+
+ JR ZERO_RESULT ; back
+
+NORML_NOW:
+ RLA ; for the addition path, A is always zero.
+ ; for the mult path, ...
+
+ JR NC,OFLOW_CLR ; forward
+
+; this branch is taken only with multiplication.
+
+ CALL ADD_BACK
+
+ JR NZ,OFLOW_CLR ; forward
+
+ EXX ;
+ LD D,$80 ;
+ EXX ;
+ INC (HL) ;
+ JR Z,REPORT_6 ; forward
+
+; now transfer the mantissa from the register sets to the calculator stack
+; incorporating the sign bit already there.
+
+OFLOW_CLR:
+ PUSH HL ; save pointer to exponent on stack.
+ INC HL ; address first byte of mantissa which was
+ ; previously loaded with sign bit $00 or $80.
+
+ EXX ; - - -
+ PUSH DE ; push the most significant two bytes.
+ EXX ; - - -
+
+ POP BC ; pop - true mantissa is now BCDE.
+
+; now pick up the sign bit.
+
+ LD A,B ; first mantissa byte to A
+ RLA ; rotate out bit 7 which is set
+ RL (HL) ; rotate sign bit on stack into carry.
+ RRA ; rotate sign bit into bit 7 of mantissa.
+
+; and transfer mantissa from main registers to calculator stack.
+
+ LD (HL),A ;
+ INC HL ;
+ LD (HL),C ;
+ INC HL ;
+ LD (HL),D ;
+ INC HL ;
+ LD (HL),E ;
+
+ POP HL ; restore pointer to num1 now result.
+ POP DE ; restore pointer to num2 now STKEND.
+
+ EXX ; - - -
+ POP HL ; restore pointer to next calculator literal.
+ EXX ; - - -
+
+ RET ; return.
+
+REPORT_6:
+ RST _ERROR_1
+ DEFB 5 ; Error Report: Arithmetic overflow.
+
+; THE 'DIVISION' OPERATION
+
+; "Of all the arithmetic subroutines, division is the most complicated and
+; the least understood. It is particularly interesting to note that the
+; Sinclair programmer himself has made a mistake in his programming ( or has
+; copied over someone else's mistake!) for
+; PRINT PEEK 6352 [ $18D0 ] ('unimproved' ROM, 6351 [ $18CF ] )
+; should give 218 not 225."
+; - Dr. Ian Logan, Syntax magazine Jul/Aug 1982.
+; [ i.e. the jump should be made to div-34th ]
+
+; First check for division by zero.
+
+DIVISION:
+ EX DE,HL ; consider the second number first.
+ XOR A ; set the running sign flag.
+ CALL PREP_MULTIPLY_OR_DIVIDE
+ JR C,REPORT_6 ; back if zero
+ ; 'Arithmetic overflow'
+
+ EX DE,HL ; now prepare first number and check for zero.
+ CALL PREP_MULTIPLY_OR_DIVIDE
+ RET C ; return if zero, 0/anything is zero.
+
+ EXX ; - - -
+ PUSH HL ; save pointer to the next calculator literal.
+ EXX ; - - -
+
+ PUSH DE ; save pointer to divisor - will be STKEND.
+ PUSH HL ; save pointer to dividend - will be result.
+
+ CALL FETCH_TWO ; fetches the two numbers
+ ; into the registers H'B'C'C B
+ ; L'D'E'D E
+ EXX ; - - -
+ PUSH HL ; save the two exponents.
+
+ LD H,B ; transfer the dividend to H'L'H L
+ LD L,C ;
+ EXX ;
+ LD H,C ;
+ LD L,B ;
+
+ XOR A ; clear carry bit and accumulator.
+ LD B,$DF ; count upwards from -33 decimal
+ JR DIVISION_START ; forward to mid-loop entry point
+
+DIV_LOOP:
+ RLA ; multiply partial quotient by two
+ RL C ; setting result bit from carry.
+ EXX ;
+ RL C ;
+ RL B ;
+ EXX ;
+
+DIV_34TH:
+ ADD HL,HL ;
+ EXX ;
+ ADC HL,HL ;
+ EXX ;
+ JR C,SUBN_ONLY ; forward
+
+DIVISION_START:
+ SBC HL,DE ; subtract divisor part.
+ EXX ;
+ SBC HL,DE ;
+ EXX ;
+ JR NC,NUM_RESTORE ; forward if subtraction goes
+
+ ADD HL,DE ; else restore
+ EXX ;
+ ADC HL,DE ;
+ EXX ;
+ AND A ; clear carry
+ JR COUNT_ONE ; forward
+
+SUBN_ONLY:
+ AND A ;
+ SBC HL,DE ;
+ EXX ;
+ SBC HL,DE ;
+ EXX ;
+
+NUM_RESTORE:
+ SCF ; set carry flag
+
+COUNT_ONE:
+ INC B ; increment the counter
+ JP M,DIV_LOOP ; back while still minus to DIV_LOOP
+
+ PUSH AF ;
+ JR Z,DIVISION_START ; back to DIV_START
+
+; "This jump is made to the wrong place. No 34th bit will ever be obtained
+; without first shifting the dividend. Hence important results like 1/10 and
+; 1/1000 are not rounded up as they should be. Rounding up never occurs when
+; it depends on the 34th bit. The jump should be made to div_34th above."
+; - Dr. Frank O'Hara, "The Complete Spectrum ROM Disassembly", 1983,
+; published by Melbourne House.
+; (Note. on the ZX81 this would be JR Z,DIV_34TH)
+;
+; However if you make this change, then while (1/2=.5) will now evaluate as
+; true, (.25=1/4), which did evaluate as true, no longer does.
+
+ LD E,A ;
+ LD D,C ;
+ EXX ;
+ LD E,C ;
+ LD D,B ;
+
+ POP AF ;
+ RR B ;
+ POP AF ;
+ RR B ;
+
+ EXX ;
+ POP BC ;
+ POP HL ;
+ LD A,B ;
+ SUB C ;
+ JP DIVN_EXPT ; jump back
+
+; THE 'INTEGER TRUNCATION TOWARDS ZERO' SUBROUTINE
+
+TRUNCATE:
+ LD A,(HL) ; fetch exponent
+ CP $81 ; compare to +1
+ JR NC,T_GR_ZERO ; forward, if 1 or more
+
+; else the number is smaller than plus or minus 1 and can be made zero.
+
+ LD (HL),$00 ; make exponent zero.
+ LD A,$20 ; prepare to set 32 bits of mantissa to zero.
+ JR NIL_BYTES ; forward
+
+T_GR_ZERO:
+ SUB $A0 ; subtract +32 from exponent
+ RET P ; return if result is positive as all 32 bits
+ ; of the mantissa relate to the integer part.
+ ; The floating point is somewhere to the right
+ ; of the mantissa
+
+ NEG ; else negate to form number of rightmost bits
+ ; to be blanked.
+
+; for instance, disregarding the sign bit, the number 3.5 is held as
+; exponent $82 mantissa .11100000 00000000 00000000 00000000
+; we need to set $82 - $A0 = $E2 NEG = $1E (thirty) bits to zero to form the
+; integer.
+; The sign of the number is never considered as the first bit of the mantissa
+; must be part of the integer.
+
+NIL_BYTES:
+ PUSH DE ; save pointer to STKEND
+ EX DE,HL ; HL points at STKEND
+ DEC HL ; now at last byte of mantissa.
+ LD B,A ; Transfer bit count to B register.
+ SRL B ; divide by
+ SRL B ; eight
+ SRL B ;
+ JR Z,BITS_ZERO ; forward if zero
+
+; else the original count was eight or more and whole bytes can be blanked.
+
+BYTE_ZERO:
+ LD (HL),0 ; set eight bits to zero.
+ DEC HL ; point to more significant byte of mantissa.
+ DJNZ BYTE_ZERO ; loop back
+
+; now consider any residual bits.
+
+BITS_ZERO:
+ AND $07 ; isolate the remaining bits
+ JR Z,IX_END ; forward if none
+
+ LD B,A ; transfer bit count to B counter.
+ LD A,$FF ; form a mask 11111111
+
+LESS_MASK:
+ SLA A ; 1 <- 76543210 <- o slide mask leftwards.
+ DJNZ LESS_MASK ; loop back for bit count
+
+ AND (HL) ; lose the unwanted rightmost bits
+ LD (HL),A ; and place in mantissa byte.
+
+IX_END:
+ EX DE,HL ; restore result pointer from DE.
+ POP DE ; restore STKEND from stack.
+ RET ; return.
+
+;** FLOATING-POINT CALCULATOR **
+;********************************
+; As a general rule the calculator avoids using the IY register.
+; Exceptions are val and str$.
+; So an assembly language programmer who has disabled interrupts to use IY
+; for other purposes can still use the calculator for mathematical
+; purposes.
+
+; THE 'TABLE OF CONSTANTS'
+
+; The ZX81 has only floating-point number representation.
+; Both the ZX80 and the ZX Spectrum have integer numbers in some form.
+
+TAB_CNST:
+ ; 00 00 00 00 00
+stk_zero:
+ DEFB $00 ;;Bytes: 1
+ DEFB $B0 ;;Exponent $00
+ DEFB $00 ;;(+00,+00,+00)
+
+ ; 81 00 00 00 00
+stk_one:
+ DEFB $31 ;;Exponent $81, Bytes: 1
+ DEFB $00 ;;(+00,+00,+00)
+
+ ; 80 00 00 00 00
+stk_half:
+ DEFB $30 ;;Exponent: $80, Bytes: 1
+ DEFB $00 ;;(+00,+00,+00)
+
+ ; 81 49 0F DA A2
+stk_half_pi:
+ DEFB $F1 ;;Exponent: $81, Bytes: 4
+ DEFB $49,$0F,$DA,$A2 ;;
+
+ ; 84 20 00 00 00
+stk_ten:
+ DEFB $34 ;;Exponent: $84, Bytes: 1
+ DEFB $20 ;;(+00,+00,+00)
+
+; THE 'TABLE OF ADDRESSES'
+
+; starts with binary operations which have two operands and one result.
+; three pseudo binary operations first.
+
+tbl_addrs:
+ DEFW jump_true ; $00 Address: $1C2F - jump_true
+ DEFW exchange ; $01 Address: $1A72 - exchange
+ DEFW delete ; $02 Address: $19E3 - delete
+
+; true binary operations.
+ DEFW SUBTRACT ; $03 Address: $174C - subtract
+ DEFW MULTIPLY ; $04 Address: $176C - multiply
+ DEFW DIVISION ; $05 Address: $1882 - division
+ DEFW to_power ; $06 Address: $1DE2 - to_power
+ DEFW or ; $07 Address: $1AED - or
+
+ DEFW boolean_num_and_num ; $08 Address: $1AF3 - boolean_num_and_num
+ DEFW num_l_eql ; $09 Address: $1B03 - num_l_eql
+ DEFW num_gr_eql ; $0A Address: $1B03 - num_gr_eql
+ DEFW nums_neql ; $0B Address: $1B03 - nums_neql
+ DEFW num_grtr ; $0C Address: $1B03 - num_grtr
+ DEFW num_less ; $0D Address: $1B03 - num_less
+ DEFW nums_eql ; $0E Address: $1B03 - nums_eql
+ DEFW ADDITION ; $0F Address: $1755 - addition
+
+ DEFW strs_and_num ; $10 Address: $1AF8 - str_and_num
+ DEFW str_l_eql ; $11 Address: $1B03 - str_l_eql
+ DEFW str_gr_eql ; $12 Address: $1B03 - str_gr_eql
+ DEFW strs_neql ; $13 Address: $1B03 - strs_neql
+ DEFW str_grtr ; $14 Address: $1B03 - str_grtr
+ DEFW str_less ; $15 Address: $1B03 - str_less
+ DEFW strs_eql ; $16 Address: $1B03 - strs_eql
+ DEFW strs_add ; $17 Address: $1B62 - strs_add
+
+; unary follow
+ DEFW neg ; $18
+ DEFW code ; $19
+ DEFW val ; $1A
+ DEFW len ; $1B
+ DEFW sin ; $1C
+ DEFW cos ; $1D
+ DEFW tan ; $1E
+ DEFW asn ; $1F
+ DEFW acs ; $20
+ DEFW atn ; $21
+ DEFW ln ; $22
+ DEFW exp ; $23
+ DEFW int ; $24
+ DEFW sqr ; $25
+ DEFW sgn ; $26
+ DEFW abs ; $27
+ DEFW PEEK ; $28 Address: $1A1B - peek !!!!
+ DEFW usr_num ; $29
+ DEFW str_dollar ; $2A
+ DEFW chr_dollar ; $2B
+ DEFW not ; $2C
+
+; end of true unary
+ DEFW duplicate ; $2D
+ DEFW n_mod_m ; $2E
+
+ DEFW jump ; $2F
+ DEFW stk_data ; $30
+
+ DEFW dec_jr_nz ; $31
+ DEFW less_0 ; $32
+ DEFW greater_0 ; $33
+ DEFW end_calc ; $34
+ DEFW get_argt ; $35
+ DEFW TRUNCATE ; $36
+ DEFW FP_CALC_2 ; $37
+ DEFW e_to_fp ; $38
+
+; the following are just the next available slots for the 128 compound literals
+; which are in range $80 - $FF.
+ DEFW series_xx ; $39 : $80 - $9F.
+ DEFW stk_const_xx ; $3A : $A0 - $BF.
+ DEFW st_mem_xx ; $3B : $C0 - $DF.
+ DEFW get_mem_xx ; $3C : $E0 - $FF.
+
+; Aside: 3D - 7F are therefore unused calculator literals.
+; 39 - 7B would be available for expansion.
+
+; THE 'FLOATING POINT CALCULATOR'
+
+CALCULATE:
+ CALL STACK_POINTERS ; is called to set up the
+ ; calculator stack pointers for a default
+ ; unary operation. HL = last value on stack.
+ ; DE = STKEND first location after stack.
+
+; the calculate routine is called at this point by the series generator...
+
+GEN_ENT_1:
+ LD A,B ; fetch the Z80 B register to A
+ LD (BERG),A ; and store value in system variable BERG.
+ ; this will be the counter for dec_jr_nz
+ ; or if used from FP_CALC2 the calculator
+ ; instruction.
+
+; ... and again later at this point
+
+GEN_ENT_2:
+ EXX ; switch sets
+ EX (SP),HL ; and store the address of next instruction,
+ ; the return address, in H'L'.
+ ; If this is a recursive call then the H'L'
+ ; of the previous invocation goes on stack.
+ ; c.f. end_calc.
+ EXX ; switch back to main set.
+
+; this is the re-entry looping point when handling a string of literals.
+
+RE_ENTRY:
+ LD (STKEND),DE ; save end of stack
+ EXX ; switch to alt
+ LD A,(HL) ; get next literal
+ INC HL ; increase pointer'
+
+; single operation jumps back to here
+
+SCAN_ENT:
+ PUSH HL ; save pointer on stack *
+ AND A ; now test the literal
+ JP P,FIRST_3D ; forward if in range $00 - $3D
+ ; anything with bit 7 set will be one of
+ ; 128 compound literals.
+
+; compound literals have the following format.
+; bit 7 set indicates compound.
+; bits 6-5 the subgroup 0-3.
+; bits 4-0 the embedded parameter $00 - $1F.
+; The subgroup 0-3 needs to be manipulated to form the next available four
+; address places after the simple literals in the address table.
+
+ LD D,A ; save literal in D
+ AND $60 ; and with 01100000 to isolate subgroup
+ RRCA ; rotate bits
+ RRCA ; 4 places to right
+ RRCA ; not five as we need offset * 2
+ RRCA ; 00000xx0
+ ADD A,$72 ; add ($39 * 2) to give correct offset.
+ ; alter above if you add more literals.
+ LD L,A ; store in L for later indexing.
+ LD A,D ; bring back compound literal
+ AND $1F ; use mask to isolate parameter bits
+ JR ENT_TABLE ; forward
+
+; the branch was here with simple literals.
+
+FIRST_3D:
+ CP $18 ; compare with first unary operations.
+ JR NC,DOUBLE_A ; with unary operations
+
+; it is binary so adjust pointers.
+
+ EXX ;
+ LD BC,-5
+ LD D,H ; transfer HL, the last value, to DE.
+ LD E,L ;
+ ADD HL,BC ; subtract 5 making HL point to second
+ ; value.
+ EXX ;
+
+DOUBLE_A:
+ RLCA ; double the literal
+ LD L,A ; and store in L for indexing
+
+ENT_TABLE:
+ LD DE,tbl_addrs ; Address: tbl_addrs
+ LD H,$00 ; prepare to index
+ ADD HL,DE ; add to get address of routine
+ LD E,(HL) ; low byte to E
+ INC HL ;
+ LD D,(HL) ; high byte to D
+
+ LD HL,RE_ENTRY
+ EX (SP),HL ; goes on machine stack
+ ; address of next literal goes to HL. *
+
+
+ PUSH DE ; now the address of routine is stacked.
+ EXX ; back to main set
+ ; avoid using IY register.
+ LD BC,(STKEND+1) ; STKEND_hi
+ ; nothing much goes to C but BERG to B
+ ; and continue into next ret instruction
+ ; which has a dual identity
+
+; THE 'DELETE' SUBROUTINE
+
+; offset $02: 'delete'
+; A simple return but when used as a calculator literal this
+; deletes the last value from the calculator stack.
+; On entry, as always with binary operations,
+; HL=first number, DE=second number
+; On exit, HL=result, DE=stkend.
+; So nothing to do
+
+delete:
+ RET ; return - indirect jump if from above.
+
+; THE 'SINGLE OPERATION' SUBROUTINE
+
+; offset $37: 'FP_CALC_2'
+; this single operation is used, in the first instance, to evaluate most
+; of the mathematical and string functions found in BASIC expressions.
+
+FP_CALC_2:
+ POP AF ; drop return address.
+ LD A,(BERG) ; load accumulator from system variable BERG
+ ; value will be literal eg. 'tan'
+ EXX ; switch to alt
+ JR SCAN_ENT ; back
+ ; next literal will be end_calc in scanning
+
+; THE 'TEST 5 SPACES' SUBROUTINE
+
+; This routine is called from MOVE_FP, STK_CONST and STK_STORE to
+; test that there is enough space between the calculator stack and the
+; machine stack for another five_byte value. It returns with BC holding
+; the value 5 ready for any subsequent LDIR.
+
+TEST_5_SP:
+ PUSH DE ; save
+ PUSH HL ; registers
+ LD BC,5 ; an overhead of five bytes
+ CALL TEST_ROOM ; tests free RAM raising
+ ; an error if not.
+ POP HL ; else restore
+ POP DE ; registers.
+ RET ; return with BC set at 5.
+
+; THE 'MOVE A FLOATING POINT NUMBER' SUBROUTINE
+
+; offset $2D: 'duplicate'
+; This simple routine is a 5-byte LDIR instruction
+; that incorporates a memory check.
+; When used as a calculator literal it duplicates the last value on the
+; calculator stack.
+; Unary so on entry HL points to last value, DE to stkend
+
+duplicate:
+MOVE_FP:
+ CALL TEST_5_SP ; test free memory
+ ; and sets BC to 5.
+ LDIR ; copy the five bytes.
+ RET ; return with DE addressing new STKEND
+ ; and HL addressing new last value.
+
+; THE 'STACK LITERALS' SUBROUTINE
+
+; offset $30: 'stk_data'
+; When a calculator subroutine needs to put a value on the calculator
+; stack that is not a regular constant this routine is called with a
+; variable number of following data bytes that convey to the routine
+; the floating point form as succinctly as is possible.
+
+stk_data:
+ LD H,D ; transfer STKEND
+ LD L,E ; to HL for result.
+
+STK_CONST:
+ CALL TEST_5_SP ; tests that room exists
+ ; and sets BC to $05.
+
+ EXX ; switch to alternate set
+ PUSH HL ; save the pointer to next literal on stack
+ EXX ; switch back to main set
+
+ EX (SP),HL ; pointer to HL, destination to stack.
+
+ PUSH BC ; save BC - value 5 from test room ??.
+ LD A,(HL) ; fetch the byte following 'stk_data'
+ AND $C0 ; isolate bits 7 and 6
+ RLCA ; rotate
+ RLCA ; to bits 1 and 0 range $00 - $03.
+ LD C,A ; transfer to C
+ INC C ; and increment to give number of bytes
+ ; to read. $01 - $04
+ LD A,(HL) ; reload the first byte
+ AND $3F ; mask off to give possible exponent.
+ JR NZ,FORM_EXP ; forward to FORM_EXP if it was possible to
+ ; include the exponent.
+
+; else byte is just a byte count and exponent comes next.
+
+ INC HL ; address next byte and
+ LD A,(HL) ; pick up the exponent ( - $50).
+
+FORM_EXP:
+ ADD A,$50 ; now add $50 to form actual exponent
+ LD (DE),A ; and load into first destination byte.
+ LD A,$05 ; load accumulator with $05 and
+ SUB C ; subtract C to give count of trailing
+ ; zeros plus one.
+ INC HL ; increment source
+ INC DE ; increment destination
+
+
+ LD B,$00 ; prepare to copy. Note. B is zero.
+ LDIR ; copy C bytes
+ POP BC ; restore 5 counter to BC.
+
+ EX (SP),HL ; put HL on stack as next literal pointer
+ ; and the stack value - result pointer -
+ ; to HL.
+
+ EXX ; switch to alternate set.
+ POP HL ; restore next literal pointer from stack
+ ; to H'L'.
+ EXX ; switch back to main set.
+
+ LD B,A ; zero count to B
+ XOR A ; clear accumulator
+
+STK_ZEROS:
+ DEC B ; decrement B counter
+ RET Z ; return if zero. >>
+ ; DE points to new STKEND
+ ; HL to new number.
+
+ LD (DE),A ; else load zero to destination
+ INC DE ; increase destination
+ JR STK_ZEROS ; loop back until done.
+
+; THE 'SKIP CONSTANTS' SUBROUTINE
+
+; This routine traverses variable-length entries in the table of constants,
+; stacking intermediate, unwanted constants onto a dummy calculator stack,
+; in the first five bytes of the ZX81 ROM.
+
+SKIP_CONS:
+ AND A ; test if initially zero.
+
+SKIP_NEXT:
+ RET Z ; return if zero. >>
+
+ PUSH AF ; save count.
+ PUSH DE ; and normal STKEND
+
+ LD DE,$0000 ; dummy value for STKEND at start of ROM
+ ; Note. not a fault but this has to be
+ ; moved elsewhere when running in RAM.
+ ;
+ CALL STK_CONST ; works through variable
+ ; length records.
+
+ POP DE ; restore real STKEND
+ POP AF ; restore count
+ DEC A ; decrease
+ JR SKIP_NEXT ; loop back
+
+; THE 'MEMORY LOCATION' SUBROUTINE
+
+; This routine, when supplied with a base address in HL and an index in A,
+; will calculate the address of the A'th entry, where each entry occupies
+; five bytes. It is used for addressing floating-point numbers in the
+; calculator's memory area.
+
+LOC_MEM:
+ LD C,A ; store the original number $00-$1F.
+ RLCA ; double.
+ RLCA ; quadruple.
+ ADD A,C ; now add original value to multiply by five.
+
+ LD C,A ; place the result in C.
+ LD B,$00 ; set B to 0.
+ ADD HL,BC ; add to form address of start of number in HL.
+
+ RET ; return.
+
+; THE 'GET FROM MEMORY AREA' SUBROUTINE
+
+; offsets $E0 to $FF: 'get_mem_0', 'get_mem_1' etc.
+; A holds $00-$1F offset.
+; The calculator stack increases by 5 bytes.
+
+get_mem_xx:
+ PUSH DE ; save STKEND
+ LD HL,(MEM) ; MEM is base address of the memory cells.
+ CALL LOC_MEM ; so that HL = first byte
+ CALL MOVE_FP ; moves 5 bytes with memory
+ ; check.
+ ; DE now points to new STKEND.
+ POP HL ; the original STKEND is now RESULT pointer.
+ RET ; return.
+
+; THE 'STACK A CONSTANT' SUBROUTINE
+
+stk_const_xx:
+; offset $A0: 'stk_zero'
+; offset $A1: 'stk_one'
+; offset $A2: 'stk_half'
+; offset $A3: 'stk_half_pi'
+; offset $A4: 'stk_ten'
+;
+; This routine allows a one-byte instruction to stack up to 32 constants
+; held in short form in a table of constants. In fact only 5 constants are
+; required. On entry the A register holds the literal ANDed with $1F.
+; It isn't very efficient and it would have been better to hold the
+; numbers in full, five byte form and stack them in a similar manner
+; to that which would be used later for semi-tone table values.
+
+ LD H,D ; save STKEND - required for result
+ LD L,E ;
+ EXX ; swap
+ PUSH HL ; save pointer to next literal
+ LD HL,stk_zero ; Address: stk_zero - start of table of
+ ; constants
+ EXX ;
+ CALL SKIP_CONS
+ CALL STK_CONST
+ EXX ;
+ POP HL ; restore pointer to next literal.
+ EXX ;
+ RET ; return.
+
+; THE 'STORE IN A MEMORY AREA' SUBROUTINE
+
+; Offsets $C0 to $DF: 'st_mem_0', 'st_mem_1' etc.
+; Although 32 memory storage locations can be addressed, only six
+; $C0 to $C5 are required by the ROM and only the thirty bytes (6*5)
+; required for these are allocated. ZX81 programmers who wish to
+; use the floating point routines from assembly language may wish to
+; alter the system variable MEM to point to 160 bytes of RAM to have
+; use the full range available.
+; A holds derived offset $00-$1F.
+; Unary so on entry HL points to last value, DE to STKEND.
+
+st_mem_xx:
+ PUSH HL ; save the result pointer.
+ EX DE,HL ; transfer to DE.
+ LD HL,(MEM) ; fetch MEM the base of memory area.
+ CALL LOC_MEM ; sets HL to the destination.
+ EX DE,HL ; swap - HL is start, DE is destination.
+
+ CALL MOVE_FP
+ ; note. a short ld bc,5; ldir
+ ; the embedded memory check is not required
+ ; so these instructions would be faster!
+
+ EX DE,HL ; DE = STKEND
+ POP HL ; restore original result pointer
+ RET ; return.
+
+; THE 'EXCHANGE' SUBROUTINE
+
+; offset $01: 'exchange'
+; This routine exchanges the last two values on the calculator stack
+; On entry, as always with binary operations,
+; HL=first number, DE=second number
+; On exit, HL=result, DE=stkend.
+
+exchange:
+ LD B,$05 ; there are five bytes to be swapped
+
+; start of loop.
+
+SWAP_BYTE:
+ LD A,(DE) ; each byte of second
+ LD C,(HL) ; each byte of first
+ EX DE,HL ; swap pointers
+ LD (DE),A ; store each byte of first
+ LD (HL),C ; store each byte of second
+ INC HL ; advance both
+ INC DE ; pointers.
+ DJNZ SWAP_BYTE ; loop back until all 5 done.
+
+ EX DE,HL ; even up the exchanges so that DE addresses STKEND.
+ RET ; return.
+
+; THE 'SERIES GENERATOR' SUBROUTINE
+
+; The ZX81 uses Chebyshev polynomials to generate approximations for
+; SIN, ATN, LN and EXP. These are named after the Russian mathematician
+; Pafnuty Chebyshev, born in 1821, who did much pioneering work on numerical
+; series. As far as calculators are concerned, Chebyshev polynomials have an
+; advantage over other series, for example the Taylor series, as they can
+; reach an approximation in just six iterations for SIN, eight for EXP and
+; twelve for LN and ATN. The mechanics of the routine are interesting but
+; for full treatment of how these are generated with demonstrations in
+; Sinclair BASIC see "The Complete Spectrum ROM Disassembly" by Dr Ian Logan
+; and Dr Frank O'Hara, published 1983 by Melbourne House.
+
+series_xx:
+ LD B,A ; parameter $00 - $1F to B counter
+ CALL GEN_ENT_1
+ ; A recursive call to a special entry point
+ ; in the calculator that puts the B register
+ ; in the system variable BERG. The return
+ ; address is the next location and where
+ ; the calculator will expect its first
+ ; instruction - now pointed to by HL'.
+ ; The previous pointer to the series of
+ ; five-byte numbers goes on the machine stack.
+
+; The initialization phase.
+
+ DEFB __duplicate ;; x,x
+ DEFB __addition ;; x+x
+ DEFB __st_mem_0 ;; x+x
+ DEFB __delete ;; .
+ DEFB __stk_zero ;; 0
+ DEFB __st_mem_2 ;; 0
+
+; a loop is now entered to perform the algebraic calculation for each of
+; the numbers in the series
+
+G_LOOP:
+ DEFB __duplicate ;; v,v.
+ DEFB __get_mem_0 ;; v,v,x+2
+ DEFB __multiply ;; v,v*x+2
+ DEFB __get_mem_2 ;; v,v*x+2,v
+ DEFB __st_mem_1 ;;
+ DEFB __subtract ;;
+ DEFB __end_calc ;;
+
+; the previous pointer is fetched from the machine stack to H'L' where it
+; addresses one of the numbers of the series following the series literal.
+
+ CALL stk_data ; is called directly to
+ ; push a value and advance H'L'.
+ CALL GEN_ENT_2 ; recursively re-enters
+ ; the calculator without disturbing
+ ; system variable BERG
+ ; H'L' value goes on the machine stack and is
+ ; then loaded as usual with the next address.
+
+ DEFB __addition ;;
+ DEFB __exchange ;;
+ DEFB __st_mem_2 ;;
+ DEFB __delete ;;
+
+ DEFB __dec_jr_nz ;;
+ DEFB $EE ;;back to G_LOOP, G_LOOP
+
+; when the counted loop is complete the final subtraction yields the result
+; for example SIN X.
+
+ DEFB __get_mem_1 ;;
+ DEFB __subtract ;;
+ DEFB __end_calc ;;
+
+ RET ; return with H'L' pointing to location
+ ; after last number in series.
+
+; Handle unary minus (18)
+
+; Unary so on entry HL points to last value, DE to STKEND.
+
+neg:
+ LD A, (HL) ; fetch exponent of last value on the
+ ; calculator stack.
+ AND A ; test it.
+ RET Z ; return if zero.
+
+ INC HL ; address the byte with the sign bit.
+ LD A,(HL) ; fetch to accumulator.
+ XOR $80 ; toggle the sign bit.
+ LD (HL),A ; put it back.
+ DEC HL ; point to last value again.
+ RET ; return.
+
+; Absolute magnitude (27)
+
+; This calculator literal finds the absolute value of the last value,
+; floating point, on calculator stack.
+
+abs:
+ INC HL ; point to byte with sign bit.
+ RES 7,(HL) ; make the sign positive.
+ DEC HL ; point to last value again.
+ RET ; return.
+
+; Signum (26)
+
+; This routine replaces the last value on the calculator stack,
+; which is in floating point form, with one if positive and with -minus one
+; if negative. If it is zero then it is left as such.
+
+sgn:
+ INC HL ; point to first byte of 4-byte mantissa.
+ LD A,(HL) ; pick up the byte with the sign bit.
+ DEC HL ; point to exponent.
+ DEC (HL) ; test the exponent for
+ INC (HL) ; the value zero.
+
+ SCF ; set the carry flag.
+ CALL NZ,FP_0_OR_1 ; replaces last value with one
+ ; if exponent indicates the value is non-zero.
+ ; in either case mantissa is now four zeros.
+
+ INC HL ; point to first byte of 4-byte mantissa.
+ RLCA ; rotate original sign bit to carry.
+ RR (HL) ; rotate the carry into sign.
+ DEC HL ; point to last value.
+ RET ; return.
+
+; Handle PEEK function (28)
+
+; This function returns the contents of a memory address.
+; The entire address space can be peeked including the ROM.
+
+PEEK:
+ CALL FIND_INT ; puts address in BC.
+ LD A,(BC) ; load contents into A register.
+
+IN_PK_STK:
+ JP STACK_A ; exit via STACK_A to put value on the
+ ; calculator stack.
+; USR number (29)
+
+; The USR function followed by a number 0-65535 is the method by which
+; the ZX81 invokes machine code programs. This function returns the
+; contents of the BC register pair.
+; Note. that STACK_BC re-initializes the IY register to ERR_NR if a user-written
+; program has altered it.
+
+usr_num:
+ CALL FIND_INT ; to fetch the
+ ; supplied address into BC.
+
+ LD HL,STACK_BC ; address: STACK_BC is
+ PUSH HL ; pushed onto the machine stack.
+ PUSH BC ; then the address of the machine code
+ ; routine.
+
+ RET ; make an indirect jump to the routine
+ ; and, hopefully, to STACK_BC also.
+
+; Greater than zero ($33)
+
+; Test if the last value on the calculator stack is greater than zero.
+; This routine is also called directly from the end-tests of the comparison
+; routine.
+
+greater_0:
+ LD A,(HL) ; fetch exponent.
+ AND A ; test it for zero.
+ RET Z ; return if so.
+
+
+ LD A,$FF ; prepare XOR mask for sign bit
+ JR SIGN_TO_C ; forward to SIGN_TO_C
+ ; to put sign in carry
+ ; (carry will become set if sign is positive)
+ ; and then overwrite location with 1 or 0
+ ; as appropriate.
+
+; Handle NOT operator ($2C)
+
+; This overwrites the last value with 1 if it was zero else with zero
+; if it was any other value.
+;
+; e.g. NOT 0 returns 1, NOT 1 returns 0, NOT -3 returns 0.
+;
+; The subroutine is also called directly from the end-tests of the comparison
+; operator.
+
+not:
+ LD A,(HL) ; get exponent byte.
+ NEG ; negate - sets carry if non-zero.
+ CCF ; complement so carry set if zero, else reset.
+ JR FP_0_OR_1 ; forward to FP_0_OR_1.
+
+; Less than zero (32)
+
+; Destructively test if last value on calculator stack is less than zero.
+; Bit 7 of second byte will be set if so.
+
+less_0:
+ XOR A ; set xor mask to zero
+ ; (carry will become set if sign is negative).
+
+; transfer sign of mantissa to Carry Flag.
+
+SIGN_TO_C:
+ INC HL ; address 2nd byte.
+ XOR (HL) ; bit 7 of HL will be set if number is negative.
+ DEC HL ; address 1st byte again.
+ RLCA ; rotate bit 7 of A to carry.
+
+; Zero or one
+
+; This routine places an integer value zero or one at the addressed location
+; of calculator stack or MEM area. The value one is written if carry is set on
+; entry else zero.
+
+FP_0_OR_1:
+ PUSH HL ; save pointer to the first byte
+ LD B,$05 ; five bytes to do.
+
+FP_loop:
+ LD (HL),$00 ; insert a zero.
+ INC HL ;
+ DJNZ FP_loop ; repeat.
+
+ POP HL ;
+ RET NC ;
+
+ LD (HL),$81 ; make value 1
+ RET ; return.
+
+; Handle OR operator (07)
+
+; The Boolean OR operator. eg. X OR Y
+; The result is zero if both values are zero else a non-zero value.
+;
+; e.g. 0 OR 0 returns 0.
+; -3 OR 0 returns -3.
+; 0 OR -3 returns 1.
+; -3 OR 2 returns 1.
+;
+; A binary operation.
+; On entry HL points to first operand (X) and DE to second operand (Y).
+
+or:
+ LD A,(DE) ; fetch exponent of second number
+ AND A ; test it.
+ RET Z ; return if zero.
+
+ SCF ; set carry flag
+ JR FP_0_OR_1 ; back to FP_0_OR_1 to overwrite the first operand
+ ; with the value 1.
+
+; Handle number AND number (08)
+
+; The Boolean AND operator.
+;
+; e.g. -3 AND 2 returns -3.
+; -3 AND 0 returns 0.
+; 0 and -2 returns 0.
+; 0 and 0 returns 0.
+;
+; Compare with OR routine above.
+
+boolean_num_and_num:
+ LD A,(DE) ; fetch exponent of second number.
+ AND A ; test it.
+ RET NZ ; return if not zero.
+
+ JR FP_0_OR_1 ; back to FP_0_OR_1 to overwrite the first operand
+ ; with zero for return value.
+
+
+; Handle string AND number (10)
+
+; e.g. "YOU WIN" AND SCORE>99 will return the string if condition is true
+; or the null string if false.
+
+strs_and_num:
+ LD A,(DE) ; fetch exponent of second number.
+ AND A ; test it.
+ RET NZ ; return if number was not zero - the string
+ ; is the result.
+
+; if the number was zero (false) then the null string must be returned by
+; altering the length of the string on the calculator stack to zero.
+
+ PUSH DE ; save pointer to the now obsolete number
+ ; (which will become the new STKEND)
+
+ DEC DE ; point to the 5th byte of string descriptor.
+ XOR A ; clear the accumulator.
+ LD (DE),A ; place zero in high byte of length.
+ DEC DE ; address low byte of length.
+ LD (DE),A ; place zero there - now the null string.
+
+ POP DE ; restore pointer - new STKEND.
+ RET ; return.
+
+
+; Perform comparison ($09-$0E, $11-$16)
+
+; True binary operations.
+;
+; A single entry point is used to evaluate six numeric and six string
+; comparisons. On entry, the calculator literal is in the B register and
+; the two numeric values, or the two string parameters, are on the
+; calculator stack.
+; The individual bits of the literal are manipulated to group similar
+; operations although the SUB 8 instruction does nothing useful and merely
+; alters the string test bit.
+; Numbers are compared by subtracting one from the other, strings are
+; compared by comparing every character until a mismatch, or the end of one
+; or both, is reached.
+;
+; Numeric Comparisons.
+
+; The 'x>y' example is the easiest as it employs straight-thru logic.
+; Number y is subtracted from x and the result tested for greater_0 yielding
+; a final value 1 (true) or 0 (false).
+; For 'x<y' the same logic is used but the two values are first swapped on the
+; calculator stack.
+; For 'x=y' NOT is applied to the subtraction result yielding true if the
+; difference was zero and false with anything else.
+; The first three numeric comparisons are just the opposite of the last three
+; so the same processing steps are used and then a final NOT is applied.
+;
+; literal Test No sub 8 ExOrNot 1st RRCA exch sub ? End-Tests
+; ========= ==== == ======== === ======== ======== ==== === = === === ===
+; num_l_eql x<=y 09 00000001 dec 00000000 00000000 ---- x-y ? --- >0? NOT
+; num_gr_eql x>=y 0A 00000010 dec 00000001 10000000c swap y-x ? --- >0? NOT
+; nums_neql x<>y 0B 00000011 dec 00000010 00000001 ---- x-y ? NOT --- NOT
+; num_grtr x>y 0C 00000100 - 00000100 00000010 ---- x-y ? --- >0? ---
+; num_less x<y 0D 00000101 - 00000101 10000010c swap y-x ? --- >0? ---
+; nums_eql x=y 0E 00000110 - 00000110 00000011 ---- x-y ? NOT --- ---
+;
+; comp -> C/F
+; ==== ===
+; str_l_eql x$<=y$ 11 00001001 dec 00001000 00000100 ---- x$y$ 0 !or >0? NOT
+; str_gr_eql x$>=y$ 12 00001010 dec 00001001 10000100c swap y$x$ 0 !or >0? NOT
+; strs_neql x$<>y$ 13 00001011 dec 00001010 00000101 ---- x$y$ 0 !or >0? NOT
+; str_grtr x$>y$ 14 00001100 - 00001100 00000110 ---- x$y$ 0 !or >0? ---
+; str_less x$<y$ 15 00001101 - 00001101 10000110c swap y$x$ 0 !or >0? ---
+; strs_eql x$=y$ 16 00001110 - 00001110 00000111 ---- x$y$ 0 !or >0? ---
+;
+; String comparisons are a little different in that the eql/neql carry flag
+; from the 2nd RRCA is, as before, fed into the first of the end tests but
+; along the way it gets modified by the comparison process. The result on the
+; stack always starts off as zero and the carry fed in determines if NOT is
+; applied to it. So the only time the greater-0 test is applied is if the
+; stack holds zero which is not very efficient as the test will always yield
+; zero. The most likely explanation is that there were once separate end tests
+; for numbers and strings.
+
+; $1B03 SAME ADDRESS FOR MULTIPLE ROUTINES ???
+
+num_l_eql:
+num_gr_eql:
+nums_neql:
+num_grtr:
+num_less:
+nums_eql:
+str_l_eql:
+str_gr_eql:
+strs_neql:
+str_grtr:
+str_less:
+strs_eql:
+num_lt_eql:
+ LD A,B ; transfer literal to accumulator.
+ SUB $08 ; subtract eight - which is not useful.
+ BIT 2,A ; isolate '>', '<', '='.
+
+ JR NZ,EX_OR_NOT ; skip to EX_OR_NOT with these.
+
+ DEC A ; else make $00-$02, $08-$0A to match bits 0-2.
+
+EX_OR_NOT:
+ RRCA ; the first RRCA sets carry for a swap.
+ JR NC,NUM_OR_STR ; forward to NUM_OR_STR with other 8 cases
+
+; for the other 4 cases the two values on the calculator stack are exchanged.
+
+ PUSH AF ; save A and carry.
+ PUSH HL ; save HL - pointer to first operand.
+ ; (DE points to second operand).
+
+ CALL exchange ; routine exchange swaps the two values.
+ ; (HL = second operand, DE = STKEND)
+
+ POP DE ; DE = first operand
+ EX DE,HL ; as we were.
+ POP AF ; restore A and carry.
+
+; Note. it would be better if the 2nd RRCA preceded the string test.
+; It would save two duplicate bytes and if we also got rid of that sub 8
+; at the beginning we wouldn't have to alter which bit we test.
+
+NUM_OR_STR:
+
+ BIT 2,A ; test if a string comparison.
+ JR NZ,STRINGS ; forward to STRINGS if so.
+
+; continue with numeric comparisons.
+
+ RRCA ; 2nd RRCA causes eql/neql to set carry.
+ PUSH AF ; save A and carry
+
+ CALL SUBTRACT ; leaves result on stack.
+ JR END_TESTS ; forward to END_TESTS
+
+STRINGS:
+ RRCA ; 2nd RRCA causes eql/neql to set carry.
+ PUSH AF ; save A and carry.
+ CALL STK_FETCH ; gets 2nd string params
+ PUSH DE ; save start2 *.
+ PUSH BC ; and the length.
+
+ CALL STK_FETCH ; gets 1st string
+ ; parameters - start in DE, length in BC.
+ POP HL ; restore length of second to HL.
+
+; A loop is now entered to compare, by subtraction, each corresponding character
+; of the strings. For each successful match, the pointers are incremented and
+; the lengths decreased and the branch taken back to here. If both string
+; remainders become null at the same time, then an exact match exists.
+
+BYTE_COMP:
+ LD A,H ; test if the second string
+ OR L ; is the null string and hold flags.
+
+ EX (SP),HL ; put length2 on stack, bring start2 to HL *.
+ LD A,B ; hi byte of length1 to A
+
+ JR NZ,SEC_PLUS ; forward to SEC_PLUS if second not null.
+
+ OR C ; test length of first string.
+
+SECOND_LOW:
+ POP BC ; pop the second length off stack.
+ JR Z,BOTH_NULL ; forward if first string is also
+ ; of zero length.
+
+; the true condition - first is longer than second (SECOND_LESS)
+
+ POP AF ; restore carry (set if eql/neql)
+ CCF ; complement carry flag.
+ ; Note. equality becomes false.
+ ; Inequality is true. By swapping or applying
+ ; a terminal 'not', all comparisons have been
+ ; manipulated so that this is success path.
+ JR STR_TEST ; forward to leave via STR_TEST
+
+; the branch was here with a match
+
+BOTH_NULL:
+ POP AF ; restore carry - set for eql/neql
+ JR STR_TEST ; forward to STR_TEST
+
+; the branch was here when 2nd string not null and low byte of first is yet
+; to be tested.
+
+SEC_PLUS:
+ OR C ; test the length of first string.
+ JR Z,FRST_LESS ; forward to FRST_LESS if length is zero.
+
+; both strings have at least one character left.
+
+ LD A,(DE) ; fetch character of first string.
+ SUB (HL) ; subtract with that of 2nd string.
+ JR C,FRST_LESS ; forward to FRST_LESS if carry set
+
+ JR NZ,SECOND_LOW ; back to SECOND_LOW and then STR_TEST
+ ; if not exact match.
+
+ DEC BC ; decrease length of 1st string.
+ INC DE ; increment 1st string pointer.
+
+ INC HL ; increment 2nd string pointer.
+ EX (SP),HL ; swap with length on stack
+ DEC HL ; decrement 2nd string length
+ JR BYTE_COMP ; back to BYTE_COMP
+
+; the false condition.
+
+FRST_LESS:
+ POP BC ; discard length
+ POP AF ; pop A
+ AND A ; clear the carry for false result.
+
+; exact match and x$>y$ rejoin here
+
+STR_TEST:
+ PUSH AF ; save A and carry
+
+ RST _FP_CALC ;;
+ DEFB __stk_zero ;; an initial false value.
+ DEFB __end_calc ;;
+
+; both numeric and string paths converge here.
+
+END_TESTS:
+ POP AF ; pop carry - will be set if eql/neql
+ PUSH AF ; save it again.
+
+ CALL C,not ; sets true(1) if equal(0)
+ ; or, for strings, applies true result.
+ CALL greater_0 ; ??????????
+
+
+ POP AF ; pop A
+ RRCA ; the third RRCA - test for '<=', '>=' or '<>'.
+ CALL NC,not ; apply a terminal NOT if so.
+ RET ; return.
+
+; String concatenation ($17)
+
+; This literal combines two strings into one e.g. LET A$ = B$ + C$
+; The two parameters of the two strings to be combined are on the stack.
+
+strs_add:
+ CALL STK_FETCH ; fetches string parameters
+ ; and deletes calculator stack entry.
+ PUSH DE ; save start address.
+ PUSH BC ; and length.
+
+ CALL STK_FETCH ; for first string
+ POP HL ; re-fetch first length
+ PUSH HL ; and save again
+ PUSH DE ; save start of second string
+ PUSH BC ; and its length.
+
+ ADD HL,BC ; add the two lengths.
+ LD B,H ; transfer to BC
+ LD C,L ; and create
+ RST _BC_SPACES ; BC_SPACES in workspace.
+ ; DE points to start of space.
+
+ CALL STK_STO_STR ; stores parameters
+ ; of new string updating STKEND.
+ POP BC ; length of first
+ POP HL ; address of start
+
+ LD A,B ; test for
+ OR C ; zero length.
+ JR Z,OTHER_STR ; to OTHER_STR if null string
+ LDIR ; copy string to workspace.
+
+OTHER_STR:
+ POP BC ; now second length
+ POP HL ; and start of string
+ LD A,B ; test this one
+ OR C ; for zero length
+ JR Z,STACK_POINTERS ; skip forward to STACK_POINTERS if so as complete.
+
+ LDIR ; else copy the bytes.
+ ; and continue into next routine which
+ ; sets the calculator stack pointers.
+
+; Check stack pointers
+
+; Register DE is set to STKEND and HL, the result pointer, is set to five
+; locations below this.
+; This routine is used when it is inconvenient to save these values at the
+; time the calculator stack is manipulated due to other activity on the
+; machine stack.
+; This routine is also used to terminate the VAL routine for
+; the same reason and to initialize the calculator stack at the start of
+; the CALCULATE routine.
+
+STACK_POINTERS:
+ LD HL,(STKEND) ; fetch STKEND value from system variable.
+ LD DE,-5
+ PUSH HL ; push STKEND value.
+
+ ADD HL,DE ; subtract 5 from HL.
+
+ POP DE ; pop STKEND to DE.
+ RET ; return.
+
+; Handle CHR$ (2B)
+
+; This function returns a single character string that is a result of
+; converting a number in the range 0-255 to a string e.g. CHR$ 38 = "A".
+; Note. the ZX81 does not have an ASCII character set.
+
+chr_dollar:
+ CALL FP_TO_A ; puts the number in A.
+
+ JR C,REPORT_Bd ; forward if overflow
+ JR NZ,REPORT_Bd ; forward if negative
+ PUSH AF ; save the argument.
+ LD BC,1 ; one space required.
+ RST _BC_SPACES ; BC_SPACES makes DE point to start
+ POP AF ; restore the number.
+ LD (DE),A ; and store in workspace
+
+ CALL STK_STO_STR ; stacks descriptor.
+
+ EX DE,HL ; make HL point to result and DE to STKEND.
+ RET ; return.
+
+REPORT_Bd:
+ RST _ERROR_1
+ DEFB $0A ; Error Report: Integer out of range
+
+; Handle VAL ($1A)
+
+; VAL treats the characters in a string as a numeric expression.
+; e.g. VAL "2.3" = 2.3, VAL "2+4" = 6, VAL ("2" + "4") = 24.
+
+val:
+ LD HL,(CH_ADD) ; fetch value of system variable CH_ADD
+ PUSH HL ; and save on the machine stack.
+
+ CALL STK_FETCH ; fetches the string operand
+ ; from calculator stack.
+
+ PUSH DE ; save the address of the start of the string.
+ INC BC ; increment the length for a carriage return.
+
+ RST _BC_SPACES ; BC_SPACES creates the space in workspace.
+ POP HL ; restore start of string to HL.
+ LD (CH_ADD),DE ; load CH_ADD with start DE in workspace.
+
+ PUSH DE ; save the start in workspace
+ LDIR ; copy string from program or variables or
+ ; workspace to the workspace area.
+ EX DE,HL ; end of string + 1 to HL
+ DEC HL ; decrement HL to point to end of new area.
+ LD (HL),ZX_NEWLINE ; insert a carriage return at end.
+ ; ZX81 has a non-ASCII character set
+ RES 7,(IY+FLAGS-RAMBASE) ; signal checking syntax.
+ CALL CLASS_6 ; evaluates string
+ ; expression and checks for integer result.
+
+ CALL CHECK_2 ; checks for carriage return.
+
+ POP HL ; restore start of string in workspace.
+
+ LD (CH_ADD),HL ; set CH_ADD to the start of the string again.
+ SET 7,(IY+FLAGS-RAMBASE) ; signal running program.
+ CALL SCANNING ; evaluates the string
+ ; in full leaving result on calculator stack.
+
+ POP HL ; restore saved character address in program.
+ LD (CH_ADD),HL ; and reset the system variable CH_ADD.
+
+ JR STACK_POINTERS ; back to exit via STACK_POINTERS.
+ ; resetting the calculator stack pointers
+ ; HL and DE from STKEND as it wasn't possible
+ ; to preserve them during this routine.
+
+; Handle STR$ (2A)
+
+; This function returns a string representation of a numeric argument.
+; The method used is to trick the PRINT_FP routine into thinking it
+; is writing to a collapsed display file when in fact it is writing to
+; string workspace.
+; If there is already a newline at the intended print position and the
+; column count has not been reduced to zero then the print routine
+; assumes that there is only 1K of RAM and the screen memory, like the rest
+; of dynamic memory, expands as necessary using calls to the ONE_SPACE
+; routine. The screen is character-mapped not bit-mapped.
+
+str_dollar:
+ LD BC,1 ; create an initial byte in workspace
+ RST _BC_SPACES ; using BC_SPACES restart.
+
+ LD (HL),ZX_NEWLINE ; place a carriage return there.
+
+ LD HL,(S_POSN) ; fetch value of S_POSN column/line
+ PUSH HL ; and preserve on stack.
+
+ LD L,$FF ; make column value high to create a
+ ; contrived buffer of length 254.
+ LD (S_POSN),HL ; and store in system variable S_POSN.
+
+ LD HL,(DF_CC) ; fetch value of DF_CC
+ PUSH HL ; and preserve on stack also.
+
+ LD (DF_CC),DE ; now set DF_CC which normally addresses
+ ; somewhere in the display file to the start
+ ; of workspace.
+ PUSH DE ; save the start of new string.
+
+ CALL PRINT_FP
+
+ POP DE ; retrieve start of string.
+
+ LD HL,(DF_CC) ; fetch end of string from DF_CC.
+ AND A ; prepare for true subtraction.
+ SBC HL,DE ; subtract to give length.
+
+ LD B,H ; and transfer to the BC
+ LD C,L ; register.
+
+ POP HL ; restore original
+ LD (DF_CC),HL ; DF_CC value
+
+ POP HL ; restore original
+ LD (S_POSN),HL ; S_POSN values.
+
+ CALL STK_STO_STR ; stores the string
+ ; descriptor on the calculator stack.
+
+ EX DE,HL ; HL = last value, DE = STKEND.
+ RET ; return.
+
+; THE 'CODE' FUNCTION
+
+; (offset $19: 'code')
+; Returns the code of a character or first character of a string
+; e.g. CODE "AARDVARK" = 38 (not 65 as the ZX81 does not have an ASCII
+; character set).
+
+code:
+ CALL STK_FETCH ; fetch and delete the string parameters.
+ ; DE points to the start, BC holds the length.
+ LD A,B ; test length
+ OR C ; of the string.
+ JR Z,STK_CODE ; skip with zero if the null string.
+
+ LD A,(DE) ; else fetch the first character.
+
+STK_CODE:
+ JP STACK_A ; jump back (with memory check)
+
+; THE 'LEN' SUBROUTINE
+
+; (offset $1b: 'len')
+; Returns the length of a string.
+; In Sinclair BASIC strings can be more than twenty thousand characters long
+; so a sixteen-bit register is required to store the length
+
+len:
+ CALL STK_FETCH ; fetch and delete the
+ ; string parameters from the calculator stack.
+ ; register BC now holds the length of string.
+
+ JP STACK_BC ; jump back to save result on the
+ ; calculator stack (with memory check).
+
+
+; THE 'DECREASE THE COUNTER' SUBROUTINE
+
+; (offset $31: 'dec_jr_nz')
+; The calculator has an instruction that decrements a single-byte
+; pseudo-register and makes consequential relative jumps just like
+; the Z80's DJNZ instruction.
+
+dec_jr_nz:
+ EXX ; switch in set that addresses code
+
+ PUSH HL ; save pointer to offset byte
+ LD HL,BERG ; address BERG in system variables
+ DEC (HL) ; decrement it
+ POP HL ; restore pointer
+
+ JR NZ,JUMP_2 ; to JUMP_2 if not zero
+
+ INC HL ; step past the jump length.
+ EXX ; switch in the main set.
+ RET ; return.
+
+; Note. as a general rule the calculator avoids using the IY register
+; otherwise the cumbersome 4 instructions in the middle could be replaced by
+; dec (iy+$xx) - using three instruction bytes instead of six.
+
+
+; THE 'JUMP' SUBROUTINE
+
+; (Offset $2F; 'jump')
+; This enables the calculator to perform relative jumps just like
+; the Z80 chip's JR instruction.
+; This is one of the few routines to be polished for the ZX Spectrum.
+; See, without looking at the ZX Spectrum ROM, if you can get rid of the
+; relative jump.
+
+jump:
+ EXX ;switch in pointer set
+JUMP_2:
+ LD E,(HL) ; the jump byte 0-127 forward, 128-255 back.
+
+ XOR A ; clear accumulator.
+ BIT 7,E ; test if negative jump
+ JR Z,JUMP_3 ; skip, if positive
+ CPL ; else change to $FF.
+
+JUMP_3:
+ LD D,A ; transfer to high byte.
+ ADD HL,DE ; advance calculator pointer forward or back.
+
+ EXX ; switch out pointer set.
+ RET ; return.
+
+; THE 'JUMP ON TRUE' SUBROUTINE
+
+; (Offset $00; 'jump_true')
+; This enables the calculator to perform conditional relative jumps
+; dependent on whether the last test gave a true result
+; On the ZX81, the exponent will be zero for zero or else $81 for one.
+
+jump_true:
+ LD A,(DE) ; collect exponent byte
+
+ AND A ; is result 0 or 1 ?
+ JR NZ,jump ; back to JUMP if true (1).
+
+ EXX ; else switch in the pointer set.
+ INC HL ; step past the jump length.
+ EXX ; switch in the main set.
+ RET ; return.
+
+; THE 'MODULUS' SUBROUTINE
+
+; ( Offset $2E: 'n_mod_m' )
+; ( i1, i2 -- i3, i4 )
+; The subroutine calculate N mod M where M is the positive integer, the
+; 'last value' on the calculator stack and N is the integer beneath.
+; The subroutine returns the integer quotient as the last value and the
+; remainder as the value beneath.
+; e.g. 17 MOD 3 = 5 remainder 2
+; It is invoked during the calculation of a random number and also by
+; the PRINT_FP routine.
+
+n_mod_m:
+ RST _FP_CALC ;; 17, 3.
+ DEFB __st_mem_0 ;; 17, 3.
+ DEFB __delete ;; 17.
+ DEFB __duplicate ;; 17, 17.
+ DEFB __get_mem_0 ;; 17, 17, 3.
+ DEFB __division ;; 17, 17/3.
+ DEFB __int ;; 17, 5.
+ DEFB __get_mem_0 ;; 17, 5, 3.
+ DEFB __exchange ;; 17, 3, 5.
+ DEFB __st_mem_0 ;; 17, 3, 5.
+ DEFB __multiply ;; 17, 15.
+ DEFB __subtract ;; 2.
+ DEFB __get_mem_0 ;; 2, 5.
+ DEFB __end_calc ;; 2, 5.
+
+ RET ; return.
+
+; THE 'INTEGER' FUNCTION
+
+; (offset $24: 'int')
+; This function returns the integer of x, which is just the same as truncate
+; for positive numbers. The truncate literal truncates negative numbers
+; upwards so that -3.4 gives -3 whereas the BASIC INT function has to
+; truncate negative numbers down so that INT -3.4 is 4.
+; It is best to work through using, say, plus or minus 3.4 as examples.
+
+int:
+ RST _FP_CALC ;; x. (= 3.4 or -3.4).
+ DEFB __duplicate ;; x, x.
+ DEFB __less_0 ;; x, (1/0)
+ DEFB __jump_true ;; x, (1/0)
+ DEFB X_NEG - $ ;; X_NEG
+
+ DEFB __truncate ;; trunc 3.4 = 3.
+ DEFB __end_calc ;; 3.
+
+ RET ; return with + int x on stack.
+
+X_NEG:
+ DEFB __duplicate ;; -3.4, -3.4.
+ DEFB __truncate ;; -3.4, -3.
+ DEFB __st_mem_0 ;; -3.4, -3.
+ DEFB __subtract ;; -.4
+ DEFB __get_mem_0 ;; -.4, -3.
+ DEFB __exchange ;; -3, -.4.
+ DEFB __not ;; -3, (0).
+ DEFB __jump_true ;; -3.
+ DEFB EXIT - $ ;; -3.
+
+ DEFB __stk_one ;; -3, 1.
+ DEFB __subtract ;; -4.
+
+EXIT:
+ DEFB __end_calc ;; -4.
+
+ RET ; return.
+
+; Exponential (23)
+
+exp:
+ RST _FP_CALC ;;
+ DEFB __stk_data ;;
+ DEFB $F1 ;;Exponent: $81, Bytes: 4
+ DEFB $38,$AA,$3B,$29 ;;
+ DEFB __multiply ;;
+ DEFB __duplicate ;;
+ DEFB __int ;;
+ DEFB $C3 ;;st_mem_3
+ DEFB __subtract ;;
+ DEFB __duplicate ;;
+ DEFB __addition ;;
+ DEFB __stk_one ;;
+ DEFB __subtract ;;
+ DEFB __series_08 ;;
+ DEFB $13 ;;Exponent: $63, Bytes: 1
+ DEFB $36 ;;(+00,+00,+00)
+ DEFB $58 ;;Exponent: $68, Bytes: 2
+ DEFB $65,$66 ;;(+00,+00)
+ DEFB $9D ;;Exponent: $6D, Bytes: 3
+ DEFB $78,$65,$40 ;;(+00)
+ DEFB $A2 ;;Exponent: $72, Bytes: 3
+ DEFB $60,$32,$C9 ;;(+00)
+ DEFB $E7 ;;Exponent: $77, Bytes: 4
+ DEFB $21,$F7,$AF,$24 ;;
+ DEFB $EB ;;Exponent: $7B, Bytes: 4
+ DEFB $2F,$B0,$B0,$14 ;;
+ DEFB $EE ;;Exponent: $7E, Bytes: 4
+ DEFB $7E,$BB,$94,$58 ;;
+ DEFB $F1 ;;Exponent: $81, Bytes: 4
+ DEFB $3A,$7E,$F8,$CF ;;
+ DEFB $E3 ;;get_mem_3
+ DEFB __end_calc ;;
+
+ CALL FP_TO_A
+ JR NZ,N_NEGTV
+
+ JR C,REPORT_6b
+
+ ADD A,(HL) ;
+ JR NC,RESULT_OK
+
+
+REPORT_6b:
+ RST _ERROR_1
+ DEFB $05 ; Error Report: Number too big
+
+N_NEGTV:
+ JR C,RESULT_ZERO
+
+ SUB (HL) ;
+ JR NC,RESULT_ZERO
+
+ NEG ; Negate
+
+RESULT_OK:
+ LD (HL),A ;
+ RET ; return.
+
+RESULT_ZERO:
+ RST _FP_CALC ;;
+ DEFB __delete ;;
+ DEFB __stk_zero ;;
+ DEFB __end_calc ;;
+
+ RET ; return.
+
+; THE 'NATURAL LOGARITHM' FUNCTION
+
+; (offset $22: 'ln')
+; Like the ZX81 itself, 'natural' logarithms came from Scotland.
+; They were devised in 1614 by well-traveled Scotsman John Napier who noted
+; "Nothing doth more molest and hinder calculators than the multiplications,
+; divisions, square and cubical extractions of great numbers".
+;
+; Napier's logarithms enabled the above operations to be accomplished by
+; simple addition and subtraction simplifying the navigational and
+; astronomical calculations which beset his age.
+; Napier's logarithms were quickly overtaken by logarithms to the base 10
+; devised, in conjunction with Napier, by Henry Briggs a Cambridge-educated
+; professor of Geometry at Oxford University. These simplified the layout
+; of the tables enabling humans to easily scale calculations.
+;
+; It is only recently with the introduction of pocket calculators and
+; computers like the ZX81 that natural logarithms are once more at the fore,
+; although some computers retain logarithms to the base ten.
+; 'Natural' logarithms are powers to the base 'e', which like 'pi' is a
+; naturally occurring number in branches of mathematics.
+; Like 'pi' also, 'e' is an irrational number and starts 2.718281828...
+;
+; The tabular use of logarithms was that to multiply two numbers one looked
+; up their two logarithms in the tables, added them together and then looked
+; for the result in a table of antilogarithms to give the desired product.
+;
+; The EXP function is the BASIC equivalent of a calculator's 'antiln' function
+; and by picking any two numbers, 1.72 and 6.89 say,
+; 10 PRINT EXP ( LN 1.72 + LN 6.89 )
+; will give just the same result as
+; 20 PRINT 1.72 * 6.89.
+; Division is accomplished by subtracting the two logs.
+;
+; Napier also mentioned "square and cubicle extractions".
+; To raise a number to the power 3, find its 'ln', multiply by 3 and find the
+; 'antiln'. e.g. PRINT EXP( LN 4 * 3 ) gives 64.
+; Similarly to find the n'th root divide the logarithm by 'n'.
+; The ZX81 ROM used PRINT EXP ( LN 9 / 2 ) to find the square root of the
+; number 9. The Napieran square root function is just a special case of
+; the 'to_power' function. A cube root or indeed any root/power would be just
+; as simple.
+
+; First test that the argument to LN is a positive, non-zero number.
+
+ln:
+ RST _FP_CALC ;;
+ DEFB __duplicate ;;
+ DEFB __greater_0 ;;
+ DEFB __jump_true ;;
+ DEFB VALID - $ ;;to VALID
+
+ DEFB __end_calc ;;
+
+REPORT_Ab:
+ RST _ERROR_1
+ DEFB $09 ; Error Report: Invalid argument
+
+VALID:
+ DEFB __stk_zero ;; Note. not necessary.
+ DEFB __delete ;;
+ DEFB __end_calc ;;
+ LD A,(HL) ;
+
+ LD (HL),$80 ;
+ CALL STACK_A
+
+ RST _FP_CALC ;;
+ DEFB __stk_data ;;
+ DEFB $38 ;;Exponent: $88, Bytes: 1
+ DEFB $00 ;;(+00,+00,+00)
+ DEFB __subtract ;;
+ DEFB __exchange ;;
+ DEFB __duplicate ;;
+ DEFB __stk_data ;;
+ DEFB $F0 ;;Exponent: $80, Bytes: 4
+ DEFB $4C,$CC,$CC,$CD ;;
+ DEFB __subtract ;;
+ DEFB __greater_0 ;;
+ DEFB __jump_true ;;
+ DEFB GRE_8 - $ ;;
+
+ DEFB __exchange ;;
+ DEFB __stk_one ;;
+ DEFB __subtract ;;
+ DEFB __exchange ;;
+ DEFB __end_calc ;;
+
+ INC (HL) ;
+
+ RST _FP_CALC ;;
+
+GRE_8:
+ DEFB __exchange ;;
+ DEFB __stk_data ;;
+ DEFB $F0 ;;Exponent: $80, Bytes: 4
+ DEFB $31,$72,$17,$F8 ;;
+ DEFB __multiply ;;
+ DEFB __exchange ;;
+ DEFB __stk_half ;;
+ DEFB __subtract ;;
+ DEFB __stk_half ;;
+ DEFB __subtract ;;
+ DEFB __duplicate ;;
+ DEFB __stk_data ;;
+ DEFB $32 ;;Exponent: $82, Bytes: 1
+ DEFB $20 ;;(+00,+00,+00)
+ DEFB __multiply ;;
+ DEFB __stk_half ;;
+ DEFB __subtract ;;
+ DEFB __series_0C ;;
+ DEFB $11 ;;Exponent: $61, Bytes: 1
+ DEFB $AC ;;(+00,+00,+00)
+ DEFB $14 ;;Exponent: $64, Bytes: 1
+ DEFB $09 ;;(+00,+00,+00)
+ DEFB $56 ;;Exponent: $66, Bytes: 2
+ DEFB $DA,$A5 ;;(+00,+00)
+ DEFB $59 ;;Exponent: $69, Bytes: 2
+ DEFB $30,$C5 ;;(+00,+00)
+ DEFB $5C ;;Exponent: $6C, Bytes: 2
+ DEFB $90,$AA ;;(+00,+00)
+ DEFB $9E ;;Exponent: $6E, Bytes: 3
+ DEFB $70,$6F,$61 ;;(+00)
+ DEFB $A1 ;;Exponent: $71, Bytes: 3
+ DEFB $CB,$DA,$96 ;;(+00)
+ DEFB $A4 ;;Exponent: $74, Bytes: 3
+ DEFB $31,$9F,$B4 ;;(+00)
+ DEFB $E7 ;;Exponent: $77, Bytes: 4
+ DEFB $A0,$FE,$5C,$FC ;;
+ DEFB $EA ;;Exponent: $7A, Bytes: 4
+ DEFB $1B,$43,$CA,$36 ;;
+ DEFB $ED ;;Exponent: $7D, Bytes: 4
+ DEFB $A7,$9C,$7E,$5E ;;
+ DEFB $F0 ;;Exponent: $80, Bytes: 4
+ DEFB $6E,$23,$80,$93 ;;
+ DEFB __multiply ;;
+ DEFB __addition ;;
+ DEFB __end_calc ;;
+
+ RET ; return.
+
+; THE 'TRIGONOMETRIC' FUNCTIONS
+
+; Trigonometry is rocket science. It is also used by carpenters and pyramid
+; builders.
+; Some uses can be quite abstract but the principles can be seen in simple
+; right-angled triangles. Triangles have some special properties -
+;
+; 1) The sum of the three angles is always PI radians (180 degrees).
+; Very helpful if you know two angles and wish to find the third.
+; 2) In any right-angled triangle the sum of the squares of the two shorter
+; sides is equal to the square of the longest side opposite the right-angle.
+; Very useful if you know the length of two sides and wish to know the
+; length of the third side.
+; 3) Functions sine, cosine and tangent enable one to calculate the length
+; of an unknown side when the length of one other side and an angle is
+; known.
+; 4) Functions arcsin, arccosine and arctan enable one to calculate an unknown
+; angle when the length of two of the sides is known.
+
+; THE 'REDUCE ARGUMENT' SUBROUTINE
+
+; (offset $35: 'get_argt')
+;
+; This routine performs two functions on the angle, in radians, that forms
+; the argument to the sine and cosine functions.
+; First it ensures that the angle 'wraps round'. That if a ship turns through
+; an angle of, say, 3*PI radians (540 degrees) then the net effect is to turn
+; through an angle of PI radians (180 degrees).
+; Secondly it converts the angle in radians to a fraction of a right angle,
+; depending within which quadrant the angle lies, with the periodicity
+; resembling that of the desired sine value.
+; The result lies in the range -1 to +1.
+;
+; 90 deg.
+;
+; (pi/2)
+; II +1 I
+; |
+; sin+ |\ | /| sin+
+; cos- | \ | / | cos+
+; tan- | \ | / | tan+
+; | \|/) |
+; 180 deg. (pi) 0 |----+----|-- 0 (0) 0 degrees
+; | /|\ |
+; sin- | / | \ | sin-
+; cos- | / | \ | cos+
+; tan+ |/ | \| tan-
+; |
+; III -1 IV
+; (3pi/2)
+;
+; 270 deg.
+
+get_argt:
+ RST _FP_CALC ;; X.
+ DEFB __stk_data ;;
+ DEFB $EE ;;Exponent: $7E,
+ ;;Bytes: 4
+ DEFB $22,$F9,$83,$6E ;; X, 1/(2*PI)
+ DEFB __multiply ;; X/(2*PI) = fraction
+
+ DEFB __duplicate ;;
+ DEFB __stk_half ;;
+ DEFB __addition ;;
+ DEFB __int ;;
+
+ DEFB __subtract ;; now range -.5 to .5
+
+ DEFB __duplicate ;;
+ DEFB __addition ;; now range -1 to 1.
+ DEFB __duplicate ;;
+ DEFB __addition ;; now range -2 to 2.
+
+; quadrant I (0 to +1) and quadrant IV (-1 to 0) are now correct.
+; quadrant II ranges +1 to +2.
+; quadrant III ranges -2 to -1.
+
+ DEFB __duplicate ;; Y, Y.
+ DEFB __abs ;; Y, abs(Y). range 1 to 2
+ DEFB __stk_one ;; Y, abs(Y), 1.
+ DEFB __subtract ;; Y, abs(Y)-1. range 0 to 1
+ DEFB __duplicate ;; Y, Z, Z.
+ DEFB __greater_0 ;; Y, Z, (1/0).
+
+ DEFB __st_mem_0 ;; store as possible sign
+ ;; for cosine function.
+
+ DEFB __jump_true ;;
+ DEFB Z_PLUS - $ ;; with quadrants II and III
+
+; else the angle lies in quadrant I or IV and value Y is already correct.
+
+ DEFB __delete ;; Y delete test value.
+ DEFB __end_calc ;; Y.
+
+ RET ; return. with Q1 and Q4 >>>
+
+; The branch was here with quadrants II (0 to 1) and III (1 to 0).
+; Y will hold -2 to -1 if this is quadrant III.
+
+Z_PLUS:
+ DEFB __stk_one ;; Y, Z, 1
+ DEFB __subtract ;; Y, Z-1. Q3 = 0 to -1
+ DEFB __exchange ;; Z-1, Y.
+ DEFB __less_0 ;; Z-1, (1/0).
+ DEFB __jump_true ;; Z-1.
+ DEFB YNEG - $ ;;
+ ;;if angle in quadrant III
+
+; else angle is within quadrant II (-1 to 0)
+
+ DEFB __negate ; range +1 to 0
+
+YNEG:
+ DEFB __end_calc ;; quadrants II and III correct.
+
+ RET ; return.
+
+; THE 'COSINE' FUNCTION
+
+; (offset $1D: 'cos')
+; Cosines are calculated as the sine of the opposite angle rectifying the
+; sign depending on the quadrant rules.
+;
+;
+; /|
+; h /y|
+; / |o
+; / x |
+; /----|
+; a
+;
+; The cosine of angle x is the adjacent side (a) divided by the hypotenuse 1.
+; However if we examine angle y then a/h is the sine of that angle.
+; Since angle x plus angle y equals a right-angle, we can find angle y by
+; subtracting angle x from pi/2.
+; However it's just as easy to reduce the argument first and subtract the
+; reduced argument from the value 1 (a reduced right-angle).
+; It's even easier to subtract 1 from the angle and rectify the sign.
+; In fact, after reducing the argument, the absolute value of the argument
+; is used and rectified using the test result stored in mem-0 by 'get-argt'
+; for that purpose.
+
+cos:
+ RST _FP_CALC ;; angle in radians.
+ DEFB __get_argt ;; X reduce -1 to +1
+
+ DEFB __abs ;; ABS X 0 to 1
+ DEFB __stk_one ;; ABS X, 1.
+ DEFB __subtract ;; now opposite angle
+ ;; though negative sign.
+ DEFB __get_mem_0 ;; fetch sign indicator.
+ DEFB __jump_true ;;
+ DEFB C_ENT - $ ;;fwd to C_ENT
+ ;;forward to common code if in QII or QIII
+
+
+ DEFB __negate ;; else make positive.
+ DEFB __jump ;;
+ DEFB C_ENT - $ ;;fwd to C_ENT
+ ;;with quadrants QI and QIV
+
+; THE 'SINE' FUNCTION
+
+; (offset $1C: 'sin')
+; This is a fundamental transcendental function from which others such as cos
+; and tan are directly, or indirectly, derived.
+; It uses the series generator to produce Chebyshev polynomials.
+;
+; /|
+; 1 / |
+; / |x
+; /a |
+; /----|
+; y
+;
+; The 'get-argt' function is designed to modify the angle and its sign
+; in line with the desired sine value and afterwards it can launch straight
+; into common code.
+
+sin:
+ RST _FP_CALC ;; angle in radians
+ DEFB __get_argt ;; reduce - sign now correct.
+
+C_ENT:
+ DEFB __duplicate ;;
+ DEFB __duplicate ;;
+ DEFB __multiply ;;
+ DEFB __duplicate ;;
+ DEFB __addition ;;
+ DEFB __stk_one ;;
+ DEFB __subtract ;;
+
+ DEFB __series_06 ;;
+ DEFB $14 ;;Exponent: $64, Bytes: 1
+ DEFB $E6 ;;(+00,+00,+00)
+ DEFB $5C ;;Exponent: $6C, Bytes: 2
+ DEFB $1F,$0B ;;(+00,+00)
+ DEFB $A3 ;;Exponent: $73, Bytes: 3
+ DEFB $8F,$38,$EE ;;(+00)
+ DEFB $E9 ;;Exponent: $79, Bytes: 4
+ DEFB $15,$63,$BB,$23 ;;
+ DEFB $EE ;;Exponent: $7E, Bytes: 4
+ DEFB $92,$0D,$CD,$ED ;;
+ DEFB $F1 ;;Exponent: $81, Bytes: 4
+ DEFB $23,$5D,$1B,$EA ;;
+
+ DEFB __multiply ;;
+ DEFB __end_calc ;;
+
+ RET ; return.
+
+; THE 'TANGENT' FUNCTION
+
+; (offset $1E: 'tan')
+;
+; Evaluates tangent x as sin(x) / cos(x).
+;
+; /|
+; h / |
+; / |o
+; /x |
+; /----|
+; a
+;
+; The tangent of angle x is the ratio of the length of the opposite side
+; divided by the length of the adjacent side. As the opposite length can
+; be calculates using sin(x) and the adjacent length using cos(x) then
+; the tangent can be defined in terms of the previous two functions.
+
+; Error 6 if the argument, in radians, is too close to one like pi/2
+; which has an infinite tangent. e.g. PRINT TAN (PI/2) evaluates as 1/0.
+; Similarly PRINT TAN (3*PI/2), TAN (5*PI/2) etc.
+
+tan:
+ RST _FP_CALC ;; x.
+ DEFB __duplicate ;; x, x.
+ DEFB __sin ;; x, sin x.
+ DEFB __exchange ;; sin x, x.
+ DEFB __cos ;; sin x, cos x.
+ DEFB __division ;; sin x/cos x (= tan x).
+ DEFB __end_calc ;; tan x.
+
+ RET ; return.
+
+; THE 'ARCTAN' FUNCTION
+
+; (Offset $21: 'atn')
+; The inverse tangent function with the result in radians.
+; This is a fundamental transcendental function from which others such as
+; asn and acs are directly, or indirectly, derived.
+; It uses the series generator to produce Chebyshev polynomials.
+
+atn:
+ LD A,(HL) ; fetch exponent
+ CP $81 ; compare to that for 'one'
+ JR C,SMALL ; forward, if less
+
+ RST _FP_CALC ;; X.
+ DEFB __stk_one ;;
+ DEFB __negate ;;
+ DEFB __exchange ;;
+ DEFB __division ;;
+ DEFB __duplicate ;;
+ DEFB __less_0 ;;
+ DEFB __stk_half_pi ;;
+ DEFB __exchange ;;
+ DEFB __jump_true ;;
+ DEFB CASES - $ ;;
+
+ DEFB __negate ;;
+ DEFB __jump ;;
+ DEFB CASES - $ ;;
+
+SMALL:
+ RST _FP_CALC ;;
+ DEFB __stk_zero ;;
+
+CASES:
+ DEFB __exchange ;;
+ DEFB __duplicate ;;
+ DEFB __duplicate ;;
+ DEFB __multiply ;;
+ DEFB __duplicate ;;
+ DEFB __addition ;;
+ DEFB __stk_one ;;
+ DEFB __subtract ;;
+
+ DEFB __series_0C ;;
+ DEFB $10 ;;Exponent: $60, Bytes: 1
+ DEFB $B2 ;;(+00,+00,+00)
+ DEFB $13 ;;Exponent: $63, Bytes: 1
+ DEFB $0E ;;(+00,+00,+00)
+ DEFB $55 ;;Exponent: $65, Bytes: 2
+ DEFB $E4,$8D ;;(+00,+00)
+ DEFB $58 ;;Exponent: $68, Bytes: 2
+ DEFB $39,$BC ;;(+00,+00)
+ DEFB $5B ;;Exponent: $6B, Bytes: 2
+ DEFB $98,$FD ;;(+00,+00)
+ DEFB $9E ;;Exponent: $6E, Bytes: 3
+ DEFB $00,$36,$75 ;;(+00)
+ DEFB $A0 ;;Exponent: $70, Bytes: 3
+ DEFB $DB,$E8,$B4 ;;(+00)
+ DEFB $63 ;;Exponent: $73, Bytes: 2
+ DEFB $42,$C4 ;;(+00,+00)
+ DEFB $E6 ;;Exponent: $76, Bytes: 4
+ DEFB $B5,$09,$36,$BE ;;
+ DEFB $E9 ;;Exponent: $79, Bytes: 4
+ DEFB $36,$73,$1B,$5D ;;
+ DEFB $EC ;;Exponent: $7C, Bytes: 4
+ DEFB $D8,$DE,$63,$BE ;;
+ DEFB $F0 ;;Exponent: $80, Bytes: 4
+ DEFB $61,$A1,$B3,$0C ;;
+
+ DEFB __multiply ;;
+ DEFB __addition ;;
+ DEFB __end_calc ;;
+
+ RET ; return.
+
+
+
+; THE 'ARCSIN' FUNCTION
+
+; (Offset $1F: 'asn')
+; The inverse sine function with result in radians.
+; Derived from arctan function above.
+; Error A unless the argument is between -1 and +1 inclusive.
+; Uses an adaptation of the formula asn(x) = atn(x/sqr(1-x*x))
+;
+; /|
+; / |
+; 1/ |x
+; /a |
+; /----|
+; y
+;
+; e.g. We know the opposite side (x) and hypotenuse (1)
+; and we wish to find angle a in radians.
+; We can derive length y by Pythagoras and then use ATN instead.
+; Since y*y + x*x = 1*1 (Pythagoras Theorem) then
+; y=sqr(1-x*x) - no need to multiply 1 by itself.
+; So, asn(a) = atn(x/y)
+; or more fully,
+; asn(a) = atn(x/sqr(1-x*x))
+
+; Close but no cigar.
+
+; While PRINT ATN (x/SQR (1-x*x)) gives the same results as PRINT ASN x,
+; it leads to division by zero when x is 1 or -1.
+; To overcome this, 1 is added to y giving half the required angle and the
+; result is then doubled.
+; That is, PRINT ATN (x/(SQR (1-x*x) +1)) *2
+;
+; . /|
+; . c/ |
+; . /1 |x
+; . c b /a |
+; ---------/----|
+; 1 y
+;
+; By creating an isosceles triangle with two equal sides of 1, angles c and
+; c are also equal. If b+c+d = 180 degrees and b+a = 180 degrees then c=a/2.
+;
+; A value higher than 1 gives the required error as attempting to find the
+; square root of a negative number generates an error in Sinclair BASIC.
+
+asn:
+ RST _FP_CALC ;; x.
+ DEFB __duplicate ;; x, x.
+ DEFB __duplicate ;; x, x, x.
+ DEFB __multiply ;; x, x*x.
+ DEFB __stk_one ;; x, x*x, 1.
+ DEFB __subtract ;; x, x*x-1.
+ DEFB __negate ;; x, 1-x*x.
+ DEFB __sqr ;; x, sqr(1-x*x) = y.
+ DEFB __stk_one ;; x, y, 1.
+ DEFB __addition ;; x, y+1.
+ DEFB __division ;; x/y+1.
+ DEFB __atn ;; a/2 (half the angle)
+ DEFB __duplicate ;; a/2, a/2.
+ DEFB __addition ;; a.
+ DEFB __end_calc ;; a.
+
+ RET ; return.
+
+; THE 'ARCCOS' FUNCTION
+
+; (Offset $20: 'acs')
+; The inverse cosine function with the result in radians.
+; Error A unless the argument is between -1 and +1.
+; Result in range 0 to pi.
+; Derived from asn above which is in turn derived from the preceding atn. It
+; could have been derived directly from atn using acs(x) = atn(sqr(1-x*x)/x).
+; However, as sine and cosine are horizontal translations of each other,
+; uses acs(x) = pi/2 - asn(x)
+
+; e.g. the arccosine of a known x value will give the required angle b in
+; radians.
+; We know, from above, how to calculate the angle a using asn(x).
+; Since the three angles of any triangle add up to 180 degrees, or pi radians,
+; and the largest angle in this case is a right-angle (pi/2 radians), then
+; we can calculate angle b as pi/2 (both angles) minus asn(x) (angle a).
+;
+; /|
+; 1 /b|
+; / |x
+; /a |
+; /----|
+; y
+
+acs:
+ RST _FP_CALC ;; x.
+ DEFB __asn ;; asn(x).
+ DEFB __stk_half_pi ;; asn(x), pi/2.
+ DEFB __subtract ;; asn(x) - pi/2.
+ DEFB __negate ;; pi/2 - asn(x) = acs(x).
+ DEFB __end_calc ;; acs(x)
+
+ RET ; return.
+
+; THE 'SQUARE ROOT' FUNCTION
+
+; (Offset $25: 'sqr')
+; Error A if argument is negative.
+; This routine is remarkable for its brevity - 7 bytes.
+;
+; The ZX81 code was originally 9K and various techniques had to be
+; used to shoe-horn it into an 8K Rom chip.
+
+; This routine uses Napier's method for calculating square roots which was
+; devised in 1614 and calculates the value as EXP (LN 'x' * 0.5).
+;
+; This is a little on the slow side as it involves two polynomial series.
+; A series of 12 for LN and a series of 8 for EXP.
+; This was of no concern to John Napier since his tables were 'compiled forever'.
+
+sqr:
+ RST _FP_CALC ;; x.
+ DEFB __duplicate ;; x, x.
+ DEFB __not ;; x, 1/0
+ DEFB __jump_true ;; x, (1/0).
+ DEFB LAST - $ ;; exit if argument zero
+ ;; with zero result.
+
+; else continue to calculate as x ** .5
+
+ DEFB __stk_half ;; x, .5.
+ DEFB __end_calc ;; x, .5.
+
+; THE 'EXPONENTIATION' OPERATION
+
+; (Offset $06: 'to_power')
+; This raises the first number X to the power of the second number Y.
+; As with the ZX80,
+; 0 ** 0 = 1
+; 0 ** +n = 0
+; 0 ** -n = arithmetic overflow.
+
+to_power:
+ RST _FP_CALC ;; X,Y.
+ DEFB __exchange ;; Y,X.
+ DEFB __duplicate ;; Y,X,X.
+ DEFB __not ;; Y,X,(1/0).
+ DEFB __jump_true ;;
+ DEFB XISO - $ ;;forward to XISO if X is zero.
+
+; else X is non-zero. function 'ln' will catch a negative value of X.
+
+ DEFB __ln ;; Y, LN X.
+ DEFB __multiply ;; Y * LN X
+ DEFB __end_calc ;;
+
+ JP exp ; jump back to EXP routine. ->
+
+; These routines form the three simple results when the number is zero.
+; begin by deleting the known zero to leave Y the power factor.
+
+XISO:
+ DEFB __delete ;; Y.
+ DEFB __duplicate ;; Y, Y.
+ DEFB __not ;; Y, (1/0).
+ DEFB __jump_true ;;
+ DEFB ONE - $ ;; if Y is zero.
+
+; the power factor is not zero. If negative then an error exists.
+
+ DEFB __stk_zero ;; Y, 0.
+ DEFB __exchange ;; 0, Y.
+ DEFB __greater_0 ;; 0, (1/0).
+ DEFB __jump_true ;; 0
+ DEFB LAST - $ ;; if Y was any positive
+ ;; number.
+
+; else force division by zero thereby raising an Arithmetic overflow error.
+; There are some one and two-byte alternatives but perhaps the most formal
+; might have been to use end_calc; rst 08; defb 05.
+
+ DEFB __stk_one ;; 0, 1.
+ DEFB __exchange ;; 1, 0.
+ DEFB __division ;; 1/0 >> error
+
+ONE:
+ DEFB __delete ;; .
+ DEFB __stk_one ;; 1.
+
+LAST:
+ DEFB __end_calc ;; last value 1 or 0.
+
+ RET ; return.
+
+; THE 'SPARE LOCATIONS'
+
+SPARE:
+ DEFB $FF ; That's all folks.
+
+; THE 'ZX81 CHARACTER SET'
+
+char_set: ; - begins with space character.
+
+; $00 - Character: ' ' CHR$(0)
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+
+; $01 - Character: mosaic CHR$(1)
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+
+; $02 - Character: mosaic CHR$(2)
+ DEFB %00001111
+ DEFB %00001111
+ DEFB %00001111
+ DEFB %00001111
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+
+; $03 - Character: mosaic CHR$(3)
+ DEFB %11111111
+ DEFB %11111111
+ DEFB %11111111
+ DEFB %11111111
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+
+; $04 - Character: mosaic CHR$(4)
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %11110000
+
+; $05 - Character: mosaic CHR$(5)
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %11110000
+
+; $06 - Character: mosaic CHR$(6)
+ DEFB %00001111
+ DEFB %00001111
+ DEFB %00001111
+ DEFB %00001111
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %11110000
+
+; $07 - Character: mosaic CHR$(7)
+ DEFB %11111111
+ DEFB %11111111
+ DEFB %11111111
+ DEFB %11111111
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %11110000
+ DEFB %11110000
+
+; $08 - Character: mosaic CHR$(8)
+ DEFB %10101010
+ DEFB %01010101
+ DEFB %10101010
+ DEFB %01010101
+ DEFB %10101010
+ DEFB %01010101
+ DEFB %10101010
+ DEFB %01010101
+
+; $09 - Character: mosaic CHR$(9)
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %10101010
+ DEFB %01010101
+ DEFB %10101010
+ DEFB %01010101
+
+; $0A - Character: mosaic CHR$(10)
+ DEFB %10101010
+ DEFB %01010101
+ DEFB %10101010
+ DEFB %01010101
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+
+; $0B - Character: '"' CHR$(11)
+ DEFB %00000000
+ DEFB %00100100
+ DEFB %00100100
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+
+; $0C - Character: pound sign CHR$(12)
+ DEFB %00000000
+ DEFB %00011100
+ DEFB %00100010
+ DEFB %01111000
+ DEFB %00100000
+ DEFB %00100000
+ DEFB %01111110
+ DEFB %00000000
+
+; $0D - Character: '$' CHR$(13)
+ DEFB %00000000
+ DEFB %00001000
+ DEFB %00111110
+ DEFB %00101000
+ DEFB %00111110
+ DEFB %00001010
+ DEFB %00111110
+ DEFB %00001000
+
+; $0E - Character: ':' CHR$(14)
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00010000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00010000
+ DEFB %00000000
+
+; $0F - Character: '?' CHR$(15)
+ DEFB %00000000
+ DEFB %00111100
+ DEFB %01000010
+ DEFB %00000100
+ DEFB %00001000
+ DEFB %00000000
+ DEFB %00001000
+ DEFB %00000000
+
+; $10 - Character: '(' CHR$(16)
+ DEFB %00000000
+ DEFB %00000100
+ DEFB %00001000
+ DEFB %00001000
+ DEFB %00001000
+ DEFB %00001000
+ DEFB %00000100
+ DEFB %00000000
+
+; $11 - Character: ')' CHR$(17)
+ DEFB %00000000
+ DEFB %00100000
+ DEFB %00010000
+ DEFB %00010000
+ DEFB %00010000
+ DEFB %00010000
+ DEFB %00100000
+ DEFB %00000000
+
+; $12 - Character: '>' CHR$(18)
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00010000
+ DEFB %00001000
+ DEFB %00000100
+ DEFB %00001000
+ DEFB %00010000
+ DEFB %00000000
+
+; $13 - Character: '<' CHR$(19)
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000100
+ DEFB %00001000
+ DEFB %00010000
+ DEFB %00001000
+ DEFB %00000100
+ DEFB %00000000
+
+; $14 - Character: '=' CHR$(20)
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00111110
+ DEFB %00000000
+ DEFB %00111110
+ DEFB %00000000
+ DEFB %00000000
+
+; $15 - Character: '+' CHR$(21)
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00001000
+ DEFB %00001000
+ DEFB %00111110
+ DEFB %00001000
+ DEFB %00001000
+ DEFB %00000000
+
+; $16 - Character: '-' CHR$(22)
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00111110
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+
+; $17 - Character: '*' CHR$(23)
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00010100
+ DEFB %00001000
+ DEFB %00111110
+ DEFB %00001000
+ DEFB %00010100
+ DEFB %00000000
+
+; $18 - Character: '/' CHR$(24)
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000010
+ DEFB %00000100
+ DEFB %00001000
+ DEFB %00010000
+ DEFB %00100000
+ DEFB %00000000
+
+; $19 - Character: ';' CHR$(25)
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00010000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00010000
+ DEFB %00010000
+ DEFB %00100000
+
+; $1A - Character: ',' CHR$(26)
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00001000
+ DEFB %00001000
+ DEFB %00010000
+
+; $1B - Character: '"' CHR$(27)
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00000000
+ DEFB %00011000
+ DEFB %00011000
+ DEFB %00000000
+
+; $1C - Character: '0' CHR$(28)
+ DEFB %00000000
+ DEFB %00111100
+ DEFB %01000110
+ DEFB %01001010
+ DEFB %01010010
+ DEFB %01100010
+ DEFB %00111100
+ DEFB %00000000
+
+; $1D - Character: '1' CHR$(29)
+ DEFB %00000000
+ DEFB %00011000
+ DEFB %00101000
+ DEFB %00001000
+ DEFB %00001000
+ DEFB %00001000
+ DEFB %00111110
+ DEFB %00000000
+
+; $1E - Character: '2' CHR$(30)
+ DEFB %00000000
+ DEFB %00111100
+ DEFB %01000010
+ DEFB %00000010
+ DEFB %00111100
+ DEFB %01000000
+ DEFB %01111110
+ DEFB %00000000
+
+; $1F - Character: '3' CHR$(31)
+ DEFB %00000000
+ DEFB %00111100
+ DEFB %01000010
+ DEFB %00001100
+ DEFB %00000010
+ DEFB %01000010
+ DEFB %00111100
+ DEFB %00000000
+
+; $20 - Character: '4' CHR$(32)
+ DEFB %00000000
+ DEFB %00001000
+ DEFB %00011000
+ DEFB %00101000
+ DEFB %01001000
+ DEFB %01111110
+ DEFB %00001000
+ DEFB %00000000
+
+; $21 - Character: '5' CHR$(33)
+ DEFB %00000000
+ DEFB %01111110
+ DEFB %01000000
+ DEFB %01111100
+ DEFB %00000010
+ DEFB %01000010
+ DEFB %00111100
+ DEFB %00000000
+
+; $22 - Character: '6' CHR$(34)
+ DEFB %00000000
+ DEFB %00111100
+ DEFB %01000000
+ DEFB %01111100
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %00111100
+ DEFB %00000000
+
+; $23 - Character: '7' CHR$(35)
+ DEFB %00000000
+ DEFB %01111110
+ DEFB %00000010
+ DEFB %00000100
+ DEFB %00001000
+ DEFB %00010000
+ DEFB %00010000
+ DEFB %00000000
+
+; $24 - Character: '8' CHR$(36)
+ DEFB %00000000
+ DEFB %00111100
+ DEFB %01000010
+ DEFB %00111100
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %00111100
+ DEFB %00000000
+
+; $25 - Character: '9' CHR$(37)
+ DEFB %00000000
+ DEFB %00111100
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %00111110
+ DEFB %00000010
+ DEFB %00111100
+ DEFB %00000000
+
+; $26 - Character: 'A' CHR$(38)
+ DEFB %00000000
+ DEFB %00111100
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01111110
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %00000000
+
+; $27 - Character: 'B' CHR$(39)
+ DEFB %00000000
+ DEFB %01111100
+ DEFB %01000010
+ DEFB %01111100
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01111100
+ DEFB %00000000
+
+; $28 - Character: 'C' CHR$(40)
+ DEFB %00000000
+ DEFB %00111100
+ DEFB %01000010
+ DEFB %01000000
+ DEFB %01000000
+ DEFB %01000010
+ DEFB %00111100
+ DEFB %00000000
+
+; $29 - Character: 'D' CHR$(41)
+ DEFB %00000000
+ DEFB %01111000
+ DEFB %01000100
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01000100
+ DEFB %01111000
+ DEFB %00000000
+
+; $2A - Character: 'E' CHR$(42)
+ DEFB %00000000
+ DEFB %01111110
+ DEFB %01000000
+ DEFB %01111100
+ DEFB %01000000
+ DEFB %01000000
+ DEFB %01111110
+ DEFB %00000000
+
+; $2B - Character: 'F' CHR$(43)
+ DEFB %00000000
+ DEFB %01111110
+ DEFB %01000000
+ DEFB %01111100
+ DEFB %01000000
+ DEFB %01000000
+ DEFB %01000000
+ DEFB %00000000
+
+; $2C - Character: 'G' CHR$(44)
+ DEFB %00000000
+ DEFB %00111100
+ DEFB %01000010
+ DEFB %01000000
+ DEFB %01001110
+ DEFB %01000010
+ DEFB %00111100
+ DEFB %00000000
+
+; $2D - Character: 'H' CHR$(45)
+ DEFB %00000000
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01111110
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %00000000
+
+; $2E - Character: 'I' CHR$(46)
+ DEFB %00000000
+ DEFB %00111110
+ DEFB %00001000
+ DEFB %00001000
+ DEFB %00001000
+ DEFB %00001000
+ DEFB %00111110
+ DEFB %00000000
+
+; $2F - Character: 'J' CHR$(47)
+ DEFB %00000000
+ DEFB %00000010
+ DEFB %00000010
+ DEFB %00000010
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %00111100
+ DEFB %00000000
+
+; $30 - Character: 'K' CHR$(48)
+ DEFB %00000000
+ DEFB %01000100
+ DEFB %01001000
+ DEFB %01110000
+ DEFB %01001000
+ DEFB %01000100
+ DEFB %01000010
+ DEFB %00000000
+
+; $31 - Character: 'L' CHR$(49)
+ DEFB %00000000
+ DEFB %01000000
+ DEFB %01000000
+ DEFB %01000000
+ DEFB %01000000
+ DEFB %01000000
+ DEFB %01111110
+ DEFB %00000000
+
+; $32 - Character: 'M' CHR$(50)
+ DEFB %00000000
+ DEFB %01000010
+ DEFB %01100110
+ DEFB %01011010
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %00000000
+
+; $33 - Character: 'N' CHR$(51)
+ DEFB %00000000
+ DEFB %01000010
+ DEFB %01100010
+ DEFB %01010010
+ DEFB %01001010
+ DEFB %01000110
+ DEFB %01000010
+ DEFB %00000000
+
+; $34 - Character: 'O' CHR$(52)
+ DEFB %00000000
+ DEFB %00111100
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %00111100
+ DEFB %00000000
+
+; $35 - Character: 'P' CHR$(53)
+ DEFB %00000000
+ DEFB %01111100
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01111100
+ DEFB %01000000
+ DEFB %01000000
+ DEFB %00000000
+
+; $36 - Character: 'Q' CHR$(54)
+ DEFB %00000000
+ DEFB %00111100
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01010010
+ DEFB %01001010
+ DEFB %00111100
+ DEFB %00000000
+
+; $37 - Character: 'R' CHR$(55)
+ DEFB %00000000
+ DEFB %01111100
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01111100
+ DEFB %01000100
+ DEFB %01000010
+ DEFB %00000000
+
+; $38 - Character: 'S' CHR$(56)
+ DEFB %00000000
+ DEFB %00111100
+ DEFB %01000000
+ DEFB %00111100
+ DEFB %00000010
+ DEFB %01000010
+ DEFB %00111100
+ DEFB %00000000
+
+; $39 - Character: 'T' CHR$(57)
+ DEFB %00000000
+ DEFB %11111110
+ DEFB %00010000
+ DEFB %00010000
+ DEFB %00010000
+ DEFB %00010000
+ DEFB %00010000
+ DEFB %00000000
+
+; $3A - Character: 'U' CHR$(58)
+ DEFB %00000000
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %00111100
+ DEFB %00000000
+
+; $3B - Character: 'V' CHR$(59)
+ DEFB %00000000
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %00100100
+ DEFB %00011000
+ DEFB %00000000
+
+; $3C - Character: 'W' CHR$(60)
+ DEFB %00000000
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01000010
+ DEFB %01011010
+ DEFB %00100100
+ DEFB %00000000
+
+; $3D - Character: 'X' CHR$(61)
+ DEFB %00000000
+ DEFB %01000010
+ DEFB %00100100
+ DEFB %00011000
+ DEFB %00011000
+ DEFB %00100100
+ DEFB %01000010
+ DEFB %00000000
+
+; $3E - Character: 'Y' CHR$(62)
+ DEFB %00000000
+ DEFB %10000010
+ DEFB %01000100
+ DEFB %00101000
+ DEFB %00010000
+ DEFB %00010000
+ DEFB %00010000
+ DEFB %00000000
+
+; $3F - Character: 'Z' CHR$(63)
+ DEFB %00000000
+ DEFB %01111110
+ DEFB %00000100
+ DEFB %00001000
+ DEFB %00010000
+ DEFB %00100000
+ DEFB %01111110
+ DEFB %00000000
+