;************************************************************************ ;** An Assembly File Listing to generate a 16K ROM for the ZX Spectrum ** ;************************************************************************ ; ------------------------- ; Last updated: 13-DEC-2004 ; ------------------------- ; TASM cross-assembler directives. ; ( comment out, perhaps, for other assemblers - see Notes at end.) #define DEFB .BYTE #define DEFW .WORD #define DEFM .TEXT #define ORG .ORG #define EQU .EQU #define equ .EQU ; It is always a good idea to anchor, using ORGs, important sections such as ; the character bitmaps so that they don't move as code is added and removed. ; Generally most approaches try to maintain main entry points as they are ; often used by third-party software. ORG 0000 ;***************************************** ;** Part 1. RESTART ROUTINES AND TABLES ** ;***************************************** ; ----------- ; THE 'START' ; ----------- ; At switch on, the Z80 chip is in Interrupt Mode 0. ; The Spectrum uses Interrupt Mode 1. ; This location can also be 'called' to reset the machine. ; Typically with PRINT USR 0. ;; START L0000: DI ; Disable Interrupts. XOR A ; Signal coming from START. LD DE,$FFFF ; Set pointer to top of possible physical RAM. JP L11CB ; Jump forward to common code at START-NEW. ; ------------------- ; THE 'ERROR' RESTART ; ------------------- ; The error pointer is made to point to the position of the error to enable ; the editor to highlight the error position if it occurred during syntax ; checking. It is used at 37 places in the program. An instruction fetch ; on address $0008 may page in a peripheral ROM such as the Sinclair ; Interface 1 or Disciple Disk Interface. This was not an original design ; concept and not all errors pass through here. ;; ERROR-1 L0008: LD HL,($5C5D) ; Fetch the character address from CH_ADD. LD ($5C5F),HL ; Copy it to the error pointer X_PTR. JR L0053 ; Forward to continue at ERROR-2. ; ----------------------------- ; THE 'PRINT CHARACTER' RESTART ; ----------------------------- ; The A register holds the code of the character that is to be sent to ; the output stream of the current channel. The alternate register set is ; used to output a character in the A register so there is no need to ; preserve any of the current main registers (HL, DE, BC). ; This restart is used 21 times. ;; PRINT-A L0010: JP L15F2 ; Jump forward to continue at PRINT-A-2. ; --- DEFB $FF, $FF, $FF ; Five unused locations. DEFB $FF, $FF ; ; ------------------------------- ; THE 'COLLECT CHARACTER' RESTART ; ------------------------------- ; The contents of the location currently addressed by CH_ADD are fetched. ; A return is made if the value represents a character that has ; relevance to the BASIC parser. Otherwise CH_ADD is incremented and the ; tests repeated. CH_ADD will be addressing somewhere - ; 1) in the BASIC program area during line execution. ; 2) in workspace if evaluating, for example, a string expression. ; 3) in the edit buffer if parsing a direct command or a new BASIC line. ; 4) in workspace if accepting input but not that from INPUT LINE. ;; GET-CHAR L0018: LD HL,($5C5D) ; fetch the address from CH_ADD. LD A,(HL) ; use it to pick up current character. ;; TEST-CHAR L001C: CALL L007D ; routine SKIP-OVER tests if the character is ; relevant. RET NC ; Return if it is significant. ; ------------------------------------ ; THE 'COLLECT NEXT CHARACTER' RESTART ; ------------------------------------ ; As the BASIC commands and expressions are interpreted, this routine is ; called repeatedly to step along the line. It is used 83 times. ;; NEXT-CHAR L0020: CALL L0074 ; routine CH-ADD+1 fetches the next immediate ; character. JR L001C ; jump back to TEST-CHAR until a valid ; character is found. ; --- DEFB $FF, $FF, $FF ; unused ; ----------------------- ; THE 'CALCULATE' RESTART ; ----------------------- ; This restart enters the Spectrum's internal, floating-point, stack-based, ; FORTH-like language. ; It is further used recursively from within the calculator. ; It is used on 77 occasions. ;; FP-CALC L0028: JP L335B ; jump forward to the CALCULATE routine. ; --- DEFB $FF, $FF, $FF ; spare - note that on the ZX81, space being a DEFB $FF, $FF ; little cramped, these same locations were ; used for the five-byte end-calc literal. ; ------------------------------ ; THE 'CREATE BC SPACES' RESTART ; ------------------------------ ; This restart is used on only 12 occasions to create BC spaces ; between workspace and the calculator stack. ;; BC-SPACES L0030: PUSH BC ; Save number of spaces. LD HL,($5C61) ; Fetch WORKSP. PUSH HL ; Save address of workspace. JP L169E ; Jump forward to continuation code RESERVE. ; -------------------------------- ; THE 'MASKABLE INTERRUPT' ROUTINE ; -------------------------------- ; This routine increments the Spectrum's three-byte FRAMES counter fifty ; times a second (sixty times a second in the USA ). ; Both this routine and the called KEYBOARD subroutine use the IY register ; to access system variables and flags so a user-written program must ; disable interrupts to make use of the IY register. ;; MASK-INT L0038: PUSH AF ; Save the registers that will be used but not PUSH HL ; the IY register unfortunately. LD HL,($5C78) ; Fetch the first two bytes at FRAMES1. INC HL ; Increment lowest two bytes of counter. LD ($5C78),HL ; Place back in FRAMES1. LD A,H ; Test if the result was zero. OR L ; JR NZ,L0048 ; Forward, if not, to KEY-INT INC (IY+$40) ; otherwise increment FRAMES3 the third byte. ; Now save the rest of the main registers and read and decode the keyboard. ;; KEY-INT L0048: PUSH BC ; Save the other main registers. PUSH DE ; CALL L02BF ; Routine KEYBOARD executes a stage in the ; process of reading a key-press. POP DE ; POP BC ; Restore registers. POP HL ; POP AF ; EI ; Enable Interrupts. RET ; Return. ; --------------------- ; THE 'ERROR-2' ROUTINE ; --------------------- ; A continuation of the code at 0008. ; The error code is stored and after clearing down stacks, an indirect jump ; is made to MAIN-4, etc. to handle the error. ;; ERROR-2 L0053: POP HL ; drop the return address - the location ; after the RST 08H instruction. LD L,(HL) ; fetch the error code that follows. ; (nice to see this instruction used.) ; Note. this entry point is used when out of memory at REPORT-4. ; The L register has been loaded with the report code but X-PTR is not ; updated. ;; ERROR-3 L0055: LD (IY+$00),L ; Store it in the system variable ERR_NR. LD SP,($5C3D) ; ERR_SP points to an error handler on the ; machine stack. There may be a hierarchy ; of routines. ; To MAIN-4 initially at base. ; or REPORT-G on line entry. ; or ED-ERROR when editing. ; or ED-FULL during ed-enter. ; or IN-VAR-1 during runtime input etc. JP L16C5 ; Jump to SET-STK to clear the calculator stack ; and reset MEM to usual place in the systems ; variables area and then indirectly to MAIN-4, ; etc. ; --- DEFB $FF, $FF, $FF ; Unused locations DEFB $FF, $FF, $FF ; before the fixed-position DEFB $FF ; NMI routine. ; ------------------------------------ ; THE 'NON-MASKABLE INTERRUPT' ROUTINE ; ------------------------------------ ; ; There is no NMI switch on the standard Spectrum or its peripherals. ; When the NMI line is held low, then no matter what the Z80 was doing at ; the time, it will now execute the code at 66 Hex. ; This Interrupt Service Routine will jump to location zero if the contents ; of the system variable NMIADD are zero or return if the location holds a ; non-zero address. So attaching a simple switch to the NMI as in the book ; "Spectrum Hardware Manual" causes a reset. The logic was obviously ; intended to work the other way. Sinclair Research said that, since they ; had never advertised the NMI, they had no plans to fix the error "until ; the opportunity arose". ; ; Note. The location NMIADD was, in fact, later used by Sinclair Research ; to enhance the text channel on the ZX Interface 1. ; On later Amstrad-made Spectrums, and the Brazilian Spectrum, the logic of ; this routine was indeed reversed but not as at first intended. ; ; It can be deduced by looking elsewhere in this ROM that the NMIADD system ; variable pointed to L121C and that this enabled a Warm Restart to be ; performed at any time, even while playing machine code games, or while ; another Spectrum has been allowed to gain control of this one. ; ; Software houses would have been able to protect their games from attack by ; placing two zeros in the NMIADD system variable. ;; RESET L0066: PUSH AF ; save the PUSH HL ; registers. LD HL,($5CB0) ; fetch the system variable NMIADD. LD A,H ; test address OR L ; for zero. JR NZ,L0070 ; skip to NO-RESET if NOT ZERO JP (HL) ; jump to routine ( i.e. L0000 ) ;; NO-RESET L0070: POP HL ; restore the POP AF ; registers. RETN ; return to previous interrupt state. ; --------------------------- ; THE 'CH ADD + 1' SUBROUTINE ; --------------------------- ; This subroutine is called from RST 20, and three times from elsewhere ; to fetch the next immediate character following the current valid character ; address and update the associated system variable. ; The entry point TEMP-PTR1 is used from the SCANNING routine. ; Both TEMP-PTR1 and TEMP-PTR2 are used by the READ command routine. ;; CH-ADD+1 L0074: LD HL,($5C5D) ; fetch address from CH_ADD. ;; TEMP-PTR1 L0077: INC HL ; increase the character address by one. ;; TEMP-PTR2 L0078: LD ($5C5D),HL ; update CH_ADD with character address. X007B: LD A,(HL) ; load character to A from HL. RET ; and return. ; -------------------------- ; THE 'SKIP OVER' SUBROUTINE ; -------------------------- ; This subroutine is called once from RST 18 to skip over white-space and ; other characters irrelevant to the parsing of a BASIC line etc. . ; Initially the A register holds the character to be considered ; and HL holds its address which will not be within quoted text ; when a BASIC line is parsed. ; Although the 'tab' and 'at' characters will not appear in a BASIC line, ; they could be present in a string expression, and in other situations. ; Note. although white-space is usually placed in a program to indent loops ; and make it more readable, it can also be used for the opposite effect and ; spaces may appear in variable names although the parser never sees them. ; It is this routine that helps make the variables 'Anum bEr5 3BUS' and ; 'a number 53 bus' appear the same to the parser. ;; SKIP-OVER L007D: CP $21 ; test if higher than space. RET NC ; return with carry clear if so. CP $0D ; carriage return ? RET Z ; return also with carry clear if so. ; all other characters have no relevance ; to the parser and must be returned with ; carry set. CP $10 ; test if 0-15d RET C ; return, if so, with carry set. CP $18 ; test if 24-32d CCF ; complement carry flag. RET C ; return with carry set if so. ; now leaves 16d-23d INC HL ; all above have at least one extra character ; to be stepped over. CP $16 ; controls 22d ('at') and 23d ('tab') have two. JR C,L0090 ; forward to SKIPS with ink, paper, flash, ; bright, inverse or over controls. ; Note. the high byte of tab is for RS232 only. ; it has no relevance on this machine. INC HL ; step over the second character of 'at'/'tab'. ;; SKIPS L0090: SCF ; set the carry flag LD ($5C5D),HL ; update the CH_ADD system variable. RET ; return with carry set. ; ------------------ ; THE 'TOKEN' TABLES ; ------------------ ; The tokenized characters 134d (RND) to 255d (COPY) are expanded using ; this table. The last byte of a token is inverted to denote the end of ; the word. The first is an inverted step-over byte. ;; TKN-TABLE L0095: DEFB '?'+$80 DEFM "RN" DEFB 'D'+$80 DEFM "INKEY" DEFB '$'+$80 DEFB 'P','I'+$80 DEFB 'F','N'+$80 DEFM "POIN" DEFB 'T'+$80 DEFM "SCREEN" DEFB '$'+$80 DEFM "ATT" DEFB 'R'+$80 DEFB 'A','T'+$80 DEFM "TA" DEFB 'B'+$80 DEFM "VAL" DEFB '$'+$80 DEFM "COD" DEFB 'E'+$80 DEFM "VA" DEFB 'L'+$80 DEFM "LE" DEFB 'N'+$80 DEFM "SI" DEFB 'N'+$80 DEFM "CO" DEFB 'S'+$80 DEFM "TA" DEFB 'N'+$80 DEFM "AS" DEFB 'N'+$80 DEFM "AC" DEFB 'S'+$80 DEFM "AT" DEFB 'N'+$80 DEFB 'L','N'+$80 DEFM "EX" DEFB 'P'+$80 DEFM "IN" DEFB 'T'+$80 DEFM "SQ" DEFB 'R'+$80 DEFM "SG" DEFB 'N'+$80 DEFM "AB" DEFB 'S'+$80 DEFM "PEE" DEFB 'K'+$80 DEFB 'I','N'+$80 DEFM "US" DEFB 'R'+$80 DEFM "STR" DEFB '$'+$80 DEFM "CHR" DEFB '$'+$80 DEFM "NO" DEFB 'T'+$80 DEFM "BI" DEFB 'N'+$80 ; The previous 32 function-type words are printed without a leading space ; The following have a leading space if they begin with a letter DEFB 'O','R'+$80 DEFM "AN" DEFB 'D'+$80 DEFB $3C,'='+$80 ; <= DEFB $3E,'='+$80 ; >= DEFB $3C,$3E+$80 ; <> DEFM "LIN" DEFB 'E'+$80 DEFM "THE" DEFB 'N'+$80 DEFB 'T','O'+$80 DEFM "STE" DEFB 'P'+$80 DEFM "DEF F" DEFB 'N'+$80 DEFM "CA" DEFB 'T'+$80 DEFM "FORMA" DEFB 'T'+$80 DEFM "MOV" DEFB 'E'+$80 DEFM "ERAS" DEFB 'E'+$80 DEFM "OPEN " DEFB '#'+$80 DEFM "CLOSE " DEFB '#'+$80 DEFM "MERG" DEFB 'E'+$80 DEFM "VERIF" DEFB 'Y'+$80 DEFM "BEE" DEFB 'P'+$80 DEFM "CIRCL" DEFB 'E'+$80 DEFM "IN" DEFB 'K'+$80 DEFM "PAPE" DEFB 'R'+$80 DEFM "FLAS" DEFB 'H'+$80 DEFM "BRIGH" DEFB 'T'+$80 DEFM "INVERS" DEFB 'E'+$80 DEFM "OVE" DEFB 'R'+$80 DEFM "OU" DEFB 'T'+$80 DEFM "LPRIN" DEFB 'T'+$80 DEFM "LLIS" DEFB 'T'+$80 DEFM "STO" DEFB 'P'+$80 DEFM "REA" DEFB 'D'+$80 DEFM "DAT" DEFB 'A'+$80 DEFM "RESTOR" DEFB 'E'+$80 DEFM "NE" DEFB 'W'+$80 DEFM "BORDE" DEFB 'R'+$80 DEFM "CONTINU" DEFB 'E'+$80 DEFM "DI" DEFB 'M'+$80 DEFM "RE" DEFB 'M'+$80 DEFM "FO" DEFB 'R'+$80 DEFM "GO T" DEFB 'O'+$80 DEFM "GO SU" DEFB 'B'+$80 DEFM "INPU" DEFB 'T'+$80 DEFM "LOA" DEFB 'D'+$80 DEFM "LIS" DEFB 'T'+$80 DEFM "LE" DEFB 'T'+$80 DEFM "PAUS" DEFB 'E'+$80 DEFM "NEX" DEFB 'T'+$80 DEFM "POK" DEFB 'E'+$80 DEFM "PRIN" DEFB 'T'+$80 DEFM "PLO" DEFB 'T'+$80 DEFM "RU" DEFB 'N'+$80 DEFM "SAV" DEFB 'E'+$80 DEFM "RANDOMIZ" DEFB 'E'+$80 DEFB 'I','F'+$80 DEFM "CL" DEFB 'S'+$80 DEFM "DRA" DEFB 'W'+$80 DEFM "CLEA" DEFB 'R'+$80 DEFM "RETUR" DEFB 'N'+$80 DEFM "COP" DEFB 'Y'+$80 ; ---------------- ; THE 'KEY' TABLES ; ---------------- ; These six look-up tables are used by the keyboard reading routine ; to decode the key values. ; ; The first table contains the maps for the 39 keys of the standard ; 40-key Spectrum keyboard. The remaining key [SHIFT $27] is read directly. ; The keys consist of the 26 upper-case alphabetic characters, the 10 digit ; keys and the space, ENTER and symbol shift key. ; Unshifted alphabetic keys have $20 added to the value. ; The keywords for the main alphabetic keys are obtained by adding $A5 to ; the values obtained from this table. ;; MAIN-KEYS L0205: DEFB $42 ; B DEFB $48 ; H DEFB $59 ; Y DEFB $36 ; 6 DEFB $35 ; 5 DEFB $54 ; T DEFB $47 ; G DEFB $56 ; V DEFB $4E ; N DEFB $4A ; J DEFB $55 ; U DEFB $37 ; 7 DEFB $34 ; 4 DEFB $52 ; R DEFB $46 ; F DEFB $43 ; C DEFB $4D ; M DEFB $4B ; K DEFB $49 ; I DEFB $38 ; 8 DEFB $33 ; 3 DEFB $45 ; E DEFB $44 ; D DEFB $58 ; X DEFB $0E ; SYMBOL SHIFT DEFB $4C ; L DEFB $4F ; O DEFB $39 ; 9 DEFB $32 ; 2 DEFB $57 ; W DEFB $53 ; S DEFB $5A ; Z DEFB $20 ; SPACE DEFB $0D ; ENTER DEFB $50 ; P DEFB $30 ; 0 DEFB $31 ; 1 DEFB $51 ; Q DEFB $41 ; A ;; E-UNSHIFT ; The 26 unshifted extended mode keys for the alphabetic characters. ; The green keywords on the original keyboard. L022C: DEFB $E3 ; READ DEFB $C4 ; BIN DEFB $E0 ; LPRINT DEFB $E4 ; DATA DEFB $B4 ; TAN DEFB $BC ; SGN DEFB $BD ; ABS DEFB $BB ; SQR DEFB $AF ; CODE DEFB $B0 ; VAL DEFB $B1 ; LEN DEFB $C0 ; USR DEFB $A7 ; PI DEFB $A6 ; INKEY$ DEFB $BE ; PEEK DEFB $AD ; TAB DEFB $B2 ; SIN DEFB $BA ; INT DEFB $E5 ; RESTORE DEFB $A5 ; RND DEFB $C2 ; CHR$ DEFB $E1 ; LLIST DEFB $B3 ; COS DEFB $B9 ; EXP DEFB $C1 ; STR$ DEFB $B8 ; LN ;; EXT-SHIFT ; The 26 shifted extended mode keys for the alphabetic characters. ; The red keywords below keys on the original keyboard. L0246: DEFB $7E ; ~ DEFB $DC ; BRIGHT DEFB $DA ; PAPER DEFB $5C ; \ DEFB $B7 ; ATN DEFB $7B ; { DEFB $7D ; } DEFB $D8 ; CIRCLE DEFB $BF ; IN DEFB $AE ; VAL$ DEFB $AA ; SCREEN$ DEFB $AB ; ATTR DEFB $DD ; INVERSE DEFB $DE ; OVER DEFB $DF ; OUT DEFB $7F ; (Copyright character) DEFB $B5 ; ASN DEFB $D6 ; VERIFY DEFB $7C ; | DEFB $D5 ; MERGE DEFB $5D ; ] DEFB $DB ; FLASH DEFB $B6 ; ACS DEFB $D9 ; INK DEFB $5B ; [ DEFB $D7 ; BEEP ;; CTL-CODES ; The ten control codes assigned to the top line of digits when the shift ; key is pressed. L0260: DEFB $0C ; DELETE DEFB $07 ; EDIT DEFB $06 ; CAPS LOCK DEFB $04 ; TRUE VIDEO DEFB $05 ; INVERSE VIDEO DEFB $08 ; CURSOR LEFT DEFB $0A ; CURSOR DOWN DEFB $0B ; CURSOR UP DEFB $09 ; CURSOR RIGHT DEFB $0F ; GRAPHICS ;; SYM-CODES ; The 26 red symbols assigned to the alphabetic characters of the keyboard. ; The ten single-character digit symbols are converted without the aid of ; a table using subtraction and minor manipulation. L026A: DEFB $E2 ; STOP DEFB $2A ; * DEFB $3F ; ? DEFB $CD ; STEP DEFB $C8 ; >= DEFB $CC ; TO DEFB $CB ; THEN DEFB $5E ; ^ DEFB $AC ; AT DEFB $2D ; - DEFB $2B ; + DEFB $3D ; = DEFB $2E ; . DEFB $2C ; , DEFB $3B ; ; DEFB $22 ; " DEFB $C7 ; <= DEFB $3C ; < DEFB $C3 ; NOT DEFB $3E ; > DEFB $C5 ; OR DEFB $2F ; / DEFB $C9 ; <> DEFB $60 ; pound DEFB $C6 ; AND DEFB $3A ; : ;; E-DIGITS ; The ten keywords assigned to the digits in extended mode. ; The remaining red keywords below the keys. L0284: DEFB $D0 ; FORMAT DEFB $CE ; DEF FN DEFB $A8 ; FN DEFB $CA ; LINE DEFB $D3 ; OPEN # DEFB $D4 ; CLOSE # DEFB $D1 ; MOVE DEFB $D2 ; ERASE DEFB $A9 ; POINT DEFB $CF ; CAT ;******************************* ;** Part 2. KEYBOARD ROUTINES ** ;******************************* ; Using shift keys and a combination of modes the Spectrum 40-key keyboard ; can be mapped to 256 input characters ; --------------------------------------------------------------------------- ; ; 0 1 2 3 4 -Bits- 4 3 2 1 0 ; PORT PORT ; ; F7FE [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] | [ 6 ] [ 7 ] [ 8 ] [ 9 ] [ 0 ] EFFE ; ^ | v ; FBFE [ Q ] [ W ] [ E ] [ R ] [ T ] | [ Y ] [ U ] [ I ] [ O ] [ P ] DFFE ; ^ | v ; FDFE [ A ] [ S ] [ D ] [ F ] [ G ] | [ H ] [ J ] [ K ] [ L ] [ ENT ] BFFE ; ^ | v ; FEFE [SHI] [ Z ] [ X ] [ C ] [ V ] | [ B ] [ N ] [ M ] [sym] [ SPC ] 7FFE ; ^ $27 $18 v ; Start End ; 00100111 00011000 ; ; --------------------------------------------------------------------------- ; The above map may help in reading. ; The neat arrangement of ports means that the B register need only be ; rotated left to work up the left hand side and then down the right ; hand side of the keyboard. When the reset bit drops into the carry ; then all 8 half-rows have been read. Shift is the first key to be ; read. The lower six bits of the shifts are unambiguous. ; ------------------------------- ; THE 'KEYBOARD SCANNING' ROUTINE ; ------------------------------- ; From keyboard and s-inkey$ ; Returns 1 or 2 keys in DE, most significant shift first if any ; key values 0-39 else 255 ;; KEY-SCAN L028E: LD L,$2F ; initial key value ; valid values are obtained by subtracting ; eight five times. LD DE,$FFFF ; a buffer to receive 2 keys. LD BC,$FEFE ; the commencing port address ; B holds 11111110 initially and is also ; used to count the 8 half-rows ;; KEY-LINE L0296: IN A,(C) ; read the port to A - bits will be reset ; if a key is pressed else set. CPL ; complement - pressed key-bits are now set AND $1F ; apply 00011111 mask to pick up the ; relevant set bits. JR Z,L02AB ; forward to KEY-DONE if zero and therefore ; no keys pressed in row at all. LD H,A ; transfer row bits to H LD A,L ; load the initial key value to A ;; KEY-3KEYS L029F: INC D ; now test the key buffer RET NZ ; if we have collected 2 keys already ; then too many so quit. ;; KEY-BITS L02A1: SUB $08 ; subtract 8 from the key value ; cycling through key values (top = $27) ; e.g. 2F> 27>1F>17>0F>07 ; 2E> 26>1E>16>0E>06 SRL H ; shift key bits right into carry. JR NC,L02A1 ; back to KEY-BITS if not pressed ; but if pressed we have a value (0-39d) LD D,E ; transfer a possible previous key to D LD E,A ; transfer the new key to E JR NZ,L029F ; back to KEY-3KEYS if there were more ; set bits - H was not yet zero. ;; KEY-DONE L02AB: DEC L ; cycles 2F>2E>2D>2C>2B>2A>29>28 for ; each half-row. RLC B ; form next port address e.g. FEFE > FDFE JR C,L0296 ; back to KEY-LINE if still more rows to do. LD A,D ; now test if D is still FF ? INC A ; if it is zero we have at most 1 key ; range now $01-$28 (1-40d) RET Z ; return if one key or no key. CP $28 ; is it capsshift (was $27) ? RET Z ; return if so. CP $19 ; is it symbol shift (was $18) ? RET Z ; return also LD A,E ; now test E LD E,D ; but first switch LD D,A ; the two keys. CP $18 ; is it symbol shift ? RET ; return (with zero set if it was). ; but with symbol shift now in D ; ---------------------- ; THE 'KEYBOARD' ROUTINE ; ---------------------- ; Called from the interrupt 50 times a second. ; ;; KEYBOARD L02BF: CALL L028E ; routine KEY-SCAN RET NZ ; return if invalid combinations ; then decrease the counters within the two key-state maps ; as this could cause one to become free. ; if the keyboard has not been pressed during the last five interrupts ; then both sets will be free. LD HL,$5C00 ; point to KSTATE-0 ;; K-ST-LOOP L02C6: BIT 7,(HL) ; is it free ? (i.e. $FF) JR NZ,L02D1 ; forward to K-CH-SET if so INC HL ; address the 5-counter DEC (HL) ; decrease the counter DEC HL ; step back JR NZ,L02D1 ; forward to K-CH-SET if not at end of count LD (HL),$FF ; else mark this particular map free. ;; K-CH-SET L02D1: LD A,L ; make a copy of the low address byte. LD HL,$5C04 ; point to KSTATE-4 ; (ld l,$04 would do) CP L ; have both sets been considered ? JR NZ,L02C6 ; back to K-ST-LOOP to consider this 2nd set ; now the raw key (0-38d) is converted to a main key (uppercase). CALL L031E ; routine K-TEST to get main key in A RET NC ; return if just a single shift LD HL,$5C00 ; point to KSTATE-0 CP (HL) ; does the main key code match ? JR Z,L0310 ; forward to K-REPEAT if so ; if not consider the second key map. EX DE,HL ; save kstate-0 in de LD HL,$5C04 ; point to KSTATE-4 CP (HL) ; does the main key code match ? JR Z,L0310 ; forward to K-REPEAT if so ; having excluded a repeating key we can now consider a new key. ; the second set is always examined before the first. BIT 7,(HL) ; is the key map free ? JR NZ,L02F1 ; forward to K-NEW if so. EX DE,HL ; bring back KSTATE-0 BIT 7,(HL) ; is it free ? RET Z ; return if not. ; as we have a key but nowhere to put it yet. ; continue or jump to here if one of the buffers was free. ;; K-NEW L02F1: LD E,A ; store key in E LD (HL),A ; place in free location INC HL ; advance to the interrupt counter LD (HL),$05 ; and initialize counter to 5 INC HL ; advance to the delay LD A,($5C09) ; pick up the system variable REPDEL LD (HL),A ; and insert that for first repeat delay. INC HL ; advance to last location of state map. LD C,(IY+$07) ; pick up MODE (3 bytes) LD D,(IY+$01) ; pick up FLAGS (3 bytes) PUSH HL ; save state map location ; Note. could now have used, to avoid IY, ; ld l,$41; ld c,(hl); ld l,$3B; ld d,(hl). ; six and two threes of course. CALL L0333 ; routine K-DECODE POP HL ; restore map pointer LD (HL),A ; put the decoded key in last location of map. ;; K-END L0308: LD ($5C08),A ; update LASTK system variable. SET 5,(IY+$01) ; update FLAGS - signal a new key. RET ; return to interrupt routine. ; ----------------------- ; THE 'REPEAT KEY' BRANCH ; ----------------------- ; A possible repeat has been identified. HL addresses the raw key. ; The last location of the key map holds the decoded key from the first ; context. This could be a keyword and, with the exception of NOT a repeat ; is syntactically incorrect and not really desirable. ;; K-REPEAT L0310: INC HL ; increment the map pointer to second location. LD (HL),$05 ; maintain interrupt counter at 5. INC HL ; now point to third location. DEC (HL) ; decrease the REPDEL value which is used to ; time the delay of a repeat key. RET NZ ; return if not yet zero. LD A,($5C0A) ; fetch the system variable value REPPER. LD (HL),A ; for subsequent repeats REPPER will be used. INC HL ; advance ; LD A,(HL) ; pick up the key decoded possibly in another ; context. ; Note. should compare with $A5 (RND) and make ; a simple return if this is a keyword. ; e.g. cp $a5; ret nc; (3 extra bytes) JR L0308 ; back to K-END ; ---------------------- ; THE 'KEY-TEST' ROUTINE ; ---------------------- ; also called from s-inkey$ ; begin by testing for a shift with no other. ;; K-TEST L031E: LD B,D ; load most significant key to B ; will be $FF if not shift. LD D,$00 ; and reset D to index into main table LD A,E ; load least significant key from E CP $27 ; is it higher than 39d i.e. FF RET NC ; return with just a shift (in B now) CP $18 ; is it symbol shift ? JR NZ,L032C ; forward to K-MAIN if not ; but we could have just symbol shift and no other BIT 7,B ; is other key $FF (ie not shift) RET NZ ; return with solitary symbol shift ;; K-MAIN L032C: LD HL,L0205 ; address: MAIN-KEYS ADD HL,DE ; add offset 0-38 LD A,(HL) ; pick up main key value SCF ; set carry flag RET ; return (B has other key still) ; ---------------------------------- ; THE 'KEYBOARD DECODING' SUBROUTINE ; ---------------------------------- ; also called from s-inkey$ ;; K-DECODE L0333: LD A,E ; pick up the stored main key CP $3A ; an arbitrary point between digits and letters JR C,L0367 ; forward to K-DIGIT with digits, space, enter. DEC C ; decrease MODE ( 0='KLC', 1='E', 2='G') JP M,L034F ; to K-KLC-LET if was zero JR Z,L0341 ; to K-E-LET if was 1 for extended letters. ; proceed with graphic codes. ; Note. should selectively drop return address if code > 'U' ($55). ; i.e. abort the KEYBOARD call. ; e.g. cp 'V'; jr c,addit; pop af ;pop af ;;addit etc. (6 extra bytes). ; (s-inkey$ never gets into graphics mode.) ;; addit ADD A,$4F ; add offset to augment 'A' to graphics A say. RET ; return. ; Note. ( but [GRAPH] V gives RND, etc ). ; --- ; the jump was to here with extended mode with uppercase A-Z. ;; K-E-LET L0341: LD HL,L022C-$41 ; base address of E-UNSHIFT L022c. ; ( $01EB in standard ROM ). INC B ; test B is it empty i.e. not a shift. JR Z,L034A ; forward to K-LOOK-UP if neither shift. LD HL,L0246-$41 ; Address: $0205 L0246-$41 EXT-SHIFT base ;; K-LOOK-UP L034A: LD D,$00 ; prepare to index. ADD HL,DE ; add the main key value. LD A,(HL) ; pick up other mode value. RET ; return. ; --- ; the jump was here with mode = 0 ;; K-KLC-LET L034F: LD HL,L026A-$41 ; prepare base of sym-codes BIT 0,B ; shift=$27 sym-shift=$18 JR Z,L034A ; back to K-LOOK-UP with symbol-shift BIT 3,D ; test FLAGS is it 'K' mode (from OUT-CURS) JR Z,L0364 ; skip to K-TOKENS if so BIT 3,(IY+$30) ; test FLAGS2 - consider CAPS LOCK ? RET NZ ; return if so with main code. INC B ; is shift being pressed ? ; result zero if not RET NZ ; return if shift pressed. ADD A,$20 ; else convert the code to lower case. RET ; return. ; --- ; the jump was here for tokens ;; K-TOKENS L0364: ADD A,$A5 ; add offset to main code so that 'A' ; becomes 'NEW' etc. RET ; return. ; --- ; the jump was here with digits, space, enter and symbol shift (< $xx) ;; K-DIGIT L0367: CP $30 ; is it '0' or higher ? RET C ; return with space, enter and symbol-shift DEC C ; test MODE (was 0='KLC', 1='E', 2='G') JP M,L039D ; jump to K-KLC-DGT if was 0. JR NZ,L0389 ; forward to K-GRA-DGT if mode was 2. ; continue with extended digits 0-9. LD HL,L0284-$30 ; $0254 - base of E-DIGITS BIT 5,B ; test - shift=$27 sym-shift=$18 JR Z,L034A ; to K-LOOK-UP if sym-shift CP $38 ; is character '8' ? JR NC,L0382 ; to K-8-&-9 if greater than '7' SUB $20 ; reduce to ink range $10-$17 INC B ; shift ? RET Z ; return if not. ADD A,$08 ; add 8 to give paper range $18 - $1F RET ; return ; --- ; 89 ;; K-8-&-9 L0382: SUB $36 ; reduce to 02 and 03 bright codes INC B ; test if shift pressed. RET Z ; return if not. ADD A,$FE ; subtract 2 setting carry RET ; to give 0 and 1 flash codes. ; --- ; graphics mode with digits ;; K-GRA-DGT L0389: LD HL,L0260-$30 ; $0230 base address of CTL-CODES CP $39 ; is key '9' ? JR Z,L034A ; back to K-LOOK-UP - changed to $0F, GRAPHICS. CP $30 ; is key '0' ? JR Z,L034A ; back to K-LOOK-UP - changed to $0C, delete. ; for keys '0' - '7' we assign a mosaic character depending on shift. AND $07 ; convert character to number. 0 - 7. ADD A,$80 ; add offset - they start at $80 INC B ; destructively test for shift RET Z ; and return if not pressed. XOR $0F ; toggle bits becomes range $88-$8F RET ; return. ; --- ; now digits in 'KLC' mode ;; K-KLC-DGT L039D: INC B ; return with digit codes if neither RET Z ; shift key pressed. BIT 5,B ; test for caps shift. LD HL,L0260-$30 ; prepare base of table CTL-CODES. JR NZ,L034A ; back to K-LOOK-UP if shift pressed. ; must have been symbol shift SUB $10 ; for ASCII most will now be correct ; on a standard typewriter. CP $22 ; but '@' is not - see below. JR Z,L03B2 ; forward to K-@-CHAR if so CP $20 ; '_' is the other one that fails RET NZ ; return if not. LD A,$5F ; substitute ASCII '_' RET ; return. ; --- ;; K-@-CHAR L03B2: LD A,$40 ; substitute ASCII '@' RET ; return. ; ------------------------------------------------------------------------ ; The Spectrum Input character keys. One or two are abbreviated. ; From $00 Flash 0 to $FF COPY. The routine above has decoded all these. ; | 00 Fl0| 01 Fl1| 02 Br0| 03 Br1| 04 In0| 05 In1| 06 CAP| 07 EDT| ; | 08 LFT| 09 RIG| 0A DWN| 0B UP | 0C DEL| 0D ENT| 0E SYM| 0F GRA| ; | 10 Ik0| 11 Ik1| 12 Ik2| 13 Ik3| 14 Ik4| 15 Ik5| 16 Ik6| 17 Ik7| ; | 18 Pa0| 19 Pa1| 1A Pa2| 1B Pa3| 1C Pa4| 1D Pa5| 1E Pa6| 1F Pa7| ; | 20 SP | 21 ! | 22 " | 23 # | 24 $ | 25 % | 26 & | 27 ' | ; | 28 ( | 29 ) | 2A * | 2B + | 2C , | 2D - | 2E . | 2F / | ; | 30 0 | 31 1 | 32 2 | 33 3 | 34 4 | 35 5 | 36 6 | 37 7 | ; | 38 8 | 39 9 | 3A : | 3B ; | 3C < | 3D = | 3E > | 3F ? | ; | 40 @ | 41 A | 42 B | 43 C | 44 D | 45 E | 46 F | 47 G | ; | 48 H | 49 I | 4A J | 4B K | 4C L | 4D M | 4E N | 4F O | ; | 50 P | 51 Q | 52 R | 53 S | 54 T | 55 U | 56 V | 57 W | ; | 58 X | 59 Y | 5A Z | 5B [ | 5C \ | 5D ] | 5E ^ | 5F _ | ; | 60 £ | 61 a | 62 b | 63 c | 64 d | 65 e | 66 f | 67 g | ; | 68 h | 69 i | 6A j | 6B k | 6C l | 6D m | 6E n | 6F o | ; | 70 p | 71 q | 72 r | 73 s | 74 t | 75 u | 76 v | 77 w | ; | 78 x | 79 y | 7A z | 7B { | 7C | | 7D } | 7E ~ | 7F © | ; | 80 128| 81 129| 82 130| 83 131| 84 132| 85 133| 86 134| 87 135| ; | 88 136| 89 137| 8A 138| 8B 139| 8C 140| 8D 141| 8E 142| 8F 143| ; | 90 [A]| 91 [B]| 92 [C]| 93 [D]| 94 [E]| 95 [F]| 96 [G]| 97 [H]| ; | 98 [I]| 99 [J]| 9A [K]| 9B [L]| 9C [M]| 9D [N]| 9E [O]| 9F [P]| ; | A0 [Q]| A1 [R]| A2 [S]| A3 [T]| A4 [U]| A5 RND| A6 IK$| A7 PI | ; | A8 FN | A9 PNT| AA SC$| AB ATT| AC AT | AD TAB| AE VL$| AF COD| ; | B0 VAL| B1 LEN| B2 SIN| B3 COS| B4 TAN| B5 ASN| B6 ACS| B7 ATN| ; | B8 LN | B9 EXP| BA INT| BB SQR| BC SGN| BD ABS| BE PEK| BF IN | ; | C0 USR| C1 ST$| C2 CH$| C3 NOT| C4 BIN| C5 OR | C6 AND| C7 <= | ; | C8 >= | C9 <> | CA LIN| CB THN| CC TO | CD STP| CE DEF| CF CAT| ; | D0 FMT| D1 MOV| D2 ERS| D3 OPN| D4 CLO| D5 MRG| D6 VFY| D7 BEP| ; | D8 CIR| D9 INK| DA PAP| DB FLA| DC BRI| DD INV| DE OVR| DF OUT| ; | E0 LPR| E1 LLI| E2 STP| E3 REA| E4 DAT| E5 RES| E6 NEW| E7 BDR| ; | E8 CON| E9 DIM| EA REM| EB FOR| EC GTO| ED GSB| EE INP| EF LOA| ; | F0 LIS| F1 LET| F2 PAU| F3 NXT| F4 POK| F5 PRI| F6 PLO| F7 RUN| ; | F8 SAV| F9 RAN| FA IF | FB CLS| FC DRW| FD CLR| FE RET| FF CPY| ; Note that for simplicity, Sinclair have located all the control codes ; below the space character. ; ASCII DEL, $7F, has been made a copyright symbol. ; Also $60, '`', not used in BASIC but used in other languages, has been ; allocated the local currency symbol for the relevant country - ; £ in most Spectrums. ; ------------------------------------------------------------------------ ;********************************** ;** Part 3. LOUDSPEAKER ROUTINES ** ;********************************** ; Documented by Alvin Albrecht. ; ------------------------------ ; Routine to control loudspeaker ; ------------------------------ ; Outputs a square wave of given duration and frequency ; to the loudspeaker. ; Enter with: DE = #cycles - 1 ; HL = tone period as described next ; ; The tone period is measured in T states and consists of ; three parts: a coarse part (H register), a medium part ; (bits 7..2 of L) and a fine part (bits 1..0 of L) which ; contribute to the waveform timing as follows: ; ; coarse medium fine ; duration of low = 118 + 1024*H + 16*(L>>2) + 4*(L&0x3) ; duration of hi = 118 + 1024*H + 16*(L>>2) + 4*(L&0x3) ; Tp = tone period = 236 + 2048*H + 32*(L>>2) + 8*(L&0x3) ; = 236 + 2048*H + 8*L = 236 + 8*HL ; ; As an example, to output five seconds of middle C (261.624 Hz): ; (a) Tone period = 1/261.624 = 3.822ms ; (b) Tone period in T-States = 3.822ms*fCPU = 13378 ; where fCPU = clock frequency of the CPU = 3.5MHz ; © Find H and L for desired tone period: ; HL = (Tp - 236) / 8 = (13378 - 236) / 8 = 1643 = 0x066B ; (d) Tone duration in cycles = 5s/3.822ms = 1308 cycles ; DE = 1308 - 1 = 0x051B ; ; The resulting waveform has a duty ratio of exactly 50%. ; ; ;; BEEPER L03B5: DI ; Disable Interrupts so they don't disturb timing LD A,L ; SRL L ; SRL L ; L = medium part of tone period CPL ; AND $03 ; A = 3 - fine part of tone period LD C,A ; LD B,$00 ; LD IX,L03D1 ; Address: BE-IX+3 ADD IX,BC ; IX holds address of entry into the loop ; the loop will contain 0-3 NOPs, implementing ; the fine part of the tone period. LD A,($5C48) ; BORDCR AND $38 ; bits 5..3 contain border colour RRCA ; border colour bits moved to 2..0 RRCA ; to match border bits on port #FE RRCA ; OR $08 ; bit 3 set (tape output bit on port #FE) ; for loud sound output ;; BE-IX+3 L03D1: NOP ;(4) ; optionally executed NOPs for small ; adjustments to tone period ;; BE-IX+2 L03D2: NOP ;(4) ; ;; BE-IX+1 L03D3: NOP ;(4) ; ;; BE-IX+0 L03D4: INC B ;(4) ; INC C ;(4) ; ;; BE-H&L-LP L03D6: DEC C ;(4) ; timing loop for duration of JR NZ,L03D6 ;(12/7); high or low pulse of waveform LD C,$3F ;(7) ; DEC B ;(4) ; JP NZ,L03D6 ;(10) ; to BE-H&L-LP XOR $10 ;(7) ; toggle output beep bit OUT ($FE),A ;(11) ; output pulse LD B,H ;(4) ; B = coarse part of tone period LD C,A ;(4) ; save port #FE output byte BIT 4,A ;(8) ; if new output bit is high, go JR NZ,L03F2 ;(12/7); to BE-AGAIN LD A,D ;(4) ; one cycle of waveform has completed OR E ;(4) ; (low->low). if cycle countdown = 0 JR Z,L03F6 ;(12/7); go to BE-END LD A,C ;(4) ; restore output byte for port #FE LD C,L ;(4) ; C = medium part of tone period DEC DE ;(6) ; decrement cycle count JP (IX) ;(8) ; do another cycle ;; BE-AGAIN ; halfway through cycle L03F2: LD C,L ;(4) ; C = medium part of tone period INC C ;(4) ; adds 16 cycles to make duration of high = duration of low JP (IX) ;(8) ; do high pulse of tone ;; BE-END L03F6: EI ; Enable Interrupts RET ; ; ------------------ ; THE 'BEEP' COMMAND ; ------------------ ; BASIC interface to BEEPER subroutine. ; Invoked in BASIC with: ; BEEP dur, pitch ; where dur = duration in seconds ; pitch = # of semitones above/below middle C ; ; Enter with: pitch on top of calculator stack ; duration next on calculator stack ; ;; beep L03F8: RST 28H ;; FP-CALC DEFB $31 ;;duplicate ; duplicate pitch DEFB $27 ;;int ; convert to integer DEFB $C0 ;;st-mem-0 ; store integer pitch to memory 0 DEFB $03 ;;subtract ; calculate fractional part of pitch = fp_pitch - int_pitch DEFB $34 ;;stk-data ; push constant DEFB $EC ;;Exponent: $7C, Bytes: 4 ; constant = 0.05762265 DEFB $6C,$98,$1F,$F5 ;;($6C,$98,$1F,$F5) DEFB $04 ;;multiply ; compute: DEFB $A1 ;;stk-one ; 1 + 0.05762265 * fraction_part(pitch) DEFB $0F ;;addition DEFB $38 ;;end-calc ; leave on calc stack LD HL,$5C92 ; MEM-0: number stored here is in 16 bit integer format (pitch) ; 0, 0/FF (pos/neg), LSB, MSB, 0 ; LSB/MSB is stored in two's complement ; In the following, the pitch is checked if it is in the range -128<=p<=127 LD A,(HL) ; First byte must be zero, otherwise AND A ; error in integer conversion JR NZ,L046C ; to REPORT-B INC HL ; LD C,(HL) ; C = pos/neg flag = 0/FF INC HL ; LD B,(HL) ; B = LSB, two's complement LD A,B ; RLA ; SBC A,A ; A = 0/FF if B is pos/neg CP C ; must be the same as C if the pitch is -128<=p<=127 JR NZ,L046C ; if no, error REPORT-B INC HL ; if -128<=p<=127, MSB will be 0/FF if B is pos/neg CP (HL) ; verify this JR NZ,L046C ; if no, error REPORT-B ; now we know -128<=p<=127 LD A,B ; A = pitch + 60 ADD A,$3C ; if -60<=pitch<=67, JP P,L0425 ; goto BE-i-OK JP PO,L046C ; if pitch <= 67 goto REPORT-B ; lower bound of pitch set at -60 ;; BE-I-OK ; here, -60<=pitch<=127 ; and A=pitch+60 -> 0<=A<=187 L0425: LD B,$FA ; 6 octaves below middle C ;; BE-OCTAVE ; A=# semitones above 5 octaves below middle C L0427: INC B ; increment octave SUB $0C ; 12 semitones = one octave JR NC,L0427 ; to BE-OCTAVE ADD A,$0C ; A = # semitones above C (0-11) PUSH BC ; B = octave displacement from middle C, 2's complement: -5<=B<=10 LD HL,L046E ; Address: semi-tone CALL L3406 ; routine LOC-MEM ; HL = 5*A + $046E CALL L33B4 ; routine STACK-NUM ; read FP value (freq) from semitone table (HL) and push onto calc stack RST 28H ;; FP-CALC DEFB $04 ;;multiply mult freq by 1 + 0.0576 * fraction_part(pitch) stacked earlier ;; thus taking into account fractional part of pitch. ;; the number 0.0576*frequency is the distance in Hz to the next ;; note (verify with the frequencies recorded in the semitone ;; table below) so that the fraction_part of the pitch does ;; indeed represent a fractional distance to the next note. DEFB $38 ;;end-calc HL points to first byte of fp num on stack = middle frequency to generate POP AF ; A = octave displacement from middle C, 2's complement: -5<=A<=10 ADD A,(HL) ; increase exponent by A (equivalent to multiplying by 2^A) LD (HL),A ; RST 28H ;; FP-CALC DEFB $C0 ;;st-mem-0 ; store frequency in memory 0 DEFB $02 ;;delete ; remove from calc stack DEFB $31 ;;duplicate ; duplicate duration (seconds) DEFB $38 ;;end-calc CALL L1E94 ; routine FIND-INT1 ; FP duration to A CP $0B ; if dur > 10 seconds, JR NC,L046C ; goto REPORT-B ;;; The following calculation finds the tone period for HL and the cycle count ;;; for DE expected in the BEEPER subroutine. From the example in the BEEPER comments, ;;; ;;; HL = ((fCPU / f) - 236) / 8 = fCPU/8/f - 236/8 = 437500/f -29.5 ;;; DE = duration * frequency - 1 ;;; ;;; Note the different constant (30.125) used in the calculation of HL ;;; below. This is probably an error. RST 28H ;; FP-CALC DEFB $E0 ;;get-mem-0 ; push frequency DEFB $04 ;;multiply ; result1: #cycles = duration * frequency DEFB $E0 ;;get-mem-0 ; push frequency DEFB $34 ;;stk-data ; push constant DEFB $80 ;;Exponent $93, Bytes: 3 ; constant = 437500 DEFB $43,$55,$9F,$80 ;;($55,$9F,$80,$00) DEFB $01 ;;exchange ; frequency on top DEFB $05 ;;division ; 437500 / frequency DEFB $34 ;;stk-data ; push constant DEFB $35 ;;Exponent: $85, Bytes: 1 ; constant = 30.125 DEFB $71 ;;($71,$00,$00,$00) DEFB $03 ;;subtract ; result2: tone_period(HL) = 437500 / freq - 30.125 DEFB $38 ;;end-calc CALL L1E99 ; routine FIND-INT2 PUSH BC ; BC = tone_period(HL) CALL L1E99 ; routine FIND-INT2, BC = #cycles to generate POP HL ; HL = tone period LD D,B ; LD E,C ; DE = #cycles LD A,D ; OR E ; RET Z ; if duration = 0, skip BEEP and avoid 65536 cycle ; boondoggle that would occur next DEC DE ; DE = #cycles - 1 JP L03B5 ; to BEEPER ; --- ;; REPORT-B L046C: RST 08H ; ERROR-1 DEFB $0A ; Error Report: Integer out of range ; --------------------- ; THE 'SEMI-TONE' TABLE ; --------------------- ; ; Holds frequencies corresponding to semitones in middle octave. ; To move n octaves higher or lower, frequencies are multiplied by 2^n. ;; semi-tone five byte fp decimal freq note (middle) L046E: DEFB $89, $02, $D0, $12, $86; 261.625565290 C DEFB $89, $0A, $97, $60, $75; 277.182631135 C# DEFB $89, $12, $D5, $17, $1F; 293.664768100 D DEFB $89, $1B, $90, $41, $02; 311.126983881 D# DEFB $89, $24, $D0, $53, $CA; 329.627557039 E DEFB $89, $2E, $9D, $36, $B1; 349.228231549 F DEFB $89, $38, $FF, $49, $3E; 369.994422674 F# DEFB $89, $43, $FF, $6A, $73; 391.995436072 G DEFB $89, $4F, $A7, $00, $54; 415.304697513 G# DEFB $89, $5C, $00, $00, $00; 440.000000000 A DEFB $89, $69, $14, $F6, $24; 466.163761616 A# DEFB $89, $76, $F1, $10, $05; 493.883301378 B ; "Music is the hidden mathematical endeavour of a soul unconscious it ; is calculating" - Gottfried Wilhelm Liebnitz 1646 - 1716 ;**************************************** ;** Part 4. CASSETTE HANDLING ROUTINES ** ;**************************************** ; These routines begin with the service routines followed by a single ; command entry point. ; The first of these service routines is a curiosity. ; ----------------------- ; THE 'ZX81 NAME' ROUTINE ; ----------------------- ; This routine fetches a filename in ZX81 format and is not used by the ; cassette handling routines in this ROM. ;; zx81-name L04AA: CALL L24FB ; routine SCANNING to evaluate expression. LD A,($5C3B) ; fetch system variable FLAGS. ADD A,A ; test bit 7 - syntax, bit 6 - result type. JP M,L1C8A ; to REPORT-C if not string result ; 'Nonsense in BASIC'. POP HL ; drop return address. RET NC ; return early if checking syntax. PUSH HL ; re-save return address. CALL L2BF1 ; routine STK-FETCH fetches string parameters. LD H,D ; transfer start of filename LD L,E ; to the HL register. DEC C ; adjust to point to last character and RET M ; return if the null string. ; or multiple of 256! ADD HL,BC ; find last character of the filename. ; and also clear carry. SET 7,(HL) ; invert it. RET ; return. ; ========================================= ; ; PORT 254 ($FE) ; ; spk mic { border } ; ___ ___ ___ ___ ___ ___ ___ ___ ; PORT | | | | | | | | | ; 254 | | | | | | | | | ; $FE |___|___|___|___|___|___|___|___| ; 7 6 5 4 3 2 1 0 ; ; ---------------------------------- ; Save header and program/data bytes ; ---------------------------------- ; This routine saves a section of data. It is called from SA-CTRL to save the ; seventeen bytes of header data. It is also the exit route from that routine ; when it is set up to save the actual data. ; On entry - ; HL points to start of data. ; IX points to descriptor. ; The accumulator is set to $00 for a header, $FF for data. ;; SA-BYTES L04C2: LD HL,L053F ; address: SA/LD-RET PUSH HL ; is pushed as common exit route. ; however there is only one non-terminal exit ; point. LD HL,$1F80 ; a timing constant H=$1F, L=$80 ; inner and outer loop counters ; a five second lead-in is used for a header. BIT 7,A ; test one bit of accumulator. ; (AND A ?) JR Z,L04D0 ; skip to SA-FLAG if a header is being saved. ; else is data bytes and a shorter lead-in is used. LD HL,$0C98 ; another timing value H=$0C, L=$98. ; a two second lead-in is used for the data. ;; SA-FLAG L04D0: EX AF,AF' ; save flag INC DE ; increase length by one. DEC IX ; decrease start. DI ; disable interrupts LD A,$02 ; select red for border, microphone bit on. LD B,A ; also does as an initial slight counter value. ;; SA-LEADER L04D8: DJNZ L04D8 ; self loop to SA-LEADER for delay. ; after initial loop, count is $A4 (or $A3) OUT ($FE),A ; output byte $02/$0D to tape port. XOR $0F ; switch from RED (mic on) to CYAN (mic off). LD B,$A4 ; hold count. also timed instruction. DEC L ; originally $80 or $98. ; but subsequently cycles 256 times. JR NZ,L04D8 ; back to SA-LEADER until L is zero. ; the outer loop is counted by H DEC B ; decrement count DEC H ; originally twelve or thirty-one. JP P,L04D8 ; back to SA-LEADER until H becomes $FF ; now send a sync pulse. At this stage mic is off and A holds value ; for mic on. ; A sync pulse is much shorter than the steady pulses of the lead-in. LD B,$2F ; another short timed delay. ;; SA-SYNC-1 L04EA: DJNZ L04EA ; self loop to SA-SYNC-1 OUT ($FE),A ; switch to mic on and red. LD A,$0D ; prepare mic off - cyan LD B,$37 ; another short timed delay. ;; SA-SYNC-2 L04F2: DJNZ L04F2 ; self loop to SA-SYNC-2 OUT ($FE),A ; output mic off, cyan border. LD BC,$3B0E ; B=$3B time(*), C=$0E, YELLOW, MIC OFF. ; EX AF,AF' ; restore saved flag ; which is 1st byte to be saved. LD L,A ; and transfer to L. ; the initial parity is A, $FF or $00. JP L0507 ; JUMP forward to SA-START -> ; the mid entry point of loop. ; ------------------------- ; During the save loop a parity byte is maintained in H. ; the save loop begins by testing if reduced length is zero and if so ; the final parity byte is saved reducing count to $FFFF. ;; SA-LOOP L04FE: LD A,D ; fetch high byte OR E ; test against low byte. JR Z,L050E ; forward to SA-PARITY if zero. LD L,(IX+$00) ; load currently addressed byte to L. ;; SA-LOOP-P L0505: LD A,H ; fetch parity byte. XOR L ; exclusive or with new byte. ; -> the mid entry point of loop. ;; SA-START L0507: LD H,A ; put parity byte in H. LD A,$01 ; prepare blue, mic=on. SCF ; set carry flag ready to rotate in. JP L0525 ; JUMP forward to SA-8-BITS -8-> ; --- ;; SA-PARITY L050E: LD L,H ; transfer the running parity byte to L and JR L0505 ; back to SA-LOOP-P ; to output that byte before quitting normally. ; --- ; The entry point to save yellow part of bit. ; A bit consists of a period with mic on and blue border followed by ; a period of mic off with yellow border. ; Note. since the DJNZ instruction does not affect flags, the zero flag is ; used to indicate which of the two passes is in effect and the carry ; maintains the state of the bit to be saved. ;; SA-BIT-2 L0511: LD A,C ; fetch 'mic on and yellow' which is ; held permanently in C. BIT 7,B ; set the zero flag. B holds $3E. ; The entry point to save 1 entire bit. For first bit B holds $3B(*). ; Carry is set if saved bit is 1. zero is reset NZ on entry. ;; SA-BIT-1 L0514: DJNZ L0514 ; self loop for delay to SA-BIT-1 JR NC,L051C ; forward to SA-OUT if bit is 0. ; but if bit is 1 then the mic state is held for longer. LD B,$42 ; set timed delay. (66 decimal) ;; SA-SET L051A: DJNZ L051A ; self loop to SA-SET ; (roughly an extra 66*13 clock cycles) ;; SA-OUT L051C: OUT ($FE),A ; blue and mic on OR yellow and mic off. LD B,$3E ; set up delay JR NZ,L0511 ; back to SA-BIT-2 if zero reset NZ (first pass) ; proceed when the blue and yellow bands have been output. DEC B ; change value $3E to $3D. XOR A ; clear carry flag (ready to rotate in). INC A ; reset zero flag i.e. NZ. ; -8-> ;; SA-8-BITS L0525: RL L ; rotate left through carry ; C<76543210<C JP NZ,L0514 ; JUMP back to SA-BIT-1 ; until all 8 bits done. ; when the initial set carry is passed out again then a byte is complete. DEC DE ; decrease length INC IX ; increase byte pointer LD B,$31 ; set up timing. LD A,$7F ; test the space key and IN A,($FE) ; return to common exit (to restore border) RRA ; if a space is pressed RET NC ; return to SA/LD-RET. - - > ; now test if byte counter has reached $FFFF. LD A,D ; fetch high byte INC A ; increment. JP NZ,L04FE ; JUMP to SA-LOOP if more bytes. LD B,$3B ; a final delay. ;; SA-DELAY L053C: DJNZ L053C ; self loop to SA-DELAY RET ; return - - > ; ------------------------------ ; THE 'SAVE/LOAD RETURN' ROUTINE ; ------------------------------ ; The address of this routine is pushed on the stack prior to any load/save ; operation and it handles normal completion with the restoration of the ; border and also abnormal termination when the break key, or to be more ; precise the space key is pressed during a tape operation. ; ; - - > ;; SA/LD-RET L053F: PUSH AF ; preserve accumulator throughout. LD A,($5C48) ; fetch border colour from BORDCR. AND $38 ; mask off paper bits. RRCA ; rotate RRCA ; to the RRCA ; range 0-7. OUT ($FE),A ; change the border colour. LD A,$7F ; read from port address $7FFE the IN A,($FE) ; row with the space key at outside. RRA ; test for space key pressed. EI ; enable interrupts JR C,L0554 ; forward to SA/LD-END if not ;; REPORT-Da L0552: RST 08H ; ERROR-1 DEFB $0C ; Error Report: BREAK - CONT repeats ; --- ;; SA/LD-END L0554: POP AF ; restore the accumulator. RET ; return. ; ------------------------------------ ; Load header or block of information ; ------------------------------------ ; This routine is used to load bytes and on entry A is set to $00 for a ; header or to $FF for data. IX points to the start of receiving location ; and DE holds the length of bytes to be loaded. If, on entry the carry flag ; is set then data is loaded, if reset then it is verified. ;; LD-BYTES L0556: INC D ; reset the zero flag without disturbing carry. EX AF,AF' ; preserve entry flags. DEC D ; restore high byte of length. DI ; disable interrupts LD A,$0F ; make the border white and mic off. OUT ($FE),A ; output to port. LD HL,L053F ; Address: SA/LD-RET PUSH HL ; is saved on stack as terminating routine. ; the reading of the EAR bit (D6) will always be preceded by a test of the ; space key (D0), so store the initial post-test state. IN A,($FE) ; read the ear state - bit 6. RRA ; rotate to bit 5. AND $20 ; isolate this bit. OR $02 ; combine with red border colour. LD C,A ; and store initial state long-term in C. CP A ; set the zero flag. ; ;; LD-BREAK L056B: RET NZ ; return if at any time space is pressed. ;; LD-START L056C: CALL L05E7 ; routine LD-EDGE-1 JR NC,L056B ; back to LD-BREAK with time out and no ; edge present on tape. ; but continue when a transition is found on tape. LD HL,$0415 ; set up 16-bit outer loop counter for ; approx 1 second delay. ;; LD-WAIT L0574: DJNZ L0574 ; self loop to LD-WAIT (for 256 times) DEC HL ; decrease outer loop counter. LD A,H ; test for OR L ; zero. JR NZ,L0574 ; back to LD-WAIT, if not zero, with zero in B. ; continue after delay with H holding zero and B also. ; sample 256 edges to check that we are in the middle of a lead-in section. CALL L05E3 ; routine LD-EDGE-2 JR NC,L056B ; back to LD-BREAK ; if no edges at all. ;; LD-LEADER L0580: LD B,$9C ; set timing value. CALL L05E3 ; routine LD-EDGE-2 JR NC,L056B ; back to LD-BREAK if time-out LD A,$C6 ; two edges must be spaced apart. CP B ; compare JR NC,L056C ; back to LD-START if too close together for a ; lead-in. INC H ; proceed to test 256 edged sample. JR NZ,L0580 ; back to LD-LEADER while more to do. ; sample indicates we are in the middle of a two or five second lead-in. ; Now test every edge looking for the terminal sync signal. ;; LD-SYNC L058F: LD B,$C9 ; initial timing value in B. CALL L05E7 ; routine LD-EDGE-1 JR NC,L056B ; back to LD-BREAK with time-out. LD A,B ; fetch augmented timing value from B. CP $D4 ; compare JR NC,L058F ; back to LD-SYNC if gap too big, that is, ; a normal lead-in edge gap. ; but a short gap will be the sync pulse. ; in which case another edge should appear before B rises to $FF CALL L05E7 ; routine LD-EDGE-1 RET NC ; return with time-out. ; proceed when the sync at the end of the lead-in is found. ; We are about to load data so change the border colours. LD A,C ; fetch long-term mask from C XOR $03 ; and make blue/yellow. LD C,A ; store the new long-term byte. LD H,$00 ; set up parity byte as zero. LD B,$B0 ; timing. JR L05C8 ; forward to LD-MARKER ; the loop mid entry point with the alternate ; zero flag reset to indicate first byte ; is discarded. ; -------------- ; the loading loop loads each byte and is entered at the mid point. ;; LD-LOOP L05A9: EX AF,AF' ; restore entry flags and type in A. JR NZ,L05B3 ; forward to LD-FLAG if awaiting initial flag ; which is to be discarded. JR NC,L05BD ; forward to LD-VERIFY if not to be loaded. LD (IX+$00),L ; place loaded byte at memory location. JR L05C2 ; forward to LD-NEXT ; --- ;; LD-FLAG L05B3: RL C ; preserve carry (verify) flag in long-term ; state byte. Bit 7 can be lost. XOR L ; compare type in A with first byte in L. RET NZ ; return if no match e.g. CODE vs. DATA. ; continue when data type matches. LD A,C ; fetch byte with stored carry RRA ; rotate it to carry flag again LD C,A ; restore long-term port state. INC DE ; increment length ?? JR L05C4 ; forward to LD-DEC. ; but why not to location after ? ; --- ; for verification the byte read from tape is compared with that in memory. ;; LD-VERIFY L05BD: LD A,(IX+$00) ; fetch byte from memory. XOR L ; compare with that on tape RET NZ ; return if not zero. ;; LD-NEXT L05C2: INC IX ; increment byte pointer. ;; LD-DEC L05C4: DEC DE ; decrement length. EX AF,AF' ; store the flags. LD B,$B2 ; timing. ; when starting to read 8 bits the receiving byte is marked with bit at right. ; when this is rotated out again then 8 bits have been read. ;; LD-MARKER L05C8: LD L,$01 ; initialize as %00000001 ;; LD-8-BITS L05CA: CALL L05E3 ; routine LD-EDGE-2 increments B relative to ; gap between 2 edges. RET NC ; return with time-out. LD A,$CB ; the comparison byte. CP B ; compare to incremented value of B. ; if B is higher then bit on tape was set. ; if <= then bit on tape is reset. RL L ; rotate the carry bit into L. LD B,$B0 ; reset the B timer byte. JP NC,L05CA ; JUMP back to LD-8-BITS ; when carry set then marker bit has been passed out and byte is complete. LD A,H ; fetch the running parity byte. XOR L ; include the new byte. LD H,A ; and store back in parity register. LD A,D ; check length of OR E ; expected bytes. JR NZ,L05A9 ; back to LD-LOOP ; while there are more. ; when all bytes loaded then parity byte should be zero. LD A,H ; fetch parity byte. CP $01 ; set carry if zero. RET ; return ; in no carry then error as checksum disagrees. ; ------------------------- ; Check signal being loaded ; ------------------------- ; An edge is a transition from one mic state to another. ; More specifically a change in bit 6 of value input from port $FE. ; Graphically it is a change of border colour, say, blue to yellow. ; The first entry point looks for two adjacent edges. The second entry point ; is used to find a single edge. ; The B register holds a count, up to 256, within which the edge (or edges) ; must be found. The gap between two edges will be more for a '1' than a '0' ; so the value of B denotes the state of the bit (two edges) read from tape. ; -> ;; LD-EDGE-2 L05E3: CALL L05E7 ; call routine LD-EDGE-1 below. RET NC ; return if space pressed or time-out. ; else continue and look for another adjacent ; edge which together represent a bit on the ; tape. ; -> ; this entry point is used to find a single edge from above but also ; when detecting a read-in signal on the tape. ;; LD-EDGE-1 L05E7: LD A,$16 ; a delay value of twenty two. ;; LD-DELAY L05E9: DEC A ; decrement counter JR NZ,L05E9 ; loop back to LD-DELAY 22 times. AND A ; clear carry. ;; LD-SAMPLE L05ED: INC B ; increment the time-out counter. RET Z ; return with failure when $FF passed. LD A,$7F ; prepare to read keyboard and EAR port IN A,($FE) ; row $7FFE. bit 6 is EAR, bit 0 is SPACE key. RRA ; test outer key the space. (bit 6 moves to 5) RET NC ; return if space pressed. >>> XOR C ; compare with initial long-term state. AND $20 ; isolate bit 5 JR Z,L05ED ; back to LD-SAMPLE if no edge. ; but an edge, a transition of the EAR bit, has been found so switch the ; long-term comparison byte containing both border colour and EAR bit. LD A,C ; fetch comparison value. CPL ; switch the bits LD C,A ; and put back in C for long-term. AND $07 ; isolate new colour bits. OR $08 ; set bit 3 - MIC off. OUT ($FE),A ; send to port to effect the change of colour. SCF ; set carry flag signaling edge found within ; time allowed. RET ; return. ; --------------------------------- ; Entry point for all tape commands ; --------------------------------- ; This is the single entry point for the four tape commands. ; The routine first determines in what context it has been called by examining ; the low byte of the Syntax table entry which was stored in T_ADDR. ; Subtracting $EO (the present arrangement) gives a value of ; $00 - SAVE ; $01 - LOAD ; $02 - VERIFY ; $03 - MERGE ; As with all commands the address STMT-RET is on the stack. ;; SAVE-ETC L0605: POP AF ; discard address STMT-RET. LD A,($5C74) ; fetch T_ADDR ; Now reduce the low byte of the Syntax table entry to give command. ; Note. For ZASM use SUB $E0 as next instruction. L0609: SUB L1ADF + 1 % 256 ; subtract the known offset. ; ( is SUB $E0 in standard ROM ) LD ($5C74),A ; and put back in T_ADDR as 0,1,2, or 3 ; for future reference. CALL L1C8C ; routine EXPT-EXP checks that a string ; expression follows and stacks the ; parameters in run-time. CALL L2530 ; routine SYNTAX-Z JR Z,L0652 ; forward to SA-DATA if checking syntax. LD BC,$0011 ; presume seventeen bytes for a header. LD A,($5C74) ; fetch command from T_ADDR. AND A ; test for zero - SAVE. JR Z,L0621 ; forward to SA-SPACE if so. LD C,$22 ; else double length to thirty four. ;; SA-SPACE L0621: RST 30H ; BC-SPACES creates 17/34 bytes in workspace. PUSH DE ; transfer the start of new space to POP IX ; the available index register. ; ten spaces are required for the default filename but it is simpler to ; overwrite the first file-type indicator byte as well. LD B,$0B ; set counter to eleven. LD A,$20 ; prepare a space. ;; SA-BLANK L0629: LD (DE),A ; set workspace location to space. INC DE ; next location. DJNZ L0629 ; loop back to SA-BLANK till all eleven done. LD (IX+$01),$FF ; set first byte of ten character filename ; to $FF as a default to signal null string. CALL L2BF1 ; routine STK-FETCH fetches the filename ; parameters from the calculator stack. ; length of string in BC. ; start of string in DE. LD HL,$FFF6 ; prepare the value minus ten. DEC BC ; decrement length. ; ten becomes nine, zero becomes $FFFF. ADD HL,BC ; trial addition. INC BC ; restore true length. JR NC,L064B ; forward to SA-NAME if length is one to ten. ; the filename is more than ten characters in length or the null string. LD A,($5C74) ; fetch command from T_ADDR. AND A ; test for zero - SAVE. JR NZ,L0644 ; forward to SA-NULL if not the SAVE command. ; but no more than ten characters are allowed for SAVE. ; The first ten characters of any other command parameter are acceptable. ; Weird, but necessary, if saving to sectors. ; Note. the golden rule that there are no restriction on anything is broken. ;; REPORT-Fa L0642: RST 08H ; ERROR-1 DEFB $0E ; Error Report: Invalid file name ; continue with LOAD, MERGE, VERIFY and also SAVE within ten character limit. ;; SA-NULL L0644: LD A,B ; test length of filename OR C ; for zero. JR Z,L0652 ; forward to SA-DATA if so using the 255 ; indicator followed by spaces. LD BC,$000A ; else trim length to ten. ; other paths rejoin here with BC holding length in range 1 - 10. ;; SA-NAME L064B: PUSH IX ; push start of file descriptor. POP HL ; and pop into HL. INC HL ; HL now addresses first byte of filename. EX DE,HL ; transfer destination address to DE, start ; of string in command to HL. LDIR ; copy up to ten bytes ; if less than ten then trailing spaces follow. ; the case for the null string rejoins here. ;; SA-DATA L0652: RST 18H ; GET-CHAR CP $E4 ; is character after filename the token 'DATA' ? JR NZ,L06A0 ; forward to SA-SCR$ to consider SCREEN$ if ; not. ; continue to consider DATA. LD A,($5C74) ; fetch command from T_ADDR CP $03 ; is it 'VERIFY' ? JP Z,L1C8A ; jump forward to REPORT-C if so. ; 'Nonsense in BASIC' ; VERIFY "d" DATA is not allowed. ; continue with SAVE, LOAD, MERGE of DATA. RST 20H ; NEXT-CHAR CALL L28B2 ; routine LOOK-VARS searches variables area ; returning with carry reset if found or ; checking syntax. SET 7,C ; this converts a simple string to a ; string array. The test for an array or string ; comes later. JR NC,L0672 ; forward to SA-V-OLD if variable found. LD HL,$0000 ; set destination to zero as not fixed. LD A,($5C74) ; fetch command from T_ADDR DEC A ; test for 1 - LOAD JR Z,L0685 ; forward to SA-V-NEW with LOAD DATA. ; to load a new array. ; otherwise the variable was not found in run-time with SAVE/MERGE. ;; REPORT-2a L0670: RST 08H ; ERROR-1 DEFB $01 ; Error Report: Variable not found ; continue with SAVE/LOAD DATA ;; SA-V-OLD L0672: JP NZ,L1C8A ; to REPORT-C if not an array variable. ; or erroneously a simple string. ; 'Nonsense in BASIC' CALL L2530 ; routine SYNTAX-Z JR Z,L0692 ; forward to SA-DATA-1 if checking syntax. INC HL ; step past single character variable name. LD A,(HL) ; fetch low byte of length. LD (IX+$0B),A ; place in descriptor. INC HL ; point to high byte. LD A,(HL) ; and transfer that LD (IX+$0C),A ; to descriptor. INC HL ; increase pointer within variable. ;; SA-V-NEW L0685: LD (IX+$0E),C ; place character array name in header. LD A,$01 ; default to type numeric. BIT 6,C ; test result from look-vars. JR Z,L068F ; forward to SA-V-TYPE if numeric. INC A ; set type to 2 - string array. ;; SA-V-TYPE L068F: LD (IX+$00),A ; place type 0, 1 or 2 in descriptor. ;; SA-DATA-1 L0692: EX DE,HL ; save var pointer in DE RST 20H ; NEXT-CHAR CP $29 ; is character ')' ? JR NZ,L0672 ; back if not to SA-V-OLD to report ; 'Nonsense in BASIC' RST 20H ; NEXT-CHAR advances character address. CALL L1BEE ; routine CHECK-END errors if not end of ; the statement. EX DE,HL ; bring back variables data pointer. JP L075A ; jump forward to SA-ALL ; --- ; the branch was here to consider a 'SCREEN$', the display file. ;; SA-SCR$ L06A0: CP $AA ; is character the token 'SCREEN$' ? JR NZ,L06C3 ; forward to SA-CODE if not. LD A,($5C74) ; fetch command from T_ADDR CP $03 ; is it MERGE ? JP Z,L1C8A ; jump to REPORT-C if so. ; 'Nonsense in BASIC' ; continue with SAVE/LOAD/VERIFY SCREEN$. RST 20H ; NEXT-CHAR CALL L1BEE ; routine CHECK-END errors if not at end of ; statement. ; continue in runtime. LD (IX+$0B),$00 ; set descriptor length LD (IX+$0C),$1B ; to $1b00 to include bitmaps and attributes. LD HL,$4000 ; set start to display file start. LD (IX+$0D),L ; place start in LD (IX+$0E),H ; the descriptor. JR L0710 ; forward to SA-TYPE-3 ; --- ; the branch was here to consider CODE. ;; SA-CODE L06C3: CP $AF ; is character the token 'CODE' ? JR NZ,L0716 ; forward if not to SA-LINE to consider an ; auto-started BASIC program. LD A,($5C74) ; fetch command from T_ADDR CP $03 ; is it MERGE ? JP Z,L1C8A ; jump forward to REPORT-C if so. ; 'Nonsense in BASIC' RST 20H ; NEXT-CHAR advances character address. CALL L2048 ; routine PR-ST-END checks if a carriage ; return or ':' follows. JR NZ,L06E1 ; forward to SA-CODE-1 if there are parameters. LD A,($5C74) ; else fetch the command from T_ADDR. AND A ; test for zero - SAVE without a specification. JP Z,L1C8A ; jump to REPORT-C if so. ; 'Nonsense in BASIC' ; for LOAD/VERIFY put zero on stack to signify handle at location saved from. CALL L1CE6 ; routine USE-ZERO JR L06F0 ; forward to SA-CODE-2 ; --- ; if there are more characters after CODE expect start and possibly length. ;; SA-CODE-1 L06E1: CALL L1C82 ; routine EXPT-1NUM checks for numeric ; expression and stacks it in run-time. RST 18H ; GET-CHAR CP $2C ; does a comma follow ? JR Z,L06F5 ; forward if so to SA-CODE-3 ; else allow saved code to be loaded to a specified address. LD A,($5C74) ; fetch command from T_ADDR. AND A ; is the command SAVE which requires length ? JP Z,L1C8A ; jump to REPORT-C if so. ; 'Nonsense in BASIC' ; the command LOAD code may rejoin here with zero stacked as start. ;; SA-CODE-2 L06F0: CALL L1CE6 ; routine USE-ZERO stacks zero for length. JR L06F9 ; forward to SA-CODE-4 ; --- ; the branch was here with SAVE CODE start, ;; SA-CODE-3 L06F5: RST 20H ; NEXT-CHAR advances character address. CALL L1C82 ; routine EXPT-1NUM checks for expression ; and stacks in run-time. ; paths converge here and nothing must follow. ;; SA-CODE-4 L06F9: CALL L1BEE ; routine CHECK-END errors with extraneous ; characters and quits if checking syntax. ; in run-time there are two 16-bit parameters on the calculator stack. CALL L1E99 ; routine FIND-INT2 gets length. LD (IX+$0B),C ; place length LD (IX+$0C),B ; in descriptor. CALL L1E99 ; routine FIND-INT2 gets start. LD (IX+$0D),C ; place start LD (IX+$0E),B ; in descriptor. LD H,B ; transfer the LD L,C ; start to HL also. ;; SA-TYPE-3 L0710: LD (IX+$00),$03 ; place type 3 - code in descriptor. JR L075A ; forward to SA-ALL. ; --- ; the branch was here with BASIC to consider an optional auto-start line ; number. ;; SA-LINE L0716: CP $CA ; is character the token 'LINE' ? JR Z,L0723 ; forward to SA-LINE-1 if so. ; else all possibilities have been considered and nothing must follow. CALL L1BEE ; routine CHECK-END ; continue in run-time to save BASIC without auto-start. LD (IX+$0E),$80 ; place high line number in descriptor to ; disable auto-start. JR L073A ; forward to SA-TYPE-0 to save program. ; --- ; the branch was here to consider auto-start. ;; SA-LINE-1 L0723: LD A,($5C74) ; fetch command from T_ADDR AND A ; test for SAVE. JP NZ,L1C8A ; jump forward to REPORT-C with anything else. ; 'Nonsense in BASIC' ; RST 20H ; NEXT-CHAR CALL L1C82 ; routine EXPT-1NUM checks for numeric ; expression and stacks in run-time. CALL L1BEE ; routine CHECK-END quits if syntax path. CALL L1E99 ; routine FIND-INT2 fetches the numeric ; expression. LD (IX+$0D),C ; place the auto-start LD (IX+$0E),B ; line number in the descriptor. ; Note. this isn't checked, but is subsequently handled by the system. ; If the user typed 40000 instead of 4000 then it won't auto-start ; at line 4000, or indeed, at all. ; continue to save program and any variables. ;; SA-TYPE-0 L073A: LD (IX+$00),$00 ; place type zero - program in descriptor. LD HL,($5C59) ; fetch E_LINE to HL. LD DE,($5C53) ; fetch PROG to DE. SCF ; set carry flag to calculate from end of ; variables E_LINE -1. SBC HL,DE ; subtract to give total length. LD (IX+$0B),L ; place total length LD (IX+$0C),H ; in descriptor. LD HL,($5C4B) ; load HL from system variable VARS SBC HL,DE ; subtract to give program length. LD (IX+$0F),L ; place length of program LD (IX+$10),H ; in the descriptor. EX DE,HL ; start to HL, length to DE. ;; SA-ALL L075A: LD A,($5C74) ; fetch command from T_ADDR AND A ; test for zero - SAVE. JP Z,L0970 ; jump forward to SA-CONTRL with SAVE -> ; --- ; continue with LOAD, MERGE and VERIFY. PUSH HL ; save start. LD BC,$0011 ; prepare to add seventeen ADD IX,BC ; to point IX at second descriptor. ;; LD-LOOK-H L0767: PUSH IX ; save IX LD DE,$0011 ; seventeen bytes XOR A ; reset zero flag SCF ; set carry flag CALL L0556 ; routine LD-BYTES loads a header from tape ; to second descriptor. POP IX ; restore IX. JR NC,L0767 ; loop back to LD-LOOK-H until header found. LD A,$FE ; select system channel 'S' CALL L1601 ; routine CHAN-OPEN opens it. LD (IY+$52),$03 ; set SCR_CT to 3 lines. LD C,$80 ; C has bit 7 set to indicate type mismatch as ; a default startpoint. LD A,(IX+$00) ; fetch loaded header type to A CP (IX-$11) ; compare with expected type. JR NZ,L078A ; forward to LD-TYPE with mis-match. LD C,$F6 ; set C to minus ten - will count characters ; up to zero. ;; LD-TYPE L078A: CP $04 ; check if type in acceptable range 0 - 3. JR NC,L0767 ; back to LD-LOOK-H with 4 and over. ; else A indicates type 0-3. LD DE,L09C0 ; address base of last 4 tape messages PUSH BC ; save BC CALL L0C0A ; routine PO-MSG outputs relevant message. ; Note. all messages have a leading newline. POP BC ; restore BC PUSH IX ; transfer IX, POP DE ; the 2nd descriptor, to DE. LD HL,$FFF0 ; prepare minus seventeen. ADD HL,DE ; add to point HL to 1st descriptor. LD B,$0A ; the count will be ten characters for the ; filename. LD A,(HL) ; fetch first character and test for INC A ; value 255. JR NZ,L07A6 ; forward to LD-NAME if not the wildcard. ; but if it is the wildcard, then add ten to C which is minus ten for a type ; match or -128 for a type mismatch. Although characters have to be counted ; bit 7 of C will not alter from state set here. LD A,C ; transfer $F6 or $80 to A ADD A,B ; add $0A LD C,A ; place result, zero or -118, in C. ; At this point we have either a type mismatch, a wildcard match or ten ; characters to be counted. The characters must be shown on the screen. ;; LD-NAME L07A6: INC DE ; address next input character LD A,(DE) ; fetch character CP (HL) ; compare to expected INC HL ; address next expected character JR NZ,L07AD ; forward to LD-CH-PR with mismatch INC C ; increment matched character count ;; LD-CH-PR L07AD: RST 10H ; PRINT-A prints character DJNZ L07A6 ; loop back to LD-NAME for ten characters. ; if ten characters matched and the types previously matched then C will ; now hold zero. BIT 7,C ; test if all matched JR NZ,L0767 ; back to LD-LOOK-H if not ; else print a terminal carriage return. LD A,$0D ; prepare carriage return. RST 10H ; PRINT-A outputs it. ; The various control routines for LOAD, VERIFY and MERGE are executed ; during the one-second gap following the header on tape. POP HL ; restore xx LD A,(IX+$00) ; fetch incoming type CP $03 ; compare with CODE JR Z,L07CB ; forward to VR-CONTRL if it is CODE. ; type is a program or an array. LD A,($5C74) ; fetch command from T_ADDR DEC A ; was it LOAD ? JP Z,L0808 ; JUMP forward to LD-CONTRL if so to ; load BASIC or variables. CP $02 ; was command MERGE ? JP Z,L08B6 ; jump forward to ME-CONTRL if so. ; else continue into VERIFY control routine to verify. ; ---------------------------- ; THE 'VERIFY CONTROL' ROUTINE ; ---------------------------- ; There are two branches to this routine. ; 1) From above to verify a program or array ; 2) from earlier with no carry to load or verify code. ;; VR-CONTRL L07CB: PUSH HL ; save pointer to data. LD L,(IX-$06) ; fetch length of old data LD H,(IX-$05) ; to HL. LD E,(IX+$0B) ; fetch length of new data LD D,(IX+$0C) ; to DE. LD A,H ; check length of old OR L ; for zero. JR Z,L07E9 ; forward to VR-CONT-1 if length unspecified ; e.g. LOAD "x" CODE ; as opposed to, say, LOAD 'x' CODE 32768,300. SBC HL,DE ; subtract the two lengths. JR C,L0806 ; forward to REPORT-R if the length on tape is ; larger than that specified in command. ; 'Tape loading error' JR Z,L07E9 ; forward to VR-CONT-1 if lengths match. ; a length on tape shorter than expected is not allowed for CODE LD A,(IX+$00) ; else fetch type from tape. CP $03 ; is it CODE ? JR NZ,L0806 ; forward to REPORT-R if so ; 'Tape loading error' ;; VR-CONT-1 L07E9: POP HL ; pop pointer to data LD A,H ; test for zero OR L ; e.g. LOAD 'x' CODE JR NZ,L07F4 ; forward to VR-CONT-2 if destination specified. LD L,(IX+$0D) ; else use the destination in the header LD H,(IX+$0E) ; and load code at address saved from. ;; VR-CONT-2 L07F4: PUSH HL ; push pointer to start of data block. POP IX ; transfer to IX. LD A,($5C74) ; fetch reduced command from T_ADDR CP $02 ; is it VERIFY ? SCF ; prepare a set carry flag JR NZ,L0800 ; skip to VR-CONT-3 if not AND A ; clear carry flag for VERIFY so that ; data is not loaded. ;; VR-CONT-3 L0800: LD A,$FF ; signal data block to be loaded ; ----------------- ; Load a data block ; ----------------- ; This routine is called from 3 places other than above to load a data block. ; In all cases the accumulator is first set to $FF so the routine could be ; called at the previous instruction. ;; LD-BLOCK L0802: CALL L0556 ; routine LD-BYTES RET C ; return if successful. ;; REPORT-R L0806: RST 08H ; ERROR-1 DEFB $1A ; Error Report: Tape loading error ; -------------------------- ; THE 'LOAD CONTROL' ROUTINE ; -------------------------- ; This branch is taken when the command is LOAD with type 0, 1 or 2. ;; LD-CONTRL L0808: LD E,(IX+$0B) ; fetch length of found data block LD D,(IX+$0C) ; from 2nd descriptor. PUSH HL ; save destination LD A,H ; test for zero OR L ; JR NZ,L0819 ; forward if not to LD-CONT-1 INC DE ; increase length INC DE ; for letter name INC DE ; and 16-bit length EX DE,HL ; length to HL, JR L0825 ; forward to LD-CONT-2 ; --- ;; LD-CONT-1 L0819: LD L,(IX-$06) ; fetch length from LD H,(IX-$05) ; the first header. EX DE,HL ; SCF ; set carry flag SBC HL,DE ; JR C,L082E ; to LD-DATA ;; LD-CONT-2 L0825: LD DE,$0005 ; allow overhead of five bytes. ADD HL,DE ; add in the difference in data lengths. LD B,H ; transfer to LD C,L ; the BC register pair CALL L1F05 ; routine TEST-ROOM fails if not enough room. ;; LD-DATA L082E: POP HL ; pop destination LD A,(IX+$00) ; fetch type 0, 1 or 2. AND A ; test for program and variables. JR Z,L0873 ; forward if so to LD-PROG ; the type is a numeric or string array. LD A,H ; test the destination for zero OR L ; indicating variable does not already exist. JR Z,L084C ; forward if so to LD-DATA-1 ; else the destination is the first dimension within the array structure DEC HL ; address high byte of total length LD B,(HL) ; transfer to B. DEC HL ; address low byte of total length. LD C,(HL) ; transfer to C. DEC HL ; point to letter of variable. INC BC ; adjust length to INC BC ; include these INC BC ; three bytes also. LD ($5C5F),IX ; save header pointer in X_PTR. CALL L19E8 ; routine RECLAIM-2 reclaims the old variable ; sliding workspace including the two headers ; downwards. LD IX,($5C5F) ; reload IX from X_PTR which will have been ; adjusted down by POINTERS routine. ;; LD-DATA-1 L084C: LD HL,($5C59) ; address E_LINE DEC HL ; now point to the $80 variables end-marker. LD C,(IX+$0B) ; fetch new data length LD B,(IX+$0C) ; from 2nd header. PUSH BC ; * save it. INC BC ; adjust the INC BC ; length to include INC BC ; letter name and total length. LD A,(IX-$03) ; fetch letter name from old header. PUSH AF ; preserve accumulator though not corrupted. CALL L1655 ; routine MAKE-ROOM creates space for variable ; sliding workspace up. IX no longer addresses ; anywhere meaningful. INC HL ; point to first new location. POP AF ; fetch back the letter name. LD (HL),A ; place in first new location. POP DE ; * pop the data length. INC HL ; address 2nd location LD (HL),E ; store low byte of length. INC HL ; address next. LD (HL),D ; store high byte. INC HL ; address start of data. PUSH HL ; transfer address POP IX ; to IX register pair. SCF ; set carry flag indicating load not verify. LD A,$FF ; signal data not header. JP L0802 ; JUMP back to LD-BLOCK ; ----------------- ; the branch is here when a program as opposed to an array is to be loaded. ;; LD-PROG L0873: EX DE,HL ; transfer dest to DE. LD HL,($5C59) ; address E_LINE DEC HL ; now variables end-marker. LD ($5C5F),IX ; place the IX header pointer in X_PTR LD C,(IX+$0B) ; get new length LD B,(IX+$0C) ; from 2nd header PUSH BC ; and save it. CALL L19E5 ; routine RECLAIM-1 reclaims program and vars. ; adjusting X-PTR. POP BC ; restore new length. PUSH HL ; * save start PUSH BC ; ** and length. CALL L1655 ; routine MAKE-ROOM creates the space. LD IX,($5C5F) ; reload IX from adjusted X_PTR INC HL ; point to start of new area. LD C,(IX+$0F) ; fetch length of BASIC on tape LD B,(IX+$10) ; from 2nd descriptor ADD HL,BC ; add to address the start of variables. LD ($5C4B),HL ; set system variable VARS LD H,(IX+$0E) ; fetch high byte of autostart line number. LD A,H ; transfer to A AND $C0 ; test if greater than $3F. JR NZ,L08AD ; forward to LD-PROG-1 if so with no autostart. LD L,(IX+$0D) ; else fetch the low byte. LD ($5C42),HL ; set system variable to line number NEWPPC LD (IY+$0A),$00 ; set statement NSPPC to zero. ;; LD-PROG-1 L08AD: POP DE ; ** pop the length POP IX ; * and start. SCF ; set carry flag LD A,$FF ; signal data as opposed to a header. JP L0802 ; jump back to LD-BLOCK ; --------------------------- ; THE 'MERGE CONTROL' ROUTINE ; --------------------------- ; the branch was here to merge a program and its variables or an array. ; ;; ME-CONTRL L08B6: LD C,(IX+$0B) ; fetch length LD B,(IX+$0C) ; of data block on tape. PUSH BC ; save it. INC BC ; one for the pot. RST 30H ; BC-SPACES creates room in workspace. ; HL addresses last new location. LD (HL),$80 ; place end-marker at end. EX DE,HL ; transfer first location to HL. POP DE ; restore length to DE. PUSH HL ; save start. PUSH HL ; and transfer it POP IX ; to IX register. SCF ; set carry flag to load data on tape. LD A,$FF ; signal data not a header. CALL L0802 ; routine LD-BLOCK loads to workspace. POP HL ; restore first location in workspace to HL. X08CE LD DE,($5C53) ; set DE from system variable PROG. ; now enter a loop to merge the data block in workspace with the program and ; variables. ;; ME-NEW-LP L08D2: LD A,(HL) ; fetch next byte from workspace. AND $C0 ; compare with $3F. JR NZ,L08F0 ; forward to ME-VAR-LP if a variable or ; end-marker. ; continue when HL addresses a BASIC line number. ;; ME-OLD-LP L08D7: LD A,(DE) ; fetch high byte from program area. INC DE ; bump prog address. CP (HL) ; compare with that in workspace. INC HL ; bump workspace address. JR NZ,L08DF ; forward to ME-OLD-L1 if high bytes don't match LD A,(DE) ; fetch the low byte of program line number. CP (HL) ; compare with that in workspace. ;; ME-OLD-L1 L08DF: DEC DE ; point to start of DEC HL ; respective lines again. JR NC,L08EB ; forward to ME-NEW-L2 if line number in ; workspace is less than or equal to current ; program line as has to be added to program. PUSH HL ; else save workspace pointer. EX DE,HL ; transfer prog pointer to HL CALL L19B8 ; routine NEXT-ONE finds next line in DE. POP HL ; restore workspace pointer JR L08D7 ; back to ME-OLD-LP until destination position ; in program area found. ; --- ; the branch was here with an insertion or replacement point. ;; ME-NEW-L2 L08EB: CALL L092C ; routine ME-ENTER enters the line JR L08D2 ; loop back to ME-NEW-LP. ; --- ; the branch was here when the location in workspace held a variable. ;; ME-VAR-LP L08F0: LD A,(HL) ; fetch first byte of workspace variable. LD C,A ; copy to C also. CP $80 ; is it the end-marker ? RET Z ; return if so as complete. >>>>> PUSH HL ; save workspace area pointer. LD HL,($5C4B) ; load HL with VARS - start of variables area. ;; ME-OLD-VP L08F9: LD A,(HL) ; fetch first byte. CP $80 ; is it the end-marker ? JR Z,L0923 ; forward if so to ME-VAR-L2 to add ; variable at end of variables area. CP C ; compare with variable in workspace area. JR Z,L0909 ; forward to ME-OLD-V2 if a match to replace. ; else entire variables area has to be searched. ;; ME-OLD-V1 L0901: PUSH BC ; save character in C. CALL L19B8 ; routine NEXT-ONE gets following variable ; address in DE. POP BC ; restore character in C EX DE,HL ; transfer next address to HL. JR L08F9 ; loop back to ME-OLD-VP ; --- ; the branch was here when first characters of name matched. ;; ME-OLD-V2 L0909: AND $E0 ; keep bits 11100000 CP $A0 ; compare 10100000 - a long-named variable. JR NZ,L0921 ; forward to ME-VAR-L1 if just one-character. ; but long-named variables have to be matched character by character. POP DE ; fetch workspace 1st character pointer PUSH DE ; and save it on the stack again. PUSH HL ; save variables area pointer on stack. ;; ME-OLD-V3 L0912: INC HL ; address next character in vars area. INC DE ; address next character in workspace area. LD A,(DE) ; fetch workspace character. CP (HL) ; compare to variables character. JR NZ,L091E ; forward to ME-OLD-V4 with a mismatch. RLA ; test if the terminal inverted character. JR NC,L0912 ; loop back to ME-OLD-V3 if more to test. ; otherwise the long name matches in its entirety. POP HL ; restore pointer to first character of variable JR L0921 ; forward to ME-VAR-L1 ; --- ; the branch is here when two characters don't match ;; ME-OLD-V4 L091E: POP HL ; restore the prog/vars pointer. JR L0901 ; back to ME-OLD-V1 to resume search. ; --- ; branch here when variable is to replace an existing one ;; ME-VAR-L1 L0921: LD A,$FF ; indicate a replacement. ; this entry point is when A holds $80 indicating a new variable. ;; ME-VAR-L2 L0923: POP DE ; pop workspace pointer. EX DE,HL ; now make HL workspace pointer, DE vars pointer INC A ; zero flag set if replacement. SCF ; set carry flag indicating a variable not a ; program line. CALL L092C ; routine ME-ENTER copies variable in. JR L08F0 ; loop back to ME-VAR-LP ; ------------------------ ; Merge a Line or Variable ; ------------------------ ; A BASIC line or variable is inserted at the current point. If the line ; number or variable names match (zero flag set) then a replacement takes ; place. ;; ME-ENTER L092C: JR NZ,L093E ; forward to ME-ENT-1 for insertion only. ; but the program line or variable matches so old one is reclaimed. EX AF,AF' ; save flag?? LD ($5C5F),HL ; preserve workspace pointer in dynamic X_PTR EX DE,HL ; transfer program dest pointer to HL. CALL L19B8 ; routine NEXT-ONE finds following location ; in program or variables area. CALL L19E8 ; routine RECLAIM-2 reclaims the space between. EX DE,HL ; transfer program dest pointer back to DE. LD HL,($5C5F) ; fetch adjusted workspace pointer from X_PTR EX AF,AF' ; restore flags. ; now the new line or variable is entered. ;; ME-ENT-1 L093E: EX AF,AF' ; save or re-save flags. PUSH DE ; save dest pointer in prog/vars area. CALL L19B8 ; routine NEXT-ONE finds next in workspace. ; gets next in DE, difference in BC. ; prev addr in HL LD ($5C5F),HL ; store pointer in X_PTR LD HL,($5C53) ; load HL from system variable PROG EX (SP),HL ; swap with prog/vars pointer on stack. PUSH BC ; ** save length of new program line/variable. EX AF,AF' ; fetch flags back. JR C,L0955 ; skip to ME-ENT-2 if variable DEC HL ; address location before pointer CALL L1655 ; routine MAKE-ROOM creates room for BASIC line INC HL ; address next. JR L0958 ; forward to ME-ENT-3 ; --- ;; ME-ENT-2 L0955: CALL L1655 ; routine MAKE-ROOM creates room for variable. ;; ME-ENT-3 L0958: INC HL ; address next? POP BC ; ** pop length POP DE ; * pop value for PROG which may have been ; altered by POINTERS if first line. LD ($5C53),DE ; set PROG to original value. LD DE,($5C5F) ; fetch adjusted workspace pointer from X_PTR PUSH BC ; save length PUSH DE ; and workspace pointer EX DE,HL ; make workspace pointer source, prog/vars ; pointer the destination LDIR ; copy bytes of line or variable into new area. POP HL ; restore workspace pointer. POP BC ; restore length. PUSH DE ; save new prog/vars pointer. CALL L19E8 ; routine RECLAIM-2 reclaims the space used ; by the line or variable in workspace block ; as no longer required and space could be ; useful for adding more lines. POP DE ; restore the prog/vars pointer RET ; return. ; -------------------------- ; THE 'SAVE CONTROL' ROUTINE ; -------------------------- ; A branch from the main SAVE-ETC routine at SAVE-ALL. ; First the header data is saved. Then after a wait of 1 second ; the data itself is saved. ; HL points to start of data. ; IX points to start of descriptor. ;; SA-CONTRL L0970: PUSH HL ; save start of data LD A,$FD ; select system channel 'S' CALL L1601 ; routine CHAN-OPEN XOR A ; clear to address table directly LD DE,L09A1 ; address: tape-msgs CALL L0C0A ; routine PO-MSG - ; 'Start tape then press any key.' SET 5,(IY+$02) ; TV_FLAG - Signal lower screen requires ; clearing CALL L15D4 ; routine WAIT-KEY PUSH IX ; save pointer to descriptor. LD DE,$0011 ; there are seventeen bytes. XOR A ; signal a header. CALL L04C2 ; routine SA-BYTES POP IX ; restore descriptor pointer. LD B,$32 ; wait for a second - 50 interrupts. ;; SA-1-SEC L0991: HALT ; wait for interrupt DJNZ L0991 ; back to SA-1-SEC until pause complete. LD E,(IX+$0B) ; fetch length of bytes from the LD D,(IX+$0C) ; descriptor. LD A,$FF ; signal data bytes. POP IX ; retrieve pointer to start JP L04C2 ; jump back to SA-BYTES ; Arrangement of two headers in workspace. ; Originally IX addresses first location and only one header is required ; when saving. ; ; OLD NEW PROG DATA DATA CODE ; HEADER HEADER num chr NOTES. ; ------ ------ ---- ---- ---- ---- ----------------------------- ; IX-$11 IX+$00 0 1 2 3 Type. ; IX-$10 IX+$01 x x x x F ($FF if filename is null). ; IX-$0F IX+$02 x x x x i ; IX-$0E IX+$03 x x x x l ; IX-$0D IX+$04 x x x x e ; IX-$0C IX+$05 x x x x n ; IX-$0B IX+$06 x x x x a ; IX-$0A IX+$07 x x x x m ; IX-$09 IX+$08 x x x x e ; IX-$08 IX+$09 x x x x . ; IX-$07 IX+$0A x x x x (terminal spaces). ; IX-$06 IX+$0B lo lo lo lo Total ; IX-$05 IX+$0C hi hi hi hi Length of datablock. ; IX-$04 IX+$0D Auto - - Start Various ; IX-$03 IX+$0E Start a-z a-z addr ($80 if no autostart). ; IX-$02 IX+$0F lo - - - Length of Program ; IX-$01 IX+$10 hi - - - only i.e. without variables. ; ; ------------------------ ; Canned cassette messages ; ------------------------ ; The last-character-inverted Cassette messages. ; Starts with normal initial step-over byte. ;; tape-msgs L09A1: DEFB $80 DEFM "Start tape, then press any key" L09C0: DEFB '.'+$80 DEFB $0D DEFM "Program:" DEFB ' '+$80 DEFB $0D DEFM "Number array:" DEFB ' '+$80 DEFB $0D DEFM "Character array:" DEFB ' '+$80 DEFB $0D DEFM "Bytes:" DEFB ' '+$80 ;************************************************** ;** Part 5. SCREEN AND PRINTER HANDLING ROUTINES ** ;************************************************** ; -------------------------- ; THE 'PRINT OUTPUT' ROUTINE ; -------------------------- ; This is the routine most often used by the RST 10 restart although the ; subroutine is on two occasions called directly when it is known that ; output will definitely be to the lower screen. ;; PRINT-OUT L09F4: CALL L0B03 ; routine PO-FETCH fetches print position ; to HL register pair. CP $20 ; is character a space or higher ? JP NC,L0AD9 ; jump forward to PO-ABLE if so. CP $06 ; is character in range 00-05 ? JR C,L0A69 ; to PO-QUEST to print '?' if so. CP $18 ; is character in range 24d - 31d ? JR NC,L0A69 ; to PO-QUEST to also print '?' if so. LD HL,L0A11 - 6 ; address 0A0B - the base address of control ; character table - where zero would be. LD E,A ; control character 06 - 23d LD D,$00 ; is transferred to DE. ADD HL,DE ; index into table. LD E,(HL) ; fetch the offset to routine. ADD HL,DE ; add to make HL the address. PUSH HL ; push the address. JP L0B03 ; Jump forward to PO-FETCH, ; as the screen/printer position has been ; disturbed, and then indirectly to the PO-STORE ; routine on stack. ; ----------------------------- ; THE 'CONTROL CHARACTER' TABLE ; ----------------------------- ; For control characters in the range 6 - 23d the following table ; is indexed to provide an offset to the handling routine that ; follows the table. ;; ctlchrtab L0A11: DEFB L0A5F - $ ; 06d offset $4E to Address: PO-COMMA DEFB L0A69 - $ ; 07d offset $57 to Address: PO-QUEST DEFB L0A23 - $ ; 08d offset $10 to Address: PO-BACK-1 DEFB L0A3D - $ ; 09d offset $29 to Address: PO-RIGHT DEFB L0A69 - $ ; 10d offset $54 to Address: PO-QUEST DEFB L0A69 - $ ; 11d offset $53 to Address: PO-QUEST DEFB L0A69 - $ ; 12d offset $52 to Address: PO-QUEST DEFB L0A4F - $ ; 13d offset $37 to Address: PO-ENTER DEFB L0A69 - $ ; 14d offset $50 to Address: PO-QUEST DEFB L0A69 - $ ; 15d offset $4F to Address: PO-QUEST DEFB L0A7A - $ ; 16d offset $5F to Address: PO-1-OPER DEFB L0A7A - $ ; 17d offset $5E to Address: PO-1-OPER DEFB L0A7A - $ ; 18d offset $5D to Address: PO-1-OPER DEFB L0A7A - $ ; 19d offset $5C to Address: PO-1-OPER DEFB L0A7A - $ ; 20d offset $5B to Address: PO-1-OPER DEFB L0A7A - $ ; 21d offset $5A to Address: PO-1-OPER DEFB L0A75 - $ ; 22d offset $54 to Address: PO-2-OPER DEFB L0A75 - $ ; 23d offset $53 to Address: PO-2-OPER ; ------------------------- ; THE 'CURSOR LEFT' ROUTINE ; ------------------------- ; Backspace and up a line if that action is from the left of screen. ; For ZX printer backspace up to first column but not beyond. ;; PO-BACK-1 L0A23: INC C ; move left one column. LD A,$22 ; value $21 is leftmost column. CP C ; have we passed ? JR NZ,L0A3A ; to PO-BACK-3 if not and store new position. BIT 1,(IY+$01) ; test FLAGS - is printer in use ? JR NZ,L0A38 ; to PO-BACK-2 if so, as we are unable to ; backspace from the leftmost position. INC B ; move up one screen line LD C,$02 ; the rightmost column position. LD A,$18 ; Note. This should be $19 ; credit. Dr. Frank O'Hara, 1982 CP B ; has position moved past top of screen ? JR NZ,L0A3A ; to PO-BACK-3 if not and store new position. DEC B ; else back to $18. ;; PO-BACK-2 L0A38: LD C,$21 ; the leftmost column position. ;; PO-BACK-3 L0A3A: JP L0DD9 ; to CL-SET and PO-STORE to save new ; position in system variables. ; -------------------------- ; THE 'CURSOR RIGHT' ROUTINE ; -------------------------- ; This moves the print position to the right leaving a trail in the ; current background colour. ; "However the programmer has failed to store the new print position ; so CHR$ 9 will only work if the next print position is at a newly ; defined place. ; e.g. PRINT PAPER 2; CHR$ 9; AT 4,0; ; does work but is not very helpful" ; - Dr. Ian Logan, Understanding Your Spectrum, 1982. ;; PO-RIGHT L0A3D: LD A,($5C91) ; fetch P_FLAG value PUSH AF ; and save it on stack. LD (IY+$57),$01 ; temporarily set P_FLAG 'OVER 1'. LD A,$20 ; prepare a space. CALL L0B65 ; routine PO-CHAR to print it. ; Note. could be PO-ABLE which would update ; the column position. POP AF ; restore the permanent flag. LD ($5C91),A ; and restore system variable P_FLAG RET ; return without updating column position ; ----------------------- ; Perform carriage return ; ----------------------- ; A carriage return is 'printed' to screen or printer buffer. ;; PO-ENTER L0A4F: BIT 1,(IY+$01) ; test FLAGS - is printer in use ? JP NZ,L0ECD ; to COPY-BUFF if so, to flush buffer and reset ; the print position. LD C,$21 ; the leftmost column position. CALL L0C55 ; routine PO-SCR handles any scrolling required. DEC B ; to next screen line. JP L0DD9 ; jump forward to CL-SET to store new position. ; ----------- ; Print comma ; ----------- ; The comma control character. The 32 column screen has two 16 character ; tabstops. The routine is only reached via the control character table. ;; PO-COMMA L0A5F: CALL L0B03 ; routine PO-FETCH - seems unnecessary. LD A,C ; the column position. $21-$01 DEC A ; move right. $20-$00 DEC A ; and again $1F-$00 or $FF if trailing AND $10 ; will be $00 or $10. JR L0AC3 ; forward to PO-FILL ; ------------------- ; Print question mark ; ------------------- ; This routine prints a question mark which is commonly ; used to print an unassigned control character in range 0-31d. ; there are a surprising number yet to be assigned. ;; PO-QUEST L0A69: LD A,$3F ; prepare the character '?'. JR L0AD9 ; forward to PO-ABLE. ; -------------------------------- ; Control characters with operands ; -------------------------------- ; Certain control characters are followed by 1 or 2 operands. ; The entry points from control character table are PO-2-OPER and PO-1-OPER. ; The routines alter the output address of the current channel so that ; subsequent RST $10 instructions take the appropriate action ; before finally resetting the output address back to PRINT-OUT. ;; PO-TV-2 L0A6D: LD DE,L0A87 ; address: PO-CONT will be next output routine LD ($5C0F),A ; store first operand in TVDATA-hi JR L0A80 ; forward to PO-CHANGE >> ; --- ; -> This initial entry point deals with two operands - AT or TAB. ;; PO-2-OPER L0A75: LD DE,L0A6D ; address: PO-TV-2 will be next output routine JR L0A7D ; forward to PO-TV-1 ; --- ; -> This initial entry point deals with one operand INK to OVER. ;; PO-1-OPER L0A7A: LD DE,L0A87 ; address: PO-CONT will be next output routine ;; PO-TV-1 L0A7D: LD ($5C0E),A ; store control code in TVDATA-lo ;; PO-CHANGE L0A80: LD HL,($5C51) ; use CURCHL to find current output channel. LD (HL),E ; make it INC HL ; the supplied LD (HL),D ; address from DE. RET ; return. ; --- ;; PO-CONT L0A87: LD DE,L09F4 ; Address: PRINT-OUT CALL L0A80 ; routine PO-CHANGE to restore normal channel. LD HL,($5C0E) ; TVDATA gives control code and possible ; subsequent character LD D,A ; save current character LD A,L ; the stored control code CP $16 ; was it INK to OVER (1 operand) ? JP C,L2211 ; to CO-TEMP-5 JR NZ,L0AC2 ; to PO-TAB if not 22d i.e. 23d TAB. ; else must have been 22d AT. LD B,H ; line to H (0-23d) LD C,D ; column to C (0-31d) LD A,$1F ; the value 31d SUB C ; reverse the column number. JR C,L0AAC ; to PO-AT-ERR if C was greater than 31d. ADD A,$02 ; transform to system range $02-$21 LD C,A ; and place in column register. BIT 1,(IY+$01) ; test FLAGS - is printer in use ? JR NZ,L0ABF ; to PO-AT-SET as line can be ignored. LD A,$16 ; 22 decimal SUB B ; subtract line number to reverse ; 0 - 22 becomes 22 - 0. ;; PO-AT-ERR L0AAC: JP C,L1E9F ; to REPORT-B if higher than 22 decimal ; Integer out of range. INC A ; adjust for system range $01-$17 LD B,A ; place in line register INC B ; adjust to system range $02-$18 BIT 0,(IY+$02) ; TV_FLAG - Lower screen in use ? JP NZ,L0C55 ; exit to PO-SCR to test for scrolling CP (IY+$31) ; Compare against DF_SZ JP C,L0C86 ; to REPORT-5 if too low ; Out of screen. ;; PO-AT-SET L0ABF: JP L0DD9 ; print position is valid so exit via CL-SET ; --- ; Continue here when dealing with TAB. ; Note. In BASIC, TAB is followed by a 16-bit number and was initially ; designed to work with any output device. ;; PO-TAB L0AC2: LD A,H ; transfer parameter to A ; Losing current character - ; High byte of TAB parameter. ;; PO-FILL L0AC3: CALL L0B03 ; routine PO-FETCH, HL-addr, BC=line/column. ; column 1 (right), $21 (left) ADD A,C ; add operand to current column DEC A ; range 0 - 31+ AND $1F ; make range 0 - 31d RET Z ; return if result zero LD D,A ; Counter to D SET 0,(IY+$01) ; update FLAGS - signal suppress leading space. ;; PO-SPACE L0AD0: LD A,$20 ; space character. CALL L0C3B ; routine PO-SAVE prints the character ; using alternate set (normal output routine) DEC D ; decrement counter. JR NZ,L0AD0 ; to PO-SPACE until done RET ; return ; ---------------------- ; Printable character(s) ; ---------------------- ; This routine prints printable characters and continues into ; the position store routine ;; PO-ABLE L0AD9: CALL L0B24 ; routine PO-ANY ; and continue into position store routine. ; ---------------------------- ; THE 'POSITION STORE' ROUTINE ; ---------------------------- ; This routine updates the system variables associated with the main screen, ; the lower screen/input buffer or the ZX printer. ;; PO-STORE L0ADC: BIT 1,(IY+$01) ; Test FLAGS - is printer in use ? JR NZ,L0AFC ; Forward, if so, to PO-ST-PR BIT 0,(IY+$02) ; Test TV_FLAG - is lower screen in use ? JR NZ,L0AF0 ; Forward, if so, to PO-ST-E ; This section deals with the upper screen. LD ($5C88),BC ; Update S_POSN - line/column upper screen LD ($5C84),HL ; Update DF_CC - upper display file address RET ; Return. ; --- ; This section deals with the lower screen. ;; PO-ST-E L0AF0: LD ($5C8A),BC ; Update SPOSNL line/column lower screen LD ($5C82),BC ; Update ECHO_E line/column input buffer LD ($5C86),HL ; Update DFCCL lower screen memory address RET ; Return. ; --- ; This section deals with the ZX Printer. ;; PO-ST-PR L0AFC: LD (IY+$45),C ; Update P_POSN column position printer LD ($5C80),HL ; Update PR_CC - full printer buffer memory ; address RET ; Return. ; Note. that any values stored in location 23681 will be overwritten with ; the value 91 decimal. ; Credit April 1983, Dilwyn Jones. "Delving Deeper into your ZX Spectrum". ; ---------------------------- ; THE 'POSITION FETCH' ROUTINE ; ---------------------------- ; This routine fetches the line/column and display file address of the upper ; and lower screen or, if the printer is in use, the column position and ; absolute memory address. ; Note. that PR-CC-hi (23681) is used by this routine and if, in accordance ; with the manual (that says this is unused), the location has been used for ; other purposes, then subsequent output to the printer buffer could corrupt ; a 256-byte section of memory. ;; PO-FETCH L0B03: BIT 1,(IY+$01) ; Test FLAGS - is printer in use ? JR NZ,L0B1D ; Forward, if so, to PO-F-PR ; assume upper screen in use and thus optimize for path that requires speed. LD BC,($5C88) ; Fetch line/column from S_POSN LD HL,($5C84) ; Fetch DF_CC display file address BIT 0,(IY+$02) ; Test TV_FLAG - lower screen in use ? RET Z ; Return if upper screen in use. ; Overwrite registers with values for lower screen. LD BC,($5C8A) ; Fetch line/column from SPOSNL LD HL,($5C86) ; Fetch display file address from DFCCL RET ; Return. ; --- ; This section deals with the ZX Printer. ;; PO-F-PR L0B1D: LD C,(IY+$45) ; Fetch column from P_POSN. LD HL,($5C80) ; Fetch printer buffer address from PR_CC. RET ; Return. ; --------------------------------- ; THE 'PRINT ANY CHARACTER' ROUTINE ; --------------------------------- ; This routine is used to print any character in range 32d - 255d ; It is only called from PO-ABLE which continues into PO-STORE ;; PO-ANY L0B24: CP $80 ; ASCII ? JR C,L0B65 ; to PO-CHAR is so. CP $90 ; test if a block graphic character. JR NC,L0B52 ; to PO-T&UDG to print tokens and UDGs ; The 16 2*2 mosaic characters 128-143 decimal are formed from ; bits 0-3 of the character. LD B,A ; save character CALL L0B38 ; routine PO-GR-1 to construct top half ; then bottom half. CALL L0B03 ; routine PO-FETCH fetches print position. LD DE,$5C92 ; MEM-0 is location of 8 bytes of character JR L0B7F ; to PR-ALL to print to screen or printer ; --- ;; PO-GR-1 L0B38: LD HL,$5C92 ; address MEM-0 - a temporary buffer in ; systems variables which is normally used ; by the calculator. CALL L0B3E ; routine PO-GR-2 to construct top half ; and continue into routine to construct ; bottom half. ;; PO-GR-2 L0B3E: RR B ; rotate bit 0/2 to carry SBC A,A ; result $00 or $FF AND $0F ; mask off right hand side LD C,A ; store part in C RR B ; rotate bit 1/3 of original chr to carry SBC A,A ; result $00 or $FF AND $F0 ; mask off left hand side OR C ; combine with stored pattern LD C,$04 ; four bytes for top/bottom half ;; PO-GR-3 L0B4C: LD (HL),A ; store bit patterns in temporary buffer INC HL ; next address DEC C ; jump back to JR NZ,L0B4C ; to PO-GR-3 until byte is stored 4 times RET ; return ; --- ; Tokens and User defined graphics are now separated. ;; PO-T&UDG L0B52: SUB $A5 ; the 'RND' character JR NC,L0B5F ; to PO-T to print tokens ADD A,$15 ; add 21d to restore to 0 - 20 PUSH BC ; save current print position LD BC,($5C7B) ; fetch UDG to address bit patterns JR L0B6A ; to PO-CHAR-2 - common code to lay down ; a bit patterned character ; --- ;; PO-T L0B5F: CALL L0C10 ; routine PO-TOKENS prints tokens JP L0B03 ; exit via a JUMP to PO-FETCH as this routine ; must continue into PO-STORE. ; A JR instruction could be used. ; This point is used to print ASCII characters 32d - 127d. ;; PO-CHAR L0B65: PUSH BC ; save print position LD BC,($5C36) ; address CHARS ; This common code is used to transfer the character bytes to memory. ;; PO-CHAR-2 L0B6A: EX DE,HL ; transfer destination address to DE LD HL,$5C3B ; point to FLAGS RES 0,(HL) ; allow for leading space CP $20 ; is it a space ? JR NZ,L0B76 ; to PO-CHAR-3 if not SET 0,(HL) ; signal no leading space to FLAGS ;; PO-CHAR-3 L0B76: LD H,$00 ; set high byte to 0 LD L,A ; character to A ; 0-21 UDG or 32-127 ASCII. ADD HL,HL ; multiply ADD HL,HL ; by ADD HL,HL ; eight ADD HL,BC ; HL now points to first byte of character POP BC ; the source address CHARS or UDG EX DE,HL ; character address to DE ; ---------------------------------- ; THE 'PRINT ALL CHARACTERS' ROUTINE ; ---------------------------------- ; This entry point entered from above to print ASCII and UDGs but also from ; earlier to print mosaic characters. ; HL=destination ; DE=character source ; BC=line/column ;; PR-ALL L0B7F: LD A,C ; column to A DEC A ; move right LD A,$21 ; pre-load with leftmost position JR NZ,L0B93 ; but if not zero to PR-ALL-1 DEC B ; down one line LD C,A ; load C with $21 BIT 1,(IY+$01) ; test FLAGS - Is printer in use JR Z,L0B93 ; to PR-ALL-1 if not PUSH DE ; save source address CALL L0ECD ; routine COPY-BUFF outputs line to printer POP DE ; restore character source address LD A,C ; the new column number ($21) to C ;; PR-ALL-1 L0B93: CP C ; this test is really for screen - new line ? PUSH DE ; save source CALL Z,L0C55 ; routine PO-SCR considers scrolling POP DE ; restore source PUSH BC ; save line/column PUSH HL ; and destination LD A,($5C91) ; fetch P_FLAG to accumulator LD B,$FF ; prepare OVER mask in B. RRA ; bit 0 set if OVER 1 JR C,L0BA4 ; to PR-ALL-2 INC B ; set OVER mask to 0 ;; PR-ALL-2 L0BA4: RRA ; skip bit 1 of P_FLAG RRA ; bit 2 is INVERSE SBC A,A ; will be FF for INVERSE 1 else zero LD C,A ; transfer INVERSE mask to C LD A,$08 ; prepare to count 8 bytes AND A ; clear carry to signal screen BIT 1,(IY+$01) ; test FLAGS - is printer in use ? JR Z,L0BB6 ; to PR-ALL-3 if screen SET 1,(IY+$30) ; update FLAGS2 - signal printer buffer has ; been used. SCF ; set carry flag to signal printer. ;; PR-ALL-3 L0BB6: EX DE,HL ; now HL=source, DE=destination ;; PR-ALL-4 L0BB7: EX AF,AF' ; save printer/screen flag LD A,(DE) ; fetch existing destination byte AND B ; consider OVER XOR (HL) ; now XOR with source XOR C ; now with INVERSE MASK LD (DE),A ; update screen/printer EX AF,AF' ; restore flag JR C,L0BD3 ; to PR-ALL-6 - printer address update INC D ; gives next pixel line down screen ;; PR-ALL-5 L0BC1: INC HL ; address next character byte DEC A ; the byte count is decremented JR NZ,L0BB7 ; back to PR-ALL-4 for all 8 bytes EX DE,HL ; destination to HL DEC H ; bring back to last updated screen position BIT 1,(IY+$01) ; test FLAGS - is printer in use ? CALL Z,L0BDB ; if not, call routine PO-ATTR to update ; corresponding colour attribute. POP HL ; restore original screen/printer position POP BC ; and line column DEC C ; move column to right INC HL ; increase screen/printer position RET ; return and continue into PO-STORE ; within PO-ABLE ; --- ; This branch is used to update the printer position by 32 places ; Note. The high byte of the address D remains constant (which it should). ;; PR-ALL-6 L0BD3: EX AF,AF' ; save the flag LD A,$20 ; load A with 32 decimal ADD A,E ; add this to E LD E,A ; and store result in E EX AF,AF' ; fetch the flag JR L0BC1 ; back to PR-ALL-5 ; ----------------------------------- ; THE 'GET ATTRIBUTE ADDRESS' ROUTINE ; ----------------------------------- ; This routine is entered with the HL register holding the last screen ; address to be updated by PRINT or PLOT. ; The Spectrum screen arrangement leads to the L register holding the correct ; value for the attribute file and it is only necessary to manipulate H to ; form the correct colour attribute address. ;; PO-ATTR L0BDB: LD A,H ; fetch high byte $40 - $57 RRCA ; shift RRCA ; bits 3 and 4 RRCA ; to right. AND $03 ; range is now 0 - 2 OR $58 ; form correct high byte for third of screen LD H,A ; HL is now correct LD DE,($5C8F) ; make D hold ATTR_T, E hold MASK-T LD A,(HL) ; fetch existing attribute XOR E ; apply masks AND D ; XOR E ; BIT 6,(IY+$57) ; test P_FLAG - is this PAPER 9 ?? JR Z,L0BFA ; skip to PO-ATTR-1 if not. AND $C7 ; set paper BIT 2,A ; to contrast with ink JR NZ,L0BFA ; skip to PO-ATTR-1 XOR $38 ; ;; PO-ATTR-1 L0BFA: BIT 4,(IY+$57) ; test P_FLAG - Is this INK 9 ?? JR Z,L0C08 ; skip to PO-ATTR-2 if not AND $F8 ; make ink BIT 5,A ; contrast with paper. JR NZ,L0C08 ; to PO-ATTR-2 XOR $07 ; ;; PO-ATTR-2 L0C08: LD (HL),A ; save the new attribute. RET ; return. ; --------------------------------- ; THE 'MESSAGE PRINTING' SUBROUTINE ; --------------------------------- ; This entry point is used to print tape, boot-up, scroll? and error messages. ; On entry the DE register points to an initial step-over byte or the ; inverted end-marker of the previous entry in the table. ; Register A contains the message number, often zero to print first message. ; (HL has nothing important usually P_FLAG) ;; PO-MSG L0C0A: PUSH HL ; put hi-byte zero on stack to suppress LD H,$00 ; trailing spaces EX (SP),HL ; ld h,0; push hl would have done ?. JR L0C14 ; forward to PO-TABLE. ; --- ; This entry point prints the BASIC keywords, '<>' etc. from alt set ;; PO-TOKENS L0C10: LD DE,L0095 ; address: TKN-TABLE PUSH AF ; save the token number to control ; trailing spaces - see later * ; -> ;; PO-TABLE L0C14: CALL L0C41 ; routine PO-SEARCH will set carry for ; all messages and function words. JR C,L0C22 ; forward to PO-EACH if not a command, '<>' etc. LD A,$20 ; prepare leading space BIT 0,(IY+$01) ; test FLAGS - leading space if not set CALL Z,L0C3B ; routine PO-SAVE to print a space without ; disturbing registers. ;; PO-EACH L0C22: LD A,(DE) ; Fetch character from the table. AND $7F ; Cancel any inverted bit. CALL L0C3B ; Routine PO-SAVE to print using the alternate ; set of registers. LD A,(DE) ; Re-fetch character from table. INC DE ; Address next character in the table. ADD A,A ; Was character inverted ? ; (this also doubles character) JR NC,L0C22 ; back to PO-EACH if not. POP DE ; * re-fetch trailing space byte to D CP $48 ; was the last character '$' ? JR Z,L0C35 ; forward to PO-TR-SP to consider trailing ; space if so. CP $82 ; was it < 'A' i.e. '#','>','=' from tokens ; or ' ','.' (from tape) or '?' from scroll RET C ; Return if so as no trailing space required. ;; PO-TR-SP L0C35: LD A,D ; The trailing space flag (zero if an error msg) CP $03 ; Test against RND, INKEY$ and PI which have no ; parameters and therefore no trailing space. RET C ; Return if no trailing space. LD A,$20 ; Prepare the space character and continue to ; print and make an indirect return. ; ----------------------------------- ; THE 'RECURSIVE PRINTING' SUBROUTINE ; ----------------------------------- ; This routine which is part of PRINT-OUT allows RST $10 to be used ; recursively to print tokens and the spaces associated with them. ; It is called on three occasions when the value of DE must be preserved. ;; PO-SAVE L0C3B: PUSH DE ; Save DE value. EXX ; Switch in main set RST 10H ; PRINT-A prints using this alternate set. EXX ; Switch back to this alternate set. POP DE ; Restore the initial DE value. RET ; Return. ; ------------ ; Table search ; ------------ ; This subroutine searches a message or the token table for the ; message number held in A. DE holds the address of the table. ;; PO-SEARCH L0C41: PUSH AF ; save the message/token number EX DE,HL ; transfer DE to HL INC A ; adjust for initial step-over byte ;; PO-STEP L0C44: BIT 7,(HL) ; is character inverted ? INC HL ; address next JR Z,L0C44 ; back to PO-STEP if not inverted. DEC A ; decrease counter JR NZ,L0C44 ; back to PO-STEP if not zero EX DE,HL ; transfer address to DE POP AF ; restore message/token number CP $20 ; return with carry set RET C ; for all messages and function tokens LD A,(DE) ; test first character of token SUB $41 ; and return with carry set RET ; if it is less that 'A' ; i.e. '<>', '<=', '>=' ; --------------- ; Test for scroll ; --------------- ; This test routine is called when printing carriage return, when considering ; PRINT AT and from the general PRINT ALL characters routine to test if ; scrolling is required, prompting the user if necessary. ; This is therefore using the alternate set. ; The B register holds the current line. ;; PO-SCR L0C55: BIT 1,(IY+$01) ; test FLAGS - is printer in use ? RET NZ ; return immediately if so. LD DE,L0DD9 ; set DE to address: CL-SET PUSH DE ; and push for return address. LD A,B ; transfer the line to A. BIT 0,(IY+$02) ; test TV_FLAG - lower screen in use ? JP NZ,L0D02 ; jump forward to PO-SCR-4 if so. CP (IY+$31) ; greater than DF_SZ display file size ? JR C,L0C86 ; forward to REPORT-5 if less. ; 'Out of screen' RET NZ ; return (via CL-SET) if greater BIT 4,(IY+$02) ; test TV_FLAG - Automatic listing ? JR Z,L0C88 ; forward to PO-SCR-2 if not. LD E,(IY+$2D) ; fetch BREG - the count of scroll lines to E. DEC E ; decrease and jump JR Z,L0CD2 ; to PO-SCR-3 if zero and scrolling required. LD A,$00 ; explicit - select channel zero. CALL L1601 ; routine CHAN-OPEN opens it. LD SP,($5C3F) ; set stack pointer to LIST_SP RES 4,(IY+$02) ; reset TV_FLAG - signal auto listing finished. RET ; return ignoring pushed value, CL-SET ; to MAIN or EDITOR without updating ; print position >> ; --- ;; REPORT-5 L0C86: RST 08H ; ERROR-1 DEFB $04 ; Error Report: Out of screen ; continue here if not an automatic listing. ;; PO-SCR-2 L0C88: DEC (IY+$52) ; decrease SCR_CT JR NZ,L0CD2 ; forward to PO-SCR-3 to scroll display if ; result not zero. ; now produce prompt. LD A,$18 ; reset SUB B ; the LD ($5C8C),A ; SCR_CT scroll count LD HL,($5C8F) ; L=ATTR_T, H=MASK_T PUSH HL ; save on stack LD A,($5C91) ; P_FLAG PUSH AF ; save on stack to prevent lower screen ; attributes (BORDCR etc.) being applied. LD A,$FD ; select system channel 'K' CALL L1601 ; routine CHAN-OPEN opens it XOR A ; clear to address message directly LD DE,L0CF8 ; make DE address: scrl-mssg CALL L0C0A ; routine PO-MSG prints to lower screen SET 5,(IY+$02) ; set TV_FLAG - signal lower screen requires ; clearing LD HL,$5C3B ; make HL address FLAGS SET 3,(HL) ; signal 'L' mode. RES 5,(HL) ; signal 'no new key'. EXX ; switch to main set. ; as calling chr input from alternative set. CALL L15D4 ; routine WAIT-KEY waits for new key ; Note. this is the right routine but the ; stream in use is unsatisfactory. From the ; choices available, it is however the best. EXX ; switch back to alternate set. CP $20 ; space is considered as BREAK JR Z,L0D00 ; forward to REPORT-D if so ; 'BREAK - CONT repeats' CP $E2 ; is character 'STOP' ? JR Z,L0D00 ; forward to REPORT-D if so OR $20 ; convert to lower-case CP $6E ; is character 'n' ? JR Z,L0D00 ; forward to REPORT-D if so else scroll. LD A,$FE ; select system channel 'S' CALL L1601 ; routine CHAN-OPEN POP AF ; restore original P_FLAG LD ($5C91),A ; and save in P_FLAG. POP HL ; restore original ATTR_T, MASK_T LD ($5C8F),HL ; and reset ATTR_T, MASK-T as 'scroll?' has ; been printed. ;; PO-SCR-3 L0CD2: CALL L0DFE ; routine CL-SC-ALL to scroll whole display LD B,(IY+$31) ; fetch DF_SZ to B INC B ; increase to address last line of display LD C,$21 ; set C to $21 (was $21 from above routine) PUSH BC ; save the line and column in BC. CALL L0E9B ; routine CL-ADDR finds display address. LD A,H ; now find the corresponding attribute byte RRCA ; (this code sequence is used twice RRCA ; elsewhere and is a candidate for RRCA ; a subroutine.) AND $03 ; OR $58 ; LD H,A ; LD DE,$5AE0 ; start of last 'line' of attribute area LD A,(DE) ; get attribute for last line LD C,(HL) ; transfer to base line of upper part LD B,$20 ; there are thirty two bytes EX DE,HL ; swap the pointers. ;; PO-SCR-3A L0CF0: LD (DE),A ; transfer LD (HL),C ; attributes. INC DE ; address next. INC HL ; address next. DJNZ L0CF0 ; loop back to PO-SCR-3A for all adjacent ; attribute lines. POP BC ; restore the line/column. RET ; return via CL-SET (was pushed on stack). ; --- ; The message 'scroll?' appears here with last byte inverted. ;; scrl-mssg L0CF8: DEFB $80 ; initial step-over byte. DEFM "scroll" DEFB '?'+$80 ;; REPORT-D L0D00: RST 08H ; ERROR-1 DEFB $0C ; Error Report: BREAK - CONT repeats ; continue here if using lower display - A holds line number. ;; PO-SCR-4 L0D02: CP $02 ; is line number less than 2 ? JR C,L0C86 ; to REPORT-5 if so ; 'Out of Screen'. ADD A,(IY+$31) ; add DF_SZ SUB $19 ; RET NC ; return if scrolling unnecessary NEG ; Negate to give number of scrolls required. PUSH BC ; save line/column LD B,A ; count to B LD HL,($5C8F) ; fetch current ATTR_T, MASK_T to HL. PUSH HL ; and save LD HL,($5C91) ; fetch P_FLAG PUSH HL ; and save. ; to prevent corruption by input AT CALL L0D4D ; routine TEMPS sets to BORDCR etc LD A,B ; transfer scroll number to A. ;; PO-SCR-4A L0D1C: PUSH AF ; save scroll number. LD HL,$5C6B ; address DF_SZ LD B,(HL) ; fetch old value LD A,B ; transfer to A INC A ; and increment LD (HL),A ; then put back. LD HL,$5C89 ; address S_POSN_hi - line CP (HL) ; compare JR C,L0D2D ; forward to PO-SCR-4B if scrolling required INC (HL) ; else increment S_POSN_hi LD B,$18 ; set count to whole display ?? ; Note. should be $17 and the top line will be ; scrolled into the ROM which is harmless on ; the standard set up. ; credit P.Giblin 1984. ;; PO-SCR-4B L0D2D: CALL L0E00 ; routine CL-SCROLL scrolls B lines POP AF ; restore scroll counter. DEC A ; decrease JR NZ,L0D1C ; back to PO-SCR-4A until done POP HL ; restore original P_FLAG. LD (IY+$57),L ; and overwrite system variable P_FLAG. POP HL ; restore original ATTR_T/MASK_T. LD ($5C8F),HL ; and update system variables. LD BC,($5C88) ; fetch S_POSN to BC. RES 0,(IY+$02) ; signal to TV_FLAG - main screen in use. CALL L0DD9 ; call routine CL-SET for upper display. SET 0,(IY+$02) ; signal to TV_FLAG - lower screen in use. POP BC ; restore line/column RET ; return via CL-SET for lower display. ; ---------------------- ; Temporary colour items ; ---------------------- ; This subroutine is called 11 times to copy the permanent colour items ; to the temporary ones. ;; TEMPS L0D4D: XOR A ; clear the accumulator LD HL,($5C8D) ; fetch L=ATTR_P and H=MASK_P BIT 0,(IY+$02) ; test TV_FLAG - is lower screen in use ? JR Z,L0D5B ; skip to TEMPS-1 if not LD H,A ; set H, MASK P, to 00000000. LD L,(IY+$0E) ; fetch BORDCR to L which is used for lower ; screen. ;; TEMPS-1 L0D5B: LD ($5C8F),HL ; transfer values to ATTR_T and MASK_T ; for the print flag the permanent values are odd bits, temporary even bits. LD HL,$5C91 ; address P_FLAG. JR NZ,L0D65 ; skip to TEMPS-2 if lower screen using A=0. LD A,(HL) ; else pick up flag bits. RRCA ; rotate permanent bits to temporary bits. ;; TEMPS-2 L0D65: XOR (HL) ; AND $55 ; BIN 01010101 XOR (HL) ; permanent now as original LD (HL),A ; apply permanent bits to temporary bits. RET ; and return. ; ----------------- ; THE 'CLS' COMMAND ; ----------------- ; This command clears the display. ; The routine is also called during initialization and by the CLEAR command. ; If it's difficult to write it should be difficult to read. ;; CLS L0D6B: CALL L0DAF ; Routine CL-ALL clears the entire display and ; sets the attributes to the permanent ones ; from ATTR-P. ; Having cleared all 24 lines of the display area, continue into the ; subroutine that clears the lower display area. Note that at the moment ; the attributes for the lower lines are the same as upper ones and have ; to be changed to match the BORDER colour. ; -------------------------- ; THE 'CLS-LOWER' SUBROUTINE ; -------------------------- ; This routine is called from INPUT, and from the MAIN execution loop. ; This is very much a housekeeping routine which clears between 2 and 23 ; lines of the display, setting attributes and correcting situations where ; errors have occurred while the normal input and output routines have been ; temporarily diverted to deal with, say colour control codes. ;; CLS-LOWER L0D6E: LD HL,$5C3C ; address System Variable TV_FLAG. RES 5,(HL) ; TV_FLAG - signal do not clear lower screen. SET 0,(HL) ; TV_FLAG - signal lower screen in use. CALL L0D4D ; routine TEMPS applies permanent attributes, ; in this case BORDCR to ATTR_T. ; Note. this seems unnecessary and is repeated ; within CL-LINE. LD B,(IY+$31) ; fetch lower screen display file size DF_SZ CALL L0E44 ; routine CL-LINE clears lines to bottom of the ; display and sets attributes from BORDCR while ; preserving the B register. LD HL,$5AC0 ; set initial attribute address to the leftmost ; cell of second line up. LD A,($5C8D) ; fetch permanent attribute from ATTR_P. DEC B ; decrement lower screen display file size. JR L0D8E ; forward to enter the backfill loop at CLS-3 ; where B is decremented again. ; --- ; The backfill loop is entered at midpoint and ensures, if more than 2 ; lines have been cleared, that any other lines take the permanent screen ; attributes. ;; CLS-1 L0D87: LD C,$20 ; set counter to 32 character cells per line ;; CLS-2 L0D89: DEC HL ; decrease attribute address. LD (HL),A ; and place attributes in next line up. DEC C ; decrease the 32 counter. JR NZ,L0D89 ; loop back to CLS-2 until all 32 cells done. ;; CLS-3 L0D8E: DJNZ L0D87 ; decrease B counter and back to CLS-1 ; if not zero. LD (IY+$31),$02 ; now set DF_SZ lower screen to 2 ; This entry point is also called from CL-ALL below to ; reset the system channel input and output addresses to normal. ;; CL-CHAN L0D94: LD A,$FD ; select system channel 'K' CALL L1601 ; routine CHAN-OPEN opens it. LD HL,($5C51) ; fetch CURCHL to HL to address current channel LD DE,L09F4 ; set address to PRINT-OUT for first pass. AND A ; clear carry for first pass. ;; CL-CHAN-A L0DA0: LD (HL),E ; Insert the output address on the first pass INC HL ; or the input address on the second pass. LD (HL),D ; INC HL ; LD DE,L10A8 ; fetch address KEY-INPUT for second pass CCF ; complement carry flag - will set on pass 1. JR C,L0DA0 ; back to CL-CHAN-A if first pass else done. LD BC,$1721 ; line 23 for lower screen JR L0DD9 ; exit via CL-SET to set column ; for lower display ; --------------------------- ; Clearing whole display area ; --------------------------- ; This subroutine called from CLS, AUTO-LIST and MAIN-3 ; clears 24 lines of the display and resets the relevant system variables. ; This routine also recovers from an error situation where, for instance, an ; invalid colour or position control code has left the output routine addressing ; PO-TV-2 or PO-CONT. ;; CL-ALL L0DAF: LD HL,$0000 ; Initialize plot coordinates. LD ($5C7D),HL ; Set system variable COORDS to 0,0. RES 0,(IY+$30) ; update FLAGS2 - signal main screen is clear. CALL L0D94 ; routine CL-CHAN makes channel 'K' 'normal'. LD A,$FE ; select system channel 'S' CALL L1601 ; routine CHAN-OPEN opens it. CALL L0D4D ; routine TEMPS applies permanent attributes, ; in this case ATTR_P, to ATTR_T. ; Note. this seems unnecessary. LD B,$18 ; There are 24 lines. CALL L0E44 ; routine CL-LINE clears 24 text lines and sets ; attributes from ATTR-P. ; This routine preserves B and sets C to $21. LD HL,($5C51) ; fetch CURCHL make HL address output routine. LD DE,L09F4 ; address: PRINT-OUT LD (HL),E ; is made INC HL ; the normal LD (HL),D ; output address. LD (IY+$52),$01 ; set SCR_CT - scroll count - to default. ; Note. BC already contains $1821. LD BC,$1821 ; reset column and line to 0,0 ; and continue into CL-SET, below, exiting ; via PO-STORE (for the upper screen). ; -------------------- ; THE 'CL-SET' ROUTINE ; -------------------- ; This important subroutine is used to calculate the character output ; address for screens or printer based on the line/column for screens ; or the column for printer. ;; CL-SET L0DD9: LD HL,$5B00 ; the base address of printer buffer BIT 1,(IY+$01) ; test FLAGS - is printer in use ? JR NZ,L0DF4 ; forward to CL-SET-2 if so. LD A,B ; transfer line to A. BIT 0,(IY+$02) ; test TV_FLAG - lower screen in use ? JR Z,L0DEE ; skip to CL-SET-1 if handling upper part ADD A,(IY+$31) ; add DF_SZ for lower screen SUB $18 ; and adjust. ;; CL-SET-1 L0DEE: PUSH BC ; save the line/column. LD B,A ; transfer line to B ; (adjusted if lower screen) CALL L0E9B ; routine CL-ADDR calculates address at left ; of screen. POP BC ; restore the line/column. ;; CL-SET-2 L0DF4: LD A,$21 ; the column $01-$21 is reversed SUB C ; to range $00 - $20 LD E,A ; now transfer to DE LD D,$00 ; prepare for addition ADD HL,DE ; and add to base address JP L0ADC ; exit via PO-STORE to update the relevant ; system variables. ; ---------------- ; Handle scrolling ; ---------------- ; The routine CL-SC-ALL is called once from PO to scroll all the display ; and from the routine CL-SCROLL, once, to scroll part of the display. ;; CL-SC-ALL L0DFE: LD B,$17 ; scroll 23 lines, after 'scroll?'. ;; CL-SCROLL L0E00: CALL L0E9B ; routine CL-ADDR gets screen address in HL. LD C,$08 ; there are 8 pixel lines to scroll. ;; CL-SCR-1 L0E05: PUSH BC ; save counters. PUSH HL ; and initial address. LD A,B ; get line count. AND $07 ; will set zero if all third to be scrolled. LD A,B ; re-fetch the line count. JR NZ,L0E19 ; forward to CL-SCR-3 if partial scroll. ; HL points to top line of third and must be copied to bottom of previous 3rd. ; ( so HL = $4800 or $5000 ) ( but also sometimes $4000 ) ;; CL-SCR-2 L0E0D: EX DE,HL ; copy HL to DE. LD HL,$F8E0 ; subtract $08 from H and add $E0 to L - ADD HL,DE ; to make destination bottom line of previous ; third. EX DE,HL ; restore the source and destination. LD BC,$0020 ; thirty-two bytes are to be copied. DEC A ; decrement the line count. LDIR ; copy a pixel line to previous third. ;; CL-SCR-3 L0E19: EX DE,HL ; save source in DE. LD HL,$FFE0 ; load the value -32. ADD HL,DE ; add to form destination in HL. EX DE,HL ; switch source and destination LD B,A ; save the count in B. AND $07 ; mask to find count applicable to current RRCA ; third and RRCA ; multiply by RRCA ; thirty two (same as 5 RLCAs) LD C,A ; transfer byte count to C ($E0 at most) LD A,B ; store line count to A LD B,$00 ; make B zero LDIR ; copy bytes (BC=0, H incremented, L=0) LD B,$07 ; set B to 7, C is zero. ADD HL,BC ; add 7 to H to address next third. AND $F8 ; has last third been done ? JR NZ,L0E0D ; back to CL-SCR-2 if not. POP HL ; restore topmost address. INC H ; next pixel line down. POP BC ; restore counts. DEC C ; reduce pixel line count. JR NZ,L0E05 ; back to CL-SCR-1 if all eight not done. CALL L0E88 ; routine CL-ATTR gets address in attributes ; from current 'ninth line', count in BC. LD HL,$FFE0 ; set HL to the 16-bit value -32. ADD HL,DE ; and add to form destination address. EX DE,HL ; swap source and destination addresses. LDIR ; copy bytes scrolling the linear attributes. LD B,$01 ; continue to clear the bottom line. ; ------------------------------ ; THE 'CLEAR TEXT LINES' ROUTINE ; ------------------------------ ; This subroutine, called from CL-ALL, CLS-LOWER and AUTO-LIST and above, ; clears text lines at bottom of display. ; The B register holds on entry the number of lines to be cleared 1-24. ;; CL-LINE L0E44: PUSH BC ; save line count CALL L0E9B ; routine CL-ADDR gets top address LD C,$08 ; there are eight screen lines to a text line. ;; CL-LINE-1 L0E4A: PUSH BC ; save pixel line count PUSH HL ; and save the address LD A,B ; transfer the line to A (1-24). ;; CL-LINE-2 L0E4D: AND $07 ; mask 0-7 to consider thirds at a time RRCA ; multiply RRCA ; by 32 (same as five RLCA instructions) RRCA ; now 32 - 256(0) LD C,A ; store result in C LD A,B ; save line in A (1-24) LD B,$00 ; set high byte to 0, prepare for ldir. DEC C ; decrement count 31-255. LD D,H ; copy HL LD E,L ; to DE. LD (HL),$00 ; blank the first byte. INC DE ; make DE point to next byte. LDIR ; ldir will clear lines. LD DE,$0701 ; now address next third adjusting ADD HL,DE ; register E to address left hand side DEC A ; decrease the line count. AND $F8 ; will be 16, 8 or 0 (AND $18 will do). LD B,A ; transfer count to B. JR NZ,L0E4D ; back to CL-LINE-2 if 16 or 8 to do ; the next third. POP HL ; restore start address. INC H ; address next line down. POP BC ; fetch counts. DEC C ; decrement pixel line count JR NZ,L0E4A ; back to CL-LINE-1 till all done. CALL L0E88 ; routine CL-ATTR gets attribute address ; in DE and B * 32 in BC. LD H,D ; transfer the address LD L,E ; to HL. INC DE ; make DE point to next location. LD A,($5C8D) ; fetch ATTR_P - permanent attributes BIT 0,(IY+$02) ; test TV_FLAG - lower screen in use ? JR Z,L0E80 ; skip to CL-LINE-3 if not. LD A,($5C48) ; else lower screen uses BORDCR as attribute. ;; CL-LINE-3 L0E80: LD (HL),A ; put attribute in first byte. DEC BC ; decrement the counter. LDIR ; copy bytes to set all attributes. POP BC ; restore the line $01-$24. LD C,$21 ; make column $21. (No use is made of this) RET ; return to the calling routine. ; ------------------ ; Attribute handling ; ------------------ ; This subroutine is called from CL-LINE or CL-SCROLL with the HL register ; pointing to the 'ninth' line and H needs to be decremented before or after ; the division. Had it been done first then either present code or that used ; at the start of PO-ATTR could have been used. ; The Spectrum screen arrangement leads to the L register already holding ; the correct value for the attribute file and it is only necessary ; to manipulate H to form the correct colour attribute address. ;; CL-ATTR L0E88: LD A,H ; fetch H to A - $48, $50, or $58. RRCA ; divide by RRCA ; eight. RRCA ; $09, $0A or $0B. DEC A ; $08, $09 or $0A. OR $50 ; $58, $59 or $5A. LD H,A ; save high byte of attributes. EX DE,HL ; transfer attribute address to DE LD H,C ; set H to zero - from last LDIR. LD L,B ; load L with the line from B. ADD HL,HL ; multiply ADD HL,HL ; by ADD HL,HL ; thirty two ADD HL,HL ; to give count of attribute ADD HL,HL ; cells to the end of display. LD B,H ; transfer the result LD C,L ; to register BC. RET ; return. ; ------------------------------- ; Handle display with line number ; ------------------------------- ; This subroutine is called from four places to calculate the address ; of the start of a screen character line which is supplied in B. ;; CL-ADDR L0E9B: LD A,$18 ; reverse the line number SUB B ; to range $00 - $17. LD D,A ; save line in D for later. RRCA ; multiply RRCA ; by RRCA ; thirty-two. AND $E0 ; mask off low bits to make LD L,A ; L a multiple of 32. LD A,D ; bring back the line to A. AND $18 ; now $00, $08 or $10. OR $40 ; add the base address of screen. LD H,A ; HL now has the correct address. RET ; return. ; ------------------- ; Handle COPY command ; ------------------- ; This command copies the top 176 lines to the ZX Printer ; It is popular to call this from machine code at point ; L0EAF with B holding 192 (and interrupts disabled) for a full-screen ; copy. This particularly applies to 16K Spectrums as time-critical ; machine code routines cannot be written in the first 16K of RAM as ; it is shared with the ULA which has precedence over the Z80 chip. ;; COPY L0EAC: DI ; disable interrupts as this is time-critical. LD B,$B0 ; top 176 lines. L0EAF: LD HL,$4000 ; address start of the display file. ; now enter a loop to handle each pixel line. ;; COPY-1 L0EB2: PUSH HL ; save the screen address. PUSH BC ; and the line counter. CALL L0EF4 ; routine COPY-LINE outputs one line. POP BC ; restore the line counter. POP HL ; and display address. INC H ; next line down screen within 'thirds'. LD A,H ; high byte to A. AND $07 ; result will be zero if we have left third. JR NZ,L0EC9 ; forward to COPY-2 if not to continue loop. LD A,L ; consider low byte first. ADD A,$20 ; increase by 32 - sets carry if back to zero. LD L,A ; will be next group of 8. CCF ; complement - carry set if more lines in ; the previous third. SBC A,A ; will be FF, if more, else 00. AND $F8 ; will be F8 (-8) or 00. ADD A,H ; that is subtract 8, if more to do in third. LD H,A ; and reset address. ;; COPY-2 L0EC9: DJNZ L0EB2 ; back to COPY-1 for all lines. JR L0EDA ; forward to COPY-END to switch off the printer ; motor and enable interrupts. ; Note. Nothing else is required. ; ------------------------------ ; Pass printer buffer to printer ; ------------------------------ ; This routine is used to copy 8 text lines from the printer buffer ; to the ZX Printer. These text lines are mapped linearly so HL does ; not need to be adjusted at the end of each line. ;; COPY-BUFF L0ECD: DI ; disable interrupts LD HL,$5B00 ; the base address of the Printer Buffer. LD B,$08 ; set count to 8 lines of 32 bytes. ;; COPY-3 L0ED3: PUSH BC ; save counter. CALL L0EF4 ; routine COPY-LINE outputs 32 bytes POP BC ; restore counter. DJNZ L0ED3 ; loop back to COPY-3 for all 8 lines. ; then stop motor and clear buffer. ; Note. the COPY command rejoins here, essentially to execute the next ; three instructions. ;; COPY-END L0EDA: LD A,$04 ; output value 4 to port OUT ($FB),A ; to stop the slowed printer motor. EI ; enable interrupts. ; -------------------- ; Clear Printer Buffer ; -------------------- ; This routine clears an arbitrary 256 bytes of memory. ; Note. The routine seems designed to clear a buffer that follows the ; system variables. ; The routine should check a flag or HL address and simply return if COPY ; is in use. ; As a consequence of this omission the buffer will needlessly ; be cleared when COPY is used and the screen/printer position may be set to ; the start of the buffer and the line number to 0 (B) ; giving an 'Out of Screen' error. ; There seems to have been an unsuccessful attempt to circumvent the use ; of PR_CC_hi. ;; CLEAR-PRB L0EDF: LD HL,$5B00 ; the location of the buffer. LD (IY+$46),L ; update PR_CC_lo - set to zero - superfluous. XOR A ; clear the accumulator. LD B,A ; set count to 256 bytes. ;; PRB-BYTES L0EE7: LD (HL),A ; set addressed location to zero. INC HL ; address next byte - Note. not INC L. DJNZ L0EE7 ; back to PRB-BYTES. repeat for 256 bytes. RES 1,(IY+$30) ; set FLAGS2 - signal printer buffer is clear. LD C,$21 ; set the column position . JP L0DD9 ; exit via CL-SET and then PO-STORE. ; ----------------- ; Copy line routine ; ----------------- ; This routine is called from COPY and COPY-BUFF to output a line of ; 32 bytes to the ZX Printer. ; Output to port $FB - ; bit 7 set - activate stylus. ; bit 7 low - deactivate stylus. ; bit 2 set - stops printer. ; bit 2 reset - starts printer ; bit 1 set - slows printer. ; bit 1 reset - normal speed. ;; COPY-LINE L0EF4: LD A,B ; fetch the counter 1-8 or 1-176 CP $03 ; is it 01 or 02 ?. SBC A,A ; result is $FF if so else $00. AND $02 ; result is 02 now else 00. ; bit 1 set slows the printer. OUT ($FB),A ; slow the printer for the ; last two lines. LD D,A ; save the mask to control the printer later. ;; COPY-L-1 L0EFD: CALL L1F54 ; call BREAK-KEY to read keyboard immediately. JR C,L0F0C ; forward to COPY-L-2 if 'break' not pressed. LD A,$04 ; else stop the OUT ($FB),A ; printer motor. EI ; enable interrupts. CALL L0EDF ; call routine CLEAR-PRB. ; Note. should not be cleared if COPY in use. ;; REPORT-Dc L0F0A: RST 08H ; ERROR-1 DEFB $0C ; Error Report: BREAK - CONT repeats ;; COPY-L-2 L0F0C: IN A,($FB) ; test now to see if ADD A,A ; a printer is attached. RET M ; return if not - but continue with parent ; command. JR NC,L0EFD ; back to COPY-L-1 if stylus of printer not ; in position. LD C,$20 ; set count to 32 bytes. ;; COPY-L-3 L0F14: LD E,(HL) ; fetch a byte from line. INC HL ; address next location. Note. not INC L. LD B,$08 ; count the bits. ;; COPY-L-4 L0F18: RL D ; prepare mask to receive bit. RL E ; rotate leftmost print bit to carry RR D ; and back to bit 7 of D restoring bit 1 ;; COPY-L-5 L0F1E: IN A,($FB) ; read the port. RRA ; bit 0 to carry. JR NC,L0F1E ; back to COPY-L-5 if stylus not in position. LD A,D ; transfer command bits to A. OUT ($FB),A ; and output to port. DJNZ L0F18 ; loop back to COPY-L-4 for all 8 bits. DEC C ; decrease the byte count. JR NZ,L0F14 ; back to COPY-L-3 until 256 bits done. RET ; return to calling routine COPY/COPY-BUFF. ; ---------------------------------- ; Editor routine for BASIC and INPUT ; ---------------------------------- ; The editor is called to prepare or edit a BASIC line. ; It is also called from INPUT to input a numeric or string expression. ; The behaviour and options are quite different in the various modes ; and distinguished by bit 5 of FLAGX. ; ; This is a compact and highly versatile routine. ;; EDITOR L0F2C: LD HL,($5C3D) ; fetch ERR_SP PUSH HL ; save on stack ;; ED-AGAIN L0F30: LD HL,L107F ; address: ED-ERROR PUSH HL ; save address on stack and LD ($5C3D),SP ; make ERR_SP point to it. ; Note. While in editing/input mode should an error occur then RST 08 will ; update X_PTR to the location reached by CH_ADD and jump to ED-ERROR ; where the error will be cancelled and the loop begin again from ED-AGAIN ; above. The position of the error will be apparent when the lower screen is ; reprinted. If no error then the re-iteration is to ED-LOOP below when ; input is arriving from the keyboard. ;; ED-LOOP L0F38: CALL L15D4 ; routine WAIT-KEY gets key possibly ; changing the mode. PUSH AF ; save key. LD D,$00 ; and give a short click based LD E,(IY-$01) ; on PIP value for duration. LD HL,$00C8 ; and pitch. CALL L03B5 ; routine BEEPER gives click - effective ; with rubber keyboard. POP AF ; get saved key value. LD HL,L0F38 ; address: ED-LOOP is loaded to HL. PUSH HL ; and pushed onto stack. ; At this point there is a looping return address on the stack, an error ; handler and an input stream set up to supply characters. ; The character that has been received can now be processed. CP $18 ; range 24 to 255 ? JR NC,L0F81 ; forward to ADD-CHAR if so. CP $07 ; lower than 7 ? JR C,L0F81 ; forward to ADD-CHAR also. ; Note. This is a 'bug' and chr$ 6, the comma ; control character, should have had an ; entry in the ED-KEYS table. ; Steven Vickers, 1984, Pitman. CP $10 ; less than 16 ? JR C,L0F92 ; forward to ED-KEYS if editing control ; range 7 to 15 dealt with by a table LD BC,$0002 ; prepare for ink/paper etc. LD D,A ; save character in D CP $16 ; is it ink/paper/bright etc. ? JR C,L0F6C ; forward to ED-CONTR if so ; leaves 22d AT and 23d TAB ; which can't be entered via KEY-INPUT. ; so this code is never normally executed ; when the keyboard is used for input. INC BC ; if it was AT/TAB - 3 locations required BIT 7,(IY+$37) ; test FLAGX - Is this INPUT LINE ? JP Z,L101E ; jump to ED-IGNORE if not, else CALL L15D4 ; routine WAIT-KEY - input address is KEY-NEXT ; but is reset to KEY-INPUT LD E,A ; save first in E ;; ED-CONTR L0F6C: CALL L15D4 ; routine WAIT-KEY for control. ; input address will be key-next. PUSH DE ; saved code/parameters LD HL,($5C5B) ; fetch address of keyboard cursor from K_CUR RES 0,(IY+$07) ; set MODE to 'L' CALL L1655 ; routine MAKE-ROOM makes 2/3 spaces at cursor POP BC ; restore code/parameters INC HL ; address first location LD (HL),B ; place code (ink etc.) INC HL ; address next LD (HL),C ; place possible parameter. If only one ; then DE points to this location also. JR L0F8B ; forward to ADD-CH-1 ; ------------------------ ; Add code to current line ; ------------------------ ; this is the branch used to add normal non-control characters ; with ED-LOOP as the stacked return address. ; it is also the OUTPUT service routine for system channel 'R'. ;; ADD-CHAR L0F81: RES 0,(IY+$07) ; set MODE to 'L' X0F85: LD HL,($5C5B) ; fetch address of keyboard cursor from K_CUR CALL L1652 ; routine ONE-SPACE creates one space. ; either a continuation of above or from ED-CONTR with ED-LOOP on stack. ;; ADD-CH-1 L0F8B: LD (DE),A ; load current character to last new location. INC DE ; address next LD ($5C5B),DE ; and update K_CUR system variable. RET ; return - either a simple return ; from ADD-CHAR or to ED-LOOP on stack. ; --- ; a branch of the editing loop to deal with control characters ; using a look-up table. ;; ED-KEYS L0F92: LD E,A ; character to E. LD D,$00 ; prepare to add. LD HL,L0FA0 - 7 ; base address of editing keys table. $0F99 ADD HL,DE ; add E LD E,(HL) ; fetch offset to E ADD HL,DE ; add offset for address of handling routine. PUSH HL ; push the address on machine stack. LD HL,($5C5B) ; load address of cursor from K_CUR. RET ; Make an indirect jump forward to routine. ; ------------------ ; Editing keys table ; ------------------ ; For each code in the range $07 to $0F this table contains a ; single offset byte to the routine that services that code. ; Note. for what was intended there should also have been an ; entry for chr$ 6 with offset to ed-symbol. ;; ed-keys-t L0FA0: DEFB L0FA9 - $ ; 07d offset $09 to Address: ED-EDIT DEFB L1007 - $ ; 08d offset $66 to Address: ED-LEFT DEFB L100C - $ ; 09d offset $6A to Address: ED-RIGHT DEFB L0FF3 - $ ; 10d offset $50 to Address: ED-DOWN DEFB L1059 - $ ; 11d offset $B5 to Address: ED-UP DEFB L1015 - $ ; 12d offset $70 to Address: ED-DELETE DEFB L1024 - $ ; 13d offset $7E to Address: ED-ENTER DEFB L1076 - $ ; 14d offset $CF to Address: ED-SYMBOL DEFB L107C - $ ; 15d offset $D4 to Address: ED-GRAPH ; --------------- ; Handle EDIT key ; --------------- ; The user has pressed SHIFT 1 to bring edit line down to bottom of screen. ; Alternatively the user wishes to clear the input buffer and start again. ; Alternatively ... ;; ED-EDIT L0FA9: LD HL,($5C49) ; fetch E_PPC the last line number entered. ; Note. may not exist and may follow program. BIT 5,(IY+$37) ; test FLAGX - input mode ? JP NZ,L1097 ; jump forward to CLEAR-SP if not in editor. CALL L196E ; routine LINE-ADDR to find address of line ; or following line if it doesn't exist. CALL L1695 ; routine LINE-NO will get line number from ; address or previous line if at end-marker. LD A,D ; if there is no program then DE will OR E ; contain zero so test for this. JP Z,L1097 ; jump to CLEAR-SP if so. ; Note. at this point we have a validated line number, not just an ; approximation and it would be best to update E_PPC with the true ; cursor line value which would enable the line cursor to be suppressed ; in all situations - see shortly. PUSH HL ; save address of line. INC HL ; address low byte of length. LD C,(HL) ; transfer to C INC HL ; next to high byte LD B,(HL) ; transfer to B. LD HL,$000A ; an overhead of ten bytes ADD HL,BC ; is added to length. LD B,H ; transfer adjusted value LD C,L ; to BC register. CALL L1F05 ; routine TEST-ROOM checks free memory. CALL L1097 ; routine CLEAR-SP clears editing area. LD HL,($5C51) ; address CURCHL EX (SP),HL ; swap with line address on stack PUSH HL ; save line address underneath LD A,$FF ; select system channel 'R' CALL L1601 ; routine CHAN-OPEN opens it POP HL ; drop line address DEC HL ; make it point to first byte of line num. DEC (IY+$0F) ; decrease E_PPC_lo to suppress line cursor. ; Note. ineffective when E_PPC is one ; greater than last line of program perhaps ; as a result of a delete. ; credit. Paul Harrison 1982. CALL L1855 ; routine OUT-LINE outputs the BASIC line ; to the editing area. INC (IY+$0F) ; restore E_PPC_lo to the previous value. LD HL,($5C59) ; address E_LINE in editing area. INC HL ; advance INC HL ; past space INC HL ; and digit characters INC HL ; of line number. LD ($5C5B),HL ; update K_CUR to address start of BASIC. POP HL ; restore the address of CURCHL. CALL L1615 ; routine CHAN-FLAG sets flags for it. RET ; RETURN to ED-LOOP. ; ------------------- ; Cursor down editing ; ------------------- ; The BASIC lines are displayed at the top of the screen and the user ; wishes to move the cursor down one line in edit mode. ; With INPUT LINE, this key must be used instead of entering STOP. ;; ED-DOWN L0FF3: BIT 5,(IY+$37) ; test FLAGX - Input Mode ? JR NZ,L1001 ; skip to ED-STOP if so LD HL,$5C49 ; address E_PPC - 'current line' CALL L190F ; routine LN-FETCH fetches number of next ; line or same if at end of program. JR L106E ; forward to ED-LIST to produce an ; automatic listing. ; --- ;; ED-STOP L1001: LD (IY+$00),$10 ; set ERR_NR to 'STOP in INPUT' code JR L1024 ; forward to ED-ENTER to produce error. ; ------------------- ; Cursor left editing ; ------------------- ; This acts on the cursor in the lower section of the screen in both ; editing and input mode. ;; ED-LEFT L1007: CALL L1031 ; routine ED-EDGE moves left if possible JR L1011 ; forward to ED-CUR to update K-CUR ; and return to ED-LOOP. ; -------------------- ; Cursor right editing ; -------------------- ; This acts on the cursor in the lower screen in both editing and input ; mode and moves it to the right. ;; ED-RIGHT L100C: LD A,(HL) ; fetch addressed character. CP $0D ; is it carriage return ? RET Z ; return if so to ED-LOOP INC HL ; address next character ;; ED-CUR L1011: LD ($5C5B),HL ; update K_CUR system variable RET ; return to ED-LOOP ; -------------- ; DELETE editing ; -------------- ; This acts on the lower screen and deletes the character to left of ; cursor. If control characters are present these are deleted first ; leaving the naked parameter (0-7) which appears as a '?' except in the ; case of chr$ 6 which is the comma control character. It is not mandatory ; to delete these second characters. ;; ED-DELETE L1015: CALL L1031 ; routine ED-EDGE moves cursor to left. LD BC,$0001 ; of character to be deleted. JP L19E8 ; to RECLAIM-2 reclaim the character. ; ------------------------------------------ ; Ignore next 2 codes from key-input routine ; ------------------------------------------ ; Since AT and TAB cannot be entered this point is never reached ; from the keyboard. If inputting from a tape device or network then ; the control and two following characters are ignored and processing ; continues as if a carriage return had been received. ; Here, perhaps, another Spectrum has said print #15; AT 0,0; "This is yellow" ; and this one is interpreting input #15; a$. ;; ED-IGNORE L101E: CALL L15D4 ; routine WAIT-KEY to ignore keystroke. CALL L15D4 ; routine WAIT-KEY to ignore next key. ; ------------- ; Enter/newline ; ------------- ; The enter key has been pressed to have BASIC line or input accepted. ;; ED-ENTER L1024: POP HL ; discard address ED-LOOP POP HL ; drop address ED-ERROR ;; ED-END L1026: POP HL ; the previous value of ERR_SP LD ($5C3D),HL ; is restored to ERR_SP system variable BIT 7,(IY+$00) ; is ERR_NR $FF (= 'OK') ? RET NZ ; return if so LD SP,HL ; else put error routine on stack RET ; and make an indirect jump to it. ; ----------------------------- ; Move cursor left when editing ; ----------------------------- ; This routine moves the cursor left. The complication is that it must ; not position the cursor between control codes and their parameters. ; It is further complicated in that it deals with TAB and AT characters ; which are never present from the keyboard. ; The method is to advance from the beginning of the line each time, ; jumping one, two, or three characters as necessary saving the original ; position at each jump in DE. Once it arrives at the cursor then the next ; legitimate leftmost position is in DE. ;; ED-EDGE L1031: SCF ; carry flag must be set to call the nested CALL L1195 ; subroutine SET-DE. ; if input then DE=WORKSP ; if editing then DE=E_LINE SBC HL,DE ; subtract address from start of line ADD HL,DE ; and add back. INC HL ; adjust for carry. POP BC ; drop return address RET C ; return to ED-LOOP if already at left ; of line. PUSH BC ; resave return address - ED-LOOP. LD B,H ; transfer HL - cursor address LD C,L ; to BC register pair. ; at this point DE addresses start of line. ;; ED-EDGE-1 L103E: LD H,D ; transfer DE - leftmost pointer LD L,E ; to HL INC HL ; address next leftmost character to ; advance position each time. LD A,(DE) ; pick up previous in A AND $F0 ; lose the low bits CP $10 ; is it INK to TAB $10-$1F ? ; that is, is it followed by a parameter ? JR NZ,L1051 ; to ED-EDGE-2 if not ; HL has been incremented once INC HL ; address next as at least one parameter. ; in fact since 'tab' and 'at' cannot be entered the next section seems ; superfluous. ; The test will always fail and the jump to ED-EDGE-2 will be taken. LD A,(DE) ; reload leftmost character SUB $17 ; decimal 23 ('tab') ADC A,$00 ; will be 0 for 'tab' and 'at'. JR NZ,L1051 ; forward to ED-EDGE-2 if not ; HL has been incremented twice INC HL ; increment a third time for 'at'/'tab' ;; ED-EDGE-2 L1051: AND A ; prepare for true subtraction SBC HL,BC ; subtract cursor address from pointer ADD HL,BC ; and add back ; Note when HL matches the cursor position BC, ; there is no carry and the previous ; position is in DE. EX DE,HL ; transfer result to DE if looping again. ; transfer DE to HL to be used as K-CUR ; if exiting loop. JR C,L103E ; back to ED-EDGE-1 if cursor not matched. RET ; return. ; ----------------- ; Cursor up editing ; ----------------- ; The main screen displays part of the BASIC program and the user wishes ; to move up one line scrolling if necessary. ; This has no alternative use in input mode. ;; ED-UP L1059: BIT 5,(IY+$37) ; test FLAGX - input mode ? RET NZ ; return if not in editor - to ED-LOOP. LD HL,($5C49) ; get current line from E_PPC CALL L196E ; routine LINE-ADDR gets address EX DE,HL ; and previous in DE CALL L1695 ; routine LINE-NO gets prev line number LD HL,$5C4A ; set HL to E_PPC_hi as next routine stores ; top first. CALL L191C ; routine LN-STORE loads DE value to HL ; high byte first - E_PPC_lo takes E ; this branch is also taken from ed-down. ;; ED-LIST L106E: CALL L1795 ; routine AUTO-LIST lists to upper screen ; including adjusted current line. LD A,$00 ; select lower screen again JP L1601 ; exit via CHAN-OPEN to ED-LOOP ; -------------------------------- ; Use of symbol and graphics codes ; -------------------------------- ; These will not be encountered with the keyboard but would be handled ; otherwise as follows. ; As noted earlier, Vickers says there should have been an entry in ; the KEYS table for chr$ 6 which also pointed here. ; If, for simplicity, two Spectrums were both using #15 as a bi-directional ; channel connected to each other:- ; then when the other Spectrum has said PRINT #15; x, y ; input #15; i ; j would treat the comma control as a newline and the ; control would skip to input j. ; You can get round the missing chr$ 6 handler by sending multiple print ; items separated by a newline '. ; chr$14 would have the same functionality. ; This is chr$ 14. ;; ED-SYMBOL L1076: BIT 7,(IY+$37) ; test FLAGX - is this INPUT LINE ? JR Z,L1024 ; back to ED-ENTER if not to treat as if ; enter had been pressed. ; else continue and add code to buffer. ; Next is chr$ 15 ; Note that ADD-CHAR precedes the table so we can't offset to it directly. ;; ED-GRAPH L107C: JP L0F81 ; jump back to ADD-CHAR ; -------------------- ; Editor error routine ; -------------------- ; If an error occurs while editing, or inputting, then ERR_SP ; points to the stack location holding address ED_ERROR. ;; ED-ERROR L107F: BIT 4,(IY+$30) ; test FLAGS2 - is K channel in use ? JR Z,L1026 ; back to ED-END if not. ; but as long as we're editing lines or inputting from the keyboard, then ; we've run out of memory so give a short rasp. LD (IY+$00),$FF ; reset ERR_NR to 'OK'. LD D,$00 ; prepare for beeper. LD E,(IY-$02) ; use RASP value. LD HL,$1A90 ; set the pitch - or tone period. CALL L03B5 ; routine BEEPER emits a warning rasp. JP L0F30 ; to ED-AGAIN to re-stack address of ; this routine and make ERR_SP point to it. ; --------------------- ; Clear edit/work space ; --------------------- ; The editing area or workspace is cleared depending on context. ; This is called from ED-EDIT to clear workspace if edit key is ; used during input, to clear editing area if no program exists ; and to clear editing area prior to copying the edit line to it. ; It is also used by the error routine to clear the respective ; area depending on FLAGX. ;; CLEAR-SP L1097: PUSH HL ; preserve HL CALL L1190 ; routine SET-HL ; if in edit HL = WORKSP-1, DE = E_LINE ; if in input HL = STKBOT, DE = WORKSP DEC HL ; adjust CALL L19E5 ; routine RECLAIM-1 reclaims space LD ($5C5B),HL ; set K_CUR to start of empty area LD (IY+$07),$00 ; set MODE to 'KLC' POP HL ; restore HL. RET ; return. ; ---------------------------- ; THE 'KEYBOARD INPUT' ROUTINE ; ---------------------------- ; This is the service routine for the input stream of the keyboard channel 'K'. ;; KEY-INPUT L10A8: BIT 3,(IY+$02) ; test TV_FLAG - has a key been pressed in ; editor ? CALL NZ,L111D ; routine ED-COPY, if so, to reprint the lower ; screen at every keystroke/mode change. AND A ; clear carry flag - required exit condition. BIT 5,(IY+$01) ; test FLAGS - has a new key been pressed ? RET Z ; return if not. >> LD A,($5C08) ; system variable LASTK will hold last key - ; from the interrupt routine. RES 5,(IY+$01) ; update FLAGS - reset the new key flag. PUSH AF ; save the input character. BIT 5,(IY+$02) ; test TV_FLAG - clear lower screen ? CALL NZ,L0D6E ; routine CLS-LOWER if so. POP AF ; restore the character code. CP $20 ; if space or higher then JR NC,L111B ; forward to KEY-DONE2 and return with carry ; set to signal key-found. CP $10 ; with 16d INK and higher skip JR NC,L10FA ; forward to KEY-CONTR. CP $06 ; for 6 - 15d JR NC,L10DB ; skip forward to KEY-M-CL to handle Modes ; and CapsLock. ; that only leaves 0-5, the flash bright inverse switches. LD B,A ; save character in B AND $01 ; isolate the embedded parameter (0/1). LD C,A ; and store in C LD A,B ; re-fetch copy (0-5) RRA ; halve it 0, 1 or 2. ADD A,$12 ; add 18d gives 'flash', 'bright' ; and 'inverse'. JR L1105 ; forward to KEY-DATA with the ; parameter (0/1) in C. ; --- ; Now separate capslock 06 from modes 7-15. ;; KEY-M-CL L10DB: JR NZ,L10E6 ; forward to KEY-MODE if not 06 (capslock) LD HL,$5C6A ; point to FLAGS2 LD A,$08 ; value 00001000 XOR (HL) ; toggle BIT 3 of FLAGS2 the capslock bit LD (HL),A ; and store result in FLAGS2 again. JR L10F4 ; forward to KEY-FLAG to signal no-key. ; --- ;; KEY-MODE L10E6: CP $0E ; compare with chr 14d RET C ; return with carry set "key found" for ; codes 7 - 13d leaving 14d and 15d ; which are converted to mode codes. SUB $0D ; subtract 13d leaving 1 and 2 ; 1 is 'E' mode, 2 is 'G' mode. LD HL,$5C41 ; address the MODE system variable. CP (HL) ; compare with existing value before LD (HL),A ; inserting the new value. JR NZ,L10F4 ; forward to KEY-FLAG if it has changed. LD (HL),$00 ; else make MODE zero - KLC mode ; Note. while in Extended/Graphics mode, ; the Extended Mode/Graphics key is pressed ; again to get out. ;; KEY-FLAG L10F4: SET 3,(IY+$02) ; update TV_FLAG - show key state has changed CP A ; clear carry and reset zero flags - ; no actual key returned. RET ; make the return. ; --- ; now deal with colour controls - 16-23 ink, 24-31 paper ;; KEY-CONTR L10FA: LD B,A ; make a copy of character. AND $07 ; mask to leave bits 0-7 LD C,A ; and store in C. LD A,$10 ; initialize to 16d - INK. BIT 3,B ; was it paper ? JR NZ,L1105 ; forward to KEY-DATA with INK 16d and ; colour in C. INC A ; else change from INK to PAPER (17d) if so. ;; KEY-DATA L1105: LD (IY-$2D),C ; put the colour (0-7)/state(0/1) in KDATA LD DE,L110D ; address: KEY-NEXT will be next input stream JR L1113 ; forward to KEY-CHAN to change it ... ; --- ; ... so that INPUT_AD directs control to here at next call to WAIT-KEY ;; KEY-NEXT L110D: LD A,($5C0D) ; pick up the parameter stored in KDATA. LD DE,L10A8 ; address: KEY-INPUT will be next input stream ; continue to restore default channel and ; make a return with the control code. ;; KEY-CHAN L1113: LD HL,($5C4F) ; address start of CHANNELS area using CHANS ; system variable. ; Note. One might have expected CURCHL to ; have been used. INC HL ; step over the INC HL ; output address LD (HL),E ; and update the input INC HL ; routine address for LD (HL),D ; the next call to WAIT-KEY. ;; KEY-DONE2 L111B: SCF ; set carry flag to show a key has been found RET ; and return. ; -------------------- ; Lower screen copying ; -------------------- ; This subroutine is called whenever the line in the editing area or ; input workspace is required to be printed to the lower screen. ; It is by calling this routine after any change that the cursor, for ; instance, appears to move to the left. ; Remember the edit line will contain characters and tokens ; e.g. "1000 LET a=1" is 8 characters. ;; ED-COPY L111D: CALL L0D4D ; routine TEMPS sets temporary attributes. RES 3,(IY+$02) ; update TV_FLAG - signal no change in mode RES 5,(IY+$02) ; update TV_FLAG - signal don't clear lower ; screen. LD HL,($5C8A) ; fetch SPOSNL PUSH HL ; and save on stack. LD HL,($5C3D) ; fetch ERR_SP PUSH HL ; and save also LD HL,L1167 ; address: ED-FULL PUSH HL ; is pushed as the error routine LD ($5C3D),SP ; and ERR_SP made to point to it. LD HL,($5C82) ; fetch ECHO_E PUSH HL ; and push also SCF ; set carry flag to control SET-DE CALL L1195 ; call routine SET-DE ; if in input DE = WORKSP ; if in edit DE = E_LINE EX DE,HL ; start address to HL CALL L187D ; routine OUT-LINE2 outputs entire line up to ; carriage return including initial ; characterized line number when present. EX DE,HL ; transfer new address to DE CALL L18E1 ; routine OUT-CURS considers a ; terminating cursor. LD HL,($5C8A) ; fetch updated SPOSNL EX (SP),HL ; exchange with ECHO_E on stack EX DE,HL ; transfer ECHO_E to DE CALL L0D4D ; routine TEMPS to re-set attributes ; if altered. ; the lower screen was not cleared, at the outset, so if deleting then old ; text from a previous print may follow this line and requires blanking. ;; ED-BLANK L1150: LD A,($5C8B) ; fetch SPOSNL_hi is current line SUB D ; compare with old JR C,L117C ; forward to ED-C-DONE if no blanking JR NZ,L115E ; forward to ED-SPACES if line has changed LD A,E ; old column to A SUB (IY+$50) ; subtract new in SPOSNL_lo JR NC,L117C ; forward to ED-C-DONE if no backfilling. ;; ED-SPACES L115E: LD A,$20 ; prepare a space. PUSH DE ; save old line/column. CALL L09F4 ; routine PRINT-OUT prints a space over ; any text from previous print. ; Note. Since the blanking only occurs when ; using $09F4 to print to the lower screen, ; there is no need to vector via a RST 10 ; and we can use this alternate set. POP DE ; restore the old line column. JR L1150 ; back to ED-BLANK until all old text blanked. ; ------------------------------- ; THE 'EDITOR-FULL' ERROR ROUTINE ; ------------------------------- ; This is the error routine addressed by ERR_SP. This is not for the out of ; memory situation as we're just printing. The pitch and duration are exactly ; the same as used by ED-ERROR from which this has been augmented. The ; situation is that the lower screen is full and a rasp is given to suggest ; that this is perhaps not the best idea you've had that day. ;; ED-FULL L1167: LD D,$00 ; prepare to moan. LD E,(IY-$02) ; fetch RASP value. LD HL,$1A90 ; set pitch or tone period. CALL L03B5 ; routine BEEPER. LD (IY+$00),$FF ; clear ERR_NR. LD DE,($5C8A) ; fetch SPOSNL. JR L117E ; forward to ED-C-END ; ------- ; the exit point from line printing continues here. ;; ED-C-DONE L117C: POP DE ; fetch new line/column. POP HL ; fetch the error address. ; the error path rejoins here. ;; ED-C-END L117E: POP HL ; restore the old value of ERR_SP. LD ($5C3D),HL ; update the system variable ERR_SP POP BC ; old value of SPOSN_L PUSH DE ; save new value CALL L0DD9 ; routine CL-SET and PO-STORE ; update ECHO_E and SPOSN_L from BC POP HL ; restore new value LD ($5C82),HL ; and overwrite ECHO_E LD (IY+$26),$00 ; make error pointer X_PTR_hi out of bounds RET ; return ; ----------------------------------------------- ; Point to first and last locations of work space ; ----------------------------------------------- ; These two nested routines ensure that the appropriate pointers are ; selected for the editing area or workspace. The routines that call ; these routines are designed to work on either area. ; this routine is called once ;; SET-HL L1190: LD HL,($5C61) ; fetch WORKSP to HL. DEC HL ; point to last location of editing area. AND A ; clear carry to limit exit points to first ; or last. ; this routine is called with carry set and exits at a conditional return. ;; SET-DE L1195: LD DE,($5C59) ; fetch E_LINE to DE BIT 5,(IY+$37) ; test FLAGX - Input Mode ? RET Z ; return now if in editing mode LD DE,($5C61) ; fetch WORKSP to DE RET C ; return if carry set ( entry = set-de) LD HL,($5C63) ; fetch STKBOT to HL as well RET ; and return (entry = set-hl (in input)) ; ----------------------------------- ; THE 'REMOVE FLOATING POINT' ROUTINE ; ----------------------------------- ; When a BASIC LINE or the INPUT BUFFER is parsed any numbers will have ; an invisible chr 14d inserted after them and the 5-byte integer or ; floating point form inserted after that. Similar invisible value holders ; are also created after the numeric and string variables in a DEF FN list. ; This routine removes these 'compiled' numbers from the edit line or ; input workspace. ;; REMOVE-FP L11A7: LD A,(HL) ; fetch character CP $0E ; is it the CHR$ 14 number marker ? LD BC,$0006 ; prepare to strip six bytes CALL Z,L19E8 ; routine RECLAIM-2 reclaims bytes if CHR$ 14. LD A,(HL) ; reload next (or same) character INC HL ; and advance address CP $0D ; end of line or input buffer ? JR NZ,L11A7 ; back to REMOVE-FP until entire line done. RET ; return. ; ********************************* ; ** Part 6. EXECUTIVE ROUTINES ** ; ********************************* ; The memory. ; ; +---------+-----------+------------+--------------+-------------+-- ; | BASIC | Display | Attributes | ZX Printer | System | ; | ROM | File | File | Buffer | Variables | ; +---------+-----------+------------+--------------+-------------+-- ; ^ ^ ^ ^ ^ ^ ; $0000 $4000 $5800 $5B00 $5C00 $5CB6 = CHANS ; ; ; --+----------+---+---------+-----------+---+------------+--+---+-- ; | Channel |$80| BASIC | Variables |$80| Edit Line |NL|$80| ; | Info | | Program | Area | | or Command | | | ; --+----------+---+---------+-----------+---+------------+--+---+-- ; ^ ^ ^ ^ ^ ; CHANS PROG VARS E_LINE WORKSP ; ; ; ---5--> <---2--- <--3--- ; --+-------+--+------------+-------+-------+---------+-------+-+---+------+ ; | INPUT |NL| Temporary | Calc. | Spare | Machine | GOSUB |?|$3E| UDGs | ; | data | | Work Space | Stack | | Stack | Stack | | | | ; --+-------+--+------------+-------+-------+---------+-------+-+---+------+ ; ^ ^ ^ ^ ^ ^ ^ ; WORKSP STKBOT STKEND sp RAMTOP UDG P_RAMT ; ; ----------------- ; THE 'NEW' COMMAND ; ----------------- ; The NEW command is about to set all RAM below RAMTOP to zero and then ; re-initialize the system. All RAM above RAMTOP should, and will be, ; preserved. ; There is nowhere to store values in RAM or on the stack which becomes ; inoperable. Similarly PUSH and CALL instructions cannot be used to store ; values or section common code. The alternate register set is the only place ; available to store 3 persistent 16-bit system variables. ;; NEW L11B7: DI ; Disable Interrupts - machine stack will be ; cleared. LD A,$FF ; Flag coming from NEW. LD DE,($5CB2) ; Fetch RAMTOP as top value. EXX ; Switch in alternate set. LD BC,($5CB4) ; Fetch P-RAMT differs on 16K/48K machines. LD DE,($5C38) ; Fetch RASP/PIP. LD HL,($5C7B) ; Fetch UDG differs on 16K/48K machines. EXX ; Switch back to main set and continue into... ; ---------------------- ; THE 'START-NEW' BRANCH ; ---------------------- ; This branch is taken from above and from RST 00h. ; The common code tests RAM and sets it to zero re-initializing all the ; non-zero system variables and channel information. The A register flags ; if coming from START or NEW. ;; START-NEW L11CB: LD B,A ; Save the flag to control later branching. LD A,$07 ; Select a white border OUT ($FE),A ; and set it now by writing to a port. LD A,$3F ; Load the accumulator with last page in ROM. LD I,A ; Set the I register - this remains constant ; and can't be in the range $40 - $7F as 'snow' ; appears on the screen. NOP ; These seem unnecessary. NOP ; NOP ; NOP ; NOP ; NOP ; ; ----------------------- ; THE 'RAM CHECK' SECTION ; ----------------------- ; Typically, a Spectrum will have 16K or 48K of RAM and this code will test ; it all till it finds an unpopulated location or, less likely, a faulty ; location. Usually it stops when it reaches the top $FFFF, or in the case ; of NEW the supplied top value. The entire screen turns black with ; sometimes red stripes on black paper just visible. ;; ram-check L11DA: LD H,D ; Transfer the top value to the HL register LD L,E ; pair. ;; RAM-FILL L11DC: LD (HL),$02 ; Load memory with $02 - red ink on black paper. DEC HL ; Decrement memory address. CP H ; Have we reached ROM - $3F ? JR NZ,L11DC ; Back to RAM-FILL if not. ;; RAM-READ L11E2: AND A ; Clear carry - prepare to subtract. SBC HL,DE ; subtract and add back setting ADD HL,DE ; carry when back at start. INC HL ; and increment for next iteration. JR NC,L11EF ; forward to RAM-DONE if we've got back to ; starting point with no errors. DEC (HL) ; decrement to 1. JR Z,L11EF ; forward to RAM-DONE if faulty. DEC (HL) ; decrement to zero. JR Z,L11E2 ; back to RAM-READ if zero flag was set. ;; RAM-DONE L11EF: DEC HL ; step back to last valid location. EXX ; regardless of state, set up possibly ; stored system variables in case from NEW. LD ($5CB4),BC ; insert P-RAMT. LD ($5C38),DE ; insert RASP/PIP. LD ($5C7B),HL ; insert UDG. EXX ; switch in main set. INC B ; now test if we arrived here from NEW. JR Z,L1219 ; forward to RAM-SET if we did. ; This section applies to START only. LD ($5CB4),HL ; set P-RAMT to the highest working RAM ; address. LD DE,$3EAF ; address of last byte of 'U' bitmap in ROM. LD BC,$00A8 ; there are 21 user defined graphics. EX DE,HL ; switch pointers and make the UDGs a LDDR ; copy of the standard characters A - U. EX DE,HL ; switch the pointer to HL. INC HL ; update to start of 'A' in RAM. LD ($5C7B),HL ; make UDG system variable address the first ; bitmap. DEC HL ; point at RAMTOP again. LD BC,$0040 ; set the values of LD ($5C38),BC ; the PIP and RASP system variables. ; The NEW command path rejoins here. ;; RAM-SET L1219: LD ($5CB2),HL ; set system variable RAMTOP to HL. ; ; Note. this entry point is a disabled Warm Restart that was almost certainly ; once pointed to by the System Variable NMIADD. It would be essential that ; any NMI Handler would perform the tasks from here to the EI instruction ; below. ;; NMI_VECT L121C: LD HL,$3C00 ; a strange place to set the pointer to the LD ($5C36),HL ; character set, CHARS - as no printing yet. LD HL,($5CB2) ; fetch RAMTOP to HL again as we've lost it. LD (HL),$3E ; top of user ram holds GOSUB end marker ; an impossible line number - see RETURN. ; no significance in the number $3E. It has ; been traditional since the ZX80. DEC HL ; followed by empty byte (not important). LD SP,HL ; set up the machine stack pointer. DEC HL ; DEC HL ; LD ($5C3D),HL ; ERR_SP is where the error pointer is ; at moment empty - will take address MAIN-4 ; at the call preceding that address, ; although interrupts and calls will make use ; of this location in meantime. IM 1 ; select interrupt mode 1. LD IY,$5C3A ; set IY to ERR_NR. IY can reach all standard ; system variables but shadow ROM system ; variables will be mostly out of range. EI ; enable interrupts now that we have a stack. ; If, as suggested above, the NMI service routine pointed to this section of ; code then a decision would have to be made at this point to jump forward, ; in a Warm Restart scenario, to produce a report code, leaving any program ; intact. LD HL,$5CB6 ; The address of the channels - initially ; following system variables. LD ($5C4F),HL ; Set the CHANS system variable. LD DE,L15AF ; Address: init-chan in ROM. LD BC,$0015 ; There are 21 bytes of initial data in ROM. EX DE,HL ; swap the pointers. LDIR ; Copy the bytes to RAM. EX DE,HL ; Swap pointers. HL points to program area. DEC HL ; Decrement address. LD ($5C57),HL ; Set DATADD to location before program area. INC HL ; Increment again. LD ($5C53),HL ; Set PROG the location where BASIC starts. LD ($5C4B),HL ; Set VARS to same location with a LD (HL),$80 ; variables end-marker. INC HL ; Advance address. LD ($5C59),HL ; Set E_LINE, where the edit line ; will be created. ; Note. it is not strictly necessary to ; execute the next fifteen bytes of code ; as this will be done by the call to SET-MIN. ; -- LD (HL),$0D ; initially just has a carriage return INC HL ; followed by LD (HL),$80 ; an end-marker. INC HL ; address the next location. LD ($5C61),HL ; set WORKSP - empty workspace. LD ($5C63),HL ; set STKBOT - bottom of the empty stack. LD ($5C65),HL ; set STKEND to the end of the empty stack. ; -- LD A,$38 ; the colour system is set to white paper, ; black ink, no flash or bright. LD ($5C8D),A ; set ATTR_P permanent colour attributes. LD ($5C8F),A ; set ATTR_T temporary colour attributes. LD ($5C48),A ; set BORDCR the border colour/lower screen ; attributes. LD HL,$0523 ; The keyboard repeat and delay values are LD ($5C09),HL ; loaded to REPDEL and REPPER. DEC (IY-$3A) ; set KSTATE-0 to $FF - keyboard map available. DEC (IY-$36) ; set KSTATE-4 to $FF - keyboard map available. LD HL,L15C6 ; set source to ROM Address: init-strm LD DE,$5C10 ; set destination to system variable STRMS-FD LD BC,$000E ; copy the 14 bytes of initial 7 streams data LDIR ; from ROM to RAM. SET 1,(IY+$01) ; update FLAGS - signal printer in use. CALL L0EDF ; call routine CLEAR-PRB to initialize system ; variables associated with printer. ; The buffer is clear. LD (IY+$31),$02 ; set DF_SZ the lower screen display size to ; two lines CALL L0D6B ; call routine CLS to set up system ; variables associated with screen and clear ; the screen and set attributes. XOR A ; clear accumulator so that we can address LD DE,L1539 - 1 ; the message table directly. CALL L0C0A ; routine PO-MSG puts ; ' © 1982 Sinclair Research Ltd' ; at bottom of display. SET 5,(IY+$02) ; update TV_FLAG - signal lower screen will ; require clearing. JR L12A9 ; forward to MAIN-1 ; ------------------------- ; THE 'MAIN EXECUTION LOOP' ; ------------------------- ; ; ;; MAIN-EXEC L12A2: LD (IY+$31),$02 ; set DF_SZ lower screen display file size to ; two lines. CALL L1795 ; routine AUTO-LIST ;; MAIN-1 L12A9: CALL L16B0 ; routine SET-MIN clears work areas. ;; MAIN-2 L12AC: LD A,$00 ; select channel 'K' the keyboard CALL L1601 ; routine CHAN-OPEN opens it CALL L0F2C ; routine EDITOR is called. ; Note the above routine is where the Spectrum ; waits for user-interaction. Perhaps the ; most common input at this stage ; is LOAD "". CALL L1B17 ; routine LINE-SCAN scans the input. BIT 7,(IY+$00) ; test ERR_NR - will be $FF if syntax is OK. JR NZ,L12CF ; forward, if correct, to MAIN-3. ; BIT 4,(IY+$30) ; test FLAGS2 - K channel in use ? JR Z,L1303 ; forward to MAIN-4 if not. ; LD HL,($5C59) ; an editing error so address E_LINE. CALL L11A7 ; routine REMOVE-FP removes the hidden ; floating-point forms. LD (IY+$00),$FF ; system variable ERR_NR is reset to 'OK'. JR L12AC ; back to MAIN-2 to allow user to correct. ; --- ; the branch was here if syntax has passed test. ;; MAIN-3 L12CF: LD HL,($5C59) ; fetch the edit line address from E_LINE. LD ($5C5D),HL ; system variable CH_ADD is set to first ; character of edit line. ; Note. the above two instructions are a little ; inadequate. ; They are repeated with a subtle difference ; at the start of the next subroutine and are ; therefore not required above. CALL L19FB ; routine E-LINE-NO will fetch any line ; number to BC if this is a program line. LD A,B ; test if the number of OR C ; the line is non-zero. JP NZ,L155D ; jump forward to MAIN-ADD if so to add the ; line to the BASIC program. ; Has the user just pressed the ENTER key ? RST 18H ; GET-CHAR gets character addressed by CH_ADD. CP $0D ; is it a carriage return ? JR Z,L12A2 ; back to MAIN-EXEC if so for an automatic ; listing. ; this must be a direct command. BIT 0,(IY+$30) ; test FLAGS2 - clear the main screen ? CALL NZ,L0DAF ; routine CL-ALL, if so, e.g. after listing. CALL L0D6E ; routine CLS-LOWER anyway. LD A,$19 ; compute scroll count as 25 minus SUB (IY+$4F) ; value of S_POSN_hi. LD ($5C8C),A ; update SCR_CT system variable. SET 7,(IY+$01) ; update FLAGS - signal running program. LD (IY+$00),$FF ; set ERR_NR to 'OK'. LD (IY+$0A),$01 ; set NSPPC to one for first statement. CALL L1B8A ; call routine LINE-RUN to run the line. ; sysvar ERR_SP therefore addresses MAIN-4 ; Examples of direct commands are RUN, CLS, LOAD "", PRINT USR 40000, ; LPRINT "A"; etc.. ; If a user written machine-code program disables interrupts then it ; must enable them to pass the next step. We also jumped to here if the ; keyboard was not being used. ;; MAIN-4 L1303: HALT ; wait for interrupt the only routine that can ; set bit 5 of FLAGS. RES 5,(IY+$01) ; update bit 5 of FLAGS - signal no new key. BIT 1,(IY+$30) ; test FLAGS2 - is printer buffer clear ? CALL NZ,L0ECD ; call routine COPY-BUFF if not. ; Note. the programmer has neglected ; to set bit 1 of FLAGS first. LD A,($5C3A) ; fetch ERR_NR INC A ; increment to give true code. ; Now deal with a runtime error as opposed to an editing error. ; However if the error code is now zero then the OK message will be printed. ;; MAIN-G L1313: PUSH AF ; save the error number. LD HL,$0000 ; prepare to clear some system variables. LD (IY+$37),H ; clear all the bits of FLAGX. LD (IY+$26),H ; blank X_PTR_hi to suppress error marker. LD ($5C0B),HL ; blank DEFADD to signal that no defined ; function is currently being evaluated. LD HL,$0001 ; explicit - inc hl would do. LD ($5C16),HL ; ensure STRMS-00 is keyboard. CALL L16B0 ; routine SET-MIN clears workspace etc. RES 5,(IY+$37) ; update FLAGX - signal in EDIT not INPUT mode. ; Note. all the bits were reset earlier. CALL L0D6E ; call routine CLS-LOWER. SET 5,(IY+$02) ; update TV_FLAG - signal lower screen ; requires clearing. POP AF ; bring back the true error number LD B,A ; and make a copy in B. CP $0A ; is it a print-ready digit ? JR C,L133C ; forward to MAIN-5 if so. ADD A,$07 ; add ASCII offset to letters. ;; MAIN-5 L133C: CALL L15EF ; call routine OUT-CODE to print the code. LD A,$20 ; followed by a space. RST 10H ; PRINT-A LD A,B ; fetch stored report code. LD DE,L1391 ; address: rpt-mesgs. CALL L0C0A ; call routine PO-MSG to print the message. X1349: XOR A ; clear accumulator to directly LD DE,L1537 - 1 ; address the comma and space message. CALL L0C0A ; routine PO-MSG prints ', ' although it would ; be more succinct to use RST $10. LD BC,($5C45) ; fetch PPC the current line number. CALL L1A1B ; routine OUT-NUM-1 will print that LD A,$3A ; then a ':' character. RST 10H ; PRINT-A LD C,(IY+$0D) ; then SUBPPC for statement LD B,$00 ; limited to 127 CALL L1A1B ; routine OUT-NUM-1 prints BC. CALL L1097 ; routine CLEAR-SP clears editing area which ; probably contained 'RUN'. LD A,($5C3A) ; fetch ERR_NR again INC A ; test for no error originally $FF. JR Z,L1386 ; forward to MAIN-9 if no error. CP $09 ; is code Report 9 STOP ? JR Z,L1373 ; forward to MAIN-6 if so CP $15 ; is code Report L Break ? JR NZ,L1376 ; forward to MAIN-7 if not ; Stop or Break was encountered so consider CONTINUE. ;; MAIN-6 L1373: INC (IY+$0D) ; increment SUBPPC to next statement. ;; MAIN-7 L1376: LD BC,$0003 ; prepare to copy 3 system variables to LD DE,$5C70 ; address OSPPC - statement for CONTINUE. ; also updating OLDPPC line number below. LD HL,$5C44 ; set source top to NSPPC next statement. BIT 7,(HL) ; did BREAK occur before the jump ? ; e.g. between GO TO and next statement. JR Z,L1384 ; skip forward to MAIN-8, if not, as set-up ; is correct. ADD HL,BC ; set source to SUBPPC number of current ; statement/line which will be repeated. ;; MAIN-8 L1384: LDDR ; copy PPC to OLDPPC and SUBPPC to OSPCC ; or NSPPC to OLDPPC and NEWPPC to OSPCC ;; MAIN-9 L1386: LD (IY+$0A),$FF ; update NSPPC - signal 'no jump'. RES 3,(IY+$01) ; update FLAGS - signal use 'K' mode for ; the first character in the editor and JP L12AC ; jump back to MAIN-2. ; ---------------------- ; Canned report messages ; ---------------------- ; The Error reports with the last byte inverted. The first entry ; is a dummy entry. The last, which begins with $7F, the Spectrum ; character for copyright symbol, is placed here for convenience ; as is the preceding comma and space. ; The report line must accommodate a 4-digit line number and a 3-digit ; statement number which limits the length of the message text to twenty ; characters. ; e.g. "B Integer out of range, 1000:127" ;; rpt-mesgs L1391: DEFB $80 DEFB 'O','K'+$80 ; 0 DEFM "NEXT without FO" DEFB 'R'+$80 ; 1 DEFM "Variable not foun" DEFB 'd'+$80 ; 2 DEFM "Subscript wron" DEFB 'g'+$80 ; 3 DEFM "Out of memor" DEFB 'y'+$80 ; 4 DEFM "Out of scree" DEFB 'n'+$80 ; 5 DEFM "Number too bi" DEFB 'g'+$80 ; 6 DEFM "RETURN without GOSU" DEFB 'B'+$80 ; 7 DEFM "End of fil" DEFB 'e'+$80 ; 8 DEFM "STOP statemen" DEFB 't'+$80 ; 9 DEFM "Invalid argumen" DEFB 't'+$80 ; A DEFM "Integer out of rang" DEFB 'e'+$80 ; B DEFM "Nonsense in BASI" DEFB 'C'+$80 ; C DEFM "BREAK - CONT repeat" DEFB 's'+$80 ; D DEFM "Out of DAT" DEFB 'A'+$80 ; E DEFM "Invalid file nam" DEFB 'e'+$80 ; F DEFM "No room for lin" DEFB 'e'+$80 ; G DEFM "STOP in INPU" DEFB 'T'+$80 ; H DEFM "FOR without NEX" DEFB 'T'+$80 ; I DEFM "Invalid I/O devic" DEFB 'e'+$80 ; J DEFM "Invalid colou" DEFB 'r'+$80 ; K DEFM "BREAK into progra" DEFB 'm'+$80 ; L DEFM "RAMTOP no goo" DEFB 'd'+$80 ; M DEFM "Statement los" DEFB 't'+$80 ; N DEFM "Invalid strea" DEFB 'm'+$80 ; O DEFM "FN without DE" DEFB 'F'+$80 ; P DEFM "Parameter erro" DEFB 'r'+$80 ; Q DEFM "Tape loading erro" DEFB 'r'+$80 ; R ;; comma-sp L1537: DEFB ',',' '+$80 ; used in report line. ;; copyright L1539: DEFB $7F ; copyright DEFM " 1982 Sinclair Research Lt" DEFB 'd'+$80 ; ------------- ; REPORT-G ; ------------- ; Note ERR_SP points here during line entry which allows the ; normal 'Out of Memory' report to be augmented to the more ; precise 'No Room for line' report. ;; REPORT-G ; No Room for line L1555: LD A,$10 ; i.e. 'G' -$30 -$07 LD BC,$0000 ; this seems unnecessary. JP L1313 ; jump back to MAIN-G ; ----------------------------- ; Handle addition of BASIC line ; ----------------------------- ; Note this is not a subroutine but a branch of the main execution loop. ; System variable ERR_SP still points to editing error handler. ; A new line is added to the BASIC program at the appropriate place. ; An existing line with same number is deleted first. ; Entering an existing line number deletes that line. ; Entering a non-existent line allows the subsequent line to be edited next. ;; MAIN-ADD L155D: LD ($5C49),BC ; set E_PPC to extracted line number. LD HL,($5C5D) ; fetch CH_ADD - points to location after the ; initial digits (set in E_LINE_NO). EX DE,HL ; save start of BASIC in DE. LD HL,L1555 ; Address: REPORT-G PUSH HL ; is pushed on stack and addressed by ERR_SP. ; the only error that can occur is ; 'Out of memory'. LD HL,($5C61) ; fetch WORKSP - end of line. SCF ; prepare for true subtraction. SBC HL,DE ; find length of BASIC and PUSH HL ; save it on stack. LD H,B ; transfer line number LD L,C ; to HL register. CALL L196E ; routine LINE-ADDR will see if ; a line with the same number exists. JR NZ,L157D ; forward if no existing line to MAIN-ADD1. CALL L19B8 ; routine NEXT-ONE finds the existing line. CALL L19E8 ; routine RECLAIM-2 reclaims it. ;; MAIN-ADD1 L157D: POP BC ; retrieve the length of the new line. LD A,C ; and test if carriage return only DEC A ; i.e. one byte long. OR B ; result would be zero. JR Z,L15AB ; forward to MAIN-ADD2 is so. PUSH BC ; save the length again. INC BC ; adjust for inclusion INC BC ; of line number (two bytes) INC BC ; and line length INC BC ; (two bytes). DEC HL ; HL points to location before the destination LD DE,($5C53) ; fetch the address of PROG PUSH DE ; and save it on the stack CALL L1655 ; routine MAKE-ROOM creates BC spaces in ; program area and updates pointers. POP HL ; restore old program pointer. LD ($5C53),HL ; and put back in PROG as it may have been ; altered by the POINTERS routine. POP BC ; retrieve BASIC length PUSH BC ; and save again. INC DE ; points to end of new area. LD HL,($5C61) ; set HL to WORKSP - location after edit line. DEC HL ; decrement to address end marker. DEC HL ; decrement to address carriage return. LDDR ; copy the BASIC line back to initial command. LD HL,($5C49) ; fetch E_PPC - line number. EX DE,HL ; swap it to DE, HL points to last of ; four locations. POP BC ; retrieve length of line. LD (HL),B ; high byte last. DEC HL ; LD (HL),C ; then low byte of length. DEC HL ; LD (HL),E ; then low byte of line number. DEC HL ; LD (HL),D ; then high byte range $0 - $27 (1-9999). ;; MAIN-ADD2 L15AB: POP AF ; drop the address of Report G JP L12A2 ; and back to MAIN-EXEC producing a listing ; and to reset ERR_SP in EDITOR. ; --------------------------------- ; THE 'INITIAL CHANNEL' INFORMATION ; --------------------------------- ; This initial channel information is copied from ROM to RAM, during ; initialization. It's new location is after the system variables and is ; addressed by the system variable CHANS which means that it can slide up and ; down in memory. The table is never searched, by this ROM, and the last ; character, which could be anything other than a comma, provides a ; convenient resting place for DATADD. ;; init-chan L15AF: DEFW L09F4 ; PRINT-OUT DEFW L10A8 ; KEY-INPUT DEFB $4B ; 'K' DEFW L09F4 ; PRINT-OUT DEFW L15C4 ; REPORT-J DEFB $53 ; 'S' DEFW L0F81 ; ADD-CHAR DEFW L15C4 ; REPORT-J DEFB $52 ; 'R' DEFW L09F4 ; PRINT-OUT DEFW L15C4 ; REPORT-J DEFB $50 ; 'P' DEFB $80 ; End Marker ;; REPORT-J L15C4: RST 08H ; ERROR-1 DEFB $12 ; Error Report: Invalid I/O device ; ------------------------- ; THE 'INITIAL STREAM' DATA ; ------------------------- ; This is the initial stream data for the seven streams $FD - $03 that is ; copied from ROM to the STRMS system variables area during initialization. ; There are reserved locations there for another 12 streams. Each location ; contains an offset to the second byte of a channel. The first byte of a ; channel can't be used as that would result in an offset of zero for some ; and zero is used to denote that a stream is closed. ;; init-strm L15C6: DEFB $01, $00 ; stream $FD offset to channel 'K' DEFB $06, $00 ; stream $FE offset to channel 'S' DEFB $0B, $00 ; stream $FF offset to channel 'R' DEFB $01, $00 ; stream $00 offset to channel 'K' DEFB $01, $00 ; stream $01 offset to channel 'K' DEFB $06, $00 ; stream $02 offset to channel 'S' DEFB $10, $00 ; stream $03 offset to channel 'P' ; ------------------------------ ; THE 'INPUT CONTROL' SUBROUTINE ; ------------------------------ ; ;; WAIT-KEY L15D4: BIT 5,(IY+$02) ; test TV_FLAG - clear lower screen ? JR NZ,L15DE ; forward to WAIT-KEY1 if so. SET 3,(IY+$02) ; update TV_FLAG - signal reprint the edit ; line to the lower screen. ;; WAIT-KEY1 L15DE: CALL L15E6 ; routine INPUT-AD is called. RET C ; return with acceptable keys. JR Z,L15DE ; back to WAIT-KEY1 if no key is pressed ; or it has been handled within INPUT-AD. ; Note. When inputting from the keyboard all characters are returned with ; above conditions so this path is never taken. ;; REPORT-8 L15E4: RST 08H ; ERROR-1 DEFB $07 ; Error Report: End of file ; --------------------------- ; THE 'INPUT ADDRESS' ROUTINE ; --------------------------- ; This routine fetches the address of the input stream from the current ; channel area using the system variable CURCHL. ;; INPUT-AD L15E6: EXX ; switch in alternate set. PUSH HL ; save HL register LD HL,($5C51) ; fetch address of CURCHL - current channel. INC HL ; step over output routine INC HL ; to point to low byte of input routine. JR L15F7 ; forward to CALL-SUB. ; ------------------------- ; THE 'CODE OUTPUT' ROUTINE ; ------------------------- ; This routine is called on five occasions to print the ASCII equivalent of ; a value 0-9. ;; OUT-CODE L15EF: LD E,$30 ; add 48 decimal to give the ASCII character ADD A,E ; '0' to '9' and continue into the main output ; routine. ; ------------------------- ; THE 'MAIN OUTPUT' ROUTINE ; ------------------------- ; PRINT-A-2 is a continuation of the RST 10 restart that prints any character. ; The routine prints to the current channel and the printing of control codes ; may alter that channel to divert subsequent RST 10 instructions to temporary ; routines. The normal channel is $09F4. ;; PRINT-A-2 L15F2: EXX ; switch in alternate set PUSH HL ; save HL register LD HL,($5C51) ; fetch CURCHL the current channel. ; input-ad rejoins here also. ;; CALL-SUB L15F7: LD E,(HL) ; put the low byte in E. INC HL ; advance address. LD D,(HL) ; put the high byte to D. EX DE,HL ; transfer the stream to HL. CALL L162C ; use routine CALL-JUMP. ; in effect CALL (HL). POP HL ; restore saved HL register. EXX ; switch back to the main set and RET ; return. ; -------------------------- ; THE 'OPEN CHANNEL' ROUTINE ; -------------------------- ; This subroutine is used by the ROM to open a channel 'K', 'S', 'R' or 'P'. ; This is either for its own use or in response to a user's request, for ; example, when '#' is encountered with output - PRINT, LIST etc. ; or with input - INPUT, INKEY$ etc. ; It is entered with a system stream $FD - $FF, or a user stream $00 - $0F ; in the accumulator. ;; CHAN-OPEN L1601: ADD A,A ; double the stream ($FF will become $FE etc.) ADD A,$16 ; add the offset to stream 0 from $5C00 LD L,A ; result to L LD H,$5C ; now form the address in STRMS area. LD E,(HL) ; fetch low byte of CHANS offset INC HL ; address next LD D,(HL) ; fetch high byte of offset LD A,D ; test that the stream is open. OR E ; zero if closed. JR NZ,L1610 ; forward to CHAN-OP-1 if open. ;; REPORT-Oa L160E: RST 08H ; ERROR-1 DEFB $17 ; Error Report: Invalid stream ; continue here if stream was open. Note that the offset is from CHANS ; to the second byte of the channel. ;; CHAN-OP-1 L1610: DEC DE ; reduce offset so it points to the channel. LD HL,($5C4F) ; fetch CHANS the location of the base of ; the channel information area ADD HL,DE ; and add the offset to address the channel. ; and continue to set flags. ; ----------------- ; Set channel flags ; ----------------- ; This subroutine is used from ED-EDIT, str$ and read-in to reset the ; current channel when it has been temporarily altered. ;; CHAN-FLAG L1615: LD ($5C51),HL ; set CURCHL system variable to the ; address in HL RES 4,(IY+$30) ; update FLAGS2 - signal K channel not in use. ; Note. provide a default for channel 'R'. INC HL ; advance past INC HL ; output routine. INC HL ; advance past INC HL ; input routine. LD C,(HL) ; pick up the letter. LD HL,L162D ; address: chn-cd-lu CALL L16DC ; routine INDEXER finds offset to a ; flag-setting routine. RET NC ; but if the letter wasn't found in the ; table just return now. - channel 'R'. LD D,$00 ; prepare to add LD E,(HL) ; offset to E ADD HL,DE ; add offset to location of offset to form ; address of routine ;; CALL-JUMP L162C: JP (HL) ; jump to the routine ; Footnote. calling any location that holds JP (HL) is the equivalent to ; a pseudo Z80 instruction CALL (HL). The ROM uses the instruction above. ; -------------------------- ; Channel code look-up table ; -------------------------- ; This table is used by the routine above to find one of the three ; flag setting routines below it. ; A zero end-marker is required as channel 'R' is not present. ;; chn-cd-lu L162D: DEFB 'K', L1634-$-1 ; offset $06 to CHAN-K DEFB 'S', L1642-$-1 ; offset $12 to CHAN-S DEFB 'P', L164D-$-1 ; offset $1B to CHAN-P DEFB $00 ; end marker. ; -------------- ; Channel K flag ; -------------- ; routine to set flags for lower screen/keyboard channel. ;; CHAN-K L1634: SET 0,(IY+$02) ; update TV_FLAG - signal lower screen in use RES 5,(IY+$01) ; update FLAGS - signal no new key SET 4,(IY+$30) ; update FLAGS2 - signal K channel in use JR L1646 ; forward to CHAN-S-1 for indirect exit ; -------------- ; Channel S flag ; -------------- ; routine to set flags for upper screen channel. ;; CHAN-S L1642: RES 0,(IY+$02) ; TV_FLAG - signal main screen in use ;; CHAN-S-1 L1646: RES 1,(IY+$01) ; update FLAGS - signal printer not in use JP L0D4D ; jump back to TEMPS and exit via that ; routine after setting temporary attributes. ; -------------- ; Channel P flag ; -------------- ; This routine sets a flag so that subsequent print related commands ; print to printer or update the relevant system variables. ; This status remains in force until reset by the routine above. ;; CHAN-P L164D: SET 1,(IY+$01) ; update FLAGS - signal printer in use RET ; return ; -------------------------- ; THE 'ONE SPACE' SUBROUTINE ; -------------------------- ; This routine is called once only to create a single space ; in workspace by ADD-CHAR. ;; ONE-SPACE L1652: LD BC,$0001 ; create space for a single character. ; --------- ; Make Room ; --------- ; This entry point is used to create BC spaces in various areas such as ; program area, variables area, workspace etc.. ; The entire free RAM is available to each BASIC statement. ; On entry, HL addresses where the first location is to be created. ; Afterwards, HL will point to the location before this. ;; MAKE-ROOM L1655: PUSH HL ; save the address pointer. CALL L1F05 ; routine TEST-ROOM checks if room ; exists and generates an error if not. POP HL ; restore the address pointer. CALL L1664 ; routine POINTERS updates the ; dynamic memory location pointers. ; DE now holds the old value of STKEND. LD HL,($5C65) ; fetch new STKEND the top destination. EX DE,HL ; HL now addresses the top of the area to ; be moved up - old STKEND. LDDR ; the program, variables, etc are moved up. RET ; return with new area ready to be populated. ; HL points to location before new area, ; and DE to last of new locations. ; ----------------------------------------------- ; Adjust pointers before making or reclaiming room ; ----------------------------------------------- ; This routine is called by MAKE-ROOM to adjust upwards and by RECLAIM to ; adjust downwards the pointers within dynamic memory. ; The fourteen pointers to dynamic memory, starting with VARS and ending ; with STKEND, are updated adding BC if they are higher than the position ; in HL. ; The system variables are in no particular order except that STKEND, the first ; free location after dynamic memory must be the last encountered. ;; POINTERS L1664: PUSH AF ; preserve accumulator. PUSH HL ; put pos pointer on stack. LD HL,$5C4B ; address VARS the first of the LD A,$0E ; fourteen variables to consider. ;; PTR-NEXT L166B: LD E,(HL) ; fetch the low byte of the system variable. INC HL ; advance address. LD D,(HL) ; fetch high byte of the system variable. EX (SP),HL ; swap pointer on stack with the variable ; pointer. AND A ; prepare to subtract. SBC HL,DE ; subtract variable address ADD HL,DE ; and add back EX (SP),HL ; swap pos with system variable pointer JR NC,L167F ; forward to PTR-DONE if var before pos PUSH DE ; save system variable address. EX DE,HL ; transfer to HL ADD HL,BC ; add the offset EX DE,HL ; back to DE LD (HL),D ; load high byte DEC HL ; move back LD (HL),E ; load low byte INC HL ; advance to high byte POP DE ; restore old system variable address. ;; PTR-DONE L167F: INC HL ; address next system variable. DEC A ; decrease counter. JR NZ,L166B ; back to PTR-NEXT if more. EX DE,HL ; transfer old value of STKEND to HL. ; Note. this has always been updated. POP DE ; pop the address of the position. POP AF ; pop preserved accumulator. AND A ; clear carry flag preparing to subtract. SBC HL,DE ; subtract position from old stkend LD B,H ; to give number of data bytes LD C,L ; to be moved. INC BC ; increment as we also copy byte at old STKEND. ADD HL,DE ; recompute old stkend. EX DE,HL ; transfer to DE. RET ; return. ; ------------------- ; Collect line number ; ------------------- ; This routine extracts a line number, at an address that has previously ; been found using LINE-ADDR, and it is entered at LINE-NO. If it encounters ; the program 'end-marker' then the previous line is used and if that ; should also be unacceptable then zero is used as it must be a direct ; command. The program end-marker is the variables end-marker $80, or ; if variables exist, then the first character of any variable name. ;; LINE-ZERO L168F: DEFB $00, $00 ; dummy line number used for direct commands ;; LINE-NO-A L1691: EX DE,HL ; fetch the previous line to HL and set LD DE,L168F ; DE to LINE-ZERO should HL also fail. ; -> The Entry Point. ;; LINE-NO L1695: LD A,(HL) ; fetch the high byte - max $2F AND $C0 ; mask off the invalid bits. JR NZ,L1691 ; to LINE-NO-A if an end-marker. LD D,(HL) ; reload the high byte. INC HL ; advance address. LD E,(HL) ; pick up the low byte. RET ; return from here. ; ------------------- ; Handle reserve room ; ------------------- ; This is a continuation of the restart BC-SPACES ;; RESERVE L169E: LD HL,($5C63) ; STKBOT first location of calculator stack DEC HL ; make one less than new location CALL L1655 ; routine MAKE-ROOM creates the room. INC HL ; address the first new location INC HL ; advance to second POP BC ; restore old WORKSP LD ($5C61),BC ; system variable WORKSP was perhaps ; changed by POINTERS routine. POP BC ; restore count for return value. EX DE,HL ; switch. DE = location after first new space INC HL ; HL now location after new space RET ; return. ; --------------------------- ; Clear various editing areas ; --------------------------- ; This routine sets the editing area, workspace and calculator stack ; to their minimum configurations as at initialization and indeed this ; routine could have been relied on to perform that task. ; This routine uses HL only and returns with that register holding ; WORKSP/STKBOT/STKEND though no use is made of this. The routines also ; reset MEM to its usual place in the systems variable area should it ; have been relocated to a FOR-NEXT variable. The main entry point ; SET-MIN is called at the start of the MAIN-EXEC loop and prior to ; displaying an error. ;; SET-MIN L16B0: LD HL,($5C59) ; fetch E_LINE LD (HL),$0D ; insert carriage return LD ($5C5B),HL ; make K_CUR keyboard cursor point there. INC HL ; next location LD (HL),$80 ; holds end-marker $80 INC HL ; next location becomes LD ($5C61),HL ; start of WORKSP ; This entry point is used prior to input and prior to the execution, ; or parsing, of each statement. ;; SET-WORK L16BF: LD HL,($5C61) ; fetch WORKSP value LD ($5C63),HL ; and place in STKBOT ; This entry point is used to move the stack back to its normal place ; after temporary relocation during line entry and also from ERROR-3 ;; SET-STK L16C5: LD HL,($5C63) ; fetch STKBOT value LD ($5C65),HL ; and place in STKEND. PUSH HL ; perhaps an obsolete entry point. LD HL,$5C92 ; normal location of MEM-0 LD ($5C68),HL ; is restored to system variable MEM. POP HL ; saved value not required. RET ; return. ; ------------------ ; Reclaim edit-line? ; ------------------ ; This seems to be legacy code from the ZX80/ZX81 as it is ; not used in this ROM. ; That task, in fact, is performed here by the dual-area routine CLEAR-SP. ; This routine is designed to deal with something that is known to be in the ; edit buffer and not workspace. ; On entry, HL must point to the end of the something to be deleted. ;; REC-EDIT L16D4: LD DE,($5C59) ; fetch start of edit line from E_LINE. JP L19E5 ; jump forward to RECLAIM-1. ; -------------------------- ; The Table INDEXING routine ; -------------------------- ; This routine is used to search two-byte hash tables for a character ; held in C, returning the address of the following offset byte. ; if it is known that the character is in the table e.g. for priorities, ; then the table requires no zero end-marker. If this is not known at the ; outset then a zero end-marker is required and carry is set to signal ; success. ;; INDEXER-1 L16DB: INC HL ; address the next pair of values. ; -> The Entry Point. ;; INDEXER L16DC: LD A,(HL) ; fetch the first byte of pair AND A ; is it the end-marker ? RET Z ; return with carry reset if so. CP C ; is it the required character ? INC HL ; address next location. JR NZ,L16DB ; back to INDEXER-1 if no match. SCF ; else set the carry flag. RET ; return with carry set ; -------------------------------- ; The Channel and Streams Routines ; -------------------------------- ; A channel is an input/output route to a hardware device ; and is identified to the system by a single letter e.g. 'K' for ; the keyboard. A channel can have an input and output route ; associated with it in which case it is bi-directional like ; the keyboard. Others like the upper screen 'S' are output ; only and the input routine usually points to a report message. ; Channels 'K' and 'S' are system channels and it would be inappropriate ; to close the associated streams so a mechanism is provided to ; re-attach them. When the re-attachment is no longer required, then ; closing these streams resets them as at initialization. ; Early adverts said that the network and RS232 were in this ROM. ; Channels 'N' and 'B' are user channels and have been removed successfully ; if, as seems possible, they existed. ; Ironically the tape streamer is not accessed through streams and ; channels. ; Early demonstrations of the Spectrum showed a single microdrive being ; controlled by the main ROM. ; --------------------- ; THE 'CLOSE #' COMMAND ; --------------------- ; This command allows streams to be closed after use. ; Any temporary memory areas used by the stream would be reclaimed and ; finally flags set or reset if necessary. ;; CLOSE L16E5: CALL L171E ; routine STR-DATA fetches parameter ; from calculator stack and gets the ; existing STRMS data pointer address in HL ; and stream offset from CHANS in BC. ; Note. this offset could be zero if the ; stream is already closed. A check for this ; should occur now and an error should be ; generated, for example, ; Report S 'Stream status closed'. CALL L1701 ; routine CLOSE-2 would perform any actions ; peculiar to that stream without disturbing ; data pointer to STRMS entry in HL. LD BC,$0000 ; the stream is to be blanked. LD DE,$A3E2 ; the number of bytes from stream 4, $5C1E, ; to $10000 EX DE,HL ; transfer offset to HL, STRMS data pointer ; to DE. ADD HL,DE ; add the offset to the data pointer. JR C,L16FC ; forward to CLOSE-1 if a non-system stream. ; i.e. higher than 3. ; proceed with a negative result. LD BC,L15C6 + 14 ; prepare the address of the byte after ; the initial stream data in ROM. ($15D4) ADD HL,BC ; index into the data table with negative value. LD C,(HL) ; low byte to C INC HL ; address next. LD B,(HL) ; high byte to B. ; and for streams 0 - 3 just enter the initial data back into the STRMS entry ; streams 0 - 2 can't be closed as they are shared by the operating system. ; -> for streams 4 - 15 then blank the entry. ;; CLOSE-1 L16FC: EX DE,HL ; address of stream to HL. LD (HL),C ; place zero (or low byte). INC HL ; next address. LD (HL),B ; place zero (or high byte). RET ; return. ; ------------------------ ; THE 'CLOSE-2' SUBROUTINE ; ------------------------ ; There is not much point in coming here. ; The purpose was once to find the offset to a special closing routine, ; in this ROM and within 256 bytes of the close stream look up table that ; would reclaim any buffers associated with a stream. At least one has been ; removed. ; Any attempt to CLOSE streams $00 to $04, without first opening the stream, ; will lead to either a system restart or the production of a strange report. ; credit: Martin Wren-Hilton 1982. ;; CLOSE-2 L1701: PUSH HL ; * save address of stream data pointer ; in STRMS on the machine stack. LD HL,($5C4F) ; fetch CHANS address to HL ADD HL,BC ; add the offset to address the second ; byte of the output routine hopefully. INC HL ; step past INC HL ; the input routine. ; Note. When the Sinclair Interface1 is fitted then an instruction fetch ; on the next address pages this ROM out and the shadow ROM in. ;; ROM_TRAP L1708: INC HL ; to address channel's letter LD C,(HL) ; pick it up in C. ; Note. but if stream is already closed we ; get the value $10 (the byte preceding 'K'). EX DE,HL ; save the pointer to the letter in DE. ; Note. The string pointer is saved but not used!! LD HL,L1716 ; address: cl-str-lu in ROM. CALL L16DC ; routine INDEXER uses the code to get ; the 8-bit offset from the current point to ; the address of the closing routine in ROM. ; Note. it won't find $10 there! LD C,(HL) ; transfer the offset to C. LD B,$00 ; prepare to add. ADD HL,BC ; add offset to point to the address of the ; routine that closes the stream. ; (and presumably removes any buffers that ; are associated with it.) JP (HL) ; jump to that routine. ; -------------------------------- ; THE 'CLOSE STREAM LOOK-UP' TABLE ; -------------------------------- ; This table contains an entry for a letter found in the CHANS area. ; followed by an 8-bit displacement, from that byte's address in the ; table to the routine that performs any ancillary actions associated ; with closing the stream of that channel. ; The table doesn't require a zero end-marker as the letter has been ; picked up from a channel that has an open stream. ;; cl-str-lu L1716: DEFB 'K', L171C-$-1 ; offset 5 to CLOSE-STR DEFB 'S', L171C-$-1 ; offset 3 to CLOSE-STR DEFB 'P', L171C-$-1 ; offset 1 to CLOSE-STR ; ------------------------------ ; THE 'CLOSE STREAM' SUBROUTINES ; ------------------------------ ; The close stream routines in fact have no ancillary actions to perform ; which is not surprising with regard to 'K' and 'S'. ;; CLOSE-STR L171C: POP HL ; * now just restore the stream data pointer RET ; in STRMS and return. ; ----------- ; Stream data ; ----------- ; This routine finds the data entry in the STRMS area for the specified ; stream which is passed on the calculator stack. It returns with HL ; pointing to this system variable and BC holding a displacement from ; the CHANS area to the second byte of the stream's channel. If BC holds ; zero, then that signifies that the stream is closed. ;; STR-DATA L171E: CALL L1E94 ; routine FIND-INT1 fetches parameter to A CP $10 ; is it less than 16d ? JR C,L1727 ; skip forward to STR-DATA1 if so. ;; REPORT-Ob L1725: RST 08H ; ERROR-1 DEFB $17 ; Error Report: Invalid stream ;; STR-DATA1 L1727: ADD A,$03 ; add the offset for 3 system streams. ; range 00 - 15d becomes 3 - 18d. RLCA ; double as there are two bytes per ; stream - now 06 - 36d LD HL,$5C10 ; address STRMS - the start of the streams ; data area in system variables. LD C,A ; transfer the low byte to A. LD B,$00 ; prepare to add offset. ADD HL,BC ; add to address the data entry in STRMS. ; the data entry itself contains an offset from CHANS to the address of the ; stream LD C,(HL) ; low byte of displacement to C. INC HL ; address next. LD B,(HL) ; high byte of displacement to B. DEC HL ; step back to leave HL pointing to STRMS ; data entry. RET ; return with CHANS displacement in BC ; and address of stream data entry in HL. ; -------------------- ; Handle OPEN# command ; -------------------- ; Command syntax example: OPEN #5,"s" ; On entry the channel code entry is on the calculator stack with the next ; value containing the stream identifier. They have to swapped. ;; OPEN L1736: RST 28H ;; FP-CALC ;s,c. DEFB $01 ;;exchange ;c,s. DEFB $38 ;;end-calc CALL L171E ; routine STR-DATA fetches the stream off ; the stack and returns with the CHANS ; displacement in BC and HL addressing ; the STRMS data entry. LD A,B ; test for zero which OR C ; indicates the stream is closed. JR Z,L1756 ; skip forward to OPEN-1 if so. ; if it is a system channel then it can re-attached. EX DE,HL ; save STRMS address in DE. LD HL,($5C4F) ; fetch CHANS. ADD HL,BC ; add the offset to address the second ; byte of the channel. INC HL ; skip over the INC HL ; input routine. INC HL ; and address the letter. LD A,(HL) ; pick up the letter. EX DE,HL ; save letter pointer and bring back ; the STRMS pointer. CP $4B ; is it 'K' ? JR Z,L1756 ; forward to OPEN-1 if so CP $53 ; is it 'S' ? JR Z,L1756 ; forward to OPEN-1 if so CP $50 ; is it 'P' ? JR NZ,L1725 ; back to REPORT-Ob if not. ; to report 'Invalid stream'. ; continue if one of the upper-case letters was found. ; and rejoin here from above if stream was closed. ;; OPEN-1 L1756: CALL L175D ; routine OPEN-2 opens the stream. ; it now remains to update the STRMS variable. LD (HL),E ; insert or overwrite the low byte. INC HL ; address high byte in STRMS. LD (HL),D ; insert or overwrite the high byte. RET ; return. ; ----------------- ; OPEN-2 Subroutine ; ----------------- ; There is some point in coming here as, as well as once creating buffers, ; this routine also sets flags. ;; OPEN-2 L175D: PUSH HL ; * save the STRMS data entry pointer. CALL L2BF1 ; routine STK-FETCH now fetches the ; parameters of the channel string. ; start in DE, length in BC. LD A,B ; test that it is not OR C ; the null string. JR NZ,L1767 ; skip forward to OPEN-3 with 1 character ; or more! ;; REPORT-Fb L1765: RST 08H ; ERROR-1 DEFB $0E ; Error Report: Invalid file name ;; OPEN-3 L1767: PUSH BC ; save the length of the string. LD A,(DE) ; pick up the first character. ; Note. There can be more than one character. AND $DF ; make it upper-case. LD C,A ; place it in C. LD HL,L177A ; address: op-str-lu is loaded. CALL L16DC ; routine INDEXER will search for letter. JR NC,L1765 ; back to REPORT-F if not found ; 'Invalid filename' LD C,(HL) ; fetch the displacement to opening routine. LD B,$00 ; prepare to add. ADD HL,BC ; now form address of opening routine. POP BC ; restore the length of string. JP (HL) ; now jump forward to the relevant routine. ; ------------------------- ; OPEN stream look-up table ; ------------------------- ; The open stream look-up table consists of matched pairs. ; The channel letter is followed by an 8-bit displacement to the ; associated stream-opening routine in this ROM. ; The table requires a zero end-marker as the letter has been ; provided by the user and not the operating system. ;; op-str-lu L177A: DEFB 'K', L1781-$-1 ; $06 offset to OPEN-K DEFB 'S', L1785-$-1 ; $08 offset to OPEN-S DEFB 'P', L1789-$-1 ; $0A offset to OPEN-P DEFB $00 ; end-marker. ; ---------------------------- ; The Stream Opening Routines. ; ---------------------------- ; These routines would have opened any buffers associated with the stream ; before jumping forward to OPEN-END with the displacement value in E ; and perhaps a modified value in BC. The strange pathing does seem to ; provide for flexibility in this respect. ; ; There is no need to open the printer buffer as it is there already ; even if you are still saving up for a ZX Printer or have moved onto ; something bigger. In any case it would have to be created after ; the system variables but apart from that it is a simple task ; and all but one of the ROM routines can handle a buffer in that position. ; (PR-ALL-6 would require an extra 3 bytes of code). ; However it wouldn't be wise to have two streams attached to the ZX Printer ; as you can now, so one assumes that if PR_CC_hi was non-zero then ; the OPEN-P routine would have refused to attach a stream if another ; stream was attached. ; Something of significance is being passed to these ghost routines in the ; second character. Strings 'RB', 'RT' perhaps or a drive/station number. ; The routine would have to deal with that and exit to OPEN_END with BC ; containing $0001 or more likely there would be an exit within the routine. ; Anyway doesn't matter, these routines are long gone. ; ----------------- ; OPEN-K Subroutine ; ----------------- ; Open Keyboard stream. ;; OPEN-K L1781: LD E,$01 ; 01 is offset to second byte of channel 'K'. JR L178B ; forward to OPEN-END ; ----------------- ; OPEN-S Subroutine ; ----------------- ; Open Screen stream. ;; OPEN-S L1785: LD E,$06 ; 06 is offset to 2nd byte of channel 'S' JR L178B ; to OPEN-END ; ----------------- ; OPEN-P Subroutine ; ----------------- ; Open Printer stream. ;; OPEN-P L1789: LD E,$10 ; 16d is offset to 2nd byte of channel 'P' ;; OPEN-END L178B: DEC BC ; the stored length of 'K','S','P' or ; whatever is now tested. ?? LD A,B ; test now if initial or residual length OR C ; is one character. JR NZ,L1765 ; to REPORT-Fb 'Invalid file name' if not. LD D,A ; load D with zero to form the displacement ; in the DE register. POP HL ; * restore the saved STRMS pointer. RET ; return to update STRMS entry thereby ; signaling stream is open. ; ---------------------------------------- ; Handle CAT, ERASE, FORMAT, MOVE commands ; ---------------------------------------- ; These just generate an error report as the ROM is 'incomplete'. ; ; Luckily this provides a mechanism for extending these in a shadow ROM ; but without the powerful mechanisms set up in this ROM. ; An instruction fetch on $0008 may page in a peripheral ROM, ; e.g. the Sinclair Interface 1 ROM, to handle these commands. ; However that wasn't the plan. ; Development of this ROM continued for another three months until the cost ; of replacing it and the manual became unfeasible. ; The ultimate power of channels and streams died at birth. ;; CAT-ETC L1793: JR L1725 ; to REPORT-Ob ; ----------------- ; Perform AUTO-LIST ; ----------------- ; This produces an automatic listing in the upper screen. ;; AUTO-LIST L1795: LD ($5C3F),SP ; save stack pointer in LIST_SP LD (IY+$02),$10 ; update TV_FLAG set bit 3 CALL L0DAF ; routine CL-ALL. SET 0,(IY+$02) ; update TV_FLAG - signal lower screen in use LD B,(IY+$31) ; fetch DF_SZ to B. CALL L0E44 ; routine CL-LINE clears lower display ; preserving B. RES 0,(IY+$02) ; update TV_FLAG - signal main screen in use SET 0,(IY+$30) ; update FLAGS2 - signal will be necessary to ; clear main screen. LD HL,($5C49) ; fetch E_PPC current edit line to HL. LD DE,($5C6C) ; fetch S_TOP to DE, the current top line ; (initially zero) AND A ; prepare for true subtraction. SBC HL,DE ; subtract and ADD HL,DE ; add back. JR C,L17E1 ; to AUTO-L-2 if S_TOP higher than E_PPC ; to set S_TOP to E_PPC PUSH DE ; save the top line number. CALL L196E ; routine LINE-ADDR gets address of E_PPC. LD DE,$02C0 ; prepare known number of characters in ; the default upper screen. EX DE,HL ; offset to HL, program address to DE. SBC HL,DE ; subtract high value from low to obtain ; negated result used in addition. EX (SP),HL ; swap result with top line number on stack. CALL L196E ; routine LINE-ADDR gets address of that ; top line in HL and next line in DE. POP BC ; restore the result to balance stack. ;; AUTO-L-1 L17CE: PUSH BC ; save the result. CALL L19B8 ; routine NEXT-ONE gets address in HL of ; line after auto-line (in DE). POP BC ; restore result. ADD HL,BC ; compute back. JR C,L17E4 ; to AUTO-L-3 if line 'should' appear EX DE,HL ; address of next line to HL. LD D,(HL) ; get line INC HL ; number LD E,(HL) ; in DE. DEC HL ; adjust back to start. LD ($5C6C),DE ; update S_TOP. JR L17CE ; to AUTO-L-1 until estimate reached. ; --- ; the jump was to here if S_TOP was greater than E_PPC ;; AUTO-L-2 L17E1: LD ($5C6C),HL ; make S_TOP the same as E_PPC. ; continue here with valid starting point from above or good estimate ; from computation ;; AUTO-L-3 L17E4: LD HL,($5C6C) ; fetch S_TOP line number to HL. CALL L196E ; routine LINE-ADDR gets address in HL. ; address of next in DE. JR Z,L17ED ; to AUTO-L-4 if line exists. EX DE,HL ; else use address of next line. ;; AUTO-L-4 L17ED: CALL L1833 ; routine LIST-ALL >>> ; The return will be to here if no scrolling occurred RES 4,(IY+$02) ; update TV_FLAG - signal no auto listing. RET ; return. ; ------------ ; Handle LLIST ; ------------ ; A short form of LIST #3. The listing goes to stream 3 - default printer. ;; LLIST L17F5: LD A,$03 ; the usual stream for ZX Printer JR L17FB ; forward to LIST-1 ; ----------- ; Handle LIST ; ----------- ; List to any stream. ; Note. While a starting line can be specified it is ; not possible to specify an end line. ; Just listing a line makes it the current edit line. ;; LIST L17F9: LD A,$02 ; default is stream 2 - the upper screen. ;; LIST-1 L17FB: LD (IY+$02),$00 ; the TV_FLAG is initialized with bit 0 reset ; indicating upper screen in use. CALL L2530 ; routine SYNTAX-Z - checking syntax ? CALL NZ,L1601 ; routine CHAN-OPEN if in run-time. RST 18H ; GET-CHAR CALL L2070 ; routine STR-ALTER will alter if '#'. JR C,L181F ; forward to LIST-4 not a '#' . RST 18H ; GET-CHAR CP $3B ; is it ';' ? JR Z,L1814 ; skip to LIST-2 if so. CP $2C ; is it ',' ? JR NZ,L181A ; forward to LIST-3 if neither separator. ; we have, say, LIST #15, and a number must follow the separator. ;; LIST-2 L1814: RST 20H ; NEXT-CHAR CALL L1C82 ; routine EXPT-1NUM JR L1822 ; forward to LIST-5 ; --- ; the branch was here with just LIST #3 etc. ;; LIST-3 L181A: CALL L1CE6 ; routine USE-ZERO JR L1822 ; forward to LIST-5 ; --- ; the branch was here with LIST ;; LIST-4 L181F: CALL L1CDE ; routine FETCH-NUM checks if a number ; follows else uses zero. ;; LIST-5 L1822: CALL L1BEE ; routine CHECK-END quits if syntax OK >>> CALL L1E99 ; routine FIND-INT2 fetches the number ; from the calculator stack in run-time. LD A,B ; fetch high byte of line number and AND $3F ; make less than $40 so that NEXT-ONE ; (from LINE-ADDR) doesn't lose context. ; Note. this is not satisfactory and the typo ; LIST 20000 will list an entirely different ; section than LIST 2000. Such typos are not ; available for checking if they are direct ; commands. LD H,A ; transfer the modified LD L,C ; line number to HL. LD ($5C49),HL ; update E_PPC to new line number. CALL L196E ; routine LINE-ADDR gets the address of the ; line. ; This routine is called from AUTO-LIST ;; LIST-ALL L1833: LD E,$01 ; signal current line not yet printed ;; LIST-ALL-2 L1835: CALL L1855 ; routine OUT-LINE outputs a BASIC line ; using PRINT-OUT and makes an early return ; when no more lines to print. >>> RST 10H ; PRINT-A prints the carriage return (in A) BIT 4,(IY+$02) ; test TV_FLAG - automatic listing ? JR Z,L1835 ; back to LIST-ALL-2 if not ; (loop exit is via OUT-LINE) ; continue here if an automatic listing required. LD A,($5C6B) ; fetch DF_SZ lower display file size. SUB (IY+$4F) ; subtract S_POSN_hi ithe current line number. JR NZ,L1835 ; back to LIST-ALL-2 if upper screen not full. XOR E ; A contains zero, E contains one if the ; current edit line has not been printed ; or zero if it has (from OUT-LINE). RET Z ; return if the screen is full and the line ; has been printed. ; continue with automatic listings if the screen is full and the current ; edit line is missing. OUT-LINE will scroll automatically. PUSH HL ; save the pointer address. PUSH DE ; save the E flag. LD HL,$5C6C ; fetch S_TOP the rough estimate. CALL L190F ; routine LN-FETCH updates S_TOP with ; the number of the next line. POP DE ; restore the E flag. POP HL ; restore the address of the next line. JR L1835 ; back to LIST-ALL-2. ; ------------------------ ; Print a whole BASIC line ; ------------------------ ; This routine prints a whole BASIC line and it is called ; from LIST-ALL to output the line to current channel ; and from ED-EDIT to 'sprint' the line to the edit buffer. ;; OUT-LINE L1855: LD BC,($5C49) ; fetch E_PPC the current line which may be ; unchecked and not exist. CALL L1980 ; routine CP-LINES finds match or line after. LD D,$3E ; prepare cursor '>' in D. JR Z,L1865 ; to OUT-LINE1 if matched or line after. LD DE,$0000 ; put zero in D, to suppress line cursor. RL E ; pick up carry in E if line before current ; leave E zero if same or after. ;; OUT-LINE1 L1865: LD (IY+$2D),E ; save flag in BREG which is spare. LD A,(HL) ; get high byte of line number. CP $40 ; is it too high ($2F is maximum possible) ? POP BC ; drop the return address and RET NC ; make an early return if so >>> PUSH BC ; save return address CALL L1A28 ; routine OUT-NUM-2 to print addressed number ; with leading space. INC HL ; skip low number byte. INC HL ; and the two INC HL ; length bytes. RES 0,(IY+$01) ; update FLAGS - signal leading space required. LD A,D ; fetch the cursor. AND A ; test for zero. JR Z,L1881 ; to OUT-LINE3 if zero. RST 10H ; PRINT-A prints '>' the current line cursor. ; this entry point is called from ED-COPY ;; OUT-LINE2 L187D: SET 0,(IY+$01) ; update FLAGS - suppress leading space. ;; OUT-LINE3 L1881: PUSH DE ; save flag E for a return value. EX DE,HL ; save HL address in DE. RES 2,(IY+$30) ; update FLAGS2 - signal NOT in QUOTES. LD HL,$5C3B ; point to FLAGS. RES 2,(HL) ; signal 'K' mode. (starts before keyword) BIT 5,(IY+$37) ; test FLAGX - input mode ? JR Z,L1894 ; forward to OUT-LINE4 if not. SET 2,(HL) ; signal 'L' mode. (used for input) ;; OUT-LINE4 L1894: LD HL,($5C5F) ; fetch X_PTR - possibly the error pointer ; address. AND A ; clear the carry flag. SBC HL,DE ; test if an error address has been reached. JR NZ,L18A1 ; forward to OUT-LINE5 if not. LD A,$3F ; load A with '?' the error marker. CALL L18C1 ; routine OUT-FLASH to print flashing marker. ;; OUT-LINE5 L18A1: CALL L18E1 ; routine OUT-CURS will print the cursor if ; this is the right position. EX DE,HL ; restore address pointer to HL. LD A,(HL) ; fetch the addressed character. CALL L18B6 ; routine NUMBER skips a hidden floating ; point number if present. INC HL ; now increment the pointer. CP $0D ; is character end-of-line ? JR Z,L18B4 ; to OUT-LINE6, if so, as line is finished. EX DE,HL ; save the pointer in DE. CALL L1937 ; routine OUT-CHAR to output character/token. JR L1894 ; back to OUT-LINE4 until entire line is done. ; --- ;; OUT-LINE6 L18B4: POP DE ; bring back the flag E, zero if current ; line printed else 1 if still to print. RET ; return with A holding $0D ; ------------------------- ; Check for a number marker ; ------------------------- ; this subroutine is called from two processes. while outputting BASIC lines ; and while searching statements within a BASIC line. ; during both, this routine will pass over an invisible number indicator ; and the five bytes floating-point number that follows it. ; Note that this causes floating point numbers to be stripped from ; the BASIC line when it is fetched to the edit buffer by OUT_LINE. ; the number marker also appears after the arguments of a DEF FN statement ; and may mask old 5-byte string parameters. ;; NUMBER L18B6: CP $0E ; character fourteen ? RET NZ ; return if not. INC HL ; skip the character INC HL ; and five bytes INC HL ; following. INC HL ; INC HL ; INC HL ; LD A,(HL) ; fetch the following character RET ; for return value. ; -------------------------- ; Print a flashing character ; -------------------------- ; This subroutine is called from OUT-LINE to print a flashing error ; marker '?' or from the next routine to print a flashing cursor e.g. 'L'. ; However, this only gets called from OUT-LINE when printing the edit line ; or the input buffer to the lower screen so a direct call to $09F4 can ; be used, even though out-line outputs to other streams. ; In fact the alternate set is used for the whole routine. ;; OUT-FLASH L18C1: EXX ; switch in alternate set LD HL,($5C8F) ; fetch L = ATTR_T, H = MASK-T PUSH HL ; save masks. RES 7,H ; reset flash mask bit so active. SET 7,L ; make attribute FLASH. LD ($5C8F),HL ; resave ATTR_T and MASK-T LD HL,$5C91 ; address P_FLAG LD D,(HL) ; fetch to D PUSH DE ; and save. LD (HL),$00 ; clear inverse, over, ink/paper 9 CALL L09F4 ; routine PRINT-OUT outputs character ; without the need to vector via RST 10. POP HL ; pop P_FLAG to H. LD (IY+$57),H ; and restore system variable P_FLAG. POP HL ; restore temporary masks LD ($5C8F),HL ; and restore system variables ATTR_T/MASK_T EXX ; switch back to main set RET ; return ; ---------------- ; Print the cursor ; ---------------- ; This routine is called before any character is output while outputting ; a BASIC line or the input buffer. This includes listing to a printer ; or screen, copying a BASIC line to the edit buffer and printing the ; input buffer or edit buffer to the lower screen. It is only in the ; latter two cases that it has any relevance and in the last case it ; performs another very important function also. ;; OUT-CURS L18E1: LD HL,($5C5B) ; fetch K_CUR the current cursor address AND A ; prepare for true subtraction. SBC HL,DE ; test against pointer address in DE and RET NZ ; return if not at exact position. ; the value of MODE, maintained by KEY-INPUT, is tested and if non-zero ; then this value 'E' or 'G' will take precedence. LD A,($5C41) ; fetch MODE 0='KLC', 1='E', 2='G'. RLC A ; double the value and set flags. JR Z,L18F3 ; to OUT-C-1 if still zero ('KLC'). ADD A,$43 ; add 'C' - will become 'E' if originally 1 ; or 'G' if originally 2. JR L1909 ; forward to OUT-C-2 to print. ; --- ; If mode was zero then, while printing a BASIC line, bit 2 of flags has been ; set if 'THEN' or ':' was encountered as a main character and reset otherwise. ; This is now used to determine if the 'K' cursor is to be printed but this ; transient state is also now transferred permanently to bit 3 of FLAGS ; to let the interrupt routine know how to decode the next key. ;; OUT-C-1 L18F3: LD HL,$5C3B ; Address FLAGS RES 3,(HL) ; signal 'K' mode initially. LD A,$4B ; prepare letter 'K'. BIT 2,(HL) ; test FLAGS - was the ; previous main character ':' or 'THEN' ? JR Z,L1909 ; forward to OUT-C-2 if so to print. SET 3,(HL) ; signal 'L' mode to interrupt routine. ; Note. transient bit has been made permanent. INC A ; augment from 'K' to 'L'. BIT 3,(IY+$30) ; test FLAGS2 - consider caps lock ? ; which is maintained by KEY-INPUT. JR Z,L1909 ; forward to OUT-C-2 if not set to print. LD A,$43 ; alter 'L' to 'C'. ;; OUT-C-2 L1909: PUSH DE ; save address pointer but OK as OUT-FLASH ; uses alternate set without RST 10. CALL L18C1 ; routine OUT-FLASH to print. POP DE ; restore and RET ; return. ; ---------------------------- ; Get line number of next line ; ---------------------------- ; These two subroutines are called while editing. ; This entry point is from ED-DOWN with HL addressing E_PPC ; to fetch the next line number. ; Also from AUTO-LIST with HL addressing S_TOP just to update S_TOP ; with the value of the next line number. It gets fetched but is discarded. ; These routines never get called while the editor is being used for input. ;; LN-FETCH L190F: LD E,(HL) ; fetch low byte INC HL ; address next LD D,(HL) ; fetch high byte. PUSH HL ; save system variable hi pointer. EX DE,HL ; line number to HL, INC HL ; increment as a starting point. CALL L196E ; routine LINE-ADDR gets address in HL. CALL L1695 ; routine LINE-NO gets line number in DE. POP HL ; restore system variable hi pointer. ; This entry point is from the ED-UP with HL addressing E_PPC_hi ;; LN-STORE L191C: BIT 5,(IY+$37) ; test FLAGX - input mode ? RET NZ ; return if so. ; Note. above already checked by ED-UP/ED-DOWN. LD (HL),D ; save high byte of line number. DEC HL ; address lower LD (HL),E ; save low byte of line number. RET ; return. ; ----------------------------------------- ; Outputting numbers at start of BASIC line ; ----------------------------------------- ; This routine entered at OUT-SP-NO is used to compute then output the first ; three digits of a 4-digit BASIC line printing a space if necessary. ; The line number, or residual part, is held in HL and the BC register ; holds a subtraction value -1000, -100 or -10. ; Note. for example line number 200 - ; space(out_char), 2(out_code), 0(out_char) final number always out-code. ;; OUT-SP-2 L1925: LD A,E ; will be space if OUT-CODE not yet called. ; or $FF if spaces are suppressed. ; else $30 ('0'). ; (from the first instruction at OUT-CODE) ; this guy is just too clever. AND A ; test bit 7 of A. RET M ; return if $FF, as leading spaces not ; required. This is set when printing line ; number and statement in MAIN-5. JR L1937 ; forward to exit via OUT-CHAR. ; --- ; -> the single entry point. ;; OUT-SP-NO L192A: XOR A ; initialize digit to 0 ;; OUT-SP-1 L192B: ADD HL,BC ; add negative number to HL. INC A ; increment digit JR C,L192B ; back to OUT-SP-1 until no carry from ; the addition. SBC HL,BC ; cancel the last addition DEC A ; and decrement the digit. JR Z,L1925 ; back to OUT-SP-2 if it is zero. JP L15EF ; jump back to exit via OUT-CODE. -> ; ------------------------------------- ; Outputting characters in a BASIC line ; ------------------------------------- ; This subroutine ... ;; OUT-CHAR L1937: CALL L2D1B ; routine NUMERIC tests if it is a digit ? JR NC,L196C ; to OUT-CH-3 to print digit without ; changing mode. Will be 'K' mode if digits ; are at beginning of edit line. CP $21 ; less than quote character ? JR C,L196C ; to OUT-CH-3 to output controls and space. RES 2,(IY+$01) ; initialize FLAGS to 'K' mode and leave ; unchanged if this character would precede ; a keyword. CP $CB ; is character 'THEN' token ? JR Z,L196C ; to OUT-CH-3 to output if so. CP $3A ; is it ':' ? JR NZ,L195A ; to OUT-CH-1 if not statement separator ; to change mode back to 'L'. BIT 5,(IY+$37) ; FLAGX - Input Mode ?? JR NZ,L1968 ; to OUT-CH-2 if in input as no statements. ; Note. this check should seemingly be at ; the start. Commands seem inappropriate in ; INPUT mode and are rejected by the syntax ; checker anyway. ; unless INPUT LINE is being used. BIT 2,(IY+$30) ; test FLAGS2 - is the ':' within quotes ? JR Z,L196C ; to OUT-CH-3 if ':' is outside quoted text. JR L1968 ; to OUT-CH-2 as ':' is within quotes ; --- ;; OUT-CH-1 L195A: CP $22 ; is it quote character '"' ? JR NZ,L1968 ; to OUT-CH-2 with others to set 'L' mode. PUSH AF ; save character. LD A,($5C6A) ; fetch FLAGS2. XOR $04 ; toggle the quotes flag. LD ($5C6A),A ; update FLAGS2 POP AF ; and restore character. ;; OUT-CH-2 L1968: SET 2,(IY+$01) ; update FLAGS - signal L mode if the cursor ; is next. ;; OUT-CH-3 L196C: RST 10H ; PRINT-A vectors the character to ; channel 'S', 'K', 'R' or 'P'. RET ; return. ; ------------------------------------------- ; Get starting address of line, or line after ; ------------------------------------------- ; This routine is used often to get the address, in HL, of a BASIC line ; number supplied in HL, or failing that the address of the following line ; and the address of the previous line in DE. ;; LINE-ADDR L196E: PUSH HL ; save line number in HL register LD HL,($5C53) ; fetch start of program from PROG LD D,H ; transfer address to LD E,L ; the DE register pair. ;; LINE-AD-1 L1974: POP BC ; restore the line number to BC CALL L1980 ; routine CP-LINES compares with that ; addressed by HL RET NC ; return if line has been passed or matched. ; if NZ, address of previous is in DE PUSH BC ; save the current line number CALL L19B8 ; routine NEXT-ONE finds address of next ; line number in DE, previous in HL. EX DE,HL ; switch so next in HL JR L1974 ; back to LINE-AD-1 for another comparison ; -------------------- ; Compare line numbers ; -------------------- ; This routine compares a line number supplied in BC with an addressed ; line number pointed to by HL. ;; CP-LINES L1980: LD A,(HL) ; Load the high byte of line number and CP B ; compare with that of supplied line number. RET NZ ; return if yet to match (carry will be set). INC HL ; address low byte of LD A,(HL) ; number and pick up in A. DEC HL ; step back to first position. CP C ; now compare. RET ; zero set if exact match. ; carry set if yet to match. ; no carry indicates a match or ; next available BASIC line or ; program end marker. ; ------------------- ; Find each statement ; ------------------- ; The single entry point EACH-STMT is used to ; 1) To find the D'th statement in a line. ; 2) To find a token in held E. ;; not-used L1988: INC HL ; INC HL ; INC HL ; ; -> entry point. ;; EACH-STMT L198B: LD ($5C5D),HL ; save HL in CH_ADD LD C,$00 ; initialize quotes flag ;; EACH-S-1 L1990: DEC D ; decrease statement count RET Z ; return if zero RST 20H ; NEXT-CHAR CP E ; is it the search token ? JR NZ,L199A ; forward to EACH-S-3 if not AND A ; clear carry RET ; return signalling success. ; --- ;; EACH-S-2 L1998: INC HL ; next address LD A,(HL) ; next character ;; EACH-S-3 L199A: CALL L18B6 ; routine NUMBER skips if number marker LD ($5C5D),HL ; save in CH_ADD CP $22 ; is it quotes '"' ? JR NZ,L19A5 ; to EACH-S-4 if not DEC C ; toggle bit 0 of C ;; EACH-S-4 L19A5: CP $3A ; is it ':' JR Z,L19AD ; to EACH-S-5 CP $CB ; 'THEN' JR NZ,L19B1 ; to EACH-S-6 ;; EACH-S-5 L19AD: BIT 0,C ; is it in quotes JR Z,L1990 ; to EACH-S-1 if not ;; EACH-S-6 L19B1: CP $0D ; end of line ? JR NZ,L1998 ; to EACH-S-2 DEC D ; decrease the statement counter ; which should be zero else ; 'Statement Lost'. SCF ; set carry flag - not found RET ; return ; ----------------------------------------------------------------------- ; Storage of variables. For full details - see chapter 24. ; ZX Spectrum BASIC Programming by Steven Vickers 1982. ; It is bits 7-5 of the first character of a variable that allow ; the six types to be distinguished. Bits 4-0 are the reduced letter. ; So any variable name is higher that $3F and can be distinguished ; also from the variables area end-marker $80. ; ; 76543210 meaning brief outline of format. ; -------- ------------------------ ----------------------- ; 010 string variable. 2 byte length + contents. ; 110 string array. 2 byte length + contents. ; 100 array of numbers. 2 byte length + contents. ; 011 simple numeric variable. 5 bytes. ; 101 variable length named numeric. 5 bytes. ; 111 for-next loop variable. 18 bytes. ; 10000000 the variables area end-marker. ; ; Note. any of the above seven will serve as a program end-marker. ; ; ----------------------------------------------------------------------- ; ------------ ; Get next one ; ------------ ; This versatile routine is used to find the address of the next line ; in the program area or the next variable in the variables area. ; The reason one routine is made to handle two apparently unrelated tasks ; is that it can be called indiscriminately when merging a line or a ; variable. ;; NEXT-ONE L19B8: PUSH HL ; save the pointer address. LD A,(HL) ; get first byte. CP $40 ; compare with upper limit for line numbers. JR C,L19D5 ; forward to NEXT-O-3 if within BASIC area. ; the continuation here is for the next variable unless the supplied ; line number was erroneously over 16383. see RESTORE command. BIT 5,A ; is it a string or an array variable ? JR Z,L19D6 ; forward to NEXT-O-4 to compute length. ADD A,A ; test bit 6 for single-character variables. JP M,L19C7 ; forward to NEXT-O-1 if so CCF ; clear the carry for long-named variables. ; it remains set for for-next loop variables. ;; NEXT-O-1 L19C7: LD BC,$0005 ; set BC to 5 for floating point number JR NC,L19CE ; forward to NEXT-O-2 if not a for/next ; variable. LD C,$12 ; set BC to eighteen locations. ; value, limit, step, line and statement. ; now deal with long-named variables ;; NEXT-O-2 L19CE: RLA ; test if character inverted. carry will also ; be set for single character variables INC HL ; address next location. LD A,(HL) ; and load character. JR NC,L19CE ; back to NEXT-O-2 if not inverted bit. ; forward immediately with single character ; variable names. JR L19DB ; forward to NEXT-O-5 to add length of ; floating point number(s etc.). ; --- ; this branch is for line numbers. ;; NEXT-O-3 L19D5: INC HL ; increment pointer to low byte of line no. ; strings and arrays rejoin here ;; NEXT-O-4 L19D6: INC HL ; increment to address the length low byte. LD C,(HL) ; transfer to C and INC HL ; point to high byte of length. LD B,(HL) ; transfer that to B INC HL ; point to start of BASIC/variable contents. ; the three types of numeric variables rejoin here ;; NEXT-O-5 L19DB: ADD HL,BC ; add the length to give address of next ; line/variable in HL. POP DE ; restore previous address to DE. ; ------------------ ; Difference routine ; ------------------ ; This routine terminates the above routine and is also called from the ; start of the next routine to calculate the length to reclaim. ;; DIFFER L19DD: AND A ; prepare for true subtraction. SBC HL,DE ; subtract the two pointers. LD B,H ; transfer result LD C,L ; to BC register pair. ADD HL,DE ; add back EX DE,HL ; and switch pointers RET ; return values are the length of area in BC, ; low pointer (previous) in HL, ; high pointer (next) in DE. ; ----------------------- ; Handle reclaiming space ; ----------------------- ; ;; RECLAIM-1 L19E5: CALL L19DD ; routine DIFFER immediately above ;; RECLAIM-2 L19E8: PUSH BC ; LD A,B ; CPL ; LD B,A ; LD A,C ; CPL ; LD C,A ; INC BC ; CALL L1664 ; routine POINTERS EX DE,HL ; POP HL ; ADD HL,DE ; PUSH DE ; LDIR ; copy bytes POP HL ; RET ; ; ---------------------------------------- ; Read line number of line in editing area ; ---------------------------------------- ; This routine reads a line number in the editing area returning the number ; in the BC register or zero if no digits exist before commands. ; It is called from LINE-SCAN to check the syntax of the digits. ; It is called from MAIN-3 to extract the line number in preparation for ; inclusion of the line in the BASIC program area. ; ; Interestingly the calculator stack is moved from its normal place at the ; end of dynamic memory to an adequate area within the system variables area. ; This ensures that in a low memory situation, that valid line numbers can ; be extracted without raising an error and that memory can be reclaimed ; by deleting lines. If the stack was in its normal place then a situation ; arises whereby the Spectrum becomes locked with no means of reclaiming space. ;; E-LINE-NO L19FB: LD HL,($5C59) ; load HL from system variable E_LINE. DEC HL ; decrease so that NEXT_CHAR can be used ; without skipping the first digit. LD ($5C5D),HL ; store in the system variable CH_ADD. RST 20H ; NEXT-CHAR skips any noise and white-space ; to point exactly at the first digit. LD HL,$5C92 ; use MEM-0 as a temporary calculator stack ; an overhead of three locations are needed. LD ($5C65),HL ; set new STKEND. CALL L2D3B ; routine INT-TO-FP will read digits till ; a non-digit found. CALL L2DA2 ; routine FP-TO-BC will retrieve number ; from stack at membot. JR C,L1A15 ; forward to E-L-1 if overflow i.e. > 65535. ; 'Nonsense in BASIC' LD HL,$D8F0 ; load HL with value -9999 ADD HL,BC ; add to line number in BC ;; E-L-1 L1A15: JP C,L1C8A ; to REPORT-C 'Nonsense in BASIC' if over. ; Note. As ERR_SP points to ED_ERROR ; the report is never produced although ; the RST $08 will update X_PTR leading to ; the error marker being displayed when ; the ED_LOOP is reiterated. ; in fact, since it is immediately ; cancelled, any report will do. ; a line in the range 0 - 9999 has been entered. JP L16C5 ; jump back to SET-STK to set the calculator ; stack back to its normal place and exit ; from there. ; --------------------------------- ; Report and line number outputting ; --------------------------------- ; Entry point OUT-NUM-1 is used by the Error Reporting code to print ; the line number and later the statement number held in BC. ; If the statement was part of a direct command then -2 is used as a ; dummy line number so that zero will be printed in the report. ; This routine is also used to print the exponent of E-format numbers. ; ; Entry point OUT-NUM-2 is used from OUT-LINE to output the line number ; addressed by HL with leading spaces if necessary. ;; OUT-NUM-1 L1A1B: PUSH DE ; save the PUSH HL ; registers. XOR A ; set A to zero. BIT 7,B ; is the line number minus two ? JR NZ,L1A42 ; forward to OUT-NUM-4 if so to print zero ; for a direct command. LD H,B ; transfer the LD L,C ; number to HL. LD E,$FF ; signal 'no leading zeros'. JR L1A30 ; forward to continue at OUT-NUM-3 ; --- ; from OUT-LINE - HL addresses line number. ;; OUT-NUM-2 L1A28: PUSH DE ; save flags LD D,(HL) ; high byte to D INC HL ; address next LD E,(HL) ; low byte to E PUSH HL ; save pointer EX DE,HL ; transfer number to HL LD E,$20 ; signal 'output leading spaces' ;; OUT-NUM-3 L1A30: LD BC,$FC18 ; value -1000 CALL L192A ; routine OUT-SP-NO outputs space or number LD BC,$FF9C ; value -100 CALL L192A ; routine OUT-SP-NO LD C,$F6 ; value -10 ( B is still $FF ) CALL L192A ; routine OUT-SP-NO LD A,L ; remainder to A. ;; OUT-NUM-4 L1A42: CALL L15EF ; routine OUT-CODE for final digit. ; else report code zero wouldn't get ; printed. POP HL ; restore the POP DE ; registers and RET ; return. ;*************************************************** ;** Part 7. BASIC LINE AND COMMAND INTERPRETATION ** ;*************************************************** ; ---------------- ; The offset table ; ---------------- ; The BASIC interpreter has found a command code $CE - $FF ; which is then reduced to range $00 - $31 and added to the base address ; of this table to give the address of an offset which, when added to ; the offset therein, gives the location in the following parameter table ; where a list of class codes, separators and addresses relevant to the ; command exists. ;; offst-tbl L1A48: DEFB L1AF9 - $ ; B1 offset to Address: P-DEF-FN DEFB L1B14 - $ ; CB offset to Address: P-CAT DEFB L1B06 - $ ; BC offset to Address: P-FORMAT DEFB L1B0A - $ ; BF offset to Address: P-MOVE DEFB L1B10 - $ ; C4 offset to Address: P-ERASE DEFB L1AFC - $ ; AF offset to Address: P-OPEN DEFB L1B02 - $ ; B4 offset to Address: P-CLOSE DEFB L1AE2 - $ ; 93 offset to Address: P-MERGE DEFB L1AE1 - $ ; 91 offset to Address: P-VERIFY DEFB L1AE3 - $ ; 92 offset to Address: P-BEEP DEFB L1AE7 - $ ; 95 offset to Address: P-CIRCLE DEFB L1AEB - $ ; 98 offset to Address: P-INK DEFB L1AEC - $ ; 98 offset to Address: P-PAPER DEFB L1AED - $ ; 98 offset to Address: P-FLASH DEFB L1AEE - $ ; 98 offset to Address: P-BRIGHT DEFB L1AEF - $ ; 98 offset to Address: P-INVERSE DEFB L1AF0 - $ ; 98 offset to Address: P-OVER DEFB L1AF1 - $ ; 98 offset to Address: P-OUT DEFB L1AD9 - $ ; 7F offset to Address: P-LPRINT DEFB L1ADC - $ ; 81 offset to Address: P-LLIST DEFB L1A8A - $ ; 2E offset to Address: P-STOP DEFB L1AC9 - $ ; 6C offset to Address: P-READ DEFB L1ACC - $ ; 6E offset to Address: P-DATA DEFB L1ACF - $ ; 70 offset to Address: P-RESTORE DEFB L1AA8 - $ ; 48 offset to Address: P-NEW DEFB L1AF5 - $ ; 94 offset to Address: P-BORDER DEFB L1AB8 - $ ; 56 offset to Address: P-CONT DEFB L1AA2 - $ ; 3F offset to Address: P-DIM DEFB L1AA5 - $ ; 41 offset to Address: P-REM DEFB L1A90 - $ ; 2B offset to Address: P-FOR DEFB L1A7D - $ ; 17 offset to Address: P-GO-TO DEFB L1A86 - $ ; 1F offset to Address: P-GO-SUB DEFB L1A9F - $ ; 37 offset to Address: P-INPUT DEFB L1AE0 - $ ; 77 offset to Address: P-LOAD DEFB L1AAE - $ ; 44 offset to Address: P-LIST DEFB L1A7A - $ ; 0F offset to Address: P-LET DEFB L1AC5 - $ ; 59 offset to Address: P-PAUSE DEFB L1A98 - $ ; 2B offset to Address: P-NEXT DEFB L1AB1 - $ ; 43 offset to Address: P-POKE DEFB L1A9C - $ ; 2D offset to Address: P-PRINT DEFB L1AC1 - $ ; 51 offset to Address: P-PLOT DEFB L1AAB - $ ; 3A offset to Address: P-RUN DEFB L1ADF - $ ; 6D offset to Address: P-SAVE DEFB L1AB5 - $ ; 42 offset to Address: P-RANDOM DEFB L1A81 - $ ; 0D offset to Address: P-IF DEFB L1ABE - $ ; 49 offset to Address: P-CLS DEFB L1AD2 - $ ; 5C offset to Address: P-DRAW DEFB L1ABB - $ ; 44 offset to Address: P-CLEAR DEFB L1A8D - $ ; 15 offset to Address: P-RETURN DEFB L1AD6 - $ ; 5D offset to Address: P-COPY ; ------------------------------- ; The parameter or "Syntax" table ; ------------------------------- ; For each command there exists a variable list of parameters. ; If the character is greater than a space it is a required separator. ; If less, then it is a command class in the range 00 - 0B. ; Note that classes 00, 03 and 05 will fetch the addresses from this table. ; Some classes e.g. 07 and 0B have the same address in all invocations ; and the command is re-computed from the low-byte of the parameter address. ; Some e.g. 02 are only called once so a call to the command is made from ; within the class routine rather than holding the address within the table. ; Some class routines check syntax entirely and some leave this task for the ; command itself. ; Others for example CIRCLE (x,y,z) check the first part (x,y) using the ; class routine and the final part (,z) within the command. ; The last few commands appear to have been added in a rush but their syntax ; is rather simple e.g. MOVE "M1","M2" ;; P-LET L1A7A: DEFB $01 ; Class-01 - A variable is required. DEFB $3D ; Separator: '=' DEFB $02 ; Class-02 - An expression, numeric or string, ; must follow. ;; P-GO-TO L1A7D: DEFB $06 ; Class-06 - A numeric expression must follow. DEFB $00 ; Class-00 - No further operands. DEFW L1E67 ; Address: $1E67; Address: GO-TO ;; P-IF L1A81: DEFB $06 ; Class-06 - A numeric expression must follow. DEFB $CB ; Separator: 'THEN' DEFB $05 ; Class-05 - Variable syntax checked ; by routine. DEFW L1CF0 ; Address: $1CF0; Address: IF ;; P-GO-SUB L1A86: DEFB $06 ; Class-06 - A numeric expression must follow. DEFB $00 ; Class-00 - No further operands. DEFW L1EED ; Address: $1EED; Address: GO-SUB ;; P-STOP L1A8A: DEFB $00 ; Class-00 - No further operands. DEFW L1CEE ; Address: $1CEE; Address: STOP ;; P-RETURN L1A8D: DEFB $00 ; Class-00 - No further operands. DEFW L1F23 ; Address: $1F23; Address: RETURN ;; P-FOR L1A90: DEFB $04 ; Class-04 - A single character variable must ; follow. DEFB $3D ; Separator: '=' DEFB $06 ; Class-06 - A numeric expression must follow. DEFB $CC ; Separator: 'TO' DEFB $06 ; Class-06 - A numeric expression must follow. DEFB $05 ; Class-05 - Variable syntax checked ; by routine. DEFW L1D03 ; Address: $1D03; Address: FOR ;; P-NEXT L1A98: DEFB $04 ; Class-04 - A single character variable must ; follow. DEFB $00 ; Class-00 - No further operands. DEFW L1DAB ; Address: $1DAB; Address: NEXT ;; P-PRINT L1A9C: DEFB $05 ; Class-05 - Variable syntax checked entirely ; by routine. DEFW L1FCD ; Address: $1FCD; Address: PRINT ;; P-INPUT L1A9F: DEFB $05 ; Class-05 - Variable syntax checked entirely ; by routine. DEFW L2089 ; Address: $2089; Address: INPUT ;; P-DIM L1AA2: DEFB $05 ; Class-05 - Variable syntax checked entirely ; by routine. DEFW L2C02 ; Address: $2C02; Address: DIM ;; P-REM L1AA5: DEFB $05 ; Class-05 - Variable syntax checked entirely ; by routine. DEFW L1BB2 ; Address: $1BB2; Address: REM ;; P-NEW L1AA8: DEFB $00 ; Class-00 - No further operands. DEFW L11B7 ; Address: $11B7; Address: NEW ;; P-RUN L1AAB: DEFB $03 ; Class-03 - A numeric expression may follow ; else default to zero. DEFW L1EA1 ; Address: $1EA1; Address: RUN ;; P-LIST L1AAE: DEFB $05 ; Class-05 - Variable syntax checked entirely ; by routine. DEFW L17F9 ; Address: $17F9; Address: LIST ;; P-POKE L1AB1: DEFB $08 ; Class-08 - Two comma-separated numeric ; expressions required. DEFB $00 ; Class-00 - No further operands. DEFW L1E80 ; Address: $1E80; Address: POKE ;; P-RANDOM L1AB5: DEFB $03 ; Class-03 - A numeric expression may follow ; else default to zero. DEFW L1E4F ; Address: $1E4F; Address: RANDOMIZE ;; P-CONT L1AB8: DEFB $00 ; Class-00 - No further operands. DEFW L1E5F ; Address: $1E5F; Address: CONTINUE ;; P-CLEAR L1ABB: DEFB $03 ; Class-03 - A numeric expression may follow ; else default to zero. DEFW L1EAC ; Address: $1EAC; Address: CLEAR ;; P-CLS L1ABE: DEFB $00 ; Class-00 - No further operands. DEFW L0D6B ; Address: $0D6B; Address: CLS ;; P-PLOT L1AC1: DEFB $09 ; Class-09 - Two comma-separated numeric ; expressions required with optional colour ; items. DEFB $00 ; Class-00 - No further operands. DEFW L22DC ; Address: $22DC; Address: PLOT ;; P-PAUSE L1AC5: DEFB $06 ; Class-06 - A numeric expression must follow. DEFB $00 ; Class-00 - No further operands. DEFW L1F3A ; Address: $1F3A; Address: PAUSE ;; P-READ L1AC9: DEFB $05 ; Class-05 - Variable syntax checked entirely ; by routine. DEFW L1DED ; Address: $1DED; Address: READ ;; P-DATA L1ACC: DEFB $05 ; Class-05 - Variable syntax checked entirely ; by routine. DEFW L1E27 ; Address: $1E27; Address: DATA ;; P-RESTORE L1ACF: DEFB $03 ; Class-03 - A numeric expression may follow ; else default to zero. DEFW L1E42 ; Address: $1E42; Address: RESTORE ;; P-DRAW L1AD2: DEFB $09 ; Class-09 - Two comma-separated numeric ; expressions required with optional colour ; items. DEFB $05 ; Class-05 - Variable syntax checked ; by routine. DEFW L2382 ; Address: $2382; Address: DRAW ;; P-COPY L1AD6: DEFB $00 ; Class-00 - No further operands. DEFW L0EAC ; Address: $0EAC; Address: COPY ;; P-LPRINT L1AD9: DEFB $05 ; Class-05 - Variable syntax checked entirely ; by routine. DEFW L1FC9 ; Address: $1FC9; Address: LPRINT ;; P-LLIST L1ADC: DEFB $05 ; Class-05 - Variable syntax checked entirely ; by routine. DEFW L17F5 ; Address: $17F5; Address: LLIST ;; P-SAVE L1ADF: DEFB $0B ; Class-0B - Offset address converted to tape ; command. ;; P-LOAD L1AE0: DEFB $0B ; Class-0B - Offset address converted to tape ; command. ;; P-VERIFY L1AE1: DEFB $0B ; Class-0B - Offset address converted to tape ; command. ;; P-MERGE L1AE2: DEFB $0B ; Class-0B - Offset address converted to tape ; command. ;; P-BEEP L1AE3: DEFB $08 ; Class-08 - Two comma-separated numeric ; expressions required. DEFB $00 ; Class-00 - No further operands. DEFW L03F8 ; Address: $03F8; Address: BEEP ;; P-CIRCLE L1AE7: DEFB $09 ; Class-09 - Two comma-separated numeric ; expressions required with optional colour ; items. DEFB $05 ; Class-05 - Variable syntax checked ; by routine. DEFW L2320 ; Address: $2320; Address: CIRCLE ;; P-INK L1AEB: DEFB $07 ; Class-07 - Offset address is converted to ; colour code. ;; P-PAPER L1AEC: DEFB $07 ; Class-07 - Offset address is converted to ; colour code. ;; P-FLASH L1AED: DEFB $07 ; Class-07 - Offset address is converted to ; colour code. ;; P-BRIGHT L1AEE: DEFB $07 ; Class-07 - Offset address is converted to ; colour code. ;; P-INVERSE L1AEF: DEFB $07 ; Class-07 - Offset address is converted to ; colour code. ;; P-OVER L1AF0: DEFB $07 ; Class-07 - Offset address is converted to ; colour code. ;; P-OUT L1AF1: DEFB $08 ; Class-08 - Two comma-separated numeric ; expressions required. DEFB $00 ; Class-00 - No further operands. DEFW L1E7A ; Address: $1E7A; Address: OUT ;; P-BORDER L1AF5: DEFB $06 ; Class-06 - A numeric expression must follow. DEFB $00 ; Class-00 - No further operands. DEFW L2294 ; Address: $2294; Address: BORDER ;; P-DEF-FN L1AF9: DEFB $05 ; Class-05 - Variable syntax checked entirely ; by routine. DEFW L1F60 ; Address: $1F60; Address: DEF-FN ;; P-OPEN L1AFC: DEFB $06 ; Class-06 - A numeric expression must follow. DEFB $2C ; Separator: ',' see Footnote * DEFB $0A ; Class-0A - A string expression must follow. DEFB $00 ; Class-00 - No further operands. DEFW L1736 ; Address: $1736; Address: OPEN ;; P-CLOSE L1B02: DEFB $06 ; Class-06 - A numeric expression must follow. DEFB $00 ; Class-00 - No further operands. DEFW L16E5 ; Address: $16E5; Address: CLOSE ;; P-FORMAT L1B06: DEFB $0A ; Class-0A - A string expression must follow. DEFB $00 ; Class-00 - No further operands. DEFW L1793 ; Address: $1793; Address: CAT-ETC ;; P-MOVE L1B0A: DEFB $0A ; Class-0A - A string expression must follow. DEFB $2C ; Separator: ',' DEFB $0A ; Class-0A - A string expression must follow. DEFB $00 ; Class-00 - No further operands. DEFW L1793 ; Address: $1793; Address: CAT-ETC ;; P-ERASE L1B10: DEFB $0A ; Class-0A - A string expression must follow. DEFB $00 ; Class-00 - No further operands. DEFW L1793 ; Address: $1793; Address: CAT-ETC ;; P-CAT L1B14: DEFB $00 ; Class-00 - No further operands. DEFW L1793 ; Address: $1793; Address: CAT-ETC ; * Note that a comma is required as a separator with the OPEN command ; but the Interface 1 programmers relaxed this allowing ';' as an ; alternative for their channels creating a confusing mixture of ; allowable syntax as it is this ROM which opens or re-opens the ; normal channels. ; ------------------------------- ; Main parser (BASIC interpreter) ; ------------------------------- ; This routine is called once from MAIN-2 when the BASIC line is to ; be entered or re-entered into the Program area and the syntax ; requires checking. ;; LINE-SCAN L1B17: RES 7,(IY+$01) ; update FLAGS - signal checking syntax CALL L19FB ; routine E-LINE-NO >> ; fetches the line number if in range. XOR A ; clear the accumulator. LD ($5C47),A ; set statement number SUBPPC to zero. DEC A ; set accumulator to $FF. LD ($5C3A),A ; set ERR_NR to 'OK' - 1. JR L1B29 ; forward to continue at STMT-L-1. ; -------------- ; Statement loop ; -------------- ; ; ;; STMT-LOOP L1B28: RST 20H ; NEXT-CHAR ; -> the entry point from above or LINE-RUN ;; STMT-L-1 L1B29: CALL L16BF ; routine SET-WORK clears workspace etc. INC (IY+$0D) ; increment statement number SUBPPC JP M,L1C8A ; to REPORT-C to raise ; 'Nonsense in BASIC' if over 127. RST 18H ; GET-CHAR LD B,$00 ; set B to zero for later indexing. ; early so any other reason ??? CP $0D ; is character carriage return ? ; i.e. an empty statement. JR Z,L1BB3 ; forward to LINE-END if so. CP $3A ; is it statement end marker ':' ? ; i.e. another type of empty statement. JR Z,L1B28 ; back to STMT-LOOP if so. LD HL,L1B76 ; address: STMT-RET PUSH HL ; is now pushed as a return address LD C,A ; transfer the current character to C. ; advance CH_ADD to a position after command and test if it is a command. RST 20H ; NEXT-CHAR to advance pointer LD A,C ; restore current character SUB $CE ; subtract 'DEF FN' - first command JP C,L1C8A ; jump to REPORT-C if less than a command ; raising ; 'Nonsense in BASIC' LD C,A ; put the valid command code back in C. ; register B is zero. LD HL,L1A48 ; address: offst-tbl ADD HL,BC ; index into table with one of 50 commands. LD C,(HL) ; pick up displacement to syntax table entry. ADD HL,BC ; add to address the relevant entry. JR L1B55 ; forward to continue at GET-PARAM ; ---------------------- ; The main scanning loop ; ---------------------- ; not documented properly ; ;; SCAN-LOOP L1B52: LD HL,($5C74) ; fetch temporary address from T_ADDR ; during subsequent loops. ; -> the initial entry point with HL addressing start of syntax table entry. ;; GET-PARAM L1B55: LD A,(HL) ; pick up the parameter. INC HL ; address next one. LD ($5C74),HL ; save pointer in system variable T_ADDR LD BC,L1B52 ; address: SCAN-LOOP PUSH BC ; is now pushed on stack as looping address. LD C,A ; store parameter in C. CP $20 ; is it greater than ' ' ? JR NC,L1B6F ; forward to SEPARATOR to check that correct ; separator appears in statement if so. LD HL,L1C01 ; address: class-tbl. LD B,$00 ; prepare to index into the class table. ADD HL,BC ; index to find displacement to routine. LD C,(HL) ; displacement to BC ADD HL,BC ; add to address the CLASS routine. PUSH HL ; push the address on the stack. RST 18H ; GET-CHAR - HL points to place in statement. DEC B ; reset the zero flag - the initial state ; for all class routines. RET ; and make an indirect jump to routine ; and then SCAN-LOOP (also on stack). ; Note. one of the class routines will eventually drop the return address ; off the stack breaking out of the above seemingly endless loop. ; ----------------------- ; THE 'SEPARATOR' ROUTINE ; ----------------------- ; This routine is called once to verify that the mandatory separator ; present in the parameter table is also present in the correct ; location following the command. For example, the 'THEN' token after ; the 'IF' token and expression. ;; SEPARATOR L1B6F: RST 18H ; GET-CHAR CP C ; does it match the character in C ? JP NZ,L1C8A ; jump forward to REPORT-C if not ; 'Nonsense in BASIC'. RST 20H ; NEXT-CHAR advance to next character RET ; return. ; ------------------------------ ; Come here after interpretation ; ------------------------------ ; ; ;; STMT-RET L1B76: CALL L1F54 ; routine BREAK-KEY is tested after every ; statement. JR C,L1B7D ; step forward to STMT-R-1 if not pressed. ;; REPORT-L L1B7B: RST 08H ; ERROR-1 DEFB $14 ; Error Report: BREAK into program ;; STMT-R-1 L1B7D: BIT 7,(IY+$0A) ; test NSPPC - will be set if $FF - ; no jump to be made. JR NZ,L1BF4 ; forward to STMT-NEXT if a program line. LD HL,($5C42) ; fetch line number from NEWPPC BIT 7,H ; will be set if minus two - direct command(s) JR Z,L1B9E ; forward to LINE-NEW if a jump is to be ; made to a new program line/statement. ; -------------------- ; Run a direct command ; -------------------- ; A direct command is to be run or, if continuing from above, ; the next statement of a direct command is to be considered. ;; LINE-RUN L1B8A: LD HL,$FFFE ; The dummy value minus two LD ($5C45),HL ; is set/reset as line number in PPC. LD HL,($5C61) ; point to end of line + 1 - WORKSP. DEC HL ; now point to $80 end-marker. LD DE,($5C59) ; address the start of line E_LINE. DEC DE ; now location before - for GET-CHAR. LD A,($5C44) ; load statement to A from NSPPC. JR L1BD1 ; forward to NEXT-LINE. ; ------------------------------ ; Find start address of new line ; ------------------------------ ; The branch was to here if a jump is to made to a new line number ; and statement. ; That is the previous statement was a GO TO, GO SUB, RUN, RETURN, NEXT etc.. ;; LINE-NEW L1B9E: CALL L196E ; routine LINE-ADDR gets address of line ; returning zero flag set if line found. LD A,($5C44) ; fetch new statement from NSPPC JR Z,L1BBF ; forward to LINE-USE if line matched. ; continue as must be a direct command. AND A ; test statement which should be zero JR NZ,L1BEC ; forward to REPORT-N if not. ; 'Statement lost' ; LD B,A ; save statement in B.?? LD A,(HL) ; fetch high byte of line number. AND $C0 ; test if using direct command ; a program line is less than $3F LD A,B ; retrieve statement. ; (we can assume it is zero). JR Z,L1BBF ; forward to LINE-USE if was a program line ; Alternatively a direct statement has finished correctly. ;; REPORT-0 L1BB0: RST 08H ; ERROR-1 DEFB $FF ; Error Report: OK ; ----------------- ; THE 'REM' COMMAND ; ----------------- ; The REM command routine. ; The return address STMT-RET is dropped and the rest of line ignored. ;; REM L1BB2: POP BC ; drop return address STMT-RET and ; continue ignoring rest of line. ; ------------ ; End of line? ; ------------ ; ; ;; LINE-END L1BB3: CALL L2530 ; routine SYNTAX-Z (UNSTACK-Z?) RET Z ; return if checking syntax. LD HL,($5C55) ; fetch NXTLIN to HL. LD A,$C0 ; test against the AND (HL) ; system limit $3F. RET NZ ; return if more as must be ; end of program. ; (or direct command) XOR A ; set statement to zero. ; and continue to set up the next following line and then consider this new one. ; --------------------- ; General line checking ; --------------------- ; The branch was here from LINE-NEW if BASIC is branching. ; or a continuation from above if dealing with a new sequential line. ; First make statement zero number one leaving others unaffected. ;; LINE-USE L1BBF: CP $01 ; will set carry if zero. ADC A,$00 ; add in any carry. LD D,(HL) ; high byte of line number to D. INC HL ; advance pointer. LD E,(HL) ; low byte of line number to E. LD ($5C45),DE ; set system variable PPC. INC HL ; advance pointer. LD E,(HL) ; low byte of line length to E. INC HL ; advance pointer. LD D,(HL) ; high byte of line length to D. EX DE,HL ; swap pointer to DE before ADD HL,DE ; adding to address the end of line. INC HL ; advance to start of next line. ; ----------------------------- ; Update NEXT LINE but consider ; previous line or edit line. ; ----------------------------- ; The pointer will be the next line if continuing from above or to ; edit line end-marker ($80) if from LINE-RUN. ;; NEXT-LINE L1BD1: LD ($5C55),HL ; store pointer in system variable NXTLIN EX DE,HL ; bring back pointer to previous or edit line LD ($5C5D),HL ; and update CH_ADD with character address. LD D,A ; store statement in D. LD E,$00 ; set E to zero to suppress token searching ; if EACH-STMT is to be called. LD (IY+$0A),$FF ; set statement NSPPC to $FF signalling ; no jump to be made. DEC D ; decrement and test statement LD (IY+$0D),D ; set SUBPPC to decremented statement number. JP Z,L1B28 ; to STMT-LOOP if result zero as statement is ; at start of line and address is known. INC D ; else restore statement. CALL L198B ; routine EACH-STMT finds the D'th statement ; address as E does not contain a token. JR Z,L1BF4 ; forward to STMT-NEXT if address found. ;; REPORT-N L1BEC: RST 08H ; ERROR-1 DEFB $16 ; Error Report: Statement lost ; ----------------- ; End of statement? ; ----------------- ; This combination of routines is called from 20 places when ; the end of a statement should have been reached and all preceding ; syntax is in order. ;; CHECK-END L1BEE: CALL L2530 ; routine SYNTAX-Z RET NZ ; return immediately in runtime POP BC ; drop address of calling routine. POP BC ; drop address STMT-RET. ; and continue to find next statement. ; -------------------- ; Go to next statement ; -------------------- ; Acceptable characters at this point are carriage return and ':'. ; If so go to next statement which in the first case will be on next line. ;; STMT-NEXT L1BF4: RST 18H ; GET-CHAR - ignoring white space etc. CP $0D ; is it carriage return ? JR Z,L1BB3 ; back to LINE-END if so. CP $3A ; is it ':' ? JP Z,L1B28 ; jump back to STMT-LOOP to consider ; further statements JP L1C8A ; jump to REPORT-C with any other character ; 'Nonsense in BASIC'. ; Note. the two-byte sequence 'rst 08; defb $0b' could replace the above jp. ; ------------------- ; Command class table ; ------------------- ; ;; class-tbl L1C01: DEFB L1C10 - $ ; 0F offset to Address: CLASS-00 DEFB L1C1F - $ ; 1D offset to Address: CLASS-01 DEFB L1C4E - $ ; 4B offset to Address: CLASS-02 DEFB L1C0D - $ ; 09 offset to Address: CLASS-03 DEFB L1C6C - $ ; 67 offset to Address: CLASS-04 DEFB L1C11 - $ ; 0B offset to Address: CLASS-05 DEFB L1C82 - $ ; 7B offset to Address: CLASS-06 DEFB L1C96 - $ ; 8E offset to Address: CLASS-07 DEFB L1C7A - $ ; 71 offset to Address: CLASS-08 DEFB L1CBE - $ ; B4 offset to Address: CLASS-09 DEFB L1C8C - $ ; 81 offset to Address: CLASS-0A DEFB L1CDB - $ ; CF offset to Address: CLASS-0B ; -------------------------------- ; Command classes---00, 03, and 05 ; -------------------------------- ; class-03 e.g. RUN or RUN 200 ; optional operand ; class-00 e.g. CONTINUE ; no operand ; class-05 e.g. PRINT ; variable syntax checked by routine ;; CLASS-03 L1C0D: CALL L1CDE ; routine FETCH-NUM ;; CLASS-00 L1C10: CP A ; reset zero flag. ; if entering here then all class routines are entered with zero reset. ;; CLASS-05 L1C11: POP BC ; drop address SCAN-LOOP. CALL Z,L1BEE ; if zero set then call routine CHECK-END >>> ; as should be no further characters. EX DE,HL ; save HL to DE. LD HL,($5C74) ; fetch T_ADDR LD C,(HL) ; fetch low byte of routine INC HL ; address next. LD B,(HL) ; fetch high byte of routine. EX DE,HL ; restore HL from DE PUSH BC ; push the address RET ; and make an indirect jump to the command. ; -------------------------------- ; Command classes---01, 02, and 04 ; -------------------------------- ; class-01 e.g. LET A = 2*3 ; a variable is reqd ; This class routine is also called from INPUT and READ to find the ; destination variable for an assignment. ;; CLASS-01 L1C1F: CALL L28B2 ; routine LOOK-VARS returns carry set if not ; found in runtime. ; ---------------------- ; Variable in assignment ; ---------------------- ; ; ;; VAR-A-1 L1C22: LD (IY+$37),$00 ; set FLAGX to zero JR NC,L1C30 ; forward to VAR-A-2 if found or checking ; syntax. SET 1,(IY+$37) ; FLAGX - Signal a new variable JR NZ,L1C46 ; to VAR-A-3 if not assigning to an array ; e.g. LET a$(3,3) = "X" ;; REPORT-2 L1C2E: RST 08H ; ERROR-1 DEFB $01 ; Error Report: Variable not found ;; VAR-A-2 L1C30: CALL Z,L2996 ; routine STK-VAR considers a subscript/slice BIT 6,(IY+$01) ; test FLAGS - Numeric or string result ? JR NZ,L1C46 ; to VAR-A-3 if numeric XOR A ; default to array/slice - to be retained. CALL L2530 ; routine SYNTAX-Z CALL NZ,L2BF1 ; routine STK-FETCH is called in runtime ; may overwrite A with 1. LD HL,$5C71 ; address system variable FLAGX OR (HL) ; set bit 0 if simple variable to be reclaimed LD (HL),A ; update FLAGX EX DE,HL ; start of string/subscript to DE ;; VAR-A-3 L1C46: LD ($5C72),BC ; update STRLEN LD ($5C4D),HL ; and DEST of assigned string. RET ; return. ; ------------------------------------------------- ; class-02 e.g. LET a = 1 + 1 ; an expression must follow ;; CLASS-02 L1C4E: POP BC ; drop return address SCAN-LOOP CALL L1C56 ; routine VAL-FET-1 is called to check ; expression and assign result in runtime CALL L1BEE ; routine CHECK-END checks nothing else ; is present in statement. RET ; Return ; ------------- ; Fetch a value ; ------------- ; ; ;; VAL-FET-1 L1C56: LD A,($5C3B) ; initial FLAGS to A ;; VAL-FET-2 L1C59: PUSH AF ; save A briefly CALL L24FB ; routine SCANNING evaluates expression. POP AF ; restore A LD D,(IY+$01) ; post-SCANNING FLAGS to D XOR D ; xor the two sets of flags AND $40 ; pick up bit 6 of xored FLAGS should be zero JR NZ,L1C8A ; forward to REPORT-C if not zero ; 'Nonsense in BASIC' - results don't agree. BIT 7,D ; test FLAGS - is syntax being checked ? JP NZ,L2AFF ; jump forward to LET to make the assignment ; in runtime. RET ; but return from here if checking syntax. ; ------------------ ; Command class---04 ; ------------------ ; class-04 e.g. FOR i ; a single character variable must follow ;; CLASS-04 L1C6C: CALL L28B2 ; routine LOOK-VARS PUSH AF ; preserve flags. LD A,C ; fetch type - should be 011xxxxx OR $9F ; combine with 10011111. INC A ; test if now $FF by incrementing. JR NZ,L1C8A ; forward to REPORT-C if result not zero. POP AF ; else restore flags. JR L1C22 ; back to VAR-A-1 ; -------------------------------- ; Expect numeric/string expression ; -------------------------------- ; This routine is used to get the two coordinates of STRING$, ATTR and POINT. ; It is also called from PRINT-ITEM to get the two numeric expressions that ; follow the AT ( in PRINT AT, INPUT AT). ;; NEXT-2NUM L1C79: RST 20H ; NEXT-CHAR advance past 'AT' or '('. ; -------- ; class-08 e.g. POKE 65535,2 ; two numeric expressions separated by comma ;; CLASS-08 ;; EXPT-2NUM L1C7A: CALL L1C82 ; routine EXPT-1NUM is called for first ; numeric expression CP $2C ; is character ',' ? JR NZ,L1C8A ; to REPORT-C if not required separator. ; 'Nonsense in BASIC'. RST 20H ; NEXT-CHAR ; -> ; class-06 e.g. GOTO a*1000 ; a numeric expression must follow ;; CLASS-06 ;; EXPT-1NUM L1C82: CALL L24FB ; routine SCANNING BIT 6,(IY+$01) ; test FLAGS - Numeric or string result ? RET NZ ; return if result is numeric. ;; REPORT-C L1C8A: RST 08H ; ERROR-1 DEFB $0B ; Error Report: Nonsense in BASIC ; --------------------------------------------------------------- ; class-0A e.g. ERASE "????" ; a string expression must follow. ; ; these only occur in unimplemented commands ; ; although the routine expt-exp is called ; ; from SAVE-ETC ;; CLASS-0A ;; EXPT-EXP L1C8C: CALL L24FB ; routine SCANNING BIT 6,(IY+$01) ; test FLAGS - Numeric or string result ? RET Z ; return if string result. JR L1C8A ; back to REPORT-C if numeric. ; --------------------- ; Set permanent colours ; class 07 ; --------------------- ; class-07 e.g. PAPER 6 ; a single class for a collection of ; ; similar commands. Clever. ; ; Note. these commands should ensure that current channel is 'S' ;; CLASS-07 L1C96: BIT 7,(IY+$01) ; test FLAGS - checking syntax only ? ; Note. there is a subroutine to do this. RES 0,(IY+$02) ; update TV_FLAG - signal main screen in use CALL NZ,L0D4D ; routine TEMPS is called in runtime. POP AF ; drop return address SCAN-LOOP LD A,($5C74) ; T_ADDR_lo to accumulator. ; points to '$07' entry + 1 ; e.g. for INK points to $EC now ; Note if you move alter the syntax table next line may have to be altered. ; Note. For ZASM assembler replace following expression with SUB $13. L1CA5: SUB L1AEB-$D8 % 256 ; convert $EB to $D8 ('INK') etc. ; ( is SUB $13 in standard ROM ) CALL L21FC ; routine CO-TEMP-4 CALL L1BEE ; routine CHECK-END check that nothing else ; in statement. ; return here in runtime. LD HL,($5C8F) ; pick up ATTR_T and MASK_T LD ($5C8D),HL ; and store in ATTR_P and MASK_P LD HL,$5C91 ; point to P_FLAG. LD A,(HL) ; pick up in A RLCA ; rotate to left XOR (HL) ; combine with HL AND $AA ; 10101010 XOR (HL) ; only permanent bits affected LD (HL),A ; reload into P_FLAG. RET ; return. ; ------------------ ; Command class---09 ; ------------------ ; e.g. PLOT PAPER 0; 128,88 ; two coordinates preceded by optional ; ; embedded colour items. ; ; Note. this command should ensure that current channel is actually 'S'. ;; CLASS-09 L1CBE: CALL L2530 ; routine SYNTAX-Z JR Z,L1CD6 ; forward to CL-09-1 if checking syntax. RES 0,(IY+$02) ; update TV_FLAG - signal main screen in use CALL L0D4D ; routine TEMPS is called. LD HL,$5C90 ; point to MASK_T LD A,(HL) ; fetch mask to accumulator. OR $F8 ; or with 11111000 paper/bright/flash 8 LD (HL),A ; mask back to MASK_T system variable. RES 6,(IY+$57) ; reset P_FLAG - signal NOT PAPER 9 ? RST 18H ; GET-CHAR ;; CL-09-1 L1CD6: CALL L21E2 ; routine CO-TEMP-2 deals with any embedded ; colour items. JR L1C7A ; exit via EXPT-2NUM to check for x,y. ; Note. if either of the numeric expressions contain STR$ then the flag setting ; above will be undone when the channel flags are reset during STR$. ; e.g. ; 10 BORDER 3 : PLOT VAL STR$ 128, VAL STR$ 100 ; credit John Elliott. ; ------------------ ; Command class---0B ; ------------------ ; Again a single class for four commands. ; This command just jumps back to SAVE-ETC to handle the four tape commands. ; The routine itself works out which command has called it by examining the ; address in T_ADDR_lo. Note therefore that the syntax table has to be ; located where these and other sequential command addresses are not split ; over a page boundary. ;; CLASS-0B L1CDB: JP L0605 ; jump way back to SAVE-ETC ; -------------- ; Fetch a number ; -------------- ; This routine is called from CLASS-03 when a command may be followed by ; an optional numeric expression e.g. RUN. If the end of statement has ; been reached then zero is used as the default. ; Also called from LIST-4. ;; FETCH-NUM L1CDE: CP $0D ; is character a carriage return ? JR Z,L1CE6 ; forward to USE-ZERO if so CP $3A ; is it ':' ? JR NZ,L1C82 ; forward to EXPT-1NUM if not. ; else continue and use zero. ; ---------------- ; Use zero routine ; ---------------- ; This routine is called four times to place the value zero on the ; calculator stack as a default value in runtime. ;; USE-ZERO L1CE6: CALL L2530 ; routine SYNTAX-Z (UNSTACK-Z?) RET Z ; RST 28H ;; FP-CALC DEFB $A0 ;;stk-zero ;0. DEFB $38 ;;end-calc RET ; return. ; ------------------- ; Handle STOP command ; ------------------- ; Command Syntax: STOP ; One of the shortest and least used commands. As with 'OK' not an error. ;; REPORT-9 ;; STOP L1CEE: RST 08H ; ERROR-1 DEFB $08 ; Error Report: STOP statement ; ----------------- ; Handle IF command ; ----------------- ; e.g. IF score>100 THEN PRINT "You Win" ; The parser has already checked the expression the result of which is on ; the calculator stack. The presence of the 'THEN' separator has also been ; checked and CH-ADD points to the command after THEN. ; ;; IF L1CF0: POP BC ; drop return address - STMT-RET CALL L2530 ; routine SYNTAX-Z JR Z,L1D00 ; forward to IF-1 if checking syntax ; to check syntax of PRINT "You Win" RST 28H ;; FP-CALC score>100 (1=TRUE 0=FALSE) DEFB $02 ;;delete . DEFB $38 ;;end-calc EX DE,HL ; make HL point to deleted value CALL L34E9 ; routine TEST-ZERO JP C,L1BB3 ; jump to LINE-END if FALSE (0) ;; IF-1 L1D00: JP L1B29 ; to STMT-L-1, if true (1) to execute command ; after 'THEN' token. ; ------------------ ; Handle FOR command ; ------------------ ; e.g. FOR i = 0 TO 1 STEP 0.1 ; Using the syntax tables, the parser has already checked for a start and ; limit value and also for the intervening separator. ; the two values v,l are on the calculator stack. ; CLASS-04 has also checked the variable and the name is in STRLEN_lo. ; The routine begins by checking for an optional STEP. ;; FOR L1D03: CP $CD ; is there a 'STEP' ? JR NZ,L1D10 ; to F-USE-1 if not to use 1 as default. RST 20H ; NEXT-CHAR CALL L1C82 ; routine EXPT-1NUM CALL L1BEE ; routine CHECK-END JR L1D16 ; to F-REORDER ; --- ;; F-USE-1 L1D10: CALL L1BEE ; routine CHECK-END RST 28H ;; FP-CALC v,l. DEFB $A1 ;;stk-one v,l,1=s. DEFB $38 ;;end-calc ;; F-REORDER L1D16: RST 28H ;; FP-CALC v,l,s. DEFB $C0 ;;st-mem-0 v,l,s. DEFB $02 ;;delete v,l. DEFB $01 ;;exchange l,v. DEFB $E0 ;;get-mem-0 l,v,s. DEFB $01 ;;exchange l,s,v. DEFB $38 ;;end-calc CALL L2AFF ; routine LET assigns the initial value v to ; the variable altering type if necessary. LD ($5C68),HL ; The system variable MEM is made to point to ; the variable instead of its normal ; location MEMBOT DEC HL ; point to single-character name LD A,(HL) ; fetch name SET 7,(HL) ; set bit 7 at location LD BC,$0006 ; add six to HL ADD HL,BC ; to address where limit should be. RLCA ; test bit 7 of original name. JR C,L1D34 ; forward to F-L-S if already a FOR/NEXT ; variable LD C,$0D ; otherwise an additional 13 bytes are needed. ; 5 for each value, two for line number and ; 1 byte for looping statement. CALL L1655 ; routine MAKE-ROOM creates them. INC HL ; make HL address limit. ;; F-L-S L1D34: PUSH HL ; save position. RST 28H ;; FP-CALC l,s. DEFB $02 ;;delete l. DEFB $02 ;;delete . DEFB $38 ;;end-calc ; DE points to STKEND, l. POP HL ; restore variable position EX DE,HL ; swap pointers LD C,$0A ; ten bytes to move LDIR ; Copy 'deleted' values to variable. LD HL,($5C45) ; Load with current line number from PPC EX DE,HL ; exchange pointers. LD (HL),E ; save the looping line INC HL ; in the next LD (HL),D ; two locations. LD D,(IY+$0D) ; fetch statement from SUBPPC system variable. INC D ; increment statement. INC HL ; and pointer LD (HL),D ; and store the looping statement. ; CALL L1DDA ; routine NEXT-LOOP considers an initial RET NC ; iteration. Return to STMT-RET if a loop is ; possible to execute next statement. ; no loop is possible so execution continues after the matching 'NEXT' LD B,(IY+$38) ; get single-character name from STRLEN_lo LD HL,($5C45) ; get the current line from PPC LD ($5C42),HL ; and store it in NEWPPC LD A,($5C47) ; fetch current statement from SUBPPC NEG ; Negate as counter decrements from zero ; initially and we are in the middle of a ; line. LD D,A ; Store result in D. LD HL,($5C5D) ; get current address from CH_ADD LD E,$F3 ; search will be for token 'NEXT' ;; F-LOOP L1D64: PUSH BC ; save variable name. LD BC,($5C55) ; fetch NXTLIN CALL L1D86 ; routine LOOK-PROG searches for 'NEXT' token. LD ($5C55),BC ; update NXTLIN POP BC ; and fetch the letter JR C,L1D84 ; forward to REPORT-I if the end of program ; was reached by LOOK-PROG. ; 'FOR without NEXT' RST 20H ; NEXT-CHAR fetches character after NEXT OR $20 ; ensure it is upper-case. CP B ; compare with FOR variable name JR Z,L1D7C ; forward to F-FOUND if it matches. ; but if no match i.e. nested FOR/NEXT loops then continue search. RST 20H ; NEXT-CHAR JR L1D64 ; back to F-LOOP ; --- ;; F-FOUND L1D7C: RST 20H ; NEXT-CHAR LD A,$01 ; subtract the negated counter from 1 SUB D ; to give the statement after the NEXT LD ($5C44),A ; set system variable NSPPC RET ; return to STMT-RET to branch to new ; line and statement. -> ; --- ;; REPORT-I L1D84: RST 08H ; ERROR-1 DEFB $11 ; Error Report: FOR without NEXT ; --------- ; LOOK-PROG ; --------- ; Find DATA, DEF FN or NEXT. ; This routine searches the program area for one of the above three keywords. ; On entry, HL points to start of search area. ; The token is in E, and D holds a statement count, decremented from zero. ;; LOOK-PROG L1D86: LD A,(HL) ; fetch current character CP $3A ; is it ':' a statement separator ? JR Z,L1DA3 ; forward to LOOK-P-2 if so. ; The starting point was PROG - 1 or the end of a line. ;; LOOK-P-1 L1D8B: INC HL ; increment pointer to address LD A,(HL) ; the high byte of line number AND $C0 ; test for program end marker $80 or a ; variable SCF ; Set Carry Flag RET NZ ; return with carry set if at end ; of program. -> LD B,(HL) ; high byte of line number to B INC HL ; LD C,(HL) ; low byte to C. LD ($5C42),BC ; set system variable NEWPPC. INC HL ; LD C,(HL) ; low byte of line length to C. INC HL ; LD B,(HL) ; high byte to B. PUSH HL ; save address ADD HL,BC ; add length to position. LD B,H ; and save result LD C,L ; in BC. POP HL ; restore address. LD D,$00 ; initialize statement counter to zero. ;; LOOK-P-2 L1DA3: PUSH BC ; save address of next line CALL L198B ; routine EACH-STMT searches current line. POP BC ; restore address. RET NC ; return if match was found. -> JR L1D8B ; back to LOOK-P-1 for next line. ; ------------------- ; Handle NEXT command ; ------------------- ; e.g. NEXT i ; The parameter tables have already evaluated the presence of a variable ;; NEXT L1DAB: BIT 1,(IY+$37) ; test FLAGX - handling a new variable ? JP NZ,L1C2E ; jump back to REPORT-2 if so ; 'Variable not found' ; now test if found variable is a simple variable uninitialized by a FOR. LD HL,($5C4D) ; load address of variable from DEST BIT 7,(HL) ; is it correct type ? JR Z,L1DD8 ; forward to REPORT-1 if not ; 'NEXT without FOR' INC HL ; step past variable name LD ($5C68),HL ; and set MEM to point to three 5-byte values ; value, limit, step. RST 28H ;; FP-CALC add step and re-store DEFB $E0 ;;get-mem-0 v. DEFB $E2 ;;get-mem-2 v,s. DEFB $0F ;;addition v+s. DEFB $C0 ;;st-mem-0 v+s. DEFB $02 ;;delete . DEFB $38 ;;end-calc CALL L1DDA ; routine NEXT-LOOP tests against limit. RET C ; return if no more iterations possible. LD HL,($5C68) ; find start of variable contents from MEM. LD DE,$000F ; add 3*5 to ADD HL,DE ; address the looping line number LD E,(HL) ; low byte to E INC HL ; LD D,(HL) ; high byte to D INC HL ; address looping statement LD H,(HL) ; and store in H EX DE,HL ; swap registers JP L1E73 ; exit via GO-TO-2 to execute another loop. ; --- ;; REPORT-1 L1DD8: RST 08H ; ERROR-1 DEFB $00 ; Error Report: NEXT without FOR ; ----------------- ; Perform NEXT loop ; ----------------- ; This routine is called from the FOR command to test for an initial ; iteration and from the NEXT command to test for all subsequent iterations. ; the system variable MEM addresses the variable's contents which, in the ; latter case, have had the step, possibly negative, added to the value. ;; NEXT-LOOP L1DDA: RST 28H ;; FP-CALC DEFB $E1 ;;get-mem-1 l. DEFB $E0 ;;get-mem-0 l,v. DEFB $E2 ;;get-mem-2 l,v,s. DEFB $36 ;;less-0 l,v,(1/0) negative step ? DEFB $00 ;;jump-true l,v.(1/0) DEFB $02 ;;to L1DE2, NEXT-1 if step negative DEFB $01 ;;exchange v,l. ;; NEXT-1 L1DE2: DEFB $03 ;;subtract l-v OR v-l. DEFB $37 ;;greater-0 (1/0) DEFB $00 ;;jump-true . DEFB $04 ;;to L1DE9, NEXT-2 if no more iterations. DEFB $38 ;;end-calc . AND A ; clear carry flag signalling another loop. RET ; return ; --- ;; NEXT-2 L1DE9: DEFB $38 ;;end-calc . SCF ; set carry flag signalling looping exhausted. RET ; return ; ------------------- ; Handle READ command ; ------------------- ; e.g. READ a, b$, c$(1000 TO 3000) ; A list of comma-separated variables is assigned from a list of ; comma-separated expressions. ; As it moves along the first list, the character address CH_ADD is stored ; in X_PTR while CH_ADD is used to read the second list. ;; READ-3 L1DEC: RST 20H ; NEXT-CHAR ; -> Entry point. ;; READ L1DED: CALL L1C1F ; routine CLASS-01 checks variable. CALL L2530 ; routine SYNTAX-Z JR Z,L1E1E ; forward to READ-2 if checking syntax RST 18H ; GET-CHAR LD ($5C5F),HL ; save character position in X_PTR. LD HL,($5C57) ; load HL with Data Address DATADD, which is ; the start of the program or the address ; after the last expression that was read or ; the address of the line number of the ; last RESTORE command. LD A,(HL) ; fetch character CP $2C ; is it a comma ? JR Z,L1E0A ; forward to READ-1 if so. ; else all data in this statement has been read so look for next DATA token LD E,$E4 ; token 'DATA' CALL L1D86 ; routine LOOK-PROG JR NC,L1E0A ; forward to READ-1 if DATA found ; else report the error. ;; REPORT-E L1E08: RST 08H ; ERROR-1 DEFB $0D ; Error Report: Out of DATA ;; READ-1 L1E0A: CALL L0077 ; routine TEMP-PTR1 advances updating CH_ADD ; with new DATADD position. CALL L1C56 ; routine VAL-FET-1 assigns value to variable ; checking type match and adjusting CH_ADD. RST 18H ; GET-CHAR fetches adjusted character position LD ($5C57),HL ; store back in DATADD LD HL,($5C5F) ; fetch X_PTR the original READ CH_ADD LD (IY+$26),$00 ; now nullify X_PTR_hi CALL L0078 ; routine TEMP-PTR2 restores READ CH_ADD ;; READ-2 L1E1E: RST 18H ; GET-CHAR CP $2C ; is it ',' indicating more variables to read ? JR Z,L1DEC ; back to READ-3 if so CALL L1BEE ; routine CHECK-END RET ; return from here in runtime to STMT-RET. ; ------------------- ; Handle DATA command ; ------------------- ; In runtime this 'command' is passed by but the syntax is checked when such ; a statement is found while parsing a line. ; e.g. DATA 1, 2, "text", score-1, a$(location, room, object), FN r(49), ; wages - tax, TRUE, The meaning of life ;; DATA L1E27: CALL L2530 ; routine SYNTAX-Z to check status JR NZ,L1E37 ; forward to DATA-2 if in runtime ;; DATA-1 L1E2C: CALL L24FB ; routine SCANNING to check syntax of ; expression CP $2C ; is it a comma ? CALL NZ,L1BEE ; routine CHECK-END checks that statement ; is complete. Will make an early exit if ; so. >>> RST 20H ; NEXT-CHAR JR L1E2C ; back to DATA-1 ; --- ;; DATA-2 L1E37: LD A,$E4 ; set token to 'DATA' and continue into ; the PASS-BY routine. ; ---------------------------------- ; Check statement for DATA or DEF FN ; ---------------------------------- ; This routine is used to backtrack to a command token and then ; forward to the next statement in runtime. ;; PASS-BY L1E39: LD B,A ; Give BC enough space to find token. CPDR ; Compare decrement and repeat. (Only use). ; Work backwards till keyword is found which ; is start of statement before any quotes. ; HL points to location before keyword. LD DE,$0200 ; count 1+1 statements, dummy value in E to ; inhibit searching for a token. JP L198B ; to EACH-STMT to find next statement ; ----------------------------------------------------------------------- ; A General Note on Invalid Line Numbers. ; ======================================= ; One of the revolutionary concepts of Sinclair BASIC was that it supported ; virtual line numbers. That is the destination of a GO TO, RESTORE etc. need ; not exist. It could be a point before or after an actual line number. ; Zero suffices for a before but the after should logically be infinity. ; Since the maximum actual line limit is 9999 then the system limit, 16383 ; when variables kick in, would serve fine as a virtual end point. ; However, ironically, only the LOAD command gets it right. It will not ; autostart a program that has been saved with a line higher than 16383. ; All the other commands deal with the limit unsatisfactorily. ; LIST, RUN, GO TO, GO SUB and RESTORE have problems and the latter may ; crash the machine when supplied with an inappropriate virtual line number. ; This is puzzling as very careful consideration must have been given to ; this point when the new variable types were allocated their masks and also ; when the routine NEXT-ONE was successfully re-written to reflect this. ; An enigma. ; ------------------------------------------------------------------------- ; ---------------------- ; Handle RESTORE command ; ---------------------- ; The restore command sets the system variable for the data address to ; point to the location before the supplied line number or first line ; thereafter. ; This alters the position where subsequent READ commands look for data. ; Note. If supplied with inappropriate high numbers the system may crash ; in the LINE-ADDR routine as it will pass the program/variables end-marker ; and then lose control of what it is looking for - variable or line number. ; - observation, Steven Vickers, 1984, Pitman. ;; RESTORE L1E42: CALL L1E99 ; routine FIND-INT2 puts integer in BC. ; Note. B should be checked against limit $3F ; and an error generated if higher. ; this entry point is used from RUN command with BC holding zero ;; REST-RUN L1E45: LD H,B ; transfer the line LD L,C ; number to the HL register. CALL L196E ; routine LINE-ADDR to fetch the address. DEC HL ; point to the location before the line. LD ($5C57),HL ; update system variable DATADD. RET ; return to STMT-RET (or RUN) ; ------------------------ ; Handle RANDOMIZE command ; ------------------------ ; This command sets the SEED for the RND function to a fixed value. ; With the parameter zero, a random start point is used depending on ; how long the computer has been switched on. ;; RANDOMIZE L1E4F: CALL L1E99 ; routine FIND-INT2 puts parameter in BC. LD A,B ; test this OR C ; for zero. JR NZ,L1E5A ; forward to RAND-1 if not zero. LD BC,($5C78) ; use the lower two bytes at FRAMES1. ;; RAND-1 L1E5A: LD ($5C76),BC ; place in SEED system variable. RET ; return to STMT-RET ; ----------------------- ; Handle CONTINUE command ; ----------------------- ; The CONTINUE command transfers the OLD (but incremented) values of ; line number and statement to the equivalent "NEW VALUE" system variables ; by using the last part of GO TO and exits indirectly to STMT-RET. ;; CONTINUE L1E5F: LD HL,($5C6E) ; fetch OLDPPC line number. LD D,(IY+$36) ; fetch OSPPC statement. JR L1E73 ; forward to GO-TO-2 ; -------------------- ; Handle GO TO command ; -------------------- ; The GO TO command routine is also called by GO SUB and RUN routines ; to evaluate the parameters of both commands. ; It updates the system variables used to fetch the next line/statement. ; It is at STMT-RET that the actual change in control takes place. ; Unlike some BASICs the line number need not exist. ; Note. the high byte of the line number is incorrectly compared with $F0 ; instead of $3F. This leads to commands with operands greater than 32767 ; being considered as having been run from the editing area and the ; error report 'Statement Lost' is given instead of 'OK'. ; - Steven Vickers, 1984. ;; GO-TO L1E67: CALL L1E99 ; routine FIND-INT2 puts operand in BC LD H,B ; transfer line LD L,C ; number to HL. LD D,$00 ; set statement to 0 - first. LD A,H ; compare high byte only CP $F0 ; to $F0 i.e. 61439 in full. JR NC,L1E9F ; forward to REPORT-B if above. ; This call entry point is used to update the system variables e.g. by RETURN. ;; GO-TO-2 L1E73: LD ($5C42),HL ; save line number in NEWPPC LD (IY+$0A),D ; and statement in NSPPC RET ; to STMT-RET (or GO-SUB command) ; ------------------ ; Handle OUT command ; ------------------ ; Syntax has been checked and the two comma-separated values are on the ; calculator stack. ;; OUT L1E7A: CALL L1E85 ; routine TWO-PARAM fetches values ; to BC and A. OUT (C),A ; perform the operation. RET ; return to STMT-RET. ; ------------------- ; Handle POKE command ; ------------------- ; This routine alters a single byte in the 64K address space. ; Happily no check is made as to whether ROM or RAM is addressed. ; Sinclair BASIC requires no poking of system variables. ;; POKE L1E80: CALL L1E85 ; routine TWO-PARAM fetches values ; to BC and A. LD (BC),A ; load memory location with A. RET ; return to STMT-RET. ; ------------------------------------ ; Fetch two parameters from calculator stack ; ------------------------------------ ; This routine fetches a byte and word from the calculator stack ; producing an error if either is out of range. ;; TWO-PARAM L1E85: CALL L2DD5 ; routine FP-TO-A JR C,L1E9F ; forward to REPORT-B if overflow occurred JR Z,L1E8E ; forward to TWO-P-1 if positive NEG ; negative numbers are made positive ;; TWO-P-1 L1E8E: PUSH AF ; save the value CALL L1E99 ; routine FIND-INT2 gets integer to BC POP AF ; restore the value RET ; return ; ------------- ; Find integers ; ------------- ; The first of these routines fetches a 8-bit integer (range 0-255) from the ; calculator stack to the accumulator and is used for colours, streams, ; durations and coordinates. ; The second routine fetches 16-bit integers to the BC register pair ; and is used to fetch command and function arguments involving line numbers ; or memory addresses and also array subscripts and tab arguments. ; -> ;; FIND-INT1 L1E94: CALL L2DD5 ; routine FP-TO-A JR L1E9C ; forward to FIND-I-1 for common exit routine. ; --- ; -> ;; FIND-INT2 L1E99: CALL L2DA2 ; routine FP-TO-BC ;; FIND-I-1 L1E9C: JR C,L1E9F ; to REPORT-Bb with overflow. RET Z ; return if positive. ;; REPORT-Bb L1E9F: RST 08H ; ERROR-1 DEFB $0A ; Error Report: Integer out of range ; ------------------ ; Handle RUN command ; ------------------ ; This command runs a program starting at an optional line. ; It performs a 'RESTORE 0' then CLEAR ;; RUN L1EA1: CALL L1E67 ; routine GO-TO puts line number in ; system variables. LD BC,$0000 ; prepare to set DATADD to first line. CALL L1E45 ; routine REST-RUN does the 'restore'. ; Note BC still holds zero. JR L1EAF ; forward to CLEAR-RUN to clear variables ; without disturbing RAMTOP and ; exit indirectly to STMT-RET ; -------------------- ; Handle CLEAR command ; -------------------- ; This command reclaims the space used by the variables. ; It also clears the screen and the GO SUB stack. ; With an integer expression, it sets the uppermost memory ; address within the BASIC system. ; "Contrary to the manual, CLEAR doesn't execute a RESTORE" - ; Steven Vickers, Pitman Pocket Guide to the Spectrum, 1984. ;; CLEAR L1EAC: CALL L1E99 ; routine FIND-INT2 fetches to BC. ;; CLEAR-RUN L1EAF: LD A,B ; test for OR C ; zero. JR NZ,L1EB7 ; skip to CLEAR-1 if not zero. LD BC,($5CB2) ; use the existing value of RAMTOP if zero. ;; CLEAR-1 L1EB7: PUSH BC ; save ramtop value. LD DE,($5C4B) ; fetch VARS LD HL,($5C59) ; fetch E_LINE DEC HL ; adjust to point at variables end-marker. CALL L19E5 ; routine RECLAIM-1 reclaims the space used by ; the variables. CALL L0D6B ; routine CLS to clear screen. LD HL,($5C65) ; fetch STKEND the start of free memory. LD DE,$0032 ; allow for another 50 bytes. ADD HL,DE ; add the overhead to HL. POP DE ; restore the ramtop value. SBC HL,DE ; if HL is greater than the value then jump JR NC,L1EDA ; forward to REPORT-M ; 'RAMTOP no good' LD HL,($5CB4) ; now P-RAMT ($7FFF on 16K RAM machine) AND A ; exact this time. SBC HL,DE ; new ramtop must be lower or the same. JR NC,L1EDC ; skip to CLEAR-2 if in actual RAM. ;; REPORT-M L1EDA: RST 08H ; ERROR-1 DEFB $15 ; Error Report: RAMTOP no good ;; CLEAR-2 L1EDC: EX DE,HL ; transfer ramtop value to HL. LD ($5CB2),HL ; update system variable RAMTOP. POP DE ; pop the return address STMT-RET. POP BC ; pop the Error Address. LD (HL),$3E ; now put the GO SUB end-marker at RAMTOP. DEC HL ; leave a location beneath it. LD SP,HL ; initialize the machine stack pointer. PUSH BC ; push the error address. LD ($5C3D),SP ; make ERR_SP point to location. EX DE,HL ; put STMT-RET in HL. JP (HL) ; and go there directly. ; --------------------- ; Handle GO SUB command ; --------------------- ; The GO SUB command diverts BASIC control to a new line number ; in a very similar manner to GO TO but ; the current line number and current statement + 1 ; are placed on the GO SUB stack as a RETURN point. ;; GO-SUB L1EED: POP DE ; drop the address STMT-RET LD H,(IY+$0D) ; fetch statement from SUBPPC and INC H ; increment it EX (SP),HL ; swap - error address to HL, ; H (statement) at top of stack, ; L (unimportant) beneath. INC SP ; adjust to overwrite unimportant byte LD BC,($5C45) ; fetch the current line number from PPC PUSH BC ; and PUSH onto GO SUB stack. ; the empty machine-stack can be rebuilt PUSH HL ; push the error address. LD ($5C3D),SP ; make system variable ERR_SP point to it. PUSH DE ; push the address STMT-RET. CALL L1E67 ; call routine GO-TO to update the system ; variables NEWPPC and NSPPC. ; then make an indirect exit to STMT-RET via LD BC,$0014 ; a 20-byte overhead memory check. ; ---------------------- ; Check available memory ; ---------------------- ; This routine is used on many occasions when extending a dynamic area ; upwards or the GO SUB stack downwards. ;; TEST-ROOM L1F05: LD HL,($5C65) ; fetch STKEND ADD HL,BC ; add the supplied test value JR C,L1F15 ; forward to REPORT-4 if over $FFFF EX DE,HL ; was less so transfer to DE LD HL,$0050 ; test against another 80 bytes ADD HL,DE ; anyway JR C,L1F15 ; forward to REPORT-4 if this passes $FFFF SBC HL,SP ; if less than the machine stack pointer RET C ; then return - OK. ;; REPORT-4 L1F15: LD L,$03 ; prepare 'Out of Memory' JP L0055 ; jump back to ERROR-3 at $0055 ; Note. this error can't be trapped at $0008 ; ------------------------------ ; THE 'FREE MEMORY' USER ROUTINE ; ------------------------------ ; This routine is not used by the ROM but allows users to evaluate ; approximate free memory with PRINT 65536 - USR 7962. ;; free-mem L1F1A: LD BC,$0000 ; allow no overhead. CALL L1F05 ; routine TEST-ROOM. LD B,H ; transfer the result LD C,L ; to the BC register. RET ; the USR function returns value of BC. ; -------------------- ; THE 'RETURN' COMMAND ; -------------------- ; As with any command, there are two values on the machine stack at the time ; it is invoked. The machine stack is below the GOSUB stack. Both grow ; downwards, the machine stack by two bytes, the GOSUB stack by 3 bytes. ; The highest location is a statement byte followed by a two-byte line number. ;; RETURN L1F23: POP BC ; drop the address STMT-RET. POP HL ; now the error address. POP DE ; now a possible BASIC return line. LD A,D ; the high byte $00 - $27 is CP $3E ; compared with the traditional end-marker $3E. JR Z,L1F36 ; forward to REPORT-7 with a match. ; 'RETURN without GOSUB' ; It was not the end-marker so a single statement byte remains at the base of ; the calculator stack. It can't be popped off. DEC SP ; adjust stack pointer to create room for two ; bytes. EX (SP),HL ; statement to H, error address to base of ; new machine stack. EX DE,HL ; statement to D, BASIC line number to HL. LD ($5C3D),SP ; adjust ERR_SP to point to new stack pointer PUSH BC ; now re-stack the address STMT-RET JP L1E73 ; to GO-TO-2 to update statement and line ; system variables and exit indirectly to the ; address just pushed on stack. ; --- ;; REPORT-7 L1F36: PUSH DE ; replace the end-marker. PUSH HL ; now restore the error address ; as will be required in a few clock cycles. RST 08H ; ERROR-1 DEFB $06 ; Error Report: RETURN without GOSUB ; -------------------- ; Handle PAUSE command ; -------------------- ; The pause command takes as its parameter the number of interrupts ; for which to wait. PAUSE 50 pauses for about a second. ; PAUSE 0 pauses indefinitely. ; Both forms can be finished by pressing a key. ;; PAUSE L1F3A: CALL L1E99 ; routine FIND-INT2 puts value in BC ;; PAUSE-1 L1F3D: HALT ; wait for interrupt. DEC BC ; decrease counter. LD A,B ; test if OR C ; result is zero. JR Z,L1F4F ; forward to PAUSE-END if so. LD A,B ; test if AND C ; now $FFFF INC A ; that is, initially zero. JR NZ,L1F49 ; skip forward to PAUSE-2 if not. INC BC ; restore counter to zero. ;; PAUSE-2 L1F49: BIT 5,(IY+$01) ; test FLAGS - has a new key been pressed ? JR Z,L1F3D ; back to PAUSE-1 if not. ;; PAUSE-END L1F4F: RES 5,(IY+$01) ; update FLAGS - signal no new key RET ; and return. ; ------------------- ; Check for BREAK key ; ------------------- ; This routine is called from COPY-LINE, when interrupts are disabled, ; to test if BREAK (SHIFT - SPACE) is being pressed. ; It is also called at STMT-RET after every statement. ;; BREAK-KEY L1F54: LD A,$7F ; Input address: $7FFE IN A,($FE) ; read lower right keys RRA ; rotate bit 0 - SPACE RET C ; return if not reset LD A,$FE ; Input address: $FEFE IN A,($FE) ; read lower left keys RRA ; rotate bit 0 - SHIFT RET ; carry will be set if not pressed. ; return with no carry if both keys ; pressed. ; --------------------- ; Handle DEF FN command ; --------------------- ; e.g. DEF FN r$(a$,a) = a$(a TO ) ; this 'command' is ignored in runtime but has its syntax checked ; during line-entry. ;; DEF-FN L1F60: CALL L2530 ; routine SYNTAX-Z JR Z,L1F6A ; forward to DEF-FN-1 if parsing LD A,$CE ; else load A with 'DEF FN' and JP L1E39 ; jump back to PASS-BY ; --- ; continue here if checking syntax. ;; DEF-FN-1 L1F6A: SET 6,(IY+$01) ; set FLAGS - Assume numeric result CALL L2C8D ; call routine ALPHA JR NC,L1F89 ; if not then to DEF-FN-4 to jump to ; 'Nonsense in BASIC' RST 20H ; NEXT-CHAR CP $24 ; is it '$' ? JR NZ,L1F7D ; to DEF-FN-2 if not as numeric. RES 6,(IY+$01) ; set FLAGS - Signal string result RST 20H ; get NEXT-CHAR ;; DEF-FN-2 L1F7D: CP $28 ; is it '(' ? JR NZ,L1FBD ; to DEF-FN-7 'Nonsense in BASIC' RST 20H ; NEXT-CHAR CP $29 ; is it ')' ? JR Z,L1FA6 ; to DEF-FN-6 if null argument ;; DEF-FN-3 L1F86: CALL L2C8D ; routine ALPHA checks that it is the expected ; alphabetic character. ;; DEF-FN-4 L1F89: JP NC,L1C8A ; to REPORT-C if not ; 'Nonsense in BASIC'. EX DE,HL ; save pointer in DE RST 20H ; NEXT-CHAR re-initializes HL from CH_ADD ; and advances. CP $24 ; '$' ? is it a string argument. JR NZ,L1F94 ; forward to DEF-FN-5 if not. EX DE,HL ; save pointer to '$' in DE RST 20H ; NEXT-CHAR re-initializes HL and advances ;; DEF-FN-5 L1F94: EX DE,HL ; bring back pointer. LD BC,$0006 ; the function requires six hidden bytes for ; each parameter passed. ; The first byte will be $0E ; then 5-byte numeric value ; or 5-byte string pointer. CALL L1655 ; routine MAKE-ROOM creates space in program ; area. INC HL ; adjust HL (set by LDDR) INC HL ; to point to first location. LD (HL),$0E ; insert the 'hidden' marker. ; Note. these invisible storage locations hold nothing meaningful for the ; moment. They will be used every time the corresponding function is ; evaluated in runtime. ; Now consider the following character fetched earlier. CP $2C ; is it ',' ? (more than one parameter) JR NZ,L1FA6 ; to DEF-FN-6 if not RST 20H ; else NEXT-CHAR JR L1F86 ; and back to DEF-FN-3 ; --- ;; DEF-FN-6 L1FA6: CP $29 ; should close with a ')' JR NZ,L1FBD ; to DEF-FN-7 if not ; 'Nonsense in BASIC' RST 20H ; get NEXT-CHAR CP $3D ; is it '=' ? JR NZ,L1FBD ; to DEF-FN-7 if not 'Nonsense...' RST 20H ; address NEXT-CHAR LD A,($5C3B) ; get FLAGS which has been set above PUSH AF ; and preserve CALL L24FB ; routine SCANNING checks syntax of expression ; and also sets flags. POP AF ; restore previous flags XOR (IY+$01) ; xor with FLAGS - bit 6 should be same ; therefore will be reset. AND $40 ; isolate bit 6. ;; DEF-FN-7 L1FBD: JP NZ,L1C8A ; jump back to REPORT-C if the expected result ; is not the same type. ; 'Nonsense in BASIC' CALL L1BEE ; routine CHECK-END will return early if ; at end of statement and move onto next ; else produce error report. >>> ; There will be no return to here. ; ------------------------------- ; Returning early from subroutine ; ------------------------------- ; All routines are capable of being run in two modes - syntax checking mode ; and runtime mode. This routine is called often to allow a routine to return ; early if checking syntax. ;; UNSTACK-Z L1FC3: CALL L2530 ; routine SYNTAX-Z sets zero flag if syntax ; is being checked. POP HL ; drop the return address. RET Z ; return to previous call in chain if checking ; syntax. JP (HL) ; jump to return address as BASIC program is ; actually running. ; --------------------- ; Handle LPRINT command ; --------------------- ; A simple form of 'PRINT #3' although it can output to 16 streams. ; Probably for compatibility with other BASICs particularly ZX81 BASIC. ; An extra UDG might have been better. ;; LPRINT L1FC9: LD A,$03 ; the printer channel JR L1FCF ; forward to PRINT-1 ; --------------------- ; Handle PRINT commands ; --------------------- ; The Spectrum's main stream output command. ; The default stream is stream 2 which is normally the upper screen ; of the computer. However the stream can be altered in range 0 - 15. ;; PRINT L1FCD: LD A,$02 ; the stream for the upper screen. ; The LPRINT command joins here. ;; PRINT-1 L1FCF: CALL L2530 ; routine SYNTAX-Z checks if program running CALL NZ,L1601 ; routine CHAN-OPEN if so CALL L0D4D ; routine TEMPS sets temporary colours. CALL L1FDF ; routine PRINT-2 - the actual item CALL L1BEE ; routine CHECK-END gives error if not at end ; of statement RET ; and return >>> ; ------------------------------------ ; this subroutine is called from above ; and also from INPUT. ;; PRINT-2 L1FDF: RST 18H ; GET-CHAR gets printable character CALL L2045 ; routine PR-END-Z checks if more printing JR Z,L1FF2 ; to PRINT-4 if not e.g. just 'PRINT :' ; This tight loop deals with combinations of positional controls and ; print items. An early return can be made from within the loop ; if the end of a print sequence is reached. ;; PRINT-3 L1FE5: CALL L204E ; routine PR-POSN-1 returns zero if more ; but returns early at this point if ; at end of statement! ; JR Z,L1FE5 ; to PRINT-3 if consecutive positioners CALL L1FFC ; routine PR-ITEM-1 deals with strings etc. CALL L204E ; routine PR-POSN-1 for more position codes JR Z,L1FE5 ; loop back to PRINT-3 if so ;; PRINT-4 L1FF2: CP $29 ; return now if this is ')' from input-item. ; (see INPUT.) RET Z ; or continue and print carriage return in ; runtime ; --------------------- ; Print carriage return ; --------------------- ; This routine which continues from above prints a carriage return ; in run-time. It is also called once from PRINT-POSN. ;; PRINT-CR L1FF5: CALL L1FC3 ; routine UNSTACK-Z LD A,$0D ; prepare a carriage return RST 10H ; PRINT-A RET ; return ; ----------- ; Print items ; ----------- ; This routine deals with print items as in ; PRINT AT 10,0;"The value of A is ";a ; It returns once a single item has been dealt with as it is part ; of a tight loop that considers sequences of positional and print items ;; PR-ITEM-1 L1FFC: RST 18H ; GET-CHAR CP $AC ; is character 'AT' ? JR NZ,L200E ; forward to PR-ITEM-2 if not. CALL L1C79 ; routine NEXT-2NUM check for two comma ; separated numbers placing them on the ; calculator stack in runtime. CALL L1FC3 ; routine UNSTACK-Z quits if checking syntax. CALL L2307 ; routine STK-TO-BC get the numbers in B and C. LD A,$16 ; prepare the 'at' control. JR L201E ; forward to PR-AT-TAB to print the sequence. ; --- ;; PR-ITEM-2 L200E: CP $AD ; is character 'TAB' ? JR NZ,L2024 ; to PR-ITEM-3 if not RST 20H ; NEXT-CHAR to address next character CALL L1C82 ; routine EXPT-1NUM CALL L1FC3 ; routine UNSTACK-Z quits if checking syntax. CALL L1E99 ; routine FIND-INT2 puts integer in BC. LD A,$17 ; prepare the 'tab' control. ;; PR-AT-TAB L201E: RST 10H ; PRINT-A outputs the control LD A,C ; first value to A RST 10H ; PRINT-A outputs it. LD A,B ; second value RST 10H ; PRINT-A RET ; return - item finished >>> ; --- ; Now consider paper 2; #2; a$ ;; PR-ITEM-3 L2024: CALL L21F2 ; routine CO-TEMP-3 will print any colour RET NC ; items - return if success. CALL L2070 ; routine STR-ALTER considers new stream RET NC ; return if altered. CALL L24FB ; routine SCANNING now to evaluate expression CALL L1FC3 ; routine UNSTACK-Z if not runtime. BIT 6,(IY+$01) ; test FLAGS - Numeric or string result ? CALL Z,L2BF1 ; routine STK-FETCH if string. ; note no flags affected. JP NZ,L2DE3 ; to PRINT-FP to print if numeric >>> ; It was a string expression - start in DE, length in BC ; Now enter a loop to print it ;; PR-STRING L203C: LD A,B ; this tests if the OR C ; length is zero and sets flag accordingly. DEC BC ; this doesn't but decrements counter. RET Z ; return if zero. LD A,(DE) ; fetch character. INC DE ; address next location. RST 10H ; PRINT-A. JR L203C ; loop back to PR-STRING. ; --------------- ; End of printing ; --------------- ; This subroutine returns zero if no further printing is required ; in the current statement. ; The first terminator is found in escaped input items only, ; the others in print_items. ;; PR-END-Z L2045: CP $29 ; is character a ')' ? RET Z ; return if so - e.g. INPUT (p$); a$ ;; PR-ST-END L2048: CP $0D ; is it a carriage return ? RET Z ; return also - e.g. PRINT a CP $3A ; is character a ':' ? RET ; return - zero flag will be set if so. ; e.g. PRINT a : ; -------------- ; Print position ; -------------- ; This routine considers a single positional character ';', ',', ''' ;; PR-POSN-1 L204E: RST 18H ; GET-CHAR CP $3B ; is it ';' ? ; i.e. print from last position. JR Z,L2067 ; forward to PR-POSN-3 if so. ; i.e. do nothing. CP $2C ; is it ',' ? ; i.e. print at next tabstop. JR NZ,L2061 ; forward to PR-POSN-2 if anything else. CALL L2530 ; routine SYNTAX-Z JR Z,L2067 ; forward to PR-POSN-3 if checking syntax. LD A,$06 ; prepare the 'comma' control character. RST 10H ; PRINT-A outputs to current channel in ; run-time. JR L2067 ; skip to PR-POSN-3. ; --- ; check for newline. ;; PR-POSN-2 L2061: CP $27 ; is character a "'" ? (newline) RET NZ ; return if no match >>> CALL L1FF5 ; routine PRINT-CR outputs a carriage return ; in runtime only. ;; PR-POSN-3 L2067: RST 20H ; NEXT-CHAR to A. CALL L2045 ; routine PR-END-Z checks if at end. JR NZ,L206E ; to PR-POSN-4 if not. POP BC ; drop return address if at end. ;; PR-POSN-4 L206E: CP A ; reset the zero flag. RET ; and return to loop or quit. ; ------------ ; Alter stream ; ------------ ; This routine is called from PRINT ITEMS above, and also LIST as in ; LIST #15 ;; STR-ALTER L2070: CP $23 ; is character '#' ? SCF ; set carry flag. RET NZ ; return if no match. RST 20H ; NEXT-CHAR CALL L1C82 ; routine EXPT-1NUM gets stream number AND A ; prepare to exit early with carry reset CALL L1FC3 ; routine UNSTACK-Z exits early if parsing CALL L1E94 ; routine FIND-INT1 gets number off stack CP $10 ; must be range 0 - 15 decimal. JP NC,L160E ; jump back to REPORT-Oa if not ; 'Invalid stream'. CALL L1601 ; routine CHAN-OPEN AND A ; clear carry - signal item dealt with. RET ; return ; ------------------- ; THE 'INPUT' COMMAND ; ------------------- ; This command is mysterious. ; ;; INPUT L2089: CALL L2530 ; routine SYNTAX-Z to check if in runtime. JR Z,L2096 ; forward to INPUT-1 if checking syntax. LD A,$01 ; select channel 'K' the keyboard for input. CALL L1601 ; routine CHAN-OPEN opens the channel and sets ; bit 0 of TV_FLAG. ; Note. As a consequence of clearing the lower screen channel 0 is made ; the current channel so the above two instructions are superfluous. CALL L0D6E ; routine CLS-LOWER clears the lower screen ; and sets DF_SZ to two and TV_FLAG to $01. ;; INPUT-1 L2096: LD (IY+$02),$01 ; update TV_FLAG - signal lower screen in use ; ensuring that the correct set of system ; variables are updated and that the border ; colour is used. ; Note. The Complete Spectrum ROM Disassembly incorrectly names DF-SZ as the ; system variable that is updated above and if, as some have done, you make ; this unnecessary alteration then there will be two blank lines between the ; lower screen and the upper screen areas which will also scroll wrongly. CALL L20C1 ; routine IN-ITEM-1 to handle the input. CALL L1BEE ; routine CHECK-END will make an early exit ; if checking syntax. >>> ; Keyboard input has been made and it remains to adjust the upper ; screen in case the lower two lines have been extended upwards. LD BC,($5C88) ; fetch S_POSN current line/column of ; the upper screen. LD A,($5C6B) ; fetch DF_SZ the display file size of ; the lower screen. CP B ; test that lower screen does not overlap JR C,L20AD ; forward to INPUT-2 if not. ; the two screens overlap so adjust upper screen. LD C,$21 ; set column of upper screen to leftmost. LD B,A ; and line to one above lower screen. ; continue forward to update upper screen ; print position. ;; INPUT-2 L20AD: LD ($5C88),BC ; set S_POSN update upper screen line/column. LD A,$19 ; subtract from twenty five SUB B ; the new line number. LD ($5C8C),A ; and place result in SCR_CT - scroll count. RES 0,(IY+$02) ; update TV_FLAG - signal main screen in use. CALL L0DD9 ; routine CL-SET sets the print position ; system variables for the upper screen. JP L0D6E ; jump back to CLS-LOWER and make ; an indirect exit >>. ; --------------------- ; INPUT ITEM subroutine ; --------------------- ; This subroutine deals with the input items and print items. ; from the current input channel. ; It is only called from the above INPUT routine but was obviously ; once called from somewhere else in another context. ;; IN-ITEM-1 L20C1: CALL L204E ; routine PR-POSN-1 deals with a single ; position item at each call. JR Z,L20C1 ; back to IN-ITEM-1 until no more in a ; sequence. CP $28 ; is character '(' ? JR NZ,L20D8 ; forward to IN-ITEM-2 if not. ; any variables within braces will be treated as part, or all, of the prompt ; instead of being used as destination variables. RST 20H ; NEXT-CHAR CALL L1FDF ; routine PRINT-2 to output the dynamic ; prompt. RST 18H ; GET-CHAR CP $29 ; is character a matching ')' ? JP NZ,L1C8A ; jump back to REPORT-C if not. ; 'Nonsense in BASIC'. RST 20H ; NEXT-CHAR JP L21B2 ; forward to IN-NEXT-2 ; --- ;; IN-ITEM-2 L20D8: CP $CA ; is the character the token 'LINE' ? JR NZ,L20ED ; forward to IN-ITEM-3 if not. RST 20H ; NEXT-CHAR - variable must come next. CALL L1C1F ; routine CLASS-01 returns destination ; address of variable to be assigned. ; or generates an error if no variable ; at this position. SET 7,(IY+$37) ; update FLAGX - signal handling INPUT LINE BIT 6,(IY+$01) ; test FLAGS - numeric or string result ? JP NZ,L1C8A ; jump back to REPORT-C if not string ; 'Nonsense in BASIC'. JR L20FA ; forward to IN-PROMPT to set up workspace. ; --- ; the jump was here for other variables. ;; IN-ITEM-3 L20ED: CALL L2C8D ; routine ALPHA checks if character is ; a suitable variable name. JP NC,L21AF ; forward to IN-NEXT-1 if not CALL L1C1F ; routine CLASS-01 returns destination ; address of variable to be assigned. RES 7,(IY+$37) ; update FLAGX - signal not INPUT LINE. ;; IN-PROMPT L20FA: CALL L2530 ; routine SYNTAX-Z JP Z,L21B2 ; forward to IN-NEXT-2 if checking syntax. CALL L16BF ; routine SET-WORK clears workspace. LD HL,$5C71 ; point to system variable FLAGX RES 6,(HL) ; signal string result. SET 5,(HL) ; signal in Input Mode for editor. LD BC,$0001 ; initialize space required to one for ; the carriage return. BIT 7,(HL) ; test FLAGX - INPUT LINE in use ? JR NZ,L211C ; forward to IN-PR-2 if so as that is ; all the space that is required. LD A,($5C3B) ; load accumulator from FLAGS AND $40 ; mask to test BIT 6 of FLAGS and clear ; the other bits in A. ; numeric result expected ? JR NZ,L211A ; forward to IN-PR-1 if so LD C,$03 ; increase space to three bytes for the ; pair of surrounding quotes. ;; IN-PR-1 L211A: OR (HL) ; if numeric result, set bit 6 of FLAGX. LD (HL),A ; and update system variable ;; IN-PR-2 L211C: RST 30H ; BC-SPACES opens 1 or 3 bytes in workspace LD (HL),$0D ; insert carriage return at last new location. LD A,C ; fetch the length, one or three. RRCA ; lose bit 0. RRCA ; test if quotes required. JR NC,L2129 ; forward to IN-PR-3 if not. LD A,$22 ; load the '"' character LD (DE),A ; place quote in first new location at DE. DEC HL ; decrease HL - from carriage return. LD (HL),A ; and place a quote in second location. ;; IN-PR-3 L2129: LD ($5C5B),HL ; set keyboard cursor K_CUR to HL BIT 7,(IY+$37) ; test FLAGX - is this INPUT LINE ?? JR NZ,L215E ; forward to IN-VAR-3 if so as input will ; be accepted without checking its syntax. LD HL,($5C5D) ; fetch CH_ADD PUSH HL ; and save on stack. LD HL,($5C3D) ; fetch ERR_SP PUSH HL ; and save on stack ;; IN-VAR-1 L213A: LD HL,L213A ; address: IN-VAR-1 - this address PUSH HL ; is saved on stack to handle errors. BIT 4,(IY+$30) ; test FLAGS2 - is K channel in use ? JR Z,L2148 ; forward to IN-VAR-2 if not using the ; keyboard for input. (??) LD ($5C3D),SP ; set ERR_SP to point to IN-VAR-1 on stack. ;; IN-VAR-2 L2148: LD HL,($5C61) ; set HL to WORKSP - start of workspace. CALL L11A7 ; routine REMOVE-FP removes floating point ; forms when looping in error condition. LD (IY+$00),$FF ; set ERR_NR to 'OK' cancelling the error. ; but X_PTR causes flashing error marker ; to be displayed at each call to the editor. CALL L0F2C ; routine EDITOR allows input to be entered ; or corrected if this is second time around. ; if we pass to next then there are no system errors RES 7,(IY+$01) ; update FLAGS - signal checking syntax CALL L21B9 ; routine IN-ASSIGN checks syntax using ; the VAL-FET-2 and powerful SCANNING routines. ; any syntax error and its back to IN-VAR-1. ; but with the flashing error marker showing ; where the error is. ; Note. the syntax of string input has to be ; checked as the user may have removed the ; bounding quotes or escaped them as with ; "hat" + "stand" for example. ; proceed if syntax passed. JR L2161 ; jump forward to IN-VAR-4 ; --- ; the jump was to here when using INPUT LINE. ;; IN-VAR-3 L215E: CALL L0F2C ; routine EDITOR is called for input ; when ENTER received rejoin other route but with no syntax check. ; INPUT and INPUT LINE converge here. ;; IN-VAR-4 L2161: LD (IY+$22),$00 ; set K_CUR_hi to a low value so that the cursor ; no longer appears in the input line. CALL L21D6 ; routine IN-CHAN-K tests if the keyboard ; is being used for input. JR NZ,L2174 ; forward to IN-VAR-5 if using another input ; channel. ; continue here if using the keyboard. CALL L111D ; routine ED-COPY overprints the edit line ; to the lower screen. The only visible ; affect is that the cursor disappears. ; if you're inputting more than one item in ; a statement then that becomes apparent. LD BC,($5C82) ; fetch line and column from ECHO_E CALL L0DD9 ; routine CL-SET sets S-POSNL to those ; values. ; if using another input channel rejoin here. ;; IN-VAR-5 L2174: LD HL,$5C71 ; point HL to FLAGX RES 5,(HL) ; signal not in input mode BIT 7,(HL) ; is this INPUT LINE ? RES 7,(HL) ; cancel the bit anyway. JR NZ,L219B ; forward to IN-VAR-6 if INPUT LINE. POP HL ; drop the looping address POP HL ; drop the address of previous ; error handler. LD ($5C3D),HL ; set ERR_SP to point to it. POP HL ; drop original CH_ADD which points to ; INPUT command in BASIC line. LD ($5C5F),HL ; save in X_PTR while input is assigned. SET 7,(IY+$01) ; update FLAGS - Signal running program CALL L21B9 ; routine IN-ASSIGN is called again ; this time the variable will be assigned ; the input value without error. ; Note. the previous example now ; becomes "hatstand" LD HL,($5C5F) ; fetch stored CH_ADD value from X_PTR. LD (IY+$26),$00 ; set X_PTR_hi so that iy is no longer relevant. LD ($5C5D),HL ; put restored value back in CH_ADD JR L21B2 ; forward to IN-NEXT-2 to see if anything ; more in the INPUT list. ; --- ; the jump was to here with INPUT LINE only ;; IN-VAR-6 L219B: LD HL,($5C63) ; STKBOT points to the end of the input. LD DE,($5C61) ; WORKSP points to the beginning. SCF ; prepare for true subtraction. SBC HL,DE ; subtract to get length LD B,H ; transfer it to LD C,L ; the BC register pair. CALL L2AB2 ; routine STK-STO-$ stores parameters on ; the calculator stack. CALL L2AFF ; routine LET assigns it to destination. JR L21B2 ; forward to IN-NEXT-2 as print items ; not allowed with INPUT LINE. ; Note. that "hat" + "stand" will, for ; example, be unchanged as also would ; 'PRINT "Iris was here"'. ; --- ; the jump was to here when ALPHA found more items while looking for ; a variable name. ;; IN-NEXT-1 L21AF: CALL L1FFC ; routine PR-ITEM-1 considers further items. ;; IN-NEXT-2 L21B2: CALL L204E ; routine PR-POSN-1 handles a position item. JP Z,L20C1 ; jump back to IN-ITEM-1 if the zero flag ; indicates more items are present. RET ; return. ; --------------------------- ; INPUT ASSIGNMENT Subroutine ; --------------------------- ; This subroutine is called twice from the INPUT command when normal ; keyboard input is assigned. On the first occasion syntax is checked ; using SCANNING. The final call with the syntax flag reset is to make ; the assignment. ;; IN-ASSIGN L21B9: LD HL,($5C61) ; fetch WORKSP start of input LD ($5C5D),HL ; set CH_ADD to first character RST 18H ; GET-CHAR ignoring leading white-space. CP $E2 ; is it 'STOP' JR Z,L21D0 ; forward to IN-STOP if so. LD A,($5C71) ; load accumulator from FLAGX CALL L1C59 ; routine VAL-FET-2 makes assignment ; or goes through the motions if checking ; syntax. SCANNING is used. RST 18H ; GET-CHAR CP $0D ; is it carriage return ? RET Z ; return if so ; either syntax is OK ; or assignment has been made. ; if another character was found then raise an error. ; User doesn't see report but the flashing error marker ; appears in the lower screen. ;; REPORT-Cb L21CE: RST 08H ; ERROR-1 DEFB $0B ; Error Report: Nonsense in BASIC ;; IN-STOP L21D0: CALL L2530 ; routine SYNTAX-Z (UNSTACK-Z?) RET Z ; return if checking syntax ; as user wouldn't see error report. ; but generate visible error report ; on second invocation. ;; REPORT-H L21D4: RST 08H ; ERROR-1 DEFB $10 ; Error Report: STOP in INPUT ; ----------------------------------- ; THE 'TEST FOR CHANNEL K' SUBROUTINE ; ----------------------------------- ; This subroutine is called once from the keyboard INPUT command to check if ; the input routine in use is the one for the keyboard. ;; IN-CHAN-K L21D6: LD HL,($5C51) ; fetch address of current channel CURCHL INC HL ; INC HL ; advance past INC HL ; input and INC HL ; output streams LD A,(HL) ; fetch the channel identifier. CP $4B ; test for 'K' RET ; return with zero set if keyboard is use. ; -------------------- ; Colour Item Routines ; -------------------- ; ; These routines have 3 entry points - ; 1) CO-TEMP-2 to handle a series of embedded Graphic colour items. ; 2) CO-TEMP-3 to handle a single embedded print colour item. ; 3) CO TEMP-4 to handle a colour command such as FLASH 1 ; ; "Due to a bug, if you bring in a peripheral channel and later use a colour ; statement, colour controls will be sent to it by mistake." - Steven Vickers ; Pitman Pocket Guide, 1984. ; ; To be fair, this only applies if the last channel was other than 'K', 'S' ; or 'P', which are all that are supported by this ROM, but if that last ; channel was a microdrive file, network channel etc. then ; PAPER 6; CLS will not turn the screen yellow and ; CIRCLE INK 2; 128,88,50 will not draw a red circle. ; ; This bug does not apply to embedded PRINT items as it is quite permissible ; to mix stream altering commands and colour items. ; The fix therefore would be to ensure that CLASS-07 and CLASS-09 make ; channel 'S' the current channel when not checking syntax. ; ----------------------------------------------------------------- ;; CO-TEMP-1 L21E1: RST 20H ; NEXT-CHAR ; -> Entry point from CLASS-09. Embedded Graphic colour items. ; e.g. PLOT INK 2; PAPER 8; 128,88 ; Loops till all colour items output, finally addressing the coordinates. ;; CO-TEMP-2 L21E2: CALL L21F2 ; routine CO-TEMP-3 to output colour control. RET C ; return if nothing more to output. -> RST 18H ; GET-CHAR CP $2C ; is it ',' separator ? JR Z,L21E1 ; back if so to CO-TEMP-1 CP $3B ; is it ';' separator ? JR Z,L21E1 ; back to CO-TEMP-1 for more. JP L1C8A ; to REPORT-C (REPORT-Cb is within range) ; 'Nonsense in BASIC' ; ------------------- ; CO-TEMP-3 ; ------------------- ; -> this routine evaluates and outputs a colour control and parameter. ; It is called from above and also from PR-ITEM-3 to handle a single embedded ; print item e.g. PRINT PAPER 6; "Hi". In the latter case, the looping for ; multiple items is within the PR-ITEM routine. ; It is quite permissible to send these to any stream. ;; CO-TEMP-3 L21F2: CP $D9 ; is it 'INK' ? RET C ; return if less. CP $DF ; compare with 'OUT' CCF ; Complement Carry Flag RET C ; return if greater than 'OVER', $DE. PUSH AF ; save the colour token. RST 20H ; address NEXT-CHAR POP AF ; restore token and continue. ; -> this entry point used by CLASS-07. e.g. the command PAPER 6. ;; CO-TEMP-4 L21FC: SUB $C9 ; reduce to control character $10 (INK) ; thru $15 (OVER). PUSH AF ; save control. CALL L1C82 ; routine EXPT-1NUM stacks addressed ; parameter on calculator stack. POP AF ; restore control. AND A ; clear carry CALL L1FC3 ; routine UNSTACK-Z returns if checking syntax. PUSH AF ; save again CALL L1E94 ; routine FIND-INT1 fetches parameter to A. LD D,A ; transfer now to D POP AF ; restore control. RST 10H ; PRINT-A outputs the control to current ; channel. LD A,D ; transfer parameter to A. RST 10H ; PRINT-A outputs parameter. RET ; return. -> ; ------------------------------------------------------------------------- ; ; {fl}{br}{ paper }{ ink } The temporary colour attributes ; ___ ___ ___ ___ ___ ___ ___ ___ system variable. ; ATTR_T | | | | | | | | | ; | | | | | | | | | ; 23695 |___|___|___|___|___|___|___|___| ; 7 6 5 4 3 2 1 0 ; ; ; {fl}{br}{ paper }{ ink } The temporary mask used for ; ___ ___ ___ ___ ___ ___ ___ ___ transparent colours. Any bit ; MASK_T | | | | | | | | | that is 1 shows that the ; | | | | | | | | | corresponding attribute is ; 23696 |___|___|___|___|___|___|___|___| taken not from ATTR-T but from ; 7 6 5 4 3 2 1 0 what is already on the screen. ; ; ; {paper9 }{ ink9 }{ inv1 }{ over1} The print flags. Even bits are ; ___ ___ ___ ___ ___ ___ ___ ___ temporary flags. The odd bits ; P_FLAG | | | | | | | | | are the permanent flags. ; | p | t | p | t | p | t | p | t | ; 23697 |___|___|___|___|___|___|___|___| ; 7 6 5 4 3 2 1 0 ; ; ----------------------------------------------------------------------- ; ------------------------------------ ; The colour system variable handler. ; ------------------------------------ ; This is an exit branch from PO-1-OPER, PO-2-OPER ; A holds control $10 (INK) to $15 (OVER) ; D holds parameter 0-9 for ink/paper 0,1 or 8 for bright/flash, ; 0 or 1 for over/inverse. ;; CO-TEMP-5 L2211: SUB $11 ; reduce range $FF-$04 ADC A,$00 ; add in carry if INK JR Z,L2234 ; forward to CO-TEMP-7 with INK and PAPER. SUB $02 ; reduce range $FF-$02 ADC A,$00 ; add carry if FLASH JR Z,L2273 ; forward to CO-TEMP-C with FLASH and BRIGHT. CP $01 ; is it 'INVERSE' ? LD A,D ; fetch parameter for INVERSE/OVER LD B,$01 ; prepare OVER mask setting bit 0. JR NZ,L2228 ; forward to CO-TEMP-6 if OVER RLCA ; shift bit 0 RLCA ; to bit 2 LD B,$04 ; set bit 2 of mask for inverse. ;; CO-TEMP-6 L2228: LD C,A ; save the A LD A,D ; re-fetch parameter CP $02 ; is it less than 2 JR NC,L2244 ; to REPORT-K if not 0 or 1. ; 'Invalid colour'. LD A,C ; restore A LD HL,$5C91 ; address system variable P_FLAG JR L226C ; forward to exit via routine CO-CHANGE ; --- ; the branch was here with INK/PAPER and carry set for INK. ;; CO-TEMP-7 L2234: LD A,D ; fetch parameter LD B,$07 ; set ink mask 00000111 JR C,L223E ; forward to CO-TEMP-8 with INK RLCA ; shift bits 0-2 RLCA ; to RLCA ; bits 3-5 LD B,$38 ; set paper mask 00111000 ; both paper and ink rejoin here ;; CO-TEMP-8 L223E: LD C,A ; value to C LD A,D ; fetch parameter CP $0A ; is it less than 10d ? JR C,L2246 ; forward to CO-TEMP-9 if so. ; ink 10 etc. is not allowed. ;; REPORT-K L2244: RST 08H ; ERROR-1 DEFB $13 ; Error Report: Invalid colour ;; CO-TEMP-9 L2246: LD HL,$5C8F ; address system variable ATTR_T initially. CP $08 ; compare with 8 JR C,L2258 ; forward to CO-TEMP-B with 0-7. LD A,(HL) ; fetch temporary attribute as no change. JR Z,L2257 ; forward to CO-TEMP-A with INK/PAPER 8 ; it is either ink 9 or paper 9 (contrasting) OR B ; or with mask to make white CPL ; make black and change other to dark AND $24 ; 00100100 JR Z,L2257 ; forward to CO-TEMP-A if black and ; originally light. LD A,B ; else just use the mask (white) ;; CO-TEMP-A L2257: LD C,A ; save A in C ;; CO-TEMP-B L2258: LD A,C ; load colour to A CALL L226C ; routine CO-CHANGE addressing ATTR-T LD A,$07 ; put 7 in accumulator CP D ; compare with parameter SBC A,A ; $00 if 0-7, $FF if 8 CALL L226C ; routine CO-CHANGE addressing MASK-T ; mask returned in A. ; now consider P-FLAG. RLCA ; 01110000 or 00001110 RLCA ; 11100000 or 00011100 AND $50 ; 01000000 or 00010000 (AND 01010000) LD B,A ; transfer to mask LD A,$08 ; load A with 8 CP D ; compare with parameter SBC A,A ; $FF if was 9, $00 if 0-8 ; continue while addressing P-FLAG ; setting bit 4 if ink 9 ; setting bit 6 if paper 9 ; ----------------------- ; Handle change of colour ; ----------------------- ; This routine addresses a system variable ATTR_T, MASK_T or P-FLAG in HL. ; colour value in A, mask in B. ;; CO-CHANGE L226C: XOR (HL) ; impress bits specified AND B ; by mask XOR (HL) ; on system variable. LD (HL),A ; update system variable. INC HL ; address next location. LD A,B ; put current value of mask in A RET ; return. ; --- ; the branch was here with flash and bright ;; CO-TEMP-C L2273: SBC A,A ; set zero flag for bright. LD A,D ; fetch original parameter 0,1 or 8 RRCA ; rotate bit 0 to bit 7 LD B,$80 ; mask for flash 10000000 JR NZ,L227D ; forward to CO-TEMP-D if flash RRCA ; rotate bit 7 to bit 6 LD B,$40 ; mask for bright 01000000 ;; CO-TEMP-D L227D: LD C,A ; store value in C LD A,D ; fetch parameter CP $08 ; compare with 8 JR Z,L2287 ; forward to CO-TEMP-E if 8 CP $02 ; test if 0 or 1 JR NC,L2244 ; back to REPORT-K if not ; 'Invalid colour' ;; CO-TEMP-E L2287: LD A,C ; value to A LD HL,$5C8F ; address ATTR_T CALL L226C ; routine CO-CHANGE addressing ATTR_T LD A,C ; fetch value RRCA ; for flash8/bright8 complete RRCA ; rotations to put set bit in RRCA ; bit 7 (flash) bit 6 (bright) JR L226C ; back to CO-CHANGE addressing MASK_T ; and indirect return. ; --------------------- ; Handle BORDER command ; --------------------- ; Command syntax example: BORDER 7 ; This command routine sets the border to one of the eight colours. ; The colours used for the lower screen are based on this. ;; BORDER L2294: CALL L1E94 ; routine FIND-INT1 CP $08 ; must be in range 0 (black) to 7 (white) JR NC,L2244 ; back to REPORT-K if not ; 'Invalid colour'. OUT ($FE),A ; outputting to port effects an immediate ; change. RLCA ; shift the colour to RLCA ; the paper bits setting the RLCA ; ink colour black. BIT 5,A ; is the number light coloured ? ; i.e. in the range green to white. JR NZ,L22A6 ; skip to BORDER-1 if so XOR $07 ; make the ink white. ;; BORDER-1 L22A6: LD ($5C48),A ; update BORDCR with new paper/ink RET ; return. ; ----------------- ; Get pixel address ; ----------------- ; ; ;; PIXEL-ADD L22AA: LD A,$AF ; load with 175 decimal. SUB B ; subtract the y value. JP C,L24F9 ; jump forward to REPORT-Bc if greater. ; 'Integer out of range' ; the high byte is derived from Y only. ; the first 3 bits are always 010 ; the next 2 bits denote in which third of the screen the byte is. ; the last 3 bits denote in which of the 8 scan lines within a third ; the byte is located. There are 24 discrete values. LD B,A ; the line number from top of screen to B. AND A ; clear carry (already clear) RRA ; 0xxxxxxx SCF ; set carry flag RRA ; 10xxxxxx AND A ; clear carry flag RRA ; 010xxxxx XOR B ; AND $F8 ; keep the top 5 bits 11111000 XOR B ; 010xxbbb LD H,A ; transfer high byte to H. ; the low byte is derived from both X and Y. LD A,C ; the x value 0-255. RLCA ; RLCA ; RLCA ; XOR B ; the y value AND $C7 ; apply mask 11000111 XOR B ; restore unmasked bits xxyyyxxx RLCA ; rotate to xyyyxxxx RLCA ; required position. yyyxxxxx LD L,A ; low byte to L. ; finally form the pixel position in A. LD A,C ; x value to A AND $07 ; mod 8 RET ; return ; ---------------- ; Point Subroutine ; ---------------- ; The point subroutine is called from s-point via the scanning functions ; table. ;; POINT-SUB L22CB: CALL L2307 ; routine STK-TO-BC CALL L22AA ; routine PIXEL-ADD finds address of pixel. LD B,A ; pixel position to B, 0-7. INC B ; increment to give rotation count 1-8. LD A,(HL) ; fetch byte from screen. ;; POINT-LP L22D4: RLCA ; rotate and loop back DJNZ L22D4 ; to POINT-LP until pixel at right. AND $01 ; test to give zero or one. JP L2D28 ; jump forward to STACK-A to save result. ; ------------------- ; Handle PLOT command ; ------------------- ; Command Syntax example: PLOT 128,88 ; ;; PLOT L22DC: CALL L2307 ; routine STK-TO-BC CALL L22E5 ; routine PLOT-SUB JP L0D4D ; to TEMPS ; ------------------- ; The Plot subroutine ; ------------------- ; A screen byte holds 8 pixels so it is necessary to rotate a mask ; into the correct position to leave the other 7 pixels unaffected. ; However all 64 pixels in the character cell take any embedded colour ; items. ; A pixel can be reset (inverse 1), toggled (over 1), or set ( with inverse ; and over switches off). With both switches on, the byte is simply put ; back on the screen though the colours may change. ;; PLOT-SUB L22E5: LD ($5C7D),BC ; store new x/y values in COORDS CALL L22AA ; routine PIXEL-ADD gets address in HL, ; count from left 0-7 in B. LD B,A ; transfer count to B. INC B ; increase 1-8. LD A,$FE ; 11111110 in A. ;; PLOT-LOOP L22F0: RRCA ; rotate mask. DJNZ L22F0 ; to PLOT-LOOP until B circular rotations. LD B,A ; load mask to B LD A,(HL) ; fetch screen byte to A LD C,(IY+$57) ; P_FLAG to C BIT 0,C ; is it to be OVER 1 ? JR NZ,L22FD ; forward to PL-TST-IN if so. ; was over 0 AND B ; combine with mask to blank pixel. ;; PL-TST-IN L22FD: BIT 2,C ; is it inverse 1 ? JR NZ,L2303 ; to PLOT-END if so. XOR B ; switch the pixel CPL ; restore other 7 bits ;; PLOT-END L2303: LD (HL),A ; load byte to the screen. JP L0BDB ; exit to PO-ATTR to set colours for cell. ; ------------------------------ ; Put two numbers in BC register ; ------------------------------ ; ; ;; STK-TO-BC L2307: CALL L2314 ; routine STK-TO-A LD B,A ; PUSH BC ; CALL L2314 ; routine STK-TO-A LD E,C ; POP BC ; LD D,C ; LD C,A ; RET ; ; ----------------------- ; Put stack in A register ; ----------------------- ; This routine puts the last value on the calculator stack into the accumulator ; deleting the last value. ;; STK-TO-A L2314: CALL L2DD5 ; routine FP-TO-A compresses last value into ; accumulator. e.g. PI would become 3. ; zero flag set if positive. JP C,L24F9 ; jump forward to REPORT-Bc if >= 255.5. LD C,$01 ; prepare a positive sign byte. RET Z ; return if FP-TO-BC indicated positive. LD C,$FF ; prepare negative sign byte and RET ; return. ; -------------------- ; THE 'CIRCLE' COMMAND ; -------------------- ; "Goe not Thou about to Square eyther circle" - ; - John Donne, Cambridge educated theologian, 1624 ; ; The CIRCLE command draws a circle as a series of straight lines. ; In some ways it can be regarded as a polygon, but the first line is drawn ; as a tangent, taking the radius as its distance from the centre. ; ; Both the CIRCLE algorithm and the ARC drawing algorithm make use of the ; 'ROTATION FORMULA' (see later). It is only necessary to work out where ; the first line will be drawn and how long it is and then the rotation ; formula takes over and calculates all other rotated points. ; ; All Spectrum circles consist of two vertical lines at each side and two ; horizontal lines at the top and bottom. The number of lines is calculated ; from the radius of the circle and is always divisible by 4. For complete ; circles it will range from 4 for a square circle to 32 for a circle of ; radius 87. The Spectrum can attempt larger circles e.g. CIRCLE 0,14,255 ; but these will error as they go off-screen after four lines are drawn. ; At the opposite end, CIRCLE 128,88,1.23 will draw a circle as a perfect 3x3 ; square using 4 straight lines although very small circles are just drawn as ; a dot on the screen. ; ; The first chord drawn is the vertical chord on the right of the circle. ; The starting point is at the base of this chord which is drawn upwards and ; the circle continues in an anti-clockwise direction. As noted earlier the ; x-coordinate of this point measured from the centre of the circle is the ; radius. ; ; The CIRCLE command makes extensive use of the calculator and as part of ; process of drawing a large circle, free memory is checked 1315 times. ; When drawing a large arc, free memory is checked 928 times. ; A single call to 'sin' involves 63 memory checks and so values of sine ; and cosine are pre-calculated and held in the mem locations. As a ; clever trick 'cos' is derived from 'sin' using simple arithmetic operations ; instead of the more expensive 'cos' function. ; ; Initially, the syntax has been partly checked using the class for the DRAW ; command which stacks the origin of the circle (X,Y). ;; CIRCLE L2320: RST 18H ; GET-CHAR x, y. CP $2C ; Is character the required comma ? JP NZ,L1C8A ; Jump, if not, to REPORT-C ; 'Nonsense in basic' RST 20H ; NEXT-CHAR advances the parsed character address. CALL L1C82 ; routine EXPT-1NUM stacks radius in runtime. CALL L1BEE ; routine CHECK-END will return here in runtime ; if nothing follows the command. ; Now make the radius positive and ensure that it is in floating point form ; so that the exponent byte can be accessed for quick testing. RST 28H ;; FP-CALC x, y, r. DEFB $2A ;;abs x, y, r. DEFB $3D ;;re-stack x, y, r. DEFB $38 ;;end-calc x, y, r. LD A,(HL) ; Fetch first, floating-point, exponent byte. CP $81 ; Compare to one. JR NC,L233B ; Forward to C-R-GRE-1 ; if circle radius is greater than one. ; The circle is no larger than a single pixel so delete the radius from the ; calculator stack and plot a point at the centre. RST 28H ;; FP-CALC x, y, r. DEFB $02 ;;delete x, y. DEFB $38 ;;end-calc x, y. JR L22DC ; back to PLOT routine to just plot x,y. ; --- ; Continue when the circle's radius measures greater than one by forming ; the angle 2 * PI radians which is 360 degrees. ;; C-R-GRE-1 L233B: RST 28H ;; FP-CALC x, y, r DEFB $A3 ;;stk-pi/2 x, y, r, pi/2. DEFB $38 ;;end-calc x, y, r, pi/2. ; Change the exponent of pi/2 from $81 to $83 giving 2*PI the central angle. ; This is quicker than multiplying by four. LD (HL),$83 ; x, y, r, 2*PI. ; Now store this important constant in mem-5 and delete so that other ; parameters can be derived from it, by a routine shared with DRAW. RST 28H ;; FP-CALC x, y, r, 2*PI. DEFB $C5 ;;st-mem-5 store 2*PI in mem-5 DEFB $02 ;;delete x, y, r. DEFB $38 ;;end-calc x, y, r. ; The parameters derived from mem-5 (A) and from the radius are set up in ; four of the other mem locations by the CIRCLE DRAW PARAMETERS routine which ; also returns the number of straight lines in the B register. CALL L247D ; routine CD-PRMS1 ; mem-0 ; A/No of lines (=a) unused ; mem-1 ; sin(a/2) will be moving x var ; mem-2 ; - will be moving y var ; mem-3 ; cos(a) const ; mem-4 ; sin(a) const ; mem-5 ; Angle of rotation (A) (2*PI) const ; B ; Number of straight lines. PUSH BC ; Preserve the number of lines in B. ; Next calculate the length of half a chord by multiplying the sine of half ; the central angle by the radius of the circle. RST 28H ;; FP-CALC x, y, r. DEFB $31 ;;duplicate x, y, r, r. DEFB $E1 ;;get-mem-1 x, y, r, r, sin(a/2). DEFB $04 ;;multiply x, y, r, half-chord. DEFB $38 ;;end-calc x, y, r, half-chord. LD A,(HL) ; fetch exponent of the half arc to A. CP $80 ; compare to a half pixel JR NC,L235A ; forward, if greater than .5, to C-ARC-GE1 ; If the first line is less than .5 then 4 'lines' would be drawn on the same ; spot so tidy the calculator stack and machine stack and plot the centre. RST 28H ;; FP-CALC x, y, r, hc. DEFB $02 ;;delete x, y, r. DEFB $02 ;;delete x, y. DEFB $38 ;;end-calc x, y. POP BC ; Balance machine stack by taking chord-count. JP L22DC ; JUMP to PLOT ; --- ; The arc is greater than 0.5 so the circle can be drawn. ;; C-ARC-GE1 L235A: RST 28H ;; FP-CALC x, y, r, hc. DEFB $C2 ;;st-mem-2 x, y, r, half chord to mem-2. DEFB $01 ;;exchange x, y, hc, r. DEFB $C0 ;;st-mem-0 x, y, hc, r. DEFB $02 ;;delete x, y, hc. ; Subtract the length of the half-chord from the absolute y coordinate to ; give the starting y coordinate sy. ; Note that for a circle this is also the end coordinate. DEFB $03 ;;subtract x, y-hc. (The start y-coord) DEFB $01 ;;exchange sy, x. ; Next simply add the radius to the x coordinate to give a fuzzy x-coordinate. ; Strictly speaking, the radius should be multiplied by cos(a/2) first but ; doing it this way makes the circle slightly larger. DEFB $E0 ;;get-mem-0 sy, x, r. DEFB $0F ;;addition sy, x+r. (The start x-coord) ; We now want three copies of this pair of values on the calculator stack. ; The first pair remain on the stack throughout the circle routine and are ; the end points. The next pair will be the moving absolute values of x and y ; that are updated after each line is drawn. The final pair will be loaded ; into the COORDS system variable so that the first vertical line starts at ; the right place. DEFB $C0 ;;st-mem-0 sy, sx. DEFB $01 ;;exchange sx, sy. DEFB $31 ;;duplicate sx, sy, sy. DEFB $E0 ;;get-mem-0 sx, sy, sy, sx. DEFB $01 ;;exchange sx, sy, sx, sy. DEFB $31 ;;duplicate sx, sy, sx, sy, sy. DEFB $E0 ;;get-mem-0 sx, sy, sx, sy, sy, sx. ; Locations mem-1 and mem-2 are the relative x and y values which are updated ; after each line is drawn. Since we are drawing a vertical line then the rx ; value in mem-1 is zero and the ry value in mem-2 is the full chord. DEFB $A0 ;;stk-zero sx, sy, sx, sy, sy, sx, 0. DEFB $C1 ;;st-mem-1 sx, sy, sx, sy, sy, sx, 0. DEFB $02 ;;delete sx, sy, sx, sy, sy, sx. ; Although the three pairs of x/y values are the same for a circle, they ; will be labelled terminating, absolute and start coordinates. DEFB $38 ;;end-calc tx, ty, ax, ay, sy, sx. ; Use the exponent manipulating trick again to double the value of mem-2. INC (IY+$62) ; Increment MEM-2-1st doubling half chord. ; Note. this first vertical chord is drawn at the radius so circles are ; slightly displaced to the right. ; It is only necessary to place the values (sx) and (sy) in the system ; variable COORDS to ensure that drawing commences at the correct pixel. ; Note. a couple of LD (COORDS),A instructions would have been quicker, and ; simpler, than using LD (COORDS),HL. CALL L1E94 ; routine FIND-INT1 fetches sx from stack to A. LD L,A ; place X value in L. PUSH HL ; save the holding register. CALL L1E94 ; routine FIND-INT1 fetches sy to A POP HL ; restore the holding register. LD H,A ; and place y value in high byte. LD ($5C7D),HL ; Update the COORDS system variable. ; ; tx, ty, ax, ay. POP BC ; restore the chord count ; values 4,8,12,16,20,24,28 or 32. JP L2420 ; forward to DRW-STEPS ; tx, ty, ax, ay. ; Note. the jump to DRW-STEPS is just to decrement B and jump into the ; middle of the arc-drawing loop. The arc count which includes the first ; vertical arc draws one less than the perceived number of arcs. ; The final arc offsets are obtained by subtracting the final COORDS value ; from the initial sx and sy values which are kept at the base of the ; calculator stack throughout the arc loop. ; This ensures that the final line finishes exactly at the starting pixel ; removing the possibility of any inaccuracy. ; Since the initial sx and sy values are not required until the final arc ; is drawn, they are not shown until then. ; As the calculator stack is quite busy, only the active parts are shown in ; each section. ; ------------------ ; THE 'DRAW' COMMAND ; ------------------ ; The Spectrum's DRAW command is overloaded and can take two parameters sets. ; ; With two parameters, it simply draws an approximation to a straight line ; at offset x,y using the LINE-DRAW routine. ; ; With three parameters, an arc is drawn to the point at offset x,y turning ; through an angle, in radians, supplied by the third parameter. ; The arc will consist of 4 to 252 straight lines each one of which is drawn ; by calls to the DRAW-LINE routine. ;; DRAW L2382: RST 18H ; GET-CHAR CP $2C ; is it the comma character ? JR Z,L238D ; forward, if so, to DR-3-PRMS ; There are two parameters e.g. DRAW 255,175 CALL L1BEE ; routine CHECK-END JP L2477 ; jump forward to LINE-DRAW ; --- ; There are three parameters e.g. DRAW 255, 175, .5 ; The first two are relative coordinates and the third is the angle of ; rotation in radians (A). ;; DR-3-PRMS L238D: RST 20H ; NEXT-CHAR skips over the 'comma'. CALL L1C82 ; routine EXPT-1NUM stacks the rotation angle. CALL L1BEE ; routine CHECK-END ; Now enter the calculator and store the complete rotation angle in mem-5 RST 28H ;; FP-CALC x, y, A. DEFB $C5 ;;st-mem-5 x, y, A. ; Test the angle for the special case of 360 degrees. DEFB $A2 ;;stk-half x, y, A, 1/2. DEFB $04 ;;multiply x, y, A/2. DEFB $1F ;;sin x, y, sin(A/2). DEFB $31 ;;duplicate x, y, sin(A/2),sin(A/2) DEFB $30 ;;not x, y, sin(A/2), (0/1). DEFB $30 ;;not x, y, sin(A/2), (1/0). DEFB $00 ;;jump-true x, y, sin(A/2). DEFB $06 ;;forward to L23A3, DR-SIN-NZ ; if sin(r/2) is not zero. ; The third parameter is 2*PI (or a multiple of 2*PI) so a 360 degrees turn ; would just be a straight line. Eliminating this case here prevents ; division by zero at later stage. DEFB $02 ;;delete x, y. DEFB $38 ;;end-calc x, y. JP L2477 ; forward to LINE-DRAW ; --- ; An arc can be drawn. ;; DR-SIN-NZ L23A3: DEFB $C0 ;;st-mem-0 x, y, sin(A/2). store mem-0 DEFB $02 ;;delete x, y. ; The next step calculates (roughly) the diameter of the circle of which the ; arc will form part. This value does not have to be too accurate as it is ; only used to evaluate the number of straight lines and then discarded. ; After all for a circle, the radius is used. Consequently, a circle of ; radius 50 will have 24 straight lines but an arc of radius 50 will have 20 ; straight lines - when drawn in any direction. ; So that simple arithmetic can be used, the length of the chord can be ; calculated as X+Y rather than by Pythagoras Theorem and the sine of the ; nearest angle within reach is used. DEFB $C1 ;;st-mem-1 x, y. store mem-1 DEFB $02 ;;delete x. DEFB $31 ;;duplicate x, x. DEFB $2A ;;abs x, x (+ve). DEFB $E1 ;;get-mem-1 x, X, y. DEFB $01 ;;exchange x, y, X. DEFB $E1 ;;get-mem-1 x, y, X, y. DEFB $2A ;;abs x, y, X, Y (+ve). DEFB $0F ;;addition x, y, X+Y. DEFB $E0 ;;get-mem-0 x, y, X+Y, sin(A/2). DEFB $05 ;;division x, y, X+Y/sin(A/2). DEFB $2A ;;abs x, y, X+Y/sin(A/2) = D. ; Bring back sin(A/2) from mem-0 which will shortly get trashed. ; Then bring D to the top of the stack again. DEFB $E0 ;;get-mem-0 x, y, D, sin(A/2). DEFB $01 ;;exchange x, y, sin(A/2), D. ; Note. that since the value at the top of the stack has arisen as a result ; of division then it can no longer be in integer form and the next re-stack ; is unnecessary. Only the Sinclair ZX80 had integer division. DEFB $3D ;;re-stack (unnecessary) DEFB $38 ;;end-calc x, y, sin(A/2), D. ; The next test avoids drawing 4 straight lines when the start and end pixels ; are adjacent (or the same) but is probably best dispensed with. LD A,(HL) ; fetch exponent byte of D. CP $81 ; compare to 1 JR NC,L23C1 ; forward, if > 1, to DR-PRMS ; else delete the top two stack values and draw a simple straight line. RST 28H ;; FP-CALC DEFB $02 ;;delete DEFB $02 ;;delete DEFB $38 ;;end-calc x, y. JP L2477 ; to LINE-DRAW ; --- ; The ARC will consist of multiple straight lines so call the CIRCLE-DRAW ; PARAMETERS ROUTINE to pre-calculate sine values from the angle (in mem-5) ; and determine also the number of straight lines from that value and the ; 'diameter' which is at the top of the calculator stack. ;; DR-PRMS L23C1: CALL L247D ; routine CD-PRMS1 ; mem-0 ; (A)/No. of lines (=a) (step angle) ; mem-1 ; sin(a/2) ; mem-2 ; - ; mem-3 ; cos(a) const ; mem-4 ; sin(a) const ; mem-5 ; Angle of rotation (A) in ; B ; Count of straight lines - max 252. PUSH BC ; Save the line count on the machine stack. ; Remove the now redundant diameter value D. RST 28H ;; FP-CALC x, y, sin(A/2), D. DEFB $02 ;;delete x, y, sin(A/2). ; Dividing the sine of the step angle by the sine of the total angle gives ; the length of the initial chord on a unary circle. This factor f is used ; to scale the coordinates of the first line which still points in the ; direction of the end point and may be larger. DEFB $E1 ;;get-mem-1 x, y, sin(A/2), sin(a/2) DEFB $01 ;;exchange x, y, sin(a/2), sin(A/2) DEFB $05 ;;division x, y, sin(a/2)/sin(A/2) DEFB $C1 ;;st-mem-1 x, y. f. DEFB $02 ;;delete x, y. ; With the factor stored, scale the x coordinate first. DEFB $01 ;;exchange y, x. DEFB $31 ;;duplicate y, x, x. DEFB $E1 ;;get-mem-1 y, x, x, f. DEFB $04 ;;multiply y, x, x*f (=xx) DEFB $C2 ;;st-mem-2 y, x, xx. DEFB $02 ;;delete y. x. ; Now scale the y coordinate. DEFB $01 ;;exchange x, y. DEFB $31 ;;duplicate x, y, y. DEFB $E1 ;;get-mem-1 x, y, y, f DEFB $04 ;;multiply x, y, y*f (=yy) ; Note. 'sin' and 'cos' trash locations mem-0 to mem-2 so fetch mem-2 to the ; calculator stack for safe keeping. DEFB $E2 ;;get-mem-2 x, y, yy, xx. ; Once we get the coordinates of the first straight line then the 'ROTATION ; FORMULA' used in the arc loop will take care of all other points, but we ; now use a variation of that formula to rotate the first arc through (A-a)/2 ; radians. ; ; xRotated = y * sin(angle) + x * cos(angle) ; yRotated = y * cos(angle) - x * sin(angle) ; DEFB $E5 ;;get-mem-5 x, y, yy, xx, A. DEFB $E0 ;;get-mem-0 x, y, yy, xx, A, a. DEFB $03 ;;subtract x, y, yy, xx, A-a. DEFB $A2 ;;stk-half x, y, yy, xx, A-a, 1/2. DEFB $04 ;;multiply x, y, yy, xx, (A-a)/2. (=angle) DEFB $31 ;;duplicate x, y, yy, xx, angle, angle. DEFB $1F ;;sin x, y, yy, xx, angle, sin(angle) DEFB $C5 ;;st-mem-5 x, y, yy, xx, angle, sin(angle) DEFB $02 ;;delete x, y, yy, xx, angle DEFB $20 ;;cos x, y, yy, xx, cos(angle). ; Note. mem-0, mem-1 and mem-2 can be used again now... DEFB $C0 ;;st-mem-0 x, y, yy, xx, cos(angle). DEFB $02 ;;delete x, y, yy, xx. DEFB $C2 ;;st-mem-2 x, y, yy, xx. DEFB $02 ;;delete x, y, yy. DEFB $C1 ;;st-mem-1 x, y, yy. DEFB $E5 ;;get-mem-5 x, y, yy, sin(angle) DEFB $04 ;;multiply x, y, yy*sin(angle). DEFB $E0 ;;get-mem-0 x, y, yy*sin(angle), cos(angle) DEFB $E2 ;;get-mem-2 x, y, yy*sin(angle), cos(angle), xx. DEFB $04 ;;multiply x, y, yy*sin(angle), xx*cos(angle). DEFB $0F ;;addition x, y, xRotated. DEFB $E1 ;;get-mem-1 x, y, xRotated, yy. DEFB $01 ;;exchange x, y, yy, xRotated. DEFB $C1 ;;st-mem-1 x, y, yy, xRotated. DEFB $02 ;;delete x, y, yy. DEFB $E0 ;;get-mem-0 x, y, yy, cos(angle). DEFB $04 ;;multiply x, y, yy*cos(angle). DEFB $E2 ;;get-mem-2 x, y, yy*cos(angle), xx. DEFB $E5 ;;get-mem-5 x, y, yy*cos(angle), xx, sin(angle). DEFB $04 ;;multiply x, y, yy*cos(angle), xx*sin(angle). DEFB $03 ;;subtract x, y, yRotated. DEFB $C2 ;;st-mem-2 x, y, yRotated. ; Now the initial x and y coordinates are made positive and summed to see ; if they measure up to anything significant. DEFB $2A ;;abs x, y, yRotated'. DEFB $E1 ;;get-mem-1 x, y, yRotated', xRotated. DEFB $2A ;;abs x, y, yRotated', xRotated'. DEFB $0F ;;addition x, y, yRotated+xRotated. DEFB $02 ;;delete x, y. DEFB $38 ;;end-calc x, y. ; Although the test value has been deleted it is still above the calculator ; stack in memory and conveniently DE which points to the first free byte ; addresses the exponent of the test value. LD A,(DE) ; Fetch exponent of the length indicator. CP $81 ; Compare to that for 1 POP BC ; Balance the machine stack JP C,L2477 ; forward, if the coordinates of first line ; don't add up to more than 1, to LINE-DRAW ; Continue when the arc will have a discernable shape. PUSH BC ; Restore line counter to the machine stack. ; The parameters of the DRAW command were relative and they are now converted ; to absolute coordinates by adding to the coordinates of the last point ; plotted. The first two values on the stack are the terminal tx and ty ; coordinates. The x-coordinate is converted first but first the last point ; plotted is saved as it will initialize the moving ax, value. RST 28H ;; FP-CALC x, y. DEFB $01 ;;exchange y, x. DEFB $38 ;;end-calc y, x. LD A,($5C7D) ; Fetch System Variable COORDS-x CALL L2D28 ; routine STACK-A RST 28H ;; FP-CALC y, x, last-x. ; Store the last point plotted to initialize the moving ax value. DEFB $C0 ;;st-mem-0 y, x, last-x. DEFB $0F ;;addition y, absolute x. DEFB $01 ;;exchange tx, y. DEFB $38 ;;end-calc tx, y. LD A,($5C7E) ; Fetch System Variable COORDS-y CALL L2D28 ; routine STACK-A RST 28H ;; FP-CALC tx, y, last-y. ; Store the last point plotted to initialize the moving ay value. DEFB $C5 ;;st-mem-5 tx, y, last-y. DEFB $0F ;;addition tx, ty. ; Fetch the moving ax and ay to the calculator stack. DEFB $E0 ;;get-mem-0 tx, ty, ax. DEFB $E5 ;;get-mem-5 tx, ty, ax, ay. DEFB $38 ;;end-calc tx, ty, ax, ay. POP BC ; Restore the straight line count. ; ----------------------------------- ; THE 'CIRCLE/DRAW CONVERGENCE POINT' ; ----------------------------------- ; The CIRCLE and ARC-DRAW commands converge here. ; ; Note. for both the CIRCLE and ARC commands the minimum initial line count ; is 4 (as set up by the CD_PARAMS routine) and so the zero flag will never ; be set and the loop is always entered. The first test is superfluous and ; the jump will always be made to ARC-START. ;; DRW-STEPS L2420: DEC B ; decrement the arc count (4,8,12,16...). JR Z,L245F ; forward, if zero (not possible), to ARC-END JR L2439 ; forward to ARC-START ; -------------- ; THE 'ARC LOOP' ; -------------- ; ; The arc drawing loop will draw up to 31 straight lines for a circle and up ; 251 straight lines for an arc between two points. In both cases the final ; closing straight line is drawn at ARC_END, but it otherwise loops back to ; here to calculate the next coordinate using the ROTATION FORMULA where (a) ; is the previously calculated, constant CENTRAL ANGLE of the arcs. ; ; Xrotated = x * cos(a) - y * sin(a) ; Yrotated = x * sin(a) + y * cos(a) ; ; The values cos(a) and sin(a) are pre-calculated and held in mem-3 and mem-4 ; for the duration of the routine. ; Memory location mem-1 holds the last relative x value (rx) and mem-2 holds ; the last relative y value (ry) used by DRAW. ; ; Note. that this is a very clever twist on what is after all a very clever, ; well-used formula. Normally the rotation formula is used with the x and y ; coordinates from the centre of the circle (or arc) and a supplied angle to ; produce two new x and y coordinates in an anticlockwise direction on the ; circumference of the circle. ; What is being used here, instead, is the relative X and Y parameters from ; the last point plotted that are required to get to the current point and ; the formula returns the next relative coordinates to use. ;; ARC-LOOP L2425: RST 28H ;; FP-CALC DEFB $E1 ;;get-mem-1 rx. DEFB $31 ;;duplicate rx, rx. DEFB $E3 ;;get-mem-3 cos(a) DEFB $04 ;;multiply rx, rx*cos(a). DEFB $E2 ;;get-mem-2 rx, rx*cos(a), ry. DEFB $E4 ;;get-mem-4 rx, rx*cos(a), ry, sin(a). DEFB $04 ;;multiply rx, rx*cos(a), ry*sin(a). DEFB $03 ;;subtract rx, rx*cos(a) - ry*sin(a) DEFB $C1 ;;st-mem-1 rx, new relative x rotated. DEFB $02 ;;delete rx. DEFB $E4 ;;get-mem-4 rx, sin(a). DEFB $04 ;;multiply rx*sin(a) DEFB $E2 ;;get-mem-2 rx*sin(a), ry. DEFB $E3 ;;get-mem-3 rx*sin(a), ry, cos(a). DEFB $04 ;;multiply rx*sin(a), ry*cos(a). DEFB $0F ;;addition rx*sin(a) + ry*cos(a). DEFB $C2 ;;st-mem-2 new relative y rotated. DEFB $02 ;;delete . DEFB $38 ;;end-calc . ; Note. the calculator stack actually holds tx, ty, ax, ay ; and the last absolute values of x and y ; are now brought into play. ; ; Magically, the two new rotated coordinates rx and ry are all that we would ; require to draw a circle or arc - on paper! ; The Spectrum DRAW routine draws to the rounded x and y coordinate and so ; repetitions of values like 3.49 would mean that the fractional parts ; would be lost until eventually the draw coordinates might differ from the ; floating point values used above by several pixels. ; For this reason the accurate offsets calculated above are added to the ; accurate, absolute coordinates maintained in ax and ay and these new ; coordinates have the integer coordinates of the last plot position ; ( from System Variable COORDS ) subtracted from them to give the relative ; coordinates required by the DRAW routine. ; The mid entry point. ;; ARC-START L2439: PUSH BC ; Preserve the arc counter on the machine stack. ; Store the absolute ay in temporary variable mem-0 for the moment. RST 28H ;; FP-CALC ax, ay. DEFB $C0 ;;st-mem-0 ax, ay. DEFB $02 ;;delete ax. ; Now add the fractional relative x coordinate to the fractional absolute ; x coordinate to obtain a new fractional x-coordinate. DEFB $E1 ;;get-mem-1 ax, xr. DEFB $0F ;;addition ax+xr (= new ax). DEFB $31 ;;duplicate ax, ax. DEFB $38 ;;end-calc ax, ax. LD A,($5C7D) ; COORDS-x last x (integer ix 0-255) CALL L2D28 ; routine STACK-A RST 28H ;; FP-CALC ax, ax, ix. DEFB $03 ;;subtract ax, ax-ix = relative DRAW Dx. ; Having calculated the x value for DRAW do the same for the y value. DEFB $E0 ;;get-mem-0 ax, Dx, ay. DEFB $E2 ;;get-mem-2 ax, Dx, ay, ry. DEFB $0F ;;addition ax, Dx, ay+ry (= new ay). DEFB $C0 ;;st-mem-0 ax, Dx, ay. DEFB $01 ;;exchange ax, ay, Dx, DEFB $E0 ;;get-mem-0 ax, ay, Dx, ay. DEFB $38 ;;end-calc ax, ay, Dx, ay. LD A,($5C7E) ; COORDS-y last y (integer iy 0-175) CALL L2D28 ; routine STACK-A RST 28H ;; FP-CALC ax, ay, Dx, ay, iy. DEFB $03 ;;subtract ax, ay, Dx, ay-iy ( = Dy). DEFB $38 ;;end-calc ax, ay, Dx, Dy. CALL L24B7 ; Routine DRAW-LINE draws (Dx,Dy) relative to ; the last pixel plotted leaving absolute x ; and y on the calculator stack. ; ax, ay. POP BC ; Restore the arc counter from the machine stack. DJNZ L2425 ; Decrement and loop while > 0 to ARC-LOOP ; ------------- ; THE 'ARC END' ; ------------- ; To recap the full calculator stack is tx, ty, ax, ay. ; Just as one would do if drawing the curve on paper, the final line would ; be drawn by joining the last point plotted to the initial start point ; in the case of a CIRCLE or to the calculated end point in the case of ; an ARC. ; The moving absolute values of x and y are no longer required and they ; can be deleted to expose the closing coordinates. ;; ARC-END L245F: RST 28H ;; FP-CALC tx, ty, ax, ay. DEFB $02 ;;delete tx, ty, ax. DEFB $02 ;;delete tx, ty. DEFB $01 ;;exchange ty, tx. DEFB $38 ;;end-calc ty, tx. ; First calculate the relative x coordinate to the end-point. LD A,($5C7D) ; COORDS-x CALL L2D28 ; routine STACK-A RST 28H ;; FP-CALC ty, tx, coords_x. DEFB $03 ;;subtract ty, rx. ; Next calculate the relative y coordinate to the end-point. DEFB $01 ;;exchange rx, ty. DEFB $38 ;;end-calc rx, ty. LD A,($5C7E) ; COORDS-y CALL L2D28 ; routine STACK-A RST 28H ;; FP-CALC rx, ty, coords_y DEFB $03 ;;subtract rx, ry. DEFB $38 ;;end-calc rx, ry. ; Finally draw the last straight line. ;; LINE-DRAW L2477: CALL L24B7 ; routine DRAW-LINE draws to the relative ; coordinates (rx, ry). JP L0D4D ; jump back and exit via TEMPS >>> ; -------------------------------------------- ; THE 'INITIAL CIRCLE/DRAW PARAMETERS' ROUTINE ; -------------------------------------------- ; Begin by calculating the number of chords which will be returned in B. ; A rule of thumb is employed that uses a value z which for a circle is the ; radius and for an arc is the diameter with, as it happens, a pinch more if ; the arc is on a slope. ; ; NUMBER OF STRAIGHT LINES = ANGLE OF ROTATION * SQUARE ROOT ( Z ) / 2 ;; CD-PRMS1 L247D: RST 28H ;; FP-CALC z. DEFB $31 ;;duplicate z, z. DEFB $28 ;;sqr z, sqr(z). DEFB $34 ;;stk-data z, sqr(z), 2. DEFB $32 ;;Exponent: $82, Bytes: 1 DEFB $00 ;;(+00,+00,+00) DEFB $01 ;;exchange z, 2, sqr(z). DEFB $05 ;;division z, 2/sqr(z). DEFB $E5 ;;get-mem-5 z, 2/sqr(z), ANGLE. DEFB $01 ;;exchange z, ANGLE, 2/sqr (z) DEFB $05 ;;division z, ANGLE*sqr(z)/2 (= No. of lines) DEFB $2A ;;abs (for arc only) DEFB $38 ;;end-calc z, number of lines. ; As an example for a circle of radius 87 the number of lines will be 29. CALL L2DD5 ; routine FP-TO-A ; The value is compressed into A register, no carry with valid circle. JR C,L2495 ; forward, if over 256, to USE-252 ; now make a multiple of 4 e.g. 29 becomes 28 AND $FC ; AND 252 ; Adding 4 could set carry for arc, for the circle example, 28 becomes 32. ADD A,$04 ; adding 4 could set carry if result is 256. JR NC,L2497 ; forward if less than 256 to DRAW-SAVE ; For an arc, a limit of 252 is imposed. ;; USE-252 L2495: LD A,$FC ; Use a value of 252 (for arc). ; For both arcs and circles, constants derived from the central angle are ; stored in the 'mem' locations. Some are not relevant for the circle. ;; DRAW-SAVE L2497: PUSH AF ; Save the line count (A) on the machine stack. CALL L2D28 ; Routine STACK-A stacks the modified count(A). RST 28H ;; FP-CALC z, A. DEFB $E5 ;;get-mem-5 z, A, ANGLE. DEFB $01 ;;exchange z, ANGLE, A. DEFB $05 ;;division z, ANGLE/A. (Angle/count = a) DEFB $31 ;;duplicate z, a, a. ; Note. that cos (a) could be formed here directly using 'cos' and stored in ; mem-3 but that would spoil a good story and be slightly slower, as also ; would using square roots to form cos (a) from sin (a). DEFB $1F ;;sin z, a, sin(a) DEFB $C4 ;;st-mem-4 z, a, sin(a) DEFB $02 ;;delete z, a. DEFB $31 ;;duplicate z, a, a. DEFB $A2 ;;stk-half z, a, a, 1/2. DEFB $04 ;;multiply z, a, a/2. DEFB $1F ;;sin z, a, sin(a/2). ; Note. after second sin, mem-0 and mem-1 become free. DEFB $C1 ;;st-mem-1 z, a, sin(a/2). DEFB $01 ;;exchange z, sin(a/2), a. DEFB $C0 ;;st-mem-0 z, sin(a/2), a. (for arc only) ; Now form cos(a) from sin(a/2) using the 'DOUBLE ANGLE FORMULA'. DEFB $02 ;;delete z, sin(a/2). DEFB $31 ;;duplicate z, sin(a/2), sin(a/2). DEFB $04 ;;multiply z, sin(a/2)*sin(a/2). DEFB $31 ;;duplicate z, sin(a/2)*sin(a/2), ;; sin(a/2)*sin(a/2). DEFB $0F ;;addition z, 2*sin(a/2)*sin(a/2). DEFB $A1 ;;stk-one z, 2*sin(a/2)*sin(a/2), 1. DEFB $03 ;;subtract z, 2*sin(a/2)*sin(a/2)-1. DEFB $1B ;;negate z, 1-2*sin(a/2)*sin(a/2). DEFB $C3 ;;st-mem-3 z, cos(a). DEFB $02 ;;delete z. DEFB $38 ;;end-calc z. ; The radius/diameter is left on the calculator stack. POP BC ; Restore the line count to the B register. RET ; Return. ; -------------------------- ; THE 'DOUBLE ANGLE FORMULA' ; -------------------------- ; This formula forms cos(a) from sin(a/2) using simple arithmetic. ; ; THE GEOMETRIC PROOF OF FORMULA cos (a) = 1 - 2 * sin(a/2) * sin(a/2) ; ; ; A ; ; . /|\ ; . / | \ ; . / | \ ; . / |a/2\ ; . / | \ ; . 1 / | \ ; . / | \ ; . / | \ ; . / | \ ; . a/2 D / a E|-+ \ ; B ---------------------/----------+-+--------\ C ; <- 1 -><- 1 -> ; ; cos a = 1 - 2 * sin(a/2) * sin(a/2) ; ; The figure shows a right triangle that inscribes a circle of radius 1 with ; centre, or origin, D. Line BC is the diameter of length 2 and A is a point ; on the circle. The periphery angle BAC is therefore a right angle by the ; Rule of Thales. ; Line AC is a chord touching two points on the circle and the angle at the ; centre is (a). ; Since the vertex of the largest triangle B touches the circle, the ; inscribed angle (a/2) is half the central angle (a). ; The cosine of (a) is the length DE as the hypotenuse is of length 1. ; This can also be expressed as 1-length CE. Examining the triangle at the ; right, the top angle is also (a/2) as angle BAE and EBA add to give a right ; angle as do BAE and EAC. ; So cos (a) = 1 - AC * sin(a/2) ; Looking at the largest triangle, side AC can be expressed as ; AC = 2 * sin(a/2) and so combining these we get ; cos (a) = 1 - 2 * sin(a/2) * sin(a/2). ; ; "I will be sufficiently rewarded if when telling it to others, you will ; not claim the discovery as your own, but will say it is mine." ; - Thales, 640 - 546 B.C. ; ; -------------------------- ; THE 'LINE DRAWING' ROUTINE ; -------------------------- ; ; ;; DRAW-LINE L24B7: CALL L2307 ; routine STK-TO-BC LD A,C ; CP B ; JR NC,L24C4 ; to DL-X-GE-Y LD L,C ; PUSH DE ; XOR A ; LD E,A ; JR L24CB ; to DL-LARGER ; --- ;; DL-X-GE-Y L24C4: OR C ; RET Z ; LD L,B ; LD B,C ; PUSH DE ; LD D,$00 ; ;; DL-LARGER L24CB: LD H,B ; LD A,B ; RRA ; ;; D-L-LOOP L24CE: ADD A,L ; JR C,L24D4 ; to D-L-DIAG CP H ; JR C,L24DB ; to D-L-HR-VT ;; D-L-DIAG L24D4: SUB H ; LD C,A ; EXX ; POP BC ; PUSH BC ; JR L24DF ; to D-L-STEP ; --- ;; D-L-HR-VT L24DB: LD C,A ; PUSH DE ; EXX ; POP BC ; ;; D-L-STEP L24DF: LD HL,($5C7D) ; COORDS LD A,B ; ADD A,H ; LD B,A ; LD A,C ; INC A ; ADD A,L ; JR C,L24F7 ; to D-L-RANGE JR Z,L24F9 ; to REPORT-Bc ;; D-L-PLOT L24EC: DEC A ; LD C,A ; CALL L22E5 ; routine PLOT-SUB EXX ; LD A,C ; DJNZ L24CE ; to D-L-LOOP POP DE ; RET ; ; --- ;; D-L-RANGE L24F7: JR Z,L24EC ; to D-L-PLOT ;; REPORT-Bc L24F9: RST 08H ; ERROR-1 DEFB $0A ; Error Report: Integer out of range ;*********************************** ;** Part 8. EXPRESSION EVALUATION ** ;*********************************** ; ; It is a this stage of the ROM that the Spectrum ceases altogether to be ; just a colourful novelty. One remarkable feature is that in all previous ; commands when the Spectrum is expecting a number or a string then an ; expression of the same type can be substituted ad infinitum. ; This is the routine that evaluates that expression. ; This is what causes 2 + 2 to give the answer 4. ; That is quite easy to understand. However you don't have to make it much ; more complex to start a remarkable juggling act. ; e.g. PRINT 2 * (VAL "2+2" + TAN 3) ; In fact, provided there is enough free RAM, the Spectrum can evaluate ; an expression of unlimited complexity. ; Apart from a couple of minor glitches, which you can now correct, the ; system is remarkably robust. ; --------------------------------- ; Scan expression or sub-expression ; --------------------------------- ; ; ;; SCANNING L24FB: RST 18H ; GET-CHAR LD B,$00 ; priority marker zero is pushed on stack ; to signify end of expression when it is ; popped off again. PUSH BC ; put in on stack. ; and proceed to consider the first character ; of the expression. ;; S-LOOP-1 L24FF: LD C,A ; store the character while a look up is done. LD HL,L2596 ; Address: scan-func CALL L16DC ; routine INDEXER is called to see if it is ; part of a limited range '+', '(', 'ATTR' etc. LD A,C ; fetch the character back JP NC,L2684 ; jump forward to S-ALPHNUM if not in primary ; operators and functions to consider in the ; first instance a digit or a variable and ; then anything else. >>> LD B,$00 ; but here if it was found in table so LD C,(HL) ; fetch offset from table and make B zero. ADD HL,BC ; add the offset to position found JP (HL) ; and jump to the routine e.g. S-BIN ; making an indirect exit from there. ; ------------------------------------------------------------------------- ; The four service subroutines for routines in the scanning function table ; ------------------------------------------------------------------------- ; PRINT """Hooray!"" he cried." ;; S-QUOTE-S L250F: CALL L0074 ; routine CH-ADD+1 points to next character ; and fetches that character. INC BC ; increase length counter. CP $0D ; is it carriage return ? ; inside a quote. JP Z,L1C8A ; jump back to REPORT-C if so. ; 'Nonsense in BASIC'. CP $22 ; is it a quote '"' ? JR NZ,L250F ; back to S-QUOTE-S if not for more. CALL L0074 ; routine CH-ADD+1 CP $22 ; compare with possible adjacent quote RET ; return. with zero set if two together. ; --- ; This subroutine is used to get two coordinate expressions for the three ; functions SCREEN$, ATTR and POINT that have two fixed parameters and ; therefore require surrounding braces. ;; S-2-COORD L2522: RST 20H ; NEXT-CHAR CP $28 ; is it the opening '(' ? JR NZ,L252D ; forward to S-RPORT-C if not ; 'Nonsense in BASIC'. CALL L1C79 ; routine NEXT-2NUM gets two comma-separated ; numeric expressions. Note. this could cause ; many more recursive calls to SCANNING but ; the parent function will be evaluated fully ; before rejoining the main juggling act. RST 18H ; GET-CHAR CP $29 ; is it the closing ')' ? ;; S-RPORT-C L252D: JP NZ,L1C8A ; jump back to REPORT-C if not. ; 'Nonsense in BASIC'. ; ------------ ; Check syntax ; ------------ ; This routine is called on a number of occasions to check if syntax is being ; checked or if the program is being run. To test the flag inline would use ; four bytes of code, but a call instruction only uses 3 bytes of code. ;; SYNTAX-Z L2530: BIT 7,(IY+$01) ; test FLAGS - checking syntax only ? RET ; return. ; ---------------- ; Scanning SCREEN$ ; ---------------- ; This function returns the code of a bit-mapped character at screen ; position at line C, column B. It is unable to detect the mosaic characters ; which are not bit-mapped but detects the ASCII 32 - 127 range. ; The bit-mapped UDGs are ignored which is curious as it requires only a ; few extra bytes of code. As usual, anything to do with CHARS is weird. ; If no match is found a null string is returned. ; No actual check on ranges is performed - that's up to the BASIC programmer. ; No real harm can come from SCREEN$(255,255) although the BASIC manual ; says that invalid values will be trapped. ; Interestingly, in the Pitman pocket guide, 1984, Vickers says that the ; range checking will be performed. ;; S-SCRN$-S L2535: CALL L2307 ; routine STK-TO-BC. LD HL,($5C36) ; fetch address of CHARS. LD DE,$0100 ; fetch offset to chr$ 32 ADD HL,DE ; and find start of bitmaps. ; Note. not inc h. ?? LD A,C ; transfer line to A. RRCA ; multiply RRCA ; by RRCA ; thirty-two. AND $E0 ; and with 11100000 XOR B ; combine with column $00 - $1F LD E,A ; to give the low byte of top line LD A,C ; column to A range 00000000 to 00011111 AND $18 ; and with 00011000 XOR $40 ; xor with 01000000 (high byte screen start) LD D,A ; register DE now holds start address of cell. LD B,$60 ; there are 96 characters in ASCII set. ;; S-SCRN-LP L254F: PUSH BC ; save count PUSH DE ; save screen start address PUSH HL ; save bitmap start LD A,(DE) ; first byte of screen to A XOR (HL) ; xor with corresponding character byte JR Z,L255A ; forward to S-SC-MTCH if they match ; if inverse result would be $FF ; if any other then mismatch INC A ; set to $00 if inverse JR NZ,L2573 ; forward to S-SCR-NXT if a mismatch DEC A ; restore $FF ; a match has been found so seven more to test. ;; S-SC-MTCH L255A: LD C,A ; load C with inverse mask $00 or $FF LD B,$07 ; count seven more bytes ;; S-SC-ROWS L255D: INC D ; increment screen address. INC HL ; increment bitmap address. LD A,(DE) ; byte to A XOR (HL) ; will give $00 or $FF (inverse) XOR C ; xor with inverse mask JR NZ,L2573 ; forward to S-SCR-NXT if no match. DJNZ L255D ; back to S-SC-ROWS until all eight matched. ; continue if a match of all eight bytes was found POP BC ; discard the POP BC ; saved POP BC ; pointers LD A,$80 ; the endpoint of character set SUB B ; subtract the counter ; to give the code 32-127 LD BC,$0001 ; make one space in workspace. RST 30H ; BC-SPACES creates the space sliding ; the calculator stack upwards. LD (DE),A ; start is addressed by DE, so insert code JR L257D ; forward to S-SCR-STO ; --- ; the jump was here if no match and more bitmaps to test. ;; S-SCR-NXT L2573: POP HL ; restore the last bitmap start LD DE,$0008 ; and prepare to add 8. ADD HL,DE ; now addresses next character bitmap. POP DE ; restore screen address POP BC ; and character counter in B DJNZ L254F ; back to S-SCRN-LP if more characters. LD C,B ; B is now zero, so BC now zero. ;; S-SCR-STO L257D: JP L2AB2 ; to STK-STO-$ to store the string in ; workspace or a string with zero length. ; (value of DE doesn't matter in last case) ; Note. this exit seems correct but the general-purpose routine S-STRING ; that calls this one will also stack any of its string results so this ; leads to a double storing of the result in this case. ; The instruction at L257D should just be a RET. ; credit Stephen Kelly and others, 1982. ; ------------- ; Scanning ATTR ; ------------- ; This function subroutine returns the attributes of a screen location - ; a numeric result. ; Again it's up to the BASIC programmer to supply valid values of line/column. ;; S-ATTR-S L2580: CALL L2307 ; routine STK-TO-BC fetches line to C, ; and column to B. LD A,C ; line to A $00 - $17 (max 00010111) RRCA ; rotate RRCA ; bits RRCA ; left. LD C,A ; store in C as an intermediate value. AND $E0 ; pick up bits 11100000 ( was 00011100 ) XOR B ; combine with column $00 - $1F LD L,A ; low byte now correct. LD A,C ; bring back intermediate result from C AND $03 ; mask to give correct third of ; screen $00 - $02 XOR $58 ; combine with base address. LD H,A ; high byte correct. LD A,(HL) ; pick up the colour attribute. JP L2D28 ; forward to STACK-A to store result ; and make an indirect exit. ; ----------------------- ; Scanning function table ; ----------------------- ; This table is used by INDEXER routine to find the offsets to ; four operators and eight functions. e.g. $A8 is the token 'FN'. ; This table is used in the first instance for the first character of an ; expression or by a recursive call to SCANNING for the first character of ; any sub-expression. It eliminates functions that have no argument or ; functions that can have more than one argument and therefore require ; braces. By eliminating and dealing with these now it can later take a ; simplistic approach to all other functions and assume that they have ; one argument. ; Similarly by eliminating BIN and '.' now it is later able to assume that ; all numbers begin with a digit and that the presence of a number or ; variable can be detected by a call to ALPHANUM. ; By default all expressions are positive and the spurious '+' is eliminated ; now as in print +2. This should not be confused with the operator '+'. ; Note. this does allow a degree of nonsense to be accepted as in ; PRINT +"3 is the greatest.". ; An acquired programming skill is the ability to include brackets where ; they are not necessary. ; A bracket at the start of a sub-expression may be spurious or necessary ; to denote that the contained expression is to be evaluated as an entity. ; In either case this is dealt with by recursive calls to SCANNING. ; An expression that begins with a quote requires special treatment. ;; scan-func L2596: DEFB $22, L25B3-$-1 ; $1C offset to S-QUOTE DEFB '(', L25E8-$-1 ; $4F offset to S-BRACKET DEFB '.', L268D-$-1 ; $F2 offset to S-DECIMAL DEFB '+', L25AF-$-1 ; $12 offset to S-U-PLUS DEFB $A8, L25F5-$-1 ; $56 offset to S-FN DEFB $A5, L25F8-$-1 ; $57 offset to S-RND DEFB $A7, L2627-$-1 ; $84 offset to S-PI DEFB $A6, L2634-$-1 ; $8F offset to S-INKEY$ DEFB $C4, L268D-$-1 ; $E6 offset to S-BIN DEFB $AA, L2668-$-1 ; $BF offset to S-SCREEN$ DEFB $AB, L2672-$-1 ; $C7 offset to S-ATTR DEFB $A9, L267B-$-1 ; $CE offset to S-POINT DEFB $00 ; zero end marker ; -------------------------- ; Scanning function routines ; -------------------------- ; These are the 11 subroutines accessed by the above table. ; S-BIN and S-DECIMAL are the same ; The 1-byte offset limits their location to within 255 bytes of their ; entry in the table. ; -> ;; S-U-PLUS L25AF: RST 20H ; NEXT-CHAR just ignore JP L24FF ; to S-LOOP-1 ; --- ; -> ;; S-QUOTE L25B3: RST 18H ; GET-CHAR INC HL ; address next character (first in quotes) PUSH HL ; save start of quoted text. LD BC,$0000 ; initialize length of string to zero. CALL L250F ; routine S-QUOTE-S JR NZ,L25D9 ; forward to S-Q-PRMS if ;; S-Q-AGAIN L25BE: CALL L250F ; routine S-QUOTE-S copies string until a ; quote is encountered JR Z,L25BE ; back to S-Q-AGAIN if two quotes WERE ; together. ; but if just an isolated quote then that terminates the string. CALL L2530 ; routine SYNTAX-Z JR Z,L25D9 ; forward to S-Q-PRMS if checking syntax. RST 30H ; BC-SPACES creates the space for true ; copy of string in workspace. POP HL ; re-fetch start of quoted text. PUSH DE ; save start in workspace. ;; S-Q-COPY L25CB: LD A,(HL) ; fetch a character from source. INC HL ; advance source address. LD (DE),A ; place in destination. INC DE ; advance destination address. CP $22 ; was it a '"' just copied ? JR NZ,L25CB ; back to S-Q-COPY to copy more if not LD A,(HL) ; fetch adjacent character from source. INC HL ; advance source address. CP $22 ; is this '"' ? - i.e. two quotes together ? JR Z,L25CB ; to S-Q-COPY if so including just one of the ; pair of quotes. ; proceed when terminating quote encountered. ;; S-Q-PRMS L25D9: DEC BC ; decrease count by 1. POP DE ; restore start of string in workspace. ;; S-STRING L25DB: LD HL,$5C3B ; Address FLAGS system variable. RES 6,(HL) ; signal string result. BIT 7,(HL) ; is syntax being checked. CALL NZ,L2AB2 ; routine STK-STO-$ is called in runtime. JP L2712 ; jump forward to S-CONT-2 ===> ; --- ; -> ;; S-BRACKET L25E8: RST 20H ; NEXT-CHAR CALL L24FB ; routine SCANNING is called recursively. CP $29 ; is it the closing ')' ? JP NZ,L1C8A ; jump back to REPORT-C if not ; 'Nonsense in BASIC' RST 20H ; NEXT-CHAR JP L2712 ; jump forward to S-CONT-2 ===> ; --- ; -> ;; S-FN L25F5: JP L27BD ; jump forward to S-FN-SBRN. ; -------------------------------------------------------------------- ; ; RANDOM THEORY from the ZX81 manual by Steven Vickers ; ; (same algorithm as the ZX Spectrum). ; ; Chapter 5. Exercise 6. (For mathematicians only.) ; ; Let p be a [large] prime, & let a be a primitive root modulo p. ; Then if b_i is the residue of a^i modulo p (1<=b_i<p-1), the ; sequence ; ; (b_i-1)/(p-1) ; ; is a cyclical sequence of p-1 distinct numbers in the range 0 to 1 ; (excluding 1). By choosing a suitably, these can be made to look ; fairly random. ; ; 65537 is a Mersenne prime 2^16-1. Note. ; ; Use this, & Gauss' law of quadratic reciprocity, to show that 75 ; is a primitive root modulo 65537. ; ; The ZX81 uses p=65537 & a=75, & stores some b_i-1 in memory. ; The function RND involves replacing b_i-1 in memory by b_(i+1)-1, ; & yielding the result (b_(i+1)-1)/(p-1). RAND n (with 1<=n<=65535) ; makes b_i equal to n+1. ; ; -------------------------------------------------------------------- ; ; Steven Vickers writing in comp.sys.sinclair on 20-DEC-1993 ; ; Note. (Of course, 65537 is 2^16 + 1, not -1.) ; ; Consider arithmetic modulo a prime p. There are p residue classes, and the ; non-zero ones are all invertible. Hence under multiplication they form a ; group (Fp*, say) of order p-1; moreover (and not so obvious) Fp* is cyclic. ; Its generators are the "primitive roots". The "quadratic residues modulo p" ; are the squares in Fp*, and the "Legendre symbol" (d/p) is defined (when p ; does not divide d) as +1 or -1, according as d is or is not a quadratic ; residue mod p. ; ; In the case when p = 65537, we can show that d is a primitive root if and ; only if it's not a quadratic residue. For let w be a primitive root, d ; congruent to w^r (mod p). If d is not primitive, then its order is a proper ; factor of 65536: hence w^{32768*r} = 1 (mod p), so 65536 divides 32768*r, ; and hence r is even and d is a square (mod p). Conversely, the squares in ; Fp* form a subgroup of (Fp*)^2 of index 2, and so cannot be generators. ; ; Hence to check whether 75 is primitive mod 65537, we want to calculate that ; (75/65537) = -1. There is a multiplicative formula (ab/p) = (a/p)(b/p) (mod ; p), so (75/65537) = (5/65537)^2 * (3/65537) = (3/65537). Now the law of ; quadratic reciprocity says that if p and q are distinct odd primes, then ; ; (p/q)(q/p) = (-1)^{(p-1)(q-1)/4} ; ; Hence (3/65537) = (65537/3) * (-1)^{65536*2/4} = (65537/3) ; = (2/3) (because 65537 = 2 mod 3) ; = -1 ; ; (I referred to Pierre Samuel's "Algebraic Theory of Numbers".) ; ; -> ;; S-RND L25F8: CALL L2530 ; routine SYNTAX-Z JR Z,L2625 ; forward to S-RND-END if checking syntax. LD BC,($5C76) ; fetch system variable SEED CALL L2D2B ; routine STACK-BC places on calculator stack RST 28H ;; FP-CALC ;s. DEFB $A1 ;;stk-one ;s,1. DEFB $0F ;;addition ;s+1. DEFB $34 ;;stk-data ; DEFB $37 ;;Exponent: $87, ;;Bytes: 1 DEFB $16 ;;(+00,+00,+00) ;s+1,75. DEFB $04 ;;multiply ;(s+1)*75 = v DEFB $34 ;;stk-data ;v. DEFB $80 ;;Bytes: 3 DEFB $41 ;;Exponent $91 DEFB $00,$00,$80 ;;(+00) ;v,65537. DEFB $32 ;;n-mod-m ;remainder, result. DEFB $02 ;;delete ;remainder. DEFB $A1 ;;stk-one ;remainder, 1. DEFB $03 ;;subtract ;remainder - 1. = rnd DEFB $31 ;;duplicate ;rnd,rnd. DEFB $38 ;;end-calc CALL L2DA2 ; routine FP-TO-BC LD ($5C76),BC ; store in SEED for next starting point. LD A,(HL) ; fetch exponent AND A ; is it zero ? JR Z,L2625 ; forward if so to S-RND-END SUB $10 ; reduce exponent by 2^16 LD (HL),A ; place back ;; S-RND-END L2625: JR L2630 ; forward to S-PI-END ; --- ; the number PI 3.14159... ; -> ;; S-PI L2627: CALL L2530 ; routine SYNTAX-Z JR Z,L2630 ; to S-PI-END if checking syntax. RST 28H ;; FP-CALC DEFB $A3 ;;stk-pi/2 pi/2. DEFB $38 ;;end-calc INC (HL) ; increment the exponent leaving pi ; on the calculator stack. ;; S-PI-END L2630: RST 20H ; NEXT-CHAR JP L26C3 ; jump forward to S-NUMERIC ; --- ; -> ;; S-INKEY$ L2634: LD BC,$105A ; priority $10, operation code $1A ('read-in') ; +$40 for string result, numeric operand. ; set this up now in case we need to use the ; calculator. RST 20H ; NEXT-CHAR CP $23 ; '#' ? JP Z,L270D ; to S-PUSH-PO if so to use the calculator ; single operation ; to read from network/RS232 etc. . ; else read a key from the keyboard. LD HL,$5C3B ; fetch FLAGS RES 6,(HL) ; signal string result. BIT 7,(HL) ; checking syntax ? JR Z,L2665 ; forward to S-INK$-EN if so CALL L028E ; routine KEY-SCAN key in E, shift in D. LD C,$00 ; the length of an empty string JR NZ,L2660 ; to S-IK$-STK to store empty string if ; no key returned. CALL L031E ; routine K-TEST get main code in A JR NC,L2660 ; to S-IK$-STK to stack null string if ; invalid DEC D ; D is expected to be FLAGS so set bit 3 $FF ; 'L' Mode so no keywords. LD E,A ; main key to A ; C is MODE 0 'KLC' from above still. CALL L0333 ; routine K-DECODE PUSH AF ; save the code LD BC,$0001 ; make room for one character RST 30H ; BC-SPACES POP AF ; bring the code back LD (DE),A ; put the key in workspace LD C,$01 ; set C length to one ;; S-IK$-STK L2660: LD B,$00 ; set high byte of length to zero CALL L2AB2 ; routine STK-STO-$ ;; S-INK$-EN L2665: JP L2712 ; to S-CONT-2 ===> ; --- ; -> ;; S-SCREEN$ L2668: CALL L2522 ; routine S-2-COORD CALL NZ,L2535 ; routine S-SCRN$-S RST 20H ; NEXT-CHAR JP L25DB ; forward to S-STRING to stack result ; --- ; -> ;; S-ATTR L2672: CALL L2522 ; routine S-2-COORD CALL NZ,L2580 ; routine S-ATTR-S RST 20H ; NEXT-CHAR JR L26C3 ; forward to S-NUMERIC ; --- ; -> ;; S-POINT L267B: CALL L2522 ; routine S-2-COORD CALL NZ,L22CB ; routine POINT-SUB RST 20H ; NEXT-CHAR JR L26C3 ; forward to S-NUMERIC ; ----------------------------- ; ==> The branch was here if not in table. ;; S-ALPHNUM L2684: CALL L2C88 ; routine ALPHANUM checks if variable or ; a digit. JR NC,L26DF ; forward to S-NEGATE if not to consider ; a '-' character then functions. CP $41 ; compare 'A' JR NC,L26C9 ; forward to S-LETTER if alpha -> ; else must have been numeric so continue ; into that routine. ; This important routine is called during runtime and from LINE-SCAN ; when a BASIC line is checked for syntax. It is this routine that ; inserts, during syntax checking, the invisible floating point numbers ; after the numeric expression. During runtime it just picks these ; numbers up. It also handles BIN format numbers. ; -> ;; S-BIN ;; S-DECIMAL L268D: CALL L2530 ; routine SYNTAX-Z JR NZ,L26B5 ; to S-STK-DEC in runtime ; this route is taken when checking syntax. CALL L2C9B ; routine DEC-TO-FP to evaluate number RST 18H ; GET-CHAR to fetch HL LD BC,$0006 ; six locations required CALL L1655 ; routine MAKE-ROOM INC HL ; to first new location LD (HL),$0E ; insert number marker INC HL ; address next EX DE,HL ; make DE destination. LD HL,($5C65) ; STKEND points to end of stack. LD C,$05 ; result is five locations lower AND A ; prepare for true subtraction SBC HL,BC ; point to start of value. LD ($5C65),HL ; update STKEND as we are taking number. LDIR ; Copy five bytes to program location EX DE,HL ; transfer pointer to HL DEC HL ; adjust CALL L0077 ; routine TEMP-PTR1 sets CH-ADD JR L26C3 ; to S-NUMERIC to record nature of result ; --- ; branch here in runtime. ;; S-STK-DEC L26B5: RST 18H ; GET-CHAR positions HL at digit. ;; S-SD-SKIP L26B6: INC HL ; advance pointer LD A,(HL) ; until we find CP $0E ; chr 14d - the number indicator JR NZ,L26B6 ; to S-SD-SKIP until a match ; it has to be here. INC HL ; point to first byte of number CALL L33B4 ; routine STACK-NUM stacks it LD ($5C5D),HL ; update system variable CH_ADD ;; S-NUMERIC L26C3: SET 6,(IY+$01) ; update FLAGS - Signal numeric result JR L26DD ; forward to S-CONT-1 ===> ; actually S-CONT-2 is destination but why ; waste a byte on a jump when a JR will do. ; Actually a JR L2712 can be used. Rats. ; end of functions accessed from scanning functions table. ; -------------------------- ; Scanning variable routines ; -------------------------- ; ; ;; S-LETTER L26C9: CALL L28B2 ; routine LOOK-VARS JP C,L1C2E ; jump back to REPORT-2 if variable not found ; 'Variable not found' ; but a variable is always 'found' if syntax ; is being checked. CALL Z,L2996 ; routine STK-VAR considers a subscript/slice LD A,($5C3B) ; fetch FLAGS value CP $C0 ; compare 11000000 JR C,L26DD ; step forward to S-CONT-1 if string ===> INC HL ; advance pointer CALL L33B4 ; routine STACK-NUM ;; S-CONT-1 L26DD: JR L2712 ; forward to S-CONT-2 ===> ; ---------------------------------------- ; -> the scanning branch was here if not alphanumeric. ; All the remaining functions will be evaluated by a single call to the ; calculator. The correct priority for the operation has to be placed in ; the B register and the operation code, calculator literal in the C register. ; the operation code has bit 7 set if result is numeric and bit 6 is ; set if operand is numeric. so ; $C0 = numeric result, numeric operand. e.g. 'sin' ; $80 = numeric result, string operand. e.g. 'code' ; $40 = string result, numeric operand. e.g. 'str$' ; $00 = string result, string operand. e.g. 'val$' ;; S-NEGATE L26DF: LD BC,$09DB ; prepare priority 09, operation code $C0 + ; 'negate' ($1B) - bits 6 and 7 set for numeric ; result and numeric operand. CP $2D ; is it '-' ? JR Z,L270D ; forward if so to S-PUSH-PO LD BC,$1018 ; prepare priority $10, operation code 'val$' - ; bits 6 and 7 reset for string result and ; string operand. CP $AE ; is it 'VAL$' ? JR Z,L270D ; forward if so to S-PUSH-PO SUB $AF ; subtract token 'CODE' value to reduce ; functions 'CODE' to 'NOT' although the ; upper range is, as yet, unchecked. ; valid range would be $00 - $14. JP C,L1C8A ; jump back to REPORT-C with anything else ; 'Nonsense in BASIC' LD BC,$04F0 ; prepare priority $04, operation $C0 + ; 'not' ($30) CP $14 ; is it 'NOT' JR Z,L270D ; forward to S-PUSH-PO if so JP NC,L1C8A ; to REPORT-C if higher ; 'Nonsense in BASIC' LD B,$10 ; priority $10 for all the rest ADD A,$DC ; make range $DC - $EF ; $C0 + 'code'($1C) thru 'chr$' ($2F) LD C,A ; transfer 'function' to C CP $DF ; is it 'sin' ? JR NC,L2707 ; forward to S-NO-TO-$ with 'sin' through ; 'chr$' as operand is numeric. ; all the rest 'cos' through 'chr$' give a numeric result except 'str$' ; and 'chr$'. RES 6,C ; signal string operand for 'code', 'val' and ; 'len'. ;; S-NO-TO-$ L2707: CP $EE ; compare 'str$' JR C,L270D ; forward to S-PUSH-PO if lower as result ; is numeric. RES 7,C ; reset bit 7 of op code for 'str$', 'chr$' ; as result is string. ; >> This is where they were all headed for. ;; S-PUSH-PO L270D: PUSH BC ; push the priority and calculator operation ; code. RST 20H ; NEXT-CHAR JP L24FF ; jump back to S-LOOP-1 to go round the loop ; again with the next character. ; -------------------------------- ; ===> there were many branches forward to here ; An important step after the evaluation of an expression is to test for ; a string expression and allow it to be sliced. If a numeric expression is ; followed by a '(' then the numeric expression is complete. ; Since a string slice can itself be sliced then loop repeatedly ; e.g. (STR$ PI) (3 TO) (TO 2) or "nonsense" (4 TO ) ;; S-CONT-2 L2712: RST 18H ; GET-CHAR ;; S-CONT-3 L2713: CP $28 ; is it '(' ? JR NZ,L2723 ; forward, if not, to S-OPERTR BIT 6,(IY+$01) ; test FLAGS - numeric or string result ? JR NZ,L2734 ; forward, if numeric, to S-LOOP ; if a string expression preceded the '(' then slice it. CALL L2A52 ; routine SLICING RST 20H ; NEXT-CHAR JR L2713 ; loop back to S-CONT-3 ; --------------------------- ; the branch was here when possibility of a '(' has been excluded. ;; S-OPERTR L2723: LD B,$00 ; prepare to add LD C,A ; possible operator to C LD HL,L2795 ; Address: $2795 - tbl-of-ops CALL L16DC ; routine INDEXER JR NC,L2734 ; forward to S-LOOP if not in table ; but if found in table the priority has to be looked up. LD C,(HL) ; operation code to C ( B is still zero ) LD HL,L27B0 - $C3 ; $26ED is base of table ADD HL,BC ; index into table. LD B,(HL) ; priority to B. ; ------------------ ; Scanning main loop ; ------------------ ; the juggling act ;; S-LOOP L2734: POP DE ; fetch last priority and operation LD A,D ; priority to A CP B ; compare with this one JR C,L2773 ; forward to S-TIGHTER to execute the ; last operation before this one as it has ; higher priority. ; the last priority was greater or equal this one. AND A ; if it is zero then so is this JP Z,L0018 ; jump to exit via get-char pointing at ; next character. ; This may be the character after the ; expression or, if exiting a recursive call, ; the next part of the expression to be ; evaluated. PUSH BC ; save current priority/operation ; as it has lower precedence than the one ; now in DE. ; the 'USR' function is special in that it is overloaded to give two types ; of result. LD HL,$5C3B ; address FLAGS LD A,E ; new operation to A register CP $ED ; is it $C0 + 'usr-no' ($2D) ? JR NZ,L274C ; forward to S-STK-LST if not BIT 6,(HL) ; string result expected ? ; (from the lower priority operand we've ; just pushed on stack ) JR NZ,L274C ; forward to S-STK-LST if numeric ; as operand bits match. LD E,$99 ; reset bit 6 and substitute $19 'usr-$' ; for string operand. ;; S-STK-LST L274C: PUSH DE ; now stack this priority/operation CALL L2530 ; routine SYNTAX-Z JR Z,L275B ; forward to S-SYNTEST if checking syntax. LD A,E ; fetch the operation code AND $3F ; mask off the result/operand bits to leave ; a calculator literal. LD B,A ; transfer to B register ; now use the calculator to perform the single operation - operand is on ; the calculator stack. ; Note. although the calculator is performing a single operation most ; functions e.g. TAN are written using other functions and literals and ; these in turn are written using further strings of calculator literals so ; another level of magical recursion joins the juggling act for a while ; as the calculator too is calling itself. RST 28H ;; FP-CALC DEFB $3B ;;fp-calc-2 L2758: DEFB $38 ;;end-calc JR L2764 ; forward to S-RUNTEST ; --- ; the branch was here if checking syntax only. ;; S-SYNTEST L275B: LD A,E ; fetch the operation code to accumulator XOR (IY+$01) ; compare with bits of FLAGS AND $40 ; bit 6 will be zero now if operand ; matched expected result. ;; S-RPORT-C2 L2761: JP NZ,L1C8A ; to REPORT-C if mismatch ; 'Nonsense in BASIC' ; else continue to set flags for next ; the branch is to here in runtime after a successful operation. ;; S-RUNTEST L2764: POP DE ; fetch the last operation from stack LD HL,$5C3B ; address FLAGS SET 6,(HL) ; set default to numeric result in FLAGS BIT 7,E ; test the operational result JR NZ,L2770 ; forward to S-LOOPEND if numeric RES 6,(HL) ; reset bit 6 of FLAGS to show string result. ;; S-LOOPEND L2770: POP BC ; fetch the previous priority/operation JR L2734 ; back to S-LOOP to perform these ; --- ; the branch was here when a stacked priority/operator had higher priority ; than the current one. ;; S-TIGHTER L2773: PUSH DE ; save high priority op on stack again LD A,C ; fetch lower priority operation code BIT 6,(IY+$01) ; test FLAGS - Numeric or string result ? JR NZ,L2790 ; forward to S-NEXT if numeric result ; if this is lower priority yet has string then must be a comparison. ; Since these can only be evaluated in context and were defaulted to ; numeric in operator look up they must be changed to string equivalents. AND $3F ; mask to give true calculator literal ADD A,$08 ; augment numeric literals to string ; equivalents. ; 'no-&-no' => 'str-&-no' ; 'no-l-eql' => 'str-l-eql' ; 'no-gr-eq' => 'str-gr-eq' ; 'nos-neql' => 'strs-neql' ; 'no-grtr' => 'str-grtr' ; 'no-less' => 'str-less' ; 'nos-eql' => 'strs-eql' ; 'addition' => 'strs-add' LD C,A ; put modified comparison operator back CP $10 ; is it now 'str-&-no' ? JR NZ,L2788 ; forward to S-NOT-AND if not. SET 6,C ; set numeric operand bit JR L2790 ; forward to S-NEXT ; --- ;; S-NOT-AND L2788: JR C,L2761 ; back to S-RPORT-C2 if less ; 'Nonsense in BASIC'. ; e.g. a$ * b$ CP $17 ; is it 'strs-add' ? JR Z,L2790 ; forward to S-NEXT if so ; (bit 6 and 7 are reset) SET 7,C ; set numeric (Boolean) result for all others ;; S-NEXT L2790: PUSH BC ; now save this priority/operation on stack RST 20H ; NEXT-CHAR JP L24FF ; jump back to S-LOOP-1 ; ------------------ ; Table of operators ; ------------------ ; This table is used to look up the calculator literals associated with ; the operator character. The thirteen calculator operations $03 - $0F ; have bits 6 and 7 set to signify a numeric result. ; Some of these codes and bits may be altered later if the context suggests ; a string comparison or operation. ; that is '+', '=', '>', '<', '<=', '>=' or '<>'. ;; tbl-of-ops L2795: DEFB '+', $CF ; $C0 + 'addition' DEFB '-', $C3 ; $C0 + 'subtract' DEFB '*', $C4 ; $C0 + 'multiply' DEFB '/', $C5 ; $C0 + 'division' DEFB '^', $C6 ; $C0 + 'to-power' DEFB '=', $CE ; $C0 + 'nos-eql' DEFB '>', $CC ; $C0 + 'no-grtr' DEFB '<', $CD ; $C0 + 'no-less' DEFB $C7, $C9 ; '<=' $C0 + 'no-l-eql' DEFB $C8, $CA ; '>=' $C0 + 'no-gr-eql' DEFB $C9, $CB ; '<>' $C0 + 'nos-neql' DEFB $C5, $C7 ; 'OR' $C0 + 'or' DEFB $C6, $C8 ; 'AND' $C0 + 'no-&-no' DEFB $00 ; zero end-marker. ; ------------------- ; Table of priorities ; ------------------- ; This table is indexed with the operation code obtained from the above ; table $C3 - $CF to obtain the priority for the respective operation. ;; tbl-priors L27B0: DEFB $06 ; '-' opcode $C3 DEFB $08 ; '*' opcode $C4 DEFB $08 ; '/' opcode $C5 DEFB $0A ; '^' opcode $C6 DEFB $02 ; 'OR' opcode $C7 DEFB $03 ; 'AND' opcode $C8 DEFB $05 ; '<=' opcode $C9 DEFB $05 ; '>=' opcode $CA DEFB $05 ; '<>' opcode $CB DEFB $05 ; '>' opcode $CC DEFB $05 ; '<' opcode $CD DEFB $05 ; '=' opcode $CE DEFB $06 ; '+' opcode $CF ; ---------------------- ; Scanning function (FN) ; ---------------------- ; This routine deals with user-defined functions. ; The definition can be anywhere in the program area but these are best ; placed near the start of the program as we shall see. ; The evaluation process is quite complex as the Spectrum has to parse two ; statements at the same time. Syntax of both has been checked previously ; and hidden locations have been created immediately after each argument ; of the DEF FN statement. Each of the arguments of the FN function is ; evaluated by SCANNING and placed in the hidden locations. Then the ; expression to the right of the DEF FN '=' is evaluated by SCANNING and for ; any variables encountered, a search is made in the DEF FN variable list ; in the program area before searching in the normal variables area. ; ; Recursion is not allowed: i.e. the definition of a function should not use ; the same function, either directly or indirectly ( through another function). ; You'll normally get error 4, ('Out of memory'), although sometimes the system ; will crash. - Vickers, Pitman 1984. ; ; As the definition is just an expression, there would seem to be no means ; of breaking out of such recursion. ; However, by the clever use of string expressions and VAL, such recursion is ; possible. ; e.g. DEF FN a(n) = VAL "n+FN a(n-1)+0" ((n<1) * 10 + 1 TO ) ; will evaluate the full 11-character expression for all values where n is ; greater than zero but just the 11th character, "0", when n drops to zero ; thereby ending the recursion producing the correct result. ; Recursive string functions are possible using VAL$ instead of VAL and the ; null string as the final addend. ; - from a turn of the century newsgroup discussion initiated by Mike Wynne. ;; S-FN-SBRN L27BD: CALL L2530 ; routine SYNTAX-Z JR NZ,L27F7 ; forward to SF-RUN in runtime RST 20H ; NEXT-CHAR CALL L2C8D ; routine ALPHA check for letters A-Z a-z JP NC,L1C8A ; jump back to REPORT-C if not ; 'Nonsense in BASIC' RST 20H ; NEXT-CHAR CP $24 ; is it '$' ? PUSH AF ; save character and flags JR NZ,L27D0 ; forward to SF-BRKT-1 with numeric function RST 20H ; NEXT-CHAR ;; SF-BRKT-1 L27D0: CP $28 ; is '(' ? JR NZ,L27E6 ; forward to SF-RPRT-C if not ; 'Nonsense in BASIC' RST 20H ; NEXT-CHAR CP $29 ; is it ')' ? JR Z,L27E9 ; forward to SF-FLAG-6 if no arguments. ;; SF-ARGMTS L27D9: CALL L24FB ; routine SCANNING checks each argument ; which may be an expression. RST 18H ; GET-CHAR CP $2C ; is it a ',' ? JR NZ,L27E4 ; forward if not to SF-BRKT-2 to test bracket RST 20H ; NEXT-CHAR if a comma was found JR L27D9 ; back to SF-ARGMTS to parse all arguments. ; --- ;; SF-BRKT-2 L27E4: CP $29 ; is character the closing ')' ? ;; SF-RPRT-C L27E6: JP NZ,L1C8A ; jump to REPORT-C ; 'Nonsense in BASIC' ; at this point any optional arguments have had their syntax checked. ;; SF-FLAG-6 L27E9: RST 20H ; NEXT-CHAR LD HL,$5C3B ; address system variable FLAGS RES 6,(HL) ; signal string result POP AF ; restore test against '$'. JR Z,L27F4 ; forward to SF-SYN-EN if string function. SET 6,(HL) ; signal numeric result ;; SF-SYN-EN L27F4: JP L2712 ; jump back to S-CONT-2 to continue scanning. ; --- ; the branch was here in runtime. ;; SF-RUN L27F7: RST 20H ; NEXT-CHAR fetches name AND $DF ; AND 11101111 - reset bit 5 - upper-case. LD B,A ; save in B RST 20H ; NEXT-CHAR SUB $24 ; subtract '$' LD C,A ; save result in C JR NZ,L2802 ; forward if not '$' to SF-ARGMT1 RST 20H ; NEXT-CHAR advances to bracket ;; SF-ARGMT1 L2802: RST 20H ; NEXT-CHAR advances to start of argument PUSH HL ; save address LD HL,($5C53) ; fetch start of program area from PROG DEC HL ; the search starting point is the previous ; location. ;; SF-FND-DF L2808: LD DE,$00CE ; search is for token 'DEF FN' in E, ; statement count in D. PUSH BC ; save C the string test, and B the letter. CALL L1D86 ; routine LOOK-PROG will search for token. POP BC ; restore BC. JR NC,L2814 ; forward to SF-CP-DEF if a match was found. ;; REPORT-P L2812: RST 08H ; ERROR-1 DEFB $18 ; Error Report: FN without DEF ;; SF-CP-DEF L2814: PUSH HL ; save address of DEF FN CALL L28AB ; routine FN-SKPOVR skips over white-space etc. ; without disturbing CH-ADD. AND $DF ; make fetched character upper-case. CP B ; compare with FN name JR NZ,L2825 ; forward to SF-NOT-FD if no match. ; the letters match so test the type. CALL L28AB ; routine FN-SKPOVR skips white-space SUB $24 ; subtract '$' from fetched character CP C ; compare with saved result of same operation ; on FN name. JR Z,L2831 ; forward to SF-VALUES with a match. ; the letters matched but one was string and the other numeric. ;; SF-NOT-FD L2825: POP HL ; restore search point. DEC HL ; make location before LD DE,$0200 ; the search is to be for the end of the ; current definition - 2 statements forward. PUSH BC ; save the letter/type CALL L198B ; routine EACH-STMT steps past rejected ; definition. POP BC ; restore letter/type JR L2808 ; back to SF-FND-DF to continue search ; --- ; Success! ; the branch was here with matching letter and numeric/string type. ;; SF-VALUES L2831: AND A ; test A ( will be zero if string '$' - '$' ) CALL Z,L28AB ; routine FN-SKPOVR advances HL past '$'. POP DE ; discard pointer to 'DEF FN'. POP DE ; restore pointer to first FN argument. LD ($5C5D),DE ; save in CH_ADD CALL L28AB ; routine FN-SKPOVR advances HL past '(' PUSH HL ; save start address in DEF FN *** CP $29 ; is character a ')' ? JR Z,L2885 ; forward to SF-R-BR-2 if no arguments. ;; SF-ARG-LP L2843: INC HL ; point to next character. LD A,(HL) ; fetch it. CP $0E ; is it the number marker LD D,$40 ; signal numeric in D. JR Z,L2852 ; forward to SF-ARG-VL if numeric. DEC HL ; back to letter CALL L28AB ; routine FN-SKPOVR skips any white-space INC HL ; advance past the expected '$' to ; the 'hidden' marker. LD D,$00 ; signal string. ;; SF-ARG-VL L2852: INC HL ; now address first of 5-byte location. PUSH HL ; save address in DEF FN statement PUSH DE ; save D - result type CALL L24FB ; routine SCANNING evaluates expression in ; the FN statement setting FLAGS and leaving ; result as last value on calculator stack. POP AF ; restore saved result type to A XOR (IY+$01) ; xor with FLAGS AND $40 ; and with 01000000 to test bit 6 JR NZ,L288B ; forward to REPORT-Q if type mismatch. ; 'Parameter error' POP HL ; pop the start address in DEF FN statement EX DE,HL ; transfer to DE ?? pop straight into de ? LD HL,($5C65) ; set HL to STKEND location after value LD BC,$0005 ; five bytes to move SBC HL,BC ; decrease HL by 5 to point to start. LD ($5C65),HL ; set STKEND 'removing' value from stack. LDIR ; copy value into DEF FN statement EX DE,HL ; set HL to location after value in DEF FN DEC HL ; step back one CALL L28AB ; routine FN-SKPOVR gets next valid character CP $29 ; is it ')' end of arguments ? JR Z,L2885 ; forward to SF-R-BR-2 if so. ; a comma separator has been encountered in the DEF FN argument list. PUSH HL ; save position in DEF FN statement RST 18H ; GET-CHAR from FN statement CP $2C ; is it ',' ? JR NZ,L288B ; forward to REPORT-Q if not ; 'Parameter error' RST 20H ; NEXT-CHAR in FN statement advances to next ; argument. POP HL ; restore DEF FN pointer CALL L28AB ; routine FN-SKPOVR advances to corresponding ; argument. JR L2843 ; back to SF-ARG-LP looping until all ; arguments are passed into the DEF FN ; hidden locations. ; --- ; the branch was here when all arguments passed. ;; SF-R-BR-2 L2885: PUSH HL ; save location of ')' in DEF FN RST 18H ; GET-CHAR gets next character in FN CP $29 ; is it a ')' also ? JR Z,L288D ; forward to SF-VALUE if so. ;; REPORT-Q L288B: RST 08H ; ERROR-1 DEFB $19 ; Error Report: Parameter error ;; SF-VALUE L288D: POP DE ; location of ')' in DEF FN to DE. EX DE,HL ; now to HL, FN ')' pointer to DE. LD ($5C5D),HL ; initialize CH_ADD to this value. ; At this point the start of the DEF FN argument list is on the machine stack. ; We also have to consider that this defined function may form part of the ; definition of another defined function (though not itself). ; As this defined function may be part of a hierarchy of defined functions ; currently being evaluated by recursive calls to SCANNING, then we have to ; preserve the original value of DEFADD and not assume that it is zero. LD HL,($5C0B) ; get original DEFADD address EX (SP),HL ; swap with DEF FN address on stack *** LD ($5C0B),HL ; set DEFADD to point to this argument list ; during scanning. PUSH DE ; save FN ')' pointer. RST 20H ; NEXT-CHAR advances past ')' in define RST 20H ; NEXT-CHAR advances past '=' to expression CALL L24FB ; routine SCANNING evaluates but searches ; initially for variables at DEFADD POP HL ; pop the FN ')' pointer LD ($5C5D),HL ; set CH_ADD to this POP HL ; pop the original DEFADD value LD ($5C0B),HL ; and re-insert into DEFADD system variable. RST 20H ; NEXT-CHAR advances to character after ')' JP L2712 ; to S-CONT-2 - to continue current ; invocation of scanning ; -------------------- ; Used to parse DEF FN ; -------------------- ; e.g. DEF FN s $ ( x ) = b $ ( TO x ) : REM exaggerated ; ; This routine is used 10 times to advance along a DEF FN statement ; skipping spaces and colour control codes. It is similar to NEXT-CHAR ; which is, at the same time, used to skip along the corresponding FN function ; except the latter has to deal with AT and TAB characters in string ; expressions. These cannot occur in a program area so this routine is ; simpler as both colour controls and their parameters are less than space. ;; FN-SKPOVR L28AB: INC HL ; increase pointer LD A,(HL) ; fetch addressed character CP $21 ; compare with space + 1 JR C,L28AB ; back to FN-SKPOVR if less RET ; return pointing to a valid character. ; --------- ; LOOK-VARS ; --------- ; ; ;; LOOK-VARS L28B2: SET 6,(IY+$01) ; update FLAGS - presume numeric result RST 18H ; GET-CHAR CALL L2C8D ; routine ALPHA tests for A-Za-z JP NC,L1C8A ; jump to REPORT-C if not. ; 'Nonsense in BASIC' PUSH HL ; save pointer to first letter ^1 AND $1F ; mask lower bits, 1 - 26 decimal 000xxxxx LD C,A ; store in C. RST 20H ; NEXT-CHAR PUSH HL ; save pointer to second character ^2 CP $28 ; is it '(' - an array ? JR Z,L28EF ; forward to V-RUN/SYN if so. SET 6,C ; set 6 signaling string if solitary 010 CP $24 ; is character a '$' ? JR Z,L28DE ; forward to V-STR-VAR SET 5,C ; signal numeric 011 CALL L2C88 ; routine ALPHANUM sets carry if second ; character is alphanumeric. JR NC,L28E3 ; forward to V-TEST-FN if just one character ; It is more than one character but re-test current character so that 6 reset ; This loop renders the similar loop at V-PASS redundant. ;; V-CHAR L28D4: CALL L2C88 ; routine ALPHANUM JR NC,L28EF ; to V-RUN/SYN when no more RES 6,C ; make long named type 001 RST 20H ; NEXT-CHAR JR L28D4 ; loop back to V-CHAR ; --- ;; V-STR-VAR L28DE: RST 20H ; NEXT-CHAR advances past '$' RES 6,(IY+$01) ; update FLAGS - signal string result. ;; V-TEST-FN L28E3: LD A,($5C0C) ; load A with DEFADD_hi AND A ; and test for zero. JR Z,L28EF ; forward to V-RUN/SYN if a defined function ; is not being evaluated. ; Note. CALL L2530 ; routine SYNTAX-Z JP NZ,L2951 ; JUMP to STK-F-ARG in runtime and then ; back to this point if no variable found. ;; V-RUN/SYN L28EF: LD B,C ; save flags in B CALL L2530 ; routine SYNTAX-Z JR NZ,L28FD ; to V-RUN to look for the variable in runtime ; if checking syntax the letter is not returned LD A,C ; copy letter/flags to A AND $E0 ; and with 11100000 to get rid of the letter SET 7,A ; use spare bit to signal checking syntax. LD C,A ; and transfer to C. JR L2934 ; forward to V-SYNTAX ; --- ; but in runtime search for the variable. ;; V-RUN L28FD: LD HL,($5C4B) ; set HL to start of variables from VARS ;; V-EACH L2900: LD A,(HL) ; get first character AND $7F ; and with 01111111 ; ignoring bit 7 which distinguishes ; arrays or for/next variables. JR Z,L2932 ; to V-80-BYTE if zero as must be 10000000 ; the variables end-marker. CP C ; compare with supplied value. JR NZ,L292A ; forward to V-NEXT if no match. RLA ; destructively test ADD A,A ; bits 5 and 6 of A ; jumping if bit 5 reset or 6 set JP P,L293F ; to V-FOUND-2 strings and arrays JR C,L293F ; to V-FOUND-2 simple and for next ; leaving long name variables. POP DE ; pop pointer to 2nd. char PUSH DE ; save it again PUSH HL ; save variable first character pointer ;; V-MATCHES L2912: INC HL ; address next character in vars area ;; V-SPACES L2913: LD A,(DE) ; pick up letter from prog area INC DE ; and advance address CP $20 ; is it a space JR Z,L2913 ; back to V-SPACES until non-space OR $20 ; convert to range 1 - 26. CP (HL) ; compare with addressed variables character JR Z,L2912 ; loop back to V-MATCHES if a match on an ; intermediate letter. OR $80 ; now set bit 7 as last character of long ; names are inverted. CP (HL) ; compare again JR NZ,L2929 ; forward to V-GET-PTR if no match ; but if they match check that this is also last letter in prog area LD A,(DE) ; fetch next character CALL L2C88 ; routine ALPHANUM sets carry if not alphanum JR NC,L293E ; forward to V-FOUND-1 with a full match. ;; V-GET-PTR L2929: POP HL ; pop saved pointer to char 1 ;; V-NEXT L292A: PUSH BC ; save flags CALL L19B8 ; routine NEXT-ONE gets next variable in DE EX DE,HL ; transfer to HL. POP BC ; restore the flags JR L2900 ; loop back to V-EACH ; to compare each variable ; --- ;; V-80-BYTE L2932: SET 7,B ; will signal not found ; the branch was here when checking syntax ;; V-SYNTAX L2934: POP DE ; discard the pointer to 2nd. character v2 ; in BASIC line/workspace. RST 18H ; GET-CHAR gets character after variable name. CP $28 ; is it '(' ? JR Z,L2943 ; forward to V-PASS ; Note. could go straight to V-END ? SET 5,B ; signal not an array JR L294B ; forward to V-END ; --------------------------- ; the jump was here when a long name matched and HL pointing to last character ; in variables area. ;; V-FOUND-1 L293E: POP DE ; discard pointer to first var letter ; the jump was here with all other matches HL points to first var char. ;; V-FOUND-2 L293F: POP DE ; discard pointer to 2nd prog char v2 POP DE ; drop pointer to 1st prog char v1 PUSH HL ; save pointer to last char in vars RST 18H ; GET-CHAR ;; V-PASS L2943: CALL L2C88 ; routine ALPHANUM JR NC,L294B ; forward to V-END if not ; but it never will be as we advanced past long-named variables earlier. RST 20H ; NEXT-CHAR JR L2943 ; back to V-PASS ; --- ;; V-END L294B: POP HL ; pop the pointer to first character in ; BASIC line/workspace. RL B ; rotate the B register left ; bit 7 to carry BIT 6,B ; test the array indicator bit. RET ; return ; ----------------------- ; Stack function argument ; ----------------------- ; This branch is taken from LOOK-VARS when a defined function is currently ; being evaluated. ; Scanning is evaluating the expression after the '=' and the variable ; found could be in the argument list to the left of the '=' or in the ; normal place after the program. Preference will be given to the former. ; The variable name to be matched is in C. ;; STK-F-ARG L2951: LD HL,($5C0B) ; set HL to DEFADD LD A,(HL) ; load the first character CP $29 ; is it ')' ? JP Z,L28EF ; JUMP back to V-RUN/SYN, if so, as there are ; no arguments. ; but proceed to search argument list of defined function first if not empty. ;; SFA-LOOP L295A: LD A,(HL) ; fetch character again. OR $60 ; or with 01100000 presume a simple variable. LD B,A ; save result in B. INC HL ; address next location. LD A,(HL) ; pick up byte. CP $0E ; is it the number marker ? JR Z,L296B ; forward to SFA-CP-VR if so. ; it was a string. White-space may be present but syntax has been checked. DEC HL ; point back to letter. CALL L28AB ; routine FN-SKPOVR skips to the '$' INC HL ; now address the hidden marker. RES 5,B ; signal a string variable. ;; SFA-CP-VR L296B: LD A,B ; transfer found variable letter to A. CP C ; compare with expected. JR Z,L2981 ; forward to SFA-MATCH with a match. INC HL ; step INC HL ; past INC HL ; the INC HL ; five INC HL ; bytes. CALL L28AB ; routine FN-SKPOVR skips to next character CP $29 ; is it ')' ? JP Z,L28EF ; jump back if so to V-RUN/SYN to look in ; normal variables area. CALL L28AB ; routine FN-SKPOVR skips past the ',' ; all syntax has been checked and these ; things can be taken as read. JR L295A ; back to SFA-LOOP while there are more ; arguments. ; --- ;; SFA-MATCH L2981: BIT 5,C ; test if numeric JR NZ,L2991 ; to SFA-END if so as will be stacked ; by scanning INC HL ; point to start of string descriptor LD DE,($5C65) ; set DE to STKEND CALL L33C0 ; routine MOVE-FP puts parameters on stack. EX DE,HL ; new free location to HL. LD ($5C65),HL ; use it to set STKEND system variable. ;; SFA-END L2991: POP DE ; discard POP DE ; pointers. XOR A ; clear carry flag. INC A ; and zero flag. RET ; return. ; ------------------------ ; Stack variable component ; ------------------------ ; This is called to evaluate a complex structure that has been found, in ; runtime, by LOOK-VARS in the variables area. ; In this case HL points to the initial letter, bits 7-5 ; of which indicate the type of variable. ; 010 - simple string, 110 - string array, 100 - array of numbers. ; ; It is called from CLASS-01 when assigning to a string or array including ; a slice. ; It is called from SCANNING to isolate the required part of the structure. ; ; An important part of the runtime process is to check that the number of ; dimensions of the variable match the number of subscripts supplied in the ; BASIC line. ; ; If checking syntax, ; the B register, which counts dimensions is set to zero (256) to allow ; the loop to continue till all subscripts are checked. While doing this it ; is reading dimension sizes from some arbitrary area of memory. Although ; these are meaningless it is of no concern as the limit is never checked by ; int-exp during syntax checking. ; ; The routine is also called from the syntax path of DIM command to check the ; syntax of both string and numeric arrays definitions except that bit 6 of C ; is reset so both are checked as numeric arrays. This ruse avoids a terminal ; slice being accepted as part of the DIM command. ; All that is being checked is that there are a valid set of comma-separated ; expressions before a terminal ')', although, as above, it will still go ; through the motions of checking dummy dimension sizes. ;; STK-VAR L2996: XOR A ; clear A LD B,A ; and B, the syntax dimension counter (256) BIT 7,C ; checking syntax ? JR NZ,L29E7 ; forward to SV-COUNT if so. ; runtime evaluation. BIT 7,(HL) ; will be reset if a simple string. JR NZ,L29AE ; forward to SV-ARRAYS otherwise INC A ; set A to 1, simple string. ;; SV-SIMPLE$ L29A1: INC HL ; address length low LD C,(HL) ; place in C INC HL ; address length high LD B,(HL) ; place in B INC HL ; address start of string EX DE,HL ; DE = start now. CALL L2AB2 ; routine STK-STO-$ stacks string parameters ; DE start in variables area, ; BC length, A=1 simple string ; the only thing now is to consider if a slice is required. RST 18H ; GET-CHAR puts character at CH_ADD in A JP L2A49 ; jump forward to SV-SLICE? to test for '(' ; -------------------------------------------------------- ; the branch was here with string and numeric arrays in runtime. ;; SV-ARRAYS L29AE: INC HL ; step past INC HL ; the total length INC HL ; to address Number of dimensions. LD B,(HL) ; transfer to B overwriting zero. BIT 6,C ; a numeric array ? JR Z,L29C0 ; forward to SV-PTR with numeric arrays DEC B ; ignore the final element of a string array ; the fixed string size. JR Z,L29A1 ; back to SV-SIMPLE$ if result is zero as has ; been created with DIM a$(10) for instance ; and can be treated as a simple string. ; proceed with multi-dimensioned string arrays in runtime. EX DE,HL ; save pointer to dimensions in DE RST 18H ; GET-CHAR looks at the BASIC line CP $28 ; is character '(' ? JR NZ,L2A20 ; to REPORT-3 if not ; 'Subscript wrong' EX DE,HL ; dimensions pointer to HL to synchronize ; with next instruction. ; runtime numeric arrays path rejoins here. ;; SV-PTR L29C0: EX DE,HL ; save dimension pointer in DE JR L29E7 ; forward to SV-COUNT with true no of dims ; in B. As there is no initial comma the ; loop is entered at the midpoint. ; ---------------------------------------------------------- ; the dimension counting loop which is entered at mid-point. ;; SV-COMMA L29C3: PUSH HL ; save counter RST 18H ; GET-CHAR POP HL ; pop counter CP $2C ; is character ',' ? JR Z,L29EA ; forward to SV-LOOP if so ; in runtime the variable definition indicates a comma should appear here BIT 7,C ; checking syntax ? JR Z,L2A20 ; forward to REPORT-3 if not ; 'Subscript error' ; proceed if checking syntax of an array? BIT 6,C ; array of strings JR NZ,L29D8 ; forward to SV-CLOSE if so ; an array of numbers. CP $29 ; is character ')' ? JR NZ,L2A12 ; forward to SV-RPT-C if not ; 'Nonsense in BASIC' RST 20H ; NEXT-CHAR moves CH-ADD past the statement RET ; return -> ; --- ; the branch was here with an array of strings. ;; SV-CLOSE L29D8: CP $29 ; as above ')' could follow the expression JR Z,L2A48 ; forward to SV-DIM if so CP $CC ; is it 'TO' ? JR NZ,L2A12 ; to SV-RPT-C with anything else ; 'Nonsense in BASIC' ; now backtrack CH_ADD to set up for slicing routine. ; Note. in a BASIC line we can safely backtrack to a colour parameter. ;; SV-CH-ADD L29E0: RST 18H ; GET-CHAR DEC HL ; backtrack HL LD ($5C5D),HL ; to set CH_ADD up for slicing routine JR L2A45 ; forward to SV-SLICE and make a return ; when all slicing complete. ; ---------------------------------------- ; -> the mid-point entry point of the loop ;; SV-COUNT L29E7: LD HL,$0000 ; initialize data pointer to zero. ;; SV-LOOP L29EA: PUSH HL ; save the data pointer. RST 20H ; NEXT-CHAR in BASIC area points to an ; expression. POP HL ; restore the data pointer. LD A,C ; transfer name/type to A. CP $C0 ; is it 11000000 ? ; Note. the letter component is absent if ; syntax checking. JR NZ,L29FB ; forward to SV-MULT if not an array of ; strings. ; proceed to check string arrays during syntax. RST 18H ; GET-CHAR CP $29 ; ')' end of subscripts ? JR Z,L2A48 ; forward to SV-DIM to consider further slice CP $CC ; is it 'TO' ? JR Z,L29E0 ; back to SV-CH-ADD to consider a slice. ; (no need to repeat get-char at L29E0) ; if neither, then an expression is required so rejoin runtime loop ?? ; registers HL and DE only point to somewhere meaningful in runtime so ; comments apply to that situation. ;; SV-MULT L29FB: PUSH BC ; save dimension number. PUSH HL ; push data pointer/rubbish. ; DE points to current dimension. CALL L2AEE ; routine DE,(DE+1) gets next dimension in DE ; and HL points to it. EX (SP),HL ; dim pointer to stack, data pointer to HL (*) EX DE,HL ; data pointer to DE, dim size to HL. CALL L2ACC ; routine INT-EXP1 checks integer expression ; and gets result in BC in runtime. JR C,L2A20 ; to REPORT-3 if > HL ; 'Subscript out of range' DEC BC ; adjust returned result from 1-x to 0-x CALL L2AF4 ; routine GET-HL*DE multiplies data pointer by ; dimension size. ADD HL,BC ; add the integer returned by expression. POP DE ; pop the dimension pointer. *** POP BC ; pop dimension counter. DJNZ L29C3 ; back to SV-COMMA if more dimensions ; Note. during syntax checking, unless there ; are more than 256 subscripts, the branch ; back to SV-COMMA is always taken. BIT 7,C ; are we checking syntax ? ; then we've got a joker here. ;; SV-RPT-C L2A12: JR NZ,L2A7A ; forward to SL-RPT-C if so ; 'Nonsense in BASIC' ; more than 256 subscripts in BASIC line. ; but in runtime the number of subscripts are at least the same as dims PUSH HL ; save data pointer. BIT 6,C ; is it a string array ? JR NZ,L2A2C ; forward to SV-ELEM$ if so. ; a runtime numeric array subscript. LD B,D ; register DE has advanced past all dimensions LD C,E ; and points to start of data in variable. ; transfer it to BC. RST 18H ; GET-CHAR checks BASIC line CP $29 ; must be a ')' ? JR Z,L2A22 ; skip to SV-NUMBER if so ; else more subscripts in BASIC line than the variable definition. ;; REPORT-3 L2A20: RST 08H ; ERROR-1 DEFB $02 ; Error Report: Subscript wrong ; continue if subscripts matched the numeric array. ;; SV-NUMBER L2A22: RST 20H ; NEXT-CHAR moves CH_ADD to next statement ; - finished parsing. POP HL ; pop the data pointer. LD DE,$0005 ; each numeric element is 5 bytes. CALL L2AF4 ; routine GET-HL*DE multiplies. ADD HL,BC ; now add to start of data in the variable. RET ; return with HL pointing at the numeric ; array subscript. -> ; --------------------------------------------------------------- ; the branch was here for string subscripts when the number of subscripts ; in the BASIC line was one less than in variable definition. ;; SV-ELEM$ L2A2C: CALL L2AEE ; routine DE,(DE+1) gets final dimension ; the length of strings in this array. EX (SP),HL ; start pointer to stack, data pointer to HL. CALL L2AF4 ; routine GET-HL*DE multiplies by element ; size. POP BC ; the start of data pointer is added ADD HL,BC ; in - now points to location before. INC HL ; point to start of required string. LD B,D ; transfer the length (final dimension size) LD C,E ; from DE to BC. EX DE,HL ; put start in DE. CALL L2AB1 ; routine STK-ST-0 stores the string parameters ; with A=0 - a slice or subscript. ; now check that there were no more subscripts in the BASIC line. RST 18H ; GET-CHAR CP $29 ; is it ')' ? JR Z,L2A48 ; forward to SV-DIM to consider a separate ; subscript or/and a slice. CP $2C ; a comma is allowed if the final subscript ; is to be sliced e.g. a$(2,3,4 TO 6). JR NZ,L2A20 ; to REPORT-3 with anything else ; 'Subscript error' ;; SV-SLICE L2A45: CALL L2A52 ; routine SLICING slices the string. ; but a slice of a simple string can itself be sliced. ;; SV-DIM L2A48: RST 20H ; NEXT-CHAR ;; SV-SLICE? L2A49: CP $28 ; is character '(' ? JR Z,L2A45 ; loop back if so to SV-SLICE RES 6,(IY+$01) ; update FLAGS - Signal string result RET ; and return. ; --- ; The above section deals with the flexible syntax allowed. ; DIM a$(3,3,10) can be considered as two dimensional array of ten-character ; strings or a 3-dimensional array of characters. ; a$(1,1) will return a 10-character string as will a$(1,1,1 TO 10) ; a$(1,1,1) will return a single character. ; a$(1,1) (1 TO 6) is the same as a$(1,1,1 TO 6) ; A slice can itself be sliced ad infinitum ; b$ () () () () () () (2 TO 10) (2 TO 9) (3) is the same as b$(5) ; ------------------------- ; Handle slicing of strings ; ------------------------- ; The syntax of string slicing is very natural and it is as well to reflect ; on the permutations possible. ; a$() and a$( TO ) indicate the entire string although just a$ would do ; and would avoid coming here. ; h$(16) indicates the single character at position 16. ; a$( TO 32) indicates the first 32 characters. ; a$(257 TO) indicates all except the first 256 characters. ; a$(19000 TO 19999) indicates the thousand characters at position 19000. ; Also a$(9 TO 5) returns a null string not an error. ; This enables a$(2 TO) to return a null string if the passed string is ; of length zero or 1. ; A string expression in brackets can be sliced. e.g. (STR$ PI) (3 TO ) ; We arrived here from SCANNING with CH-ADD pointing to the initial '(' ; or from above. ;; SLICING L2A52: CALL L2530 ; routine SYNTAX-Z CALL NZ,L2BF1 ; routine STK-FETCH fetches parameters of ; string at runtime, start in DE, length ; in BC. This could be an array subscript. RST 20H ; NEXT-CHAR CP $29 ; is it ')' ? e.g. a$() JR Z,L2AAD ; forward to SL-STORE to store entire string. PUSH DE ; else save start address of string XOR A ; clear accumulator to use as a running flag. PUSH AF ; and save on stack before any branching. PUSH BC ; save length of string to be sliced. LD DE,$0001 ; default the start point to position 1. RST 18H ; GET-CHAR POP HL ; pop length to HL as default end point ; and limit. CP $CC ; is it 'TO' ? e.g. a$( TO 10000) JR Z,L2A81 ; to SL-SECOND to evaluate second parameter. POP AF ; pop the running flag. CALL L2ACD ; routine INT-EXP2 fetches first parameter. PUSH AF ; save flag (will be $FF if parameter>limit) LD D,B ; transfer the start LD E,C ; to DE overwriting 0001. PUSH HL ; save original length. RST 18H ; GET-CHAR POP HL ; pop the limit length. CP $CC ; is it 'TO' after a start ? JR Z,L2A81 ; to SL-SECOND to evaluate second parameter CP $29 ; is it ')' ? e.g. a$(365) ;; SL-RPT-C L2A7A: JP NZ,L1C8A ; jump to REPORT-C with anything else ; 'Nonsense in BASIC' LD H,D ; copy start LD L,E ; to end - just a one character slice. JR L2A94 ; forward to SL-DEFINE. ; --------------------- ;; SL-SECOND L2A81: PUSH HL ; save limit length. RST 20H ; NEXT-CHAR POP HL ; pop the length. CP $29 ; is character ')' ? e.g. a$(7 TO ) JR Z,L2A94 ; to SL-DEFINE using length as end point. POP AF ; else restore flag. CALL L2ACD ; routine INT-EXP2 gets second expression. PUSH AF ; save the running flag. RST 18H ; GET-CHAR LD H,B ; transfer second parameter LD L,C ; to HL. e.g. a$(42 to 99) CP $29 ; is character a ')' ? JR NZ,L2A7A ; to SL-RPT-C if not ; 'Nonsense in BASIC' ; we now have start in DE and an end in HL. ;; SL-DEFINE L2A94: POP AF ; pop the running flag. EX (SP),HL ; put end point on stack, start address to HL ADD HL,DE ; add address of string to the start point. DEC HL ; point to first character of slice. EX (SP),HL ; start address to stack, end point to HL (*) AND A ; prepare to subtract. SBC HL,DE ; subtract start point from end point. LD BC,$0000 ; default the length result to zero. JR C,L2AA8 ; forward to SL-OVER if start > end. INC HL ; increment the length for inclusive byte. AND A ; now test the running flag. JP M,L2A20 ; jump back to REPORT-3 if $FF. ; 'Subscript out of range' LD B,H ; transfer the length LD C,L ; to BC. ;; SL-OVER L2AA8: POP DE ; restore start address from machine stack *** RES 6,(IY+$01) ; update FLAGS - signal string result for ; syntax. ;; SL-STORE L2AAD: CALL L2530 ; routine SYNTAX-Z (UNSTACK-Z?) RET Z ; return if checking syntax. ; but continue to store the string in runtime. ; ------------------------------------ ; other than from above, this routine is called from STK-VAR to stack ; a known string array element. ; ------------------------------------ ;; STK-ST-0 L2AB1: XOR A ; clear to signal a sliced string or element. ; ------------------------- ; this routine is called from chr$, scrn$ etc. to store a simple string result. ; -------------------------- ;; STK-STO-$ L2AB2: RES 6,(IY+$01) ; update FLAGS - signal string result. ; and continue to store parameters of string. ; --------------------------------------- ; Pass five registers to calculator stack ; --------------------------------------- ; This subroutine puts five registers on the calculator stack. ;; STK-STORE L2AB6: PUSH BC ; save two registers CALL L33A9 ; routine TEST-5-SP checks room and puts 5 ; in BC. POP BC ; fetch the saved registers. LD HL,($5C65) ; make HL point to first empty location STKEND LD (HL),A ; place the 5 registers. INC HL ; LD (HL),E ; INC HL ; LD (HL),D ; INC HL ; LD (HL),C ; INC HL ; LD (HL),B ; INC HL ; LD ($5C65),HL ; update system variable STKEND. RET ; and return. ; ------------------------------------------- ; Return result of evaluating next expression ; ------------------------------------------- ; This clever routine is used to check and evaluate an integer expression ; which is returned in BC, setting A to $FF, if greater than a limit supplied ; in HL. It is used to check array subscripts, parameters of a string slice ; and the arguments of the DIM command. In the latter case, the limit check ; is not required and H is set to $FF. When checking optional string slice ; parameters, it is entered at the second entry point so as not to disturb ; the running flag A, which may be $00 or $FF from a previous invocation. ;; INT-EXP1 L2ACC: XOR A ; set result flag to zero. ; -> The entry point is here if A is used as a running flag. ;; INT-EXP2 L2ACD: PUSH DE ; preserve DE register throughout. PUSH HL ; save the supplied limit. PUSH AF ; save the flag. CALL L1C82 ; routine EXPT-1NUM evaluates expression ; at CH_ADD returning if numeric result, ; with value on calculator stack. POP AF ; pop the flag. CALL L2530 ; routine SYNTAX-Z JR Z,L2AEB ; forward to I-RESTORE if checking syntax so ; avoiding a comparison with supplied limit. PUSH AF ; save the flag. CALL L1E99 ; routine FIND-INT2 fetches value from ; calculator stack to BC producing an error ; if too high. POP DE ; pop the flag to D. LD A,B ; test value for zero and reject OR C ; as arrays and strings begin at 1. SCF ; set carry flag. JR Z,L2AE8 ; forward to I-CARRY if zero. POP HL ; restore the limit. PUSH HL ; and save. AND A ; prepare to subtract. SBC HL,BC ; subtract value from limit. ;; I-CARRY L2AE8: LD A,D ; move flag to accumulator $00 or $FF. SBC A,$00 ; will set to $FF if carry set. ;; I-RESTORE L2AEB: POP HL ; restore the limit. POP DE ; and DE register. RET ; return. ; ----------------------- ; LD DE,(DE+1) Subroutine ; ----------------------- ; This routine just loads the DE register with the contents of the two ; locations following the location addressed by DE. ; It is used to step along the 16-bit dimension sizes in array definitions. ; Note. Such code is made into subroutines to make programs easier to ; write and it would use less space to include the five instructions in-line. ; However, there are so many exchanges going on at the places this is invoked ; that to implement it in-line would make the code hard to follow. ; It probably had a zippier label though as the intention is to simplify the ; program. ;; DE,(DE+1) L2AEE: EX DE,HL ; INC HL ; LD E,(HL) ; INC HL ; LD D,(HL) ; RET ; ; ------------------- ; HL=HL*DE Subroutine ; ------------------- ; This routine calls the mathematical routine to multiply HL by DE in runtime. ; It is called from STK-VAR and from DIM. In the latter case syntax is not ; being checked so the entry point could have been at the second CALL ; instruction to save a few clock-cycles. ;; GET-HL*DE L2AF4: CALL L2530 ; routine SYNTAX-Z. RET Z ; return if checking syntax. CALL L30A9 ; routine HL-HL*DE. JP C,L1F15 ; jump back to REPORT-4 if over 65535. RET ; else return with 16-bit result in HL. ; ----------------- ; THE 'LET' COMMAND ; ----------------- ; Sinclair BASIC adheres to the ANSI-78 standard and a LET is required in ; assignments e.g. LET a = 1 : LET h$ = "hat". ; ; Long names may contain spaces but not colour controls (when assigned). ; a substring can appear to the left of the equals sign. ; An earlier mathematician Lewis Carroll may have been pleased that ; 10 LET Babies cannot manage crocodiles = Babies are illogical AND ; Nobody is despised who can manage a crocodile AND Illogical persons ; are despised ; does not give the 'Nonsense..' error if the three variables exist. ; I digress. ;; LET L2AFF: LD HL,($5C4D) ; fetch system variable DEST to HL. BIT 1,(IY+$37) ; test FLAGX - handling a new variable ? JR Z,L2B66 ; forward to L-EXISTS if not. ; continue for a new variable. DEST points to start in BASIC line. ; from the CLASS routines. LD BC,$0005 ; assume numeric and assign an initial 5 bytes ;; L-EACH-CH L2B0B: INC BC ; increase byte count for each relevant ; character ;; L-NO-SP L2B0C: INC HL ; increase pointer. LD A,(HL) ; fetch character. CP $20 ; is it a space ? JR Z,L2B0C ; back to L-NO-SP is so. JR NC,L2B1F ; forward to L-TEST-CH if higher. CP $10 ; is it $00 - $0F ? JR C,L2B29 ; forward to L-SPACES if so. CP $16 ; is it $16 - $1F ? JR NC,L2B29 ; forward to L-SPACES if so. ; it was $10 - $15 so step over a colour code. INC HL ; increase pointer. JR L2B0C ; loop back to L-NO-SP. ; --- ; the branch was to here if higher than space. ;; L-TEST-CH L2B1F: CALL L2C88 ; routine ALPHANUM sets carry if alphanumeric JR C,L2B0B ; loop back to L-EACH-CH for more if so. CP $24 ; is it '$' ? JP Z,L2BC0 ; jump forward if so, to L-NEW$ ; with a new string. ;; L-SPACES L2B29: LD A,C ; save length lo in A. LD HL,($5C59) ; fetch E_LINE to HL. DEC HL ; point to location before, the variables ; end-marker. CALL L1655 ; routine MAKE-ROOM creates BC spaces ; for name and numeric value. INC HL ; advance to first new location. INC HL ; then to second. EX DE,HL ; set DE to second location. PUSH DE ; save this pointer. LD HL,($5C4D) ; reload HL with DEST. DEC DE ; point to first. SUB $06 ; subtract six from length_lo. LD B,A ; save count in B. JR Z,L2B4F ; forward to L-SINGLE if it was just ; one character. ; HL points to start of variable name after 'LET' in BASIC line. ;; L-CHAR L2B3E: INC HL ; increase pointer. LD A,(HL) ; pick up character. CP $21 ; is it space or higher ? JR C,L2B3E ; back to L-CHAR with space and less. OR $20 ; make variable lower-case. INC DE ; increase destination pointer. LD (DE),A ; and load to edit line. DJNZ L2B3E ; loop back to L-CHAR until B is zero. OR $80 ; invert the last character. LD (DE),A ; and overwrite that in edit line. ; now consider first character which has bit 6 set LD A,$C0 ; set A 11000000 is xor mask for a long name. ; %101 is xor/or result ; single character numerics rejoin here with %00000000 in mask. ; %011 will be xor/or result ;; L-SINGLE L2B4F: LD HL,($5C4D) ; fetch DEST - HL addresses first character. XOR (HL) ; apply variable type indicator mask (above). OR $20 ; make lowercase - set bit 5. POP HL ; restore pointer to 2nd character. CALL L2BEA ; routine L-FIRST puts A in first character. ; and returns with HL holding ; new E_LINE-1 the $80 vars end-marker. ;; L-NUMERIC L2B59: PUSH HL ; save the pointer. ; the value of variable is deleted but remains after calculator stack. RST 28H ;; FP-CALC DEFB $02 ;;delete ; delete variable value DEFB $38 ;;end-calc ; DE (STKEND) points to start of value. POP HL ; restore the pointer. LD BC,$0005 ; start of number is five bytes before. AND A ; prepare for true subtraction. SBC HL,BC ; HL points to start of value. JR L2BA6 ; forward to L-ENTER ==> ; --- ; the jump was to here if the variable already existed. ;; L-EXISTS L2B66: BIT 6,(IY+$01) ; test FLAGS - numeric or string result ? JR Z,L2B72 ; skip forward to L-DELETE$ -*-> ; if string result. ; A numeric variable could be simple or an array element. ; They are treated the same and the old value is overwritten. LD DE,$0006 ; six bytes forward points to loc past value. ADD HL,DE ; add to start of number. JR L2B59 ; back to L-NUMERIC to overwrite value. ; --- ; -*-> the branch was here if a string existed. ;; L-DELETE$ L2B72: LD HL,($5C4D) ; fetch DEST to HL. ; (still set from first instruction) LD BC,($5C72) ; fetch STRLEN to BC. BIT 0,(IY+$37) ; test FLAGX - handling a complete simple ; string ? JR NZ,L2BAF ; forward to L-ADD$ if so. ; must be a string array or a slice in workspace. ; Note. LET a$(3 TO 6) = h$ will assign "hat " if h$ = "hat" ; and "hats" if h$ = "hatstand". ; ; This is known as Procrustean lengthening and shortening after a ; character Procrustes in Greek legend who made travellers sleep in his bed, ; cutting off their feet or stretching them so they fitted the bed perfectly. ; The bloke was hatstand and slain by Theseus. LD A,B ; test if length OR C ; is zero and RET Z ; return if so. PUSH HL ; save pointer to start. RST 30H ; BC-SPACES creates room. PUSH DE ; save pointer to first new location. PUSH BC ; and length (*) LD D,H ; set DE to point to last location. LD E,L ; INC HL ; set HL to next location. LD (HL),$20 ; place a space there. LDDR ; copy bytes filling with spaces. PUSH HL ; save pointer to start. CALL L2BF1 ; routine STK-FETCH start to DE, ; length to BC. POP HL ; restore the pointer. EX (SP),HL ; (*) length to HL, pointer to stack. AND A ; prepare for true subtraction. SBC HL,BC ; subtract old length from new. ADD HL,BC ; and add back. JR NC,L2B9B ; forward if it fits to L-LENGTH. LD B,H ; otherwise set LD C,L ; length to old length. ; "hatstand" becomes "hats" ;; L-LENGTH L2B9B: EX (SP),HL ; (*) length to stack, pointer to HL. EX DE,HL ; pointer to DE, start of string to HL. LD A,B ; is the length zero ? OR C ; JR Z,L2BA3 ; forward to L-IN-W/S if so ; leaving prepared spaces. LDIR ; else copy bytes overwriting some spaces. ;; L-IN-W/S L2BA3: POP BC ; pop the new length. (*) POP DE ; pop pointer to new area. POP HL ; pop pointer to variable in assignment. ; and continue copying from workspace ; to variables area. ; ==> branch here from L-NUMERIC ;; L-ENTER L2BA6: EX DE,HL ; exchange pointers HL=STKEND DE=end of vars. LD A,B ; test the length OR C ; and make a RET Z ; return if zero (strings only). PUSH DE ; save start of destination. LDIR ; copy bytes. POP HL ; address the start. RET ; and return. ; --- ; the branch was here from L-DELETE$ if an existing simple string. ; register HL addresses start of string in variables area. ;; L-ADD$ L2BAF: DEC HL ; point to high byte of length. DEC HL ; to low byte. DEC HL ; to letter. LD A,(HL) ; fetch masked letter to A. PUSH HL ; save the pointer on stack. PUSH BC ; save new length. CALL L2BC6 ; routine L-STRING adds new string at end ; of variables area. ; if no room we still have old one. POP BC ; restore length. POP HL ; restore start. INC BC ; increase INC BC ; length by three INC BC ; to include character and length bytes. JP L19E8 ; jump to indirect exit via RECLAIM-2 ; deleting old version and adjusting pointers. ; --- ; the jump was here with a new string variable. ;; L-NEW$ L2BC0: LD A,$DF ; indicator mask %11011111 for ; %010xxxxx will be result LD HL,($5C4D) ; address DEST first character. AND (HL) ; combine mask with character. ;; L-STRING L2BC6: PUSH AF ; save first character and mask. CALL L2BF1 ; routine STK-FETCH fetches parameters of ; the string. EX DE,HL ; transfer start to HL. ADD HL,BC ; add to length. PUSH BC ; save the length. DEC HL ; point to end of string. LD ($5C4D),HL ; save pointer in DEST. ; (updated by POINTERS if in workspace) INC BC ; extra byte for letter. INC BC ; two bytes INC BC ; for the length of string. LD HL,($5C59) ; address E_LINE. DEC HL ; now end of VARS area. CALL L1655 ; routine MAKE-ROOM makes room for string. ; updating pointers including DEST. LD HL,($5C4D) ; pick up pointer to end of string from DEST. POP BC ; restore length from stack. PUSH BC ; and save again on stack. INC BC ; add a byte. LDDR ; copy bytes from end to start. EX DE,HL ; HL addresses length low INC HL ; increase to address high byte POP BC ; restore length to BC LD (HL),B ; insert high byte DEC HL ; address low byte location LD (HL),C ; insert that byte POP AF ; restore character and mask ;; L-FIRST L2BEA: DEC HL ; address variable name LD (HL),A ; and insert character. LD HL,($5C59) ; load HL with E_LINE. DEC HL ; now end of VARS area. RET ; return ; ------------------------------------ ; Get last value from calculator stack ; ------------------------------------ ; ; ;; STK-FETCH L2BF1: LD HL,($5C65) ; 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 ($5C65),HL ; STKEND RET ; ; ------------------ ; Handle DIM command ; ------------------ ; e.g. DIM a(2,3,4,7): DIM a$(32) : DIM b$(20,2,768) : DIM c$(20000) ; the only limit to dimensions is memory so, for example, ; DIM a(2,2,2,2,2,2,2,2,2,2,2,2,2) is possible and creates a multi- ; dimensional array of zeros. String arrays are initialized to spaces. ; It is not possible to erase an array, but it can be re-dimensioned to ; a minimal size of 1, after use, to free up memory. ;; DIM L2C02: CALL L28B2 ; routine LOOK-VARS ;; D-RPORT-C L2C05: JP NZ,L1C8A ; jump to REPORT-C if a long-name variable. ; DIM lottery numbers(49) doesn't work. CALL L2530 ; routine SYNTAX-Z JR NZ,L2C15 ; forward to D-RUN in runtime. RES 6,C ; signal 'numeric' array even if string as ; this simplifies the syntax checking. CALL L2996 ; routine STK-VAR checks syntax. CALL L1BEE ; routine CHECK-END performs early exit -> ; the branch was here in runtime. ;; D-RUN L2C15: JR C,L2C1F ; skip to D-LETTER if variable did not exist. ; else reclaim the old one. PUSH BC ; save type in C. CALL L19B8 ; routine NEXT-ONE find following variable ; or position of $80 end-marker. CALL L19E8 ; routine RECLAIM-2 reclaims the ; space between. POP BC ; pop the type. ;; D-LETTER L2C1F: SET 7,C ; signal array. LD B,$00 ; initialize dimensions to zero and PUSH BC ; save with the type. LD HL,$0001 ; make elements one character presuming string BIT 6,C ; is it a string ? JR NZ,L2C2D ; forward to D-SIZE if so. LD L,$05 ; make elements 5 bytes as is numeric. ;; D-SIZE L2C2D: EX DE,HL ; save the element size in DE. ; now enter a loop to parse each of the integers in the list. ;; D-NO-LOOP L2C2E: RST 20H ; NEXT-CHAR LD H,$FF ; disable limit check by setting HL high CALL L2ACC ; routine INT-EXP1 JP C,L2A20 ; to REPORT-3 if > 65280 and then some ; 'Subscript out of range' POP HL ; pop dimension counter, array type PUSH BC ; save dimension size *** INC H ; increment the dimension counter PUSH HL ; save the dimension counter LD H,B ; transfer size LD L,C ; to HL CALL L2AF4 ; routine GET-HL*DE multiplies dimension by ; running total of size required initially ; 1 or 5. EX DE,HL ; save running total in DE RST 18H ; GET-CHAR CP $2C ; is it ',' ? JR Z,L2C2E ; loop back to D-NO-LOOP until all dimensions ; have been considered ; when loop complete continue. CP $29 ; is it ')' ? JR NZ,L2C05 ; to D-RPORT-C with anything else ; 'Nonsense in BASIC' RST 20H ; NEXT-CHAR advances to next statement/CR POP BC ; pop dimension counter/type LD A,C ; type to A ; now calculate space required for array variable LD L,B ; dimensions to L since these require 16 bits ; then this value will be doubled LD H,$00 ; set high byte to zero ; another four bytes are required for letter(1), total length(2), number of ; dimensions(1) but since we have yet to double allow for two INC HL ; increment INC HL ; increment ADD HL,HL ; now double giving 4 + dimensions * 2 ADD HL,DE ; add to space required for array contents JP C,L1F15 ; to REPORT-4 if > 65535 ; 'Out of memory' PUSH DE ; save data space PUSH BC ; save dimensions/type PUSH HL ; save total space LD B,H ; total space LD C,L ; to BC LD HL,($5C59) ; address E_LINE - first location after ; variables area DEC HL ; point to location before - the $80 end-marker CALL L1655 ; routine MAKE-ROOM creates the space if ; memory is available. INC HL ; point to first new location and LD (HL),A ; store letter/type POP BC ; pop total space DEC BC ; exclude name DEC BC ; exclude the 16-bit DEC BC ; counter itself INC HL ; point to next location the 16-bit counter LD (HL),C ; insert low byte INC HL ; address next LD (HL),B ; insert high byte POP BC ; pop the number of dimensions. LD A,B ; dimensions to A INC HL ; address next LD (HL),A ; and insert "No. of dims" LD H,D ; transfer DE space + 1 from make-room LD L,E ; to HL DEC DE ; set DE to next location down. LD (HL),$00 ; presume numeric and insert a zero BIT 6,C ; test bit 6 of C. numeric or string ? JR Z,L2C7C ; skip to DIM-CLEAR if numeric LD (HL),$20 ; place a space character in HL ;; DIM-CLEAR L2C7C: POP BC ; pop the data length LDDR ; LDDR sets to zeros or spaces ; The number of dimensions is still in A. ; A loop is now entered to insert the size of each dimension that was pushed ; during the D-NO-LOOP working downwards from position before start of data. ;; DIM-SIZES L2C7F: POP BC ; pop a dimension size *** LD (HL),B ; insert high byte at position DEC HL ; next location down LD (HL),C ; insert low byte DEC HL ; next location down DEC A ; decrement dimension counter JR NZ,L2C7F ; back to DIM-SIZES until all done. RET ; return. ; ----------------------------- ; Check whether digit or letter ; ----------------------------- ; This routine checks that the character in A is alphanumeric ; returning with carry set if so. ;; ALPHANUM L2C88: CALL L2D1B ; routine NUMERIC will reset carry if so. CCF ; Complement Carry Flag RET C ; Return if numeric else continue into ; next routine. ; This routine checks that the character in A is alphabetic ;; ALPHA L2C8D: CP $41 ; less than 'A' ? CCF ; Complement Carry Flag RET NC ; return if so CP $5B ; less than 'Z'+1 ? RET C ; is within first range CP $61 ; less than 'a' ? CCF ; Complement Carry Flag RET NC ; return if so. CP $7B ; less than 'z'+1 ? RET ; carry set if within a-z. ; ------------------------- ; Decimal to floating point ; ------------------------- ; This routine finds the floating point number represented by an expression ; beginning with BIN, '.' or a digit. ; Note that BIN need not have any '0's or '1's after it. ; BIN is really just a notational symbol and not a function. ;; DEC-TO-FP L2C9B: CP $C4 ; 'BIN' token ? JR NZ,L2CB8 ; to NOT-BIN if not LD DE,$0000 ; initialize 16 bit buffer register. ;; BIN-DIGIT L2CA2: RST 20H ; NEXT-CHAR SUB $31 ; '1' ADC A,$00 ; will be zero if '1' or '0' ; carry will be set if was '0' JR NZ,L2CB3 ; forward to BIN-END if result not zero EX DE,HL ; buffer to HL CCF ; Carry now set if originally '1' ADC HL,HL ; shift the carry into HL JP C,L31AD ; to REPORT-6 if overflow - too many digits ; after first '1'. There can be an unlimited ; number of leading zeros. ; 'Number too big' - raise an error EX DE,HL ; save the buffer JR L2CA2 ; back to BIN-DIGIT for more digits ; --- ;; BIN-END L2CB3: LD B,D ; transfer 16 bit buffer LD C,E ; to BC register pair. JP L2D2B ; JUMP to STACK-BC to put on calculator stack ; --- ; continue here with .1, 42, 3.14, 5., 2.3 E -4 ;; NOT-BIN L2CB8: CP $2E ; '.' - leading decimal point ? JR Z,L2CCB ; skip to DECIMAL if so. CALL L2D3B ; routine INT-TO-FP to evaluate all digits ; This number 'x' is placed on stack. CP $2E ; '.' - mid decimal point ? JR NZ,L2CEB ; to E-FORMAT if not to consider that format RST 20H ; NEXT-CHAR CALL L2D1B ; routine NUMERIC returns carry reset if 0-9 JR C,L2CEB ; to E-FORMAT if not a digit e.g. '1.' JR L2CD5 ; to DEC-STO-1 to add the decimal part to 'x' ; --- ; a leading decimal point has been found in a number. ;; DECIMAL L2CCB: RST 20H ; NEXT-CHAR CALL L2D1B ; routine NUMERIC will reset carry if digit ;; DEC-RPT-C L2CCF: JP C,L1C8A ; to REPORT-C if just a '.' ; raise 'Nonsense in BASIC' ; since there is no leading zero put one on the calculator stack. RST 28H ;; FP-CALC DEFB $A0 ;;stk-zero ; 0. DEFB $38 ;;end-calc ; If rejoining from earlier there will be a value 'x' on stack. ; If continuing from above the value zero. ; Now store 1 in mem-0. ; Note. At each pass of the digit loop this will be divided by ten. ;; DEC-STO-1 L2CD5: RST 28H ;; FP-CALC DEFB $A1 ;;stk-one ;x or 0,1. DEFB $C0 ;;st-mem-0 ;x or 0,1. DEFB $02 ;;delete ;x or 0. DEFB $38 ;;end-calc ;; NXT-DGT-1 L2CDA: RST 18H ; GET-CHAR CALL L2D22 ; routine STK-DIGIT stacks single digit 'd' JR C,L2CEB ; exit to E-FORMAT when digits exhausted > RST 28H ;; FP-CALC ;x or 0,d. first pass. DEFB $E0 ;;get-mem-0 ;x or 0,d,1. DEFB $A4 ;;stk-ten ;x or 0,d,1,10. DEFB $05 ;;division ;x or 0,d,1/10. DEFB $C0 ;;st-mem-0 ;x or 0,d,1/10. DEFB $04 ;;multiply ;x or 0,d/10. DEFB $0F ;;addition ;x or 0 + d/10. DEFB $38 ;;end-calc last value. RST 20H ; NEXT-CHAR moves to next character JR L2CDA ; back to NXT-DGT-1 ; --- ; although only the first pass is shown it can be seen that at each pass ; the new less significant digit is multiplied by an increasingly smaller ; factor (1/100, 1/1000, 1/10000 ... ) before being added to the previous ; last value to form a new last value. ; Finally see if an exponent has been input. ;; E-FORMAT L2CEB: CP $45 ; is character 'E' ? JR Z,L2CF2 ; to SIGN-FLAG if so CP $65 ; 'e' is acceptable as well. RET NZ ; return as no exponent. ;; SIGN-FLAG L2CF2: LD B,$FF ; initialize temporary sign byte to $FF RST 20H ; NEXT-CHAR CP $2B ; is character '+' ? JR Z,L2CFE ; to SIGN-DONE CP $2D ; is character '-' ? JR NZ,L2CFF ; to ST-E-PART as no sign INC B ; set sign to zero ; now consider digits of exponent. ; Note. incidentally this is the only occasion in Spectrum BASIC when an ; expression may not be used when a number is expected. ;; SIGN-DONE L2CFE: RST 20H ; NEXT-CHAR ;; ST-E-PART L2CFF: CALL L2D1B ; routine NUMERIC JR C,L2CCF ; to DEC-RPT-C if not ; raise 'Nonsense in BASIC'. PUSH BC ; save sign (in B) CALL L2D3B ; routine INT-TO-FP places exponent on stack CALL L2DD5 ; routine FP-TO-A transfers it to A POP BC ; restore sign JP C,L31AD ; to REPORT-6 if overflow (over 255) ; raise 'Number too big'. AND A ; set flags JP M,L31AD ; to REPORT-6 if over '127'. ; raise 'Number too big'. ; 127 is still way too high and it is ; impossible to enter an exponent greater ; than 39 from the keyboard. The error gets ; raised later in E-TO-FP so two different ; error messages depending how high A is. INC B ; $FF to $00 or $00 to $01 - expendable now. JR Z,L2D18 ; forward to E-FP-JUMP if exponent positive NEG ; Negate the exponent. ;; E-FP-JUMP L2D18: JP L2D4F ; JUMP forward to E-TO-FP to assign to ; last value x on stack x * 10 to power A ; a relative jump would have done. ; --------------------- ; Check for valid digit ; --------------------- ; This routine checks that the ASCII character in A is numeric ; returning with carry reset if so. ;; NUMERIC L2D1B: CP $30 ; '0' RET C ; return if less than zero character. CP $3A ; The upper test is '9' CCF ; Complement Carry Flag RET ; Return - carry clear if character '0' - '9' ; ----------- ; Stack Digit ; ----------- ; This subroutine is called from INT-TO-FP and DEC-TO-FP to stack a digit ; on the calculator stack. ;; STK-DIGIT L2D22: CALL L2D1B ; routine NUMERIC RET C ; return if not numeric character SUB $30 ; convert from ASCII to digit ; ----------------- ; Stack accumulator ; ----------------- ; ; ;; STACK-A L2D28: LD C,A ; transfer to C LD B,$00 ; and make B zero ; ---------------------- ; Stack BC register pair ; ---------------------- ; ;; STACK-BC L2D2B: LD IY,$5C3A ; re-initialize ERR_NR XOR A ; clear to signal small integer LD E,A ; place in E for sign LD D,C ; LSB to D LD C,B ; MSB to C LD B,A ; last byte not used CALL L2AB6 ; routine STK-STORE RST 28H ;; FP-CALC DEFB $38 ;;end-calc make HL = STKEND-5 AND A ; clear carry RET ; before returning ; ------------------------- ; Integer to floating point ; ------------------------- ; This routine places one or more digits found in a BASIC line ; on the calculator stack multiplying the previous value by ten each time ; before adding in the new digit to form a last value on calculator stack. ;; INT-TO-FP L2D3B: PUSH AF ; save first character RST 28H ;; FP-CALC DEFB $A0 ;;stk-zero ; v=0. initial value DEFB $38 ;;end-calc POP AF ; fetch first character back. ;; NXT-DGT-2 L2D40: CALL L2D22 ; routine STK-DIGIT puts 0-9 on stack RET C ; will return when character is not numeric > RST 28H ;; FP-CALC ; v, d. DEFB $01 ;;exchange ; d, v. DEFB $A4 ;;stk-ten ; d, v, 10. DEFB $04 ;;multiply ; d, v*10. DEFB $0F ;;addition ; d + v*10 = newvalue DEFB $38 ;;end-calc ; v. CALL L0074 ; routine CH-ADD+1 get next character JR L2D40 ; back to NXT-DGT-2 to process as a digit ;********************************* ;** Part 9. ARITHMETIC ROUTINES ** ;********************************* ; -------------------------- ; E-format to floating point ; -------------------------- ; This subroutine is used by the PRINT-FP routine and the decimal to FP ; routines to stack a number expressed in exponent format. ; Note. Though not used by the ROM as such, it has also been set up as ; a unary calculator literal but this will not work as the accumulator ; is not available from within the calculator. ; on entry there is a value x on the calculator stack and an exponent of ten ; in A. The required value is x + 10 ^ A ;; e-to-fp ;; E-TO-FP L2D4F: RLCA ; this will set the x. RRCA ; carry if bit 7 is set JR NC,L2D55 ; to E-SAVE if positive. CPL ; make negative positive INC A ; without altering carry. ;; E-SAVE L2D55: PUSH AF ; save positive exp and sign in carry LD HL,$5C92 ; address MEM-0 CALL L350B ; routine FP-0/1 ; places an integer zero, if no carry, ; else a one in mem-0 as a sign flag RST 28H ;; FP-CALC DEFB $A4 ;;stk-ten x, 10. DEFB $38 ;;end-calc POP AF ; pop the exponent. ; now enter a loop ;; E-LOOP L2D60: SRL A ; 0>76543210>C JR NC,L2D71 ; forward to E-TST-END if no bit PUSH AF ; save shifted exponent. RST 28H ;; FP-CALC DEFB $C1 ;;st-mem-1 x, 10. DEFB $E0 ;;get-mem-0 x, 10, (0/1). DEFB $00 ;;jump-true DEFB $04 ;;to L2D6D, E-DIVSN DEFB $04 ;;multiply x*10. DEFB $33 ;;jump DEFB $02 ;;to L2D6E, E-FETCH ;; E-DIVSN L2D6D: DEFB $05 ;;division x/10. ;; E-FETCH L2D6E: DEFB $E1 ;;get-mem-1 x/10 or x*10, 10. DEFB $38 ;;end-calc new x, 10. POP AF ; restore shifted exponent ; the loop branched to here with no carry ;; E-TST-END L2D71: JR Z,L2D7B ; forward to E-END if A emptied of bits PUSH AF ; re-save shifted exponent RST 28H ;; FP-CALC DEFB $31 ;;duplicate new x, 10, 10. DEFB $04 ;;multiply new x, 100. DEFB $38 ;;end-calc POP AF ; restore shifted exponent JR L2D60 ; back to E-LOOP until all bits done. ; --- ; although only the first pass is shown it can be seen that for each set bit ; representing a power of two, x is multiplied or divided by the ; corresponding power of ten. ;; E-END L2D7B: RST 28H ;; FP-CALC final x, factor. DEFB $02 ;;delete final x. DEFB $38 ;;end-calc x. RET ; return ; ------------- ; Fetch integer ; ------------- ; This routine is called by the mathematical routines - FP-TO-BC, PRINT-FP, ; mult, re-stack and negate to fetch an integer from address HL. ; HL points to the stack or a location in MEM and no deletion occurs. ; If the number is negative then a similar process to that used in INT-STORE ; is used to restore the twos complement number to normal in DE and a sign ; in C. ;; INT-FETCH L2D7F: INC HL ; skip zero indicator. LD C,(HL) ; fetch sign to C INC HL ; address low byte LD A,(HL) ; fetch to A XOR C ; two's complement SUB C ; LD E,A ; place in E INC HL ; address high byte LD A,(HL) ; fetch to A ADC A,C ; two's complement XOR C ; LD D,A ; place in D RET ; return ; ------------------------ ; Store a positive integer ; ------------------------ ; This entry point is not used in this ROM but would ; store any integer as positive. ;; p-int-sto L2D8C: LD C,$00 ; make sign byte positive and continue ; ------------- ; Store integer ; ------------- ; this routine stores an integer in DE at address HL. ; It is called from mult, truncate, negate and sgn. ; The sign byte $00 +ve or $FF -ve is in C. ; If negative, the number is stored in 2's complement form so that it is ; ready to be added. ;; INT-STORE L2D8E: PUSH HL ; preserve HL LD (HL),$00 ; first byte zero shows integer not exponent INC HL ; LD (HL),C ; then store the sign byte INC HL ; ; e.g. +1 -1 LD A,E ; fetch low byte 00000001 00000001 XOR C ; xor sign 00000000 or 11111111 ; gives 00000001 or 11111110 SUB C ; sub sign 00000000 or 11111111 ; gives 00000001>0 or 11111111>C LD (HL),A ; store 2's complement. INC HL ; LD A,D ; high byte 00000000 00000000 ADC A,C ; sign 00000000<0 11111111<C ; gives 00000000 or 00000000 XOR C ; xor sign 00000000 11111111 LD (HL),A ; store 2's complement. INC HL ; LD (HL),$00 ; last byte always zero for integers. ; is not used and need not be looked at when ; testing for zero but comes into play should ; an integer be converted to fp. POP HL ; restore HL RET ; return. ; ----------------------------- ; Floating point to BC register ; ----------------------------- ; This routine gets a floating point number e.g. 127.4 from the calculator ; stack to the BC register. ;; FP-TO-BC L2DA2: RST 28H ;; FP-CALC set HL to DEFB $38 ;;end-calc point to last value. LD A,(HL) ; get first of 5 bytes AND A ; and test JR Z,L2DAD ; forward to FP-DELETE if an integer ; The value is first rounded up and then converted to integer. RST 28H ;; FP-CALC x. DEFB $A2 ;;stk-half x. 1/2. DEFB $0F ;;addition x + 1/2. DEFB $27 ;;int int(x + .5) DEFB $38 ;;end-calc ; now delete but leave HL pointing at integer ;; FP-DELETE L2DAD: RST 28H ;; FP-CALC DEFB $02 ;;delete DEFB $38 ;;end-calc PUSH HL ; save pointer. PUSH DE ; and STKEND. EX DE,HL ; make HL point to exponent/zero indicator LD B,(HL) ; indicator to B CALL L2D7F ; routine INT-FETCH ; gets int in DE sign byte to C ; but meaningless values if a large integer XOR A ; clear A SUB B ; subtract indicator byte setting carry ; if not a small integer. BIT 7,C ; test a bit of the sign byte setting zero ; if positive. LD B,D ; transfer int LD C,E ; to BC LD A,E ; low byte to A as a useful return value. POP DE ; pop STKEND POP HL ; and pointer to last value RET ; return ; if carry is set then the number was too big. ; ------------ ; LOG(2^A) ; ------------ ; This routine is used when printing floating point numbers to calculate ; the number of digits before the decimal point. ; first convert a one-byte signed integer to its five byte form. ;; LOG(2^A) L2DC1: LD D,A ; store a copy of A in D. RLA ; test sign bit of A. SBC A,A ; now $FF if negative or $00 LD E,A ; sign byte to E. LD C,A ; and to C XOR A ; clear A LD B,A ; and B. CALL L2AB6 ; routine STK-STORE stacks number AEDCB ; so 00 00 XX 00 00 (positive) or 00 FF XX FF 00 (negative). ; i.e. integer indicator, sign byte, low, high, unused. ; now multiply exponent by log to the base 10 of two. RST 28H ;; FP-CALC DEFB $34 ;;stk-data .30103 (log 2) DEFB $EF ;;Exponent: $7F, Bytes: 4 DEFB $1A,$20,$9A,$85 ;; DEFB $04 ;;multiply DEFB $27 ;;int DEFB $38 ;;end-calc ; ------------------- ; Floating point to A ; ------------------- ; this routine collects a floating point number from the stack into the ; accumulator returning carry set if not in range 0 - 255. ; Not all the calling routines raise an error with overflow so no attempt ; is made to produce an error report here. ;; FP-TO-A L2DD5: CALL L2DA2 ; routine FP-TO-BC returns with C in A also. RET C ; return with carry set if > 65535, overflow PUSH AF ; save the value and flags DEC B ; and test that INC B ; the high byte is zero. JR Z,L2DE1 ; forward FP-A-END if zero ; else there has been 8-bit overflow POP AF ; retrieve the value SCF ; set carry flag to show overflow RET ; and return. ; --- ;; FP-A-END L2DE1: POP AF ; restore value and success flag and RET ; return. ; ----------------------------- ; Print a floating point number ; ----------------------------- ; Not a trivial task. ; Begin by considering whether to print a leading sign for negative numbers. ;; PRINT-FP L2DE3: RST 28H ;; FP-CALC DEFB $31 ;;duplicate DEFB $36 ;;less-0 DEFB $00 ;;jump-true DEFB $0B ;;to L2DF2, PF-NEGTVE DEFB $31 ;;duplicate DEFB $37 ;;greater-0 DEFB $00 ;;jump-true DEFB $0D ;;to L2DF8, PF-POSTVE ; must be zero itself DEFB $02 ;;delete DEFB $38 ;;end-calc LD A,$30 ; prepare the character '0' RST 10H ; PRINT-A RET ; return. -> ; --- ;; PF-NEGTVE L2DF2: DEFB $2A ;;abs DEFB $38 ;;end-calc LD A,$2D ; the character '-' RST 10H ; PRINT-A ; and continue to print the now positive number. RST 28H ;; FP-CALC ;; PF-POSTVE L2DF8: DEFB $A0 ;;stk-zero x,0. begin by DEFB $C3 ;;st-mem-3 x,0. clearing a temporary DEFB $C4 ;;st-mem-4 x,0. output buffer to DEFB $C5 ;;st-mem-5 x,0. fifteen zeros. DEFB $02 ;;delete x. DEFB $38 ;;end-calc x. EXX ; in case called from 'str$' then save the PUSH HL ; pointer to whatever comes after EXX ; str$ as H'L' will be used. ; now enter a loop? ;; PF-LOOP L2E01: RST 28H ;; FP-CALC DEFB $31 ;;duplicate x,x. DEFB $27 ;;int x,int x. DEFB $C2 ;;st-mem-2 x,int x. DEFB $03 ;;subtract x-int x. fractional part. DEFB $E2 ;;get-mem-2 x-int x, int x. DEFB $01 ;;exchange int x, x-int x. DEFB $C2 ;;st-mem-2 int x, x-int x. DEFB $02 ;;delete int x. DEFB $38 ;;end-calc int x. ; ; mem-2 holds the fractional part. ; HL points to last value int x LD A,(HL) ; fetch exponent of int x. AND A ; test JR NZ,L2E56 ; forward to PF-LARGE if a large integer ; > 65535 ; continue with small positive integer components in range 0 - 65535 ; if original number was say .999 then this integer component is zero. CALL L2D7F ; routine INT-FETCH gets x in DE ; (but x is not deleted) LD B,$10 ; set B, bit counter, to 16d LD A,D ; test if AND A ; high byte is zero JR NZ,L2E1E ; forward to PF-SAVE if 16-bit integer. ; and continue with integer in range 0 - 255. OR E ; test the low byte for zero ; i.e. originally just point something or other. JR Z,L2E24 ; forward if so to PF-SMALL ; LD D,E ; transfer E to D LD B,$08 ; and reduce the bit counter to 8. ;; PF-SAVE L2E1E: PUSH DE ; save the part before decimal point. EXX ; POP DE ; and pop in into D'E' EXX ; JR L2E7B ; forward to PF-BITS ; --------------------- ; the branch was here when 'int x' was found to be zero as in say 0.5. ; The zero has been fetched from the calculator stack but not deleted and ; this should occur now. This omission leaves the stack unbalanced and while ; that causes no problems with a simple PRINT statement, it will if str$ is ; being used in an expression e.g. "2" + STR$ 0.5 gives the result "0.5" ; instead of the expected result "20.5". ; credit Tony Stratton, 1982. ; A DEFB 02 delete is required immediately on using the calculator. ;; PF-SMALL L2E24: RST 28H ;; FP-CALC int x = 0. L2E25: DEFB $E2 ;;get-mem-2 int x = 0, x-int x. DEFB $38 ;;end-calc LD A,(HL) ; fetch exponent of positive fractional number SUB $7E ; subtract CALL L2DC1 ; routine LOG(2^A) calculates leading digits. LD D,A ; transfer count to D LD A,($5CAC) ; fetch total MEM-5-1 SUB D ; LD ($5CAC),A ; MEM-5-1 LD A,D ; CALL L2D4F ; routine E-TO-FP RST 28H ;; FP-CALC DEFB $31 ;;duplicate DEFB $27 ;;int DEFB $C1 ;;st-mem-1 DEFB $03 ;;subtract DEFB $E1 ;;get-mem-1 DEFB $38 ;;end-calc CALL L2DD5 ; routine FP-TO-A PUSH HL ; save HL LD ($5CA1),A ; MEM-3-1 DEC A ; RLA ; SBC A,A ; INC A ; LD HL,$5CAB ; address MEM-5-1 leading digit counter LD (HL),A ; store counter INC HL ; address MEM-5-2 total digits ADD A,(HL) ; add counter to contents LD (HL),A ; and store updated value POP HL ; restore HL JP L2ECF ; JUMP forward to PF-FRACTN ; --- ; Note. while it would be pedantic to comment on every occasion a JP ; instruction could be replaced with a JR instruction, this applies to the ; above, which is useful if you wish to correct the unbalanced stack error ; by inserting a 'DEFB 02 delete' at L2E25, and maintain main addresses. ; the branch was here with a large positive integer > 65535 e.g. 123456789 ; the accumulator holds the exponent. ;; PF-LARGE L2E56: SUB $80 ; make exponent positive CP $1C ; compare to 28 JR C,L2E6F ; to PF-MEDIUM if integer <= 2^27 CALL L2DC1 ; routine LOG(2^A) SUB $07 ; LD B,A ; LD HL,$5CAC ; address MEM-5-1 the leading digits counter. ADD A,(HL) ; add A to contents LD (HL),A ; store updated value. LD A,B ; NEG ; negate CALL L2D4F ; routine E-TO-FP JR L2E01 ; back to PF-LOOP ; ---------------------------- ;; PF-MEDIUM L2E6F: EX DE,HL ; CALL L2FBA ; routine FETCH-TWO EXX ; SET 7,D ; LD A,L ; EXX ; SUB $80 ; LD B,A ; ; the branch was here to handle bits in DE with 8 or 16 in B if small int ; and integer in D'E', 6 nibbles will accommodate 065535 but routine does ; 32-bit numbers as well from above ;; PF-BITS L2E7B: SLA E ; C<xxxxxxxx<0 RL D ; C<xxxxxxxx<C EXX ; RL E ; C<xxxxxxxx<C RL D ; C<xxxxxxxx<C EXX ; LD HL,$5CAA ; set HL to mem-4-5th last byte of buffer LD C,$05 ; set byte count to 5 - 10 nibbles ;; PF-BYTES L2E8A: LD A,(HL) ; fetch 0 or prev value ADC A,A ; shift left add in carry C<xxxxxxxx<C DAA ; Decimal Adjust Accumulator. ; if greater than 9 then the left hand ; nibble is incremented. If greater than ; 99 then adjusted and carry set. ; so if we'd built up 7 and a carry came in ; 0000 0111 < C ; 0000 1111 ; daa 1 0101 which is 15 in BCD LD (HL),A ; put back DEC HL ; work down thru mem 4 DEC C ; decrease the 5 counter. JR NZ,L2E8A ; back to PF-BYTES until the ten nibbles rolled DJNZ L2E7B ; back to PF-BITS until 8 or 16 (or 32) done ; at most 9 digits for 32-bit number will have been loaded with digits ; each of the 9 nibbles in mem 4 is placed into ten bytes in mem-3 and mem 4 ; unless the nibble is zero as the buffer is already zero. ; ( or in the case of mem-5 will become zero as a result of RLD instruction ) XOR A ; clear to accept LD HL,$5CA6 ; address MEM-4-0 byte destination. LD DE,$5CA1 ; address MEM-3-0 nibble source. LD B,$09 ; the count is 9 (not ten) as the first ; nibble is known to be blank. RLD ; shift RH nibble to left in (HL) ; A (HL) ; 0000 0000 < 0000 3210 ; 0000 0000 3210 0000 ; A picks up the blank nibble LD C,$FF ; set a flag to indicate when a significant ; digit has been encountered. ;; PF-DIGITS L2EA1: RLD ; pick up leftmost nibble from (HL) ; A (HL) ; 0000 0000 < 7654 3210 ; 0000 7654 3210 0000 JR NZ,L2EA9 ; to PF-INSERT if non-zero value picked up. DEC C ; test INC C ; flag JR NZ,L2EB3 ; skip forward to PF-TEST-2 if flag still $FF ; indicating this is a leading zero. ; but if the zero is a significant digit e.g. 10 then include in digit totals. ; the path for non-zero digits rejoins here. ;; PF-INSERT L2EA9: LD (DE),A ; insert digit at destination INC DE ; increase the destination pointer INC (IY+$71) ; increment MEM-5-1st digit counter INC (IY+$72) ; increment MEM-5-2nd leading digit counter LD C,$00 ; set flag to zero indicating that any ; subsequent zeros are significant and not ; leading. ;; PF-TEST-2 L2EB3: BIT 0,B ; test if the nibble count is even JR Z,L2EB8 ; skip to PF-ALL-9 if so to deal with the ; other nibble in the same byte INC HL ; point to next source byte if not ;; PF-ALL-9 L2EB8: DJNZ L2EA1 ; decrement the nibble count, back to PF-DIGITS ; if all nine not done. ; For 8-bit integers there will be at most 3 digits. ; For 16-bit integers there will be at most 5 digits. ; but for larger integers there could be nine leading digits. ; if nine digits complete then the last one is rounded up as the number will ; be printed using E-format notation LD A,($5CAB) ; fetch digit count from MEM-5-1st SUB $09 ; subtract 9 - max possible JR C,L2ECB ; forward if less to PF-MORE DEC (IY+$71) ; decrement digit counter MEM-5-1st to 8 LD A,$04 ; load A with the value 4. CP (IY+$6F) ; compare with MEM-4-4th - the ninth digit JR L2F0C ; forward to PF-ROUND ; to consider rounding. ; --------------------------------------- ; now delete int x from calculator stack and fetch fractional part. ;; PF-MORE L2ECB: RST 28H ;; FP-CALC int x. DEFB $02 ;;delete . DEFB $E2 ;;get-mem-2 x - int x = f. DEFB $38 ;;end-calc f. ;; PF-FRACTN L2ECF: EX DE,HL ; CALL L2FBA ; routine FETCH-TWO EXX ; LD A,$80 ; SUB L ; LD L,$00 ; SET 7,D ; EXX ; CALL L2FDD ; routine SHIFT-FP ;; PF-FRN-LP L2EDF: LD A,(IY+$71) ; MEM-5-1st CP $08 ; JR C,L2EEC ; to PF-FR-DGT EXX ; RL D ; EXX ; JR L2F0C ; to PF-ROUND ; --- ;; PF-FR-DGT L2EEC: LD BC,$0200 ; ;; PF-FR-EXX L2EEF: LD A,E ; CALL L2F8B ; routine CA-10*A+C LD E,A ; LD A,D ; CALL L2F8B ; routine CA-10*A+C LD D,A ; PUSH BC ; EXX ; POP BC ; DJNZ L2EEF ; to PF-FR-EXX LD HL,$5CA1 ; MEM-3 LD A,C ; LD C,(IY+$71) ; MEM-5-1st ADD HL,BC ; LD (HL),A ; INC (IY+$71) ; MEM-5-1st JR L2EDF ; to PF-FRN-LP ; ---------------- ; 1) with 9 digits but 8 in mem-5-1 and A holding 4, carry set if rounding up. ; e.g. ; 999999999 is printed as 1E+9 ; 100000001 is printed as 1E+8 ; 100000009 is printed as 1.0000001E+8 ;; PF-ROUND L2F0C: PUSH AF ; save A and flags LD HL,$5CA1 ; address MEM-3 start of digits LD C,(IY+$71) ; MEM-5-1st No. of digits to C LD B,$00 ; prepare to add ADD HL,BC ; address last digit + 1 LD B,C ; No. of digits to B counter POP AF ; restore A and carry flag from comparison. ;; PF-RND-LP L2F18: DEC HL ; address digit at rounding position. LD A,(HL) ; fetch it ADC A,$00 ; add carry from the comparison LD (HL),A ; put back result even if $0A. AND A ; test A JR Z,L2F25 ; skip to PF-R-BACK if ZERO? CP $0A ; compare to 'ten' - overflow CCF ; complement carry flag so that set if ten. JR NC,L2F2D ; forward to PF-COUNT with 1 - 9. ;; PF-R-BACK L2F25: DJNZ L2F18 ; loop back to PF-RND-LP ; if B counts down to zero then we've rounded right back as in 999999995. ; and the first 8 locations all hold $0A. LD (HL),$01 ; load first location with digit 1. INC B ; make B hold 1 also. ; could save an instruction byte here. INC (IY+$72) ; make MEM-5-2nd hold 1. ; and proceed to initialize total digits to 1. ;; PF-COUNT L2F2D: LD (IY+$71),B ; MEM-5-1st ; now balance the calculator stack by deleting it RST 28H ;; FP-CALC DEFB $02 ;;delete DEFB $38 ;;end-calc ; note if used from str$ then other values may be on the calculator stack. ; we can also restore the next literal pointer from its position on the ; machine stack. EXX ; POP HL ; restore next literal pointer. EXX ; LD BC,($5CAB) ; set C to MEM-5-1st digit counter. ; set B to MEM-5-2nd leading digit counter. LD HL,$5CA1 ; set HL to start of digits at MEM-3-1 LD A,B ; CP $09 ; JR C,L2F46 ; to PF-NOT-E CP $FC ; JR C,L2F6C ; to PF-E-FRMT ;; PF-NOT-E L2F46: AND A ; test for zero leading digits as in .123 CALL Z,L15EF ; routine OUT-CODE prints a zero e.g. 0.123 ;; PF-E-SBRN L2F4A: XOR A ; SUB B ; JP M,L2F52 ; skip forward to PF-OUT-LP if originally +ve LD B,A ; else negative count now +ve JR L2F5E ; forward to PF-DC-OUT -> ; --- ;; PF-OUT-LP L2F52: LD A,C ; fetch total digit count AND A ; test for zero JR Z,L2F59 ; forward to PF-OUT-DT if so LD A,(HL) ; fetch digit INC HL ; address next digit DEC C ; decrease total digit counter ;; PF-OUT-DT L2F59: CALL L15EF ; routine OUT-CODE outputs it. DJNZ L2F52 ; loop back to PF-OUT-LP until B leading ; digits output. ;; PF-DC-OUT L2F5E: LD A,C ; fetch total digits and AND A ; test if also zero RET Z ; return if so --> ; INC B ; increment B LD A,$2E ; prepare the character '.' ;; PF-DEC-0S L2F64: RST 10H ; PRINT-A outputs the character '.' or '0' LD A,$30 ; prepare the character '0' ; (for cases like .000012345678) DJNZ L2F64 ; loop back to PF-DEC-0S for B times. LD B,C ; load B with now trailing digit counter. JR L2F52 ; back to PF-OUT-LP ; --------------------------------- ; the branch was here for E-format printing e.g. 123456789 => 1.2345679e+8 ;; PF-E-FRMT L2F6C: LD D,B ; counter to D DEC D ; decrement LD B,$01 ; load B with 1. CALL L2F4A ; routine PF-E-SBRN above LD A,$45 ; prepare character 'e' RST 10H ; PRINT-A LD C,D ; exponent to C LD A,C ; and to A AND A ; test exponent JP P,L2F83 ; to PF-E-POS if positive NEG ; negate LD C,A ; positive exponent to C LD A,$2D ; prepare character '-' JR L2F85 ; skip to PF-E-SIGN ; --- ;; PF-E-POS L2F83: LD A,$2B ; prepare character '+' ;; PF-E-SIGN L2F85: RST 10H ; PRINT-A outputs the sign LD B,$00 ; make the high byte zero. JP L1A1B ; exit via OUT-NUM-1 to print exponent in BC ; ------------------------------ ; Handle printing floating point ; ------------------------------ ; This subroutine is called twice from above when printing floating-point ; numbers. It returns 10*A +C in registers C and A ;; CA-10*A+C L2F8B: PUSH DE ; preserve DE. LD L,A ; transfer A to L LD H,$00 ; zero high byte. LD E,L ; copy HL LD D,H ; to DE. ADD HL,HL ; double (*2) ADD HL,HL ; double (*4) ADD HL,DE ; add DE (*5) ADD HL,HL ; double (*10) LD E,C ; copy C to E (D is 0) ADD HL,DE ; and add to give required result. LD C,H ; transfer to LD A,L ; destination registers. POP DE ; restore DE RET ; return with result. ; -------------- ; Prepare to add ; -------------- ; This routine is called twice by addition to prepare the two numbers. The ; exponent is picked up in A and the location made zero. Then the sign bit ; is tested before being set to the implied state. Negative numbers are twos ; complemented. ;; PREP-ADD L2F9B: LD A,(HL) ; pick up exponent LD (HL),$00 ; make location zero AND A ; test if number is zero RET Z ; return if so INC HL ; address mantissa BIT 7,(HL) ; test the sign bit SET 7,(HL) ; set it to implied state DEC HL ; point to exponent RET Z ; return if positive number. PUSH BC ; preserve BC LD BC,$0005 ; length of number ADD HL,BC ; point HL past end LD B,C ; set B to 5 counter LD C,A ; store exponent in C SCF ; set carry flag ;; NEG-BYTE L2FAF: DEC HL ; work from LSB to MSB LD A,(HL) ; fetch byte CPL ; complement ADC A,$00 ; add in initial carry or from prev operation LD (HL),A ; put back DJNZ L2FAF ; loop to NEG-BYTE till all 5 done LD A,C ; stored exponent to A POP BC ; restore original BC RET ; return ; ----------------- ; Fetch two numbers ; ----------------- ; This routine is called twice when printing floating point numbers and also ; to fetch two numbers by the addition, multiply and division routines. ; HL addresses the first number, DE addresses the second number. ; For arithmetic only, A holds the sign of the result which is stored in ; the second location. ;; FETCH-TWO L2FBA: PUSH HL ; save pointer to first number, result if math. PUSH AF ; save result sign. LD C,(HL) ; INC HL ; LD B,(HL) ; LD (HL),A ; store the sign at correct location in ; destination 5 bytes for arithmetic only. INC HL ; LD A,C ; LD C,(HL) ; PUSH BC ; INC HL ; LD C,(HL) ; INC HL ; LD B,(HL) ; EX DE,HL ; LD D,A ; LD E,(HL) ; PUSH DE ; INC HL ; LD D,(HL) ; INC HL ; LD E,(HL) ; PUSH DE ; EXX ; POP DE ; POP HL ; POP BC ; EXX ; INC HL ; LD D,(HL) ; INC HL ; LD E,(HL) ; POP AF ; restore possible result sign. POP HL ; and pointer to possible result. RET ; return. ; --------------------------------- ; Shift floating point number right ; --------------------------------- ; ; ;; SHIFT-FP L2FDD: AND A ; RET Z ; CP $21 ; JR NC,L2FF9 ; to ADDEND-0 PUSH BC ; LD B,A ; ;; ONE-SHIFT L2FE5: EXX ; SRA L ; RR D ; RR E ; EXX ; RR D ; RR E ; DJNZ L2FE5 ; to ONE-SHIFT POP BC ; RET NC ; CALL L3004 ; routine ADD-BACK RET NZ ; ;; ADDEND-0 L2FF9: EXX ; XOR A ; ;; ZEROS-4/5 L2FFB: LD L,$00 ; LD D,A ; LD E,L ; EXX ; LD DE,$0000 ; RET ; ; ------------------ ; Add back any carry ; ------------------ ; ; ;; ADD-BACK L3004: INC E ; RET NZ ; INC D ; RET NZ ; EXX ; INC E ; JR NZ,L300D ; to ALL-ADDED INC D ; ;; ALL-ADDED L300D: EXX ; RET ; ; ----------------------- ; Handle subtraction (03) ; ----------------------- ; Subtraction is done by switching the sign byte/bit of the second number ; which may be integer of floating point and continuing into addition. ;; subtract L300F: EX DE,HL ; address second number with HL CALL L346E ; routine NEGATE switches sign EX DE,HL ; address first number again ; and continue. ; -------------------- ; Handle addition (0F) ; -------------------- ; HL points to first number, DE to second. ; If they are both integers, then go for the easy route. ;; addition L3014: LD A,(DE) ; fetch first byte of second OR (HL) ; combine with first byte of first JR NZ,L303E ; forward to FULL-ADDN if at least one was ; in floating point form. ; continue if both were small integers. PUSH DE ; save pointer to lowest number for result. INC HL ; address sign byte and PUSH HL ; push the pointer. INC HL ; address low byte LD E,(HL) ; to E INC HL ; address high byte LD D,(HL) ; to D INC HL ; address unused byte INC HL ; address known zero indicator of 1st number INC HL ; address sign byte LD A,(HL) ; sign to A, $00 or $FF INC HL ; address low byte LD C,(HL) ; to C INC HL ; address high byte LD B,(HL) ; to B POP HL ; pop result sign pointer EX DE,HL ; integer to HL ADD HL,BC ; add to the other one in BC ; setting carry if overflow. EX DE,HL ; save result in DE bringing back sign pointer ADC A,(HL) ; if pos/pos A=01 with overflow else 00 ; if neg/neg A=FF with overflow else FE ; if mixture A=00 with overflow else FF RRCA ; bit 0 to (C) ADC A,$00 ; both acceptable signs now zero JR NZ,L303C ; forward to ADDN-OFLW if not SBC A,A ; restore a negative result sign LD (HL),A ; INC HL ; LD (HL),E ; INC HL ; LD (HL),D ; DEC HL ; DEC HL ; DEC HL ; POP DE ; STKEND RET ; ; --- ;; ADDN-OFLW L303C: DEC HL ; POP DE ; ;; FULL-ADDN L303E: CALL L3293 ; routine RE-ST-TWO EXX ; PUSH HL ; EXX ; PUSH DE ; PUSH HL ; CALL L2F9B ; routine PREP-ADD LD B,A ; EX DE,HL ; CALL L2F9B ; routine PREP-ADD LD C,A ; CP B ; JR NC,L3055 ; to SHIFT-LEN LD A,B ; LD B,C ; EX DE,HL ; ;; SHIFT-LEN L3055: PUSH AF ; SUB B ; CALL L2FBA ; routine FETCH-TWO CALL L2FDD ; routine SHIFT-FP POP AF ; POP HL ; LD (HL),A ; PUSH HL ; LD L,B ; LD H,C ; ADD HL,DE ; EXX ; EX DE,HL ; ADC HL,BC ; EX DE,HL ; LD A,H ; ADC A,L ; LD L,A ; RRA ; XOR L ; EXX ; EX DE,HL ; POP HL ; RRA ; JR NC,L307C ; to TEST-NEG LD A,$01 ; CALL L2FDD ; routine SHIFT-FP INC (HL) ; JR Z,L309F ; to ADD-REP-6 ;; TEST-NEG L307C: EXX ; LD A,L ; AND $80 ; EXX ; INC HL ; LD (HL),A ; DEC HL ; JR Z,L30A5 ; to GO-NC-MLT LD A,E ; NEG ; Negate CCF ; Complement Carry Flag LD E,A ; LD A,D ; CPL ; ADC A,$00 ; LD D,A ; EXX ; LD A,E ; CPL ; ADC A,$00 ; LD E,A ; LD A,D ; CPL ; ADC A,$00 ; JR NC,L30A3 ; to END-COMPL RRA ; EXX ; INC (HL) ; ;; ADD-REP-6 L309F: JP Z,L31AD ; to REPORT-6 EXX ; ;; END-COMPL L30A3: LD D,A ; EXX ; ;; GO-NC-MLT L30A5: XOR A ; JP L3155 ; to TEST-NORM ; ----------------------------- ; Used in 16 bit multiplication ; ----------------------------- ; This routine is used, in the first instance, by the multiply calculator ; literal to perform an integer multiplication in preference to ; 32-bit multiplication to which it will resort if this overflows. ; ; It is also used by STK-VAR to calculate array subscripts and by DIM to ; calculate the space required for multi-dimensional arrays. ;; HL-HL*DE L30A9: PUSH BC ; preserve BC throughout LD B,$10 ; set B to 16 LD A,H ; save H in A high byte LD C,L ; save L in C low byte LD HL,$0000 ; initialize result to zero ; now enter a loop. ;; HL-LOOP L30B1: ADD HL,HL ; double result JR C,L30BE ; to HL-END if overflow RL C ; shift AC left into carry RLA ; JR NC,L30BC ; to HL-AGAIN to skip addition if no carry ADD HL,DE ; add in DE JR C,L30BE ; to HL-END if overflow ;; HL-AGAIN L30BC: DJNZ L30B1 ; back to HL-LOOP for all 16 bits ;; HL-END L30BE: POP BC ; restore preserved BC RET ; return with carry reset if successful ; and result in HL. ; ---------------------------------------------- ; THE 'PREPARE TO MULTIPLY OR DIVIDE' SUBROUTINE ; ---------------------------------------------- ; This routine is called in succession from multiply and divide to prepare ; two mantissas by setting the leftmost bit that is used for the sign. ; On the first call A holds zero and picks up the sign bit. On the second ; call the two bits are XORed to form the result sign - minus * minus giving ; plus etc. If either number is zero then this is flagged. ; HL addresses the exponent. ;; PREP-M/D L30C0: CALL L34E9 ; routine TEST-ZERO preserves accumulator. RET C ; return carry set if zero INC HL ; address first byte of mantissa XOR (HL) ; pick up the first or xor with first. SET 7,(HL) ; now set to give true 32-bit mantissa DEC HL ; point to exponent RET ; return with carry reset ; ---------------------- ; THE 'MULTIPLY' ROUTINE ; ---------------------- ; (offset: $04 'multiply') ; ; ; "He said go forth and something about mathematics, I wasn't really ; listening" - overheard conversation between two unicorns. ; [ The Odd Streak ]. ;; multiply L30CA: LD A,(DE) ; OR (HL) ; JR NZ,L30F0 ; to MULT-LONG PUSH DE ; PUSH HL ; PUSH DE ; CALL L2D7F ; routine INT-FETCH EX DE,HL ; EX (SP),HL ; LD B,C ; CALL L2D7F ; routine INT-FETCH LD A,B ; XOR C ; LD C,A ; POP HL ; CALL L30A9 ; routine HL-HL*DE EX DE,HL ; POP HL ; JR C,L30EF ; to MULT-OFLW LD A,D ; OR E ; JR NZ,L30EA ; to MULT-RSLT LD C,A ; ;; MULT-RSLT L30EA: CALL L2D8E ; routine INT-STORE POP DE ; RET ; ; --- ;; MULT-OFLW L30EF: POP DE ; ;; MULT-LONG L30F0: CALL L3293 ; routine RE-ST-TWO XOR A ; CALL L30C0 ; routine PREP-M/D RET C ; EXX ; PUSH HL ; EXX ; PUSH DE ; EX DE,HL ; CALL L30C0 ; routine PREP-M/D EX DE,HL ; JR C,L315D ; to ZERO-RSLT PUSH HL ; CALL L2FBA ; routine FETCH-TWO LD A,B ; AND A ; SBC HL,HL ; EXX ; PUSH HL ; SBC HL,HL ; EXX ; LD B,$21 ; JR L3125 ; to STRT-MLT ; --- ;; MLT-LOOP L3114: JR NC,L311B ; to NO-ADD ADD HL,DE ; EXX ; ADC HL,DE ; EXX ; ;; NO-ADD L311B: EXX ; RR H ; RR L ; EXX ; RR H ; RR L ; ;; STRT-MLT L3125: EXX ; RR B ; RR C ; EXX ; RR C ; RRA ; DJNZ L3114 ; to MLT-LOOP EX DE,HL ; EXX ; EX DE,HL ; EXX ; POP BC ; POP HL ; LD A,B ; ADD A,C ; JR NZ,L313B ; to MAKE-EXPT AND A ; ;; MAKE-EXPT L313B: DEC A ; CCF ; Complement Carry Flag ;; DIVN-EXPT L313D: RLA ; CCF ; Complement Carry Flag RRA ; JP P,L3146 ; to OFLW1-CLR JR NC,L31AD ; to REPORT-6 AND A ; ;; OFLW1-CLR L3146: INC A ; JR NZ,L3151 ; to OFLW2-CLR JR C,L3151 ; to OFLW2-CLR EXX ; BIT 7,D ; EXX ; JR NZ,L31AD ; to REPORT-6 ;; OFLW2-CLR L3151: LD (HL),A ; EXX ; LD A,B ; EXX ; ;; TEST-NORM L3155: JR NC,L316C ; to NORMALISE LD A,(HL) ; AND A ; ;; NEAR-ZERO L3159: LD A,$80 ; JR Z,L315E ; to SKIP-ZERO ;; ZERO-RSLT L315D: XOR A ; ;; SKIP-ZERO L315E: EXX ; AND D ; CALL L2FFB ; routine ZEROS-4/5 RLCA ; LD (HL),A ; JR C,L3195 ; to OFLOW-CLR INC HL ; LD (HL),A ; DEC HL ; JR L3195 ; to OFLOW-CLR ; --- ;; NORMALISE L316C: LD B,$20 ; ;; SHIFT-ONE L316E: EXX ; BIT 7,D ; EXX ; JR NZ,L3186 ; to NORML-NOW RLCA ; RL E ; RL D ; EXX ; RL E ; RL D ; EXX ; DEC (HL) ; JR Z,L3159 ; to NEAR-ZERO DJNZ L316E ; to SHIFT-ONE JR L315D ; to ZERO-RSLT ; --- ;; NORML-NOW L3186: RLA ; JR NC,L3195 ; to OFLOW-CLR CALL L3004 ; routine ADD-BACK JR NZ,L3195 ; to OFLOW-CLR EXX ; LD D,$80 ; EXX ; INC (HL) ; JR Z,L31AD ; to REPORT-6 ;; OFLOW-CLR L3195: PUSH HL ; INC HL ; EXX ; PUSH DE ; EXX ; POP BC ; LD A,B ; RLA ; RL (HL) ; RRA ; LD (HL),A ; INC HL ; LD (HL),C ; INC HL ; LD (HL),D ; INC HL ; LD (HL),E ; POP HL ; POP DE ; EXX ; POP HL ; EXX ; RET ; ; --- ;; REPORT-6 L31AD: RST 08H ; ERROR-1 DEFB $05 ; Error Report: Number too big ; ---------------------- ; THE 'DIVISION' ROUTINE ; ---------------------- ; (offset: $05 'division') ; ; "He who can properly define and divide is to be considered a god" ; - Plato, 429 - 347 B.C. ;; division L31AF: CALL L3293 ; routine RE-ST-TWO EX DE,HL ; XOR A ; CALL L30C0 ; routine PREP-M/D JR C,L31AD ; to REPORT-6 EX DE,HL ; CALL L30C0 ; routine PREP-M/D RET C ; EXX ; PUSH HL ; EXX ; PUSH DE ; PUSH HL ; CALL L2FBA ; routine FETCH-TWO EXX ; PUSH HL ; LD H,B ; LD L,C ; EXX ; LD H,C ; LD L,B ; XOR A ; LD B,$DF ; JR L31E2 ; to DIV-START ; --- ;; DIV-LOOP L31D2: RLA ; RL C ; EXX ; RL C ; RL B ; EXX ; ;; div-34th L31DB: ADD HL,HL ; EXX ; ADC HL,HL ; EXX ; JR C,L31F2 ; to SUBN-ONLY ;; DIV-START L31E2: SBC HL,DE ; EXX ; SBC HL,DE ; EXX ; JR NC,L31F9 ; to NO-RSTORE ADD HL,DE ; EXX ; ADC HL,DE ; EXX ; AND A ; JR L31FA ; to COUNT-ONE ; --- ;; SUBN-ONLY L31F2: AND A ; SBC HL,DE ; EXX ; SBC HL,DE ; EXX ; ;; NO-RSTORE L31F9: SCF ; Set Carry Flag ;; COUNT-ONE L31FA: INC B ; JP M,L31D2 ; to DIV-LOOP PUSH AF ; JR Z,L31E2 ; to DIV-START ; ; ; ; 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 L313D ; jump back to DIVN-EXPT ; ------------------------------------ ; Integer truncation towards zero ($3A) ; ------------------------------------ ; ; ;; truncate L3214: LD A,(HL) ; AND A ; RET Z ; CP $81 ; JR NC,L3221 ; to T-GR-ZERO LD (HL),$00 ; LD A,$20 ; JR L3272 ; to NIL-BYTES ; --- ;; T-GR-ZERO L3221: CP $91 ; JR NZ,L323F ; to T-SMALL INC HL ; INC HL ; INC HL ; LD A,$80 ; AND (HL) ; DEC HL ; OR (HL) ; DEC HL ; JR NZ,L3233 ; to T-FIRST LD A,$80 ; XOR (HL) ; ;; T-FIRST L3233: DEC HL ; JR NZ,L326C ; to T-EXPNENT LD (HL),A ; INC HL ; LD (HL),$FF ; DEC HL ; LD A,$18 ; JR L3272 ; to NIL-BYTES ; --- ;; T-SMALL L323F: JR NC,L326D ; to X-LARGE PUSH DE ; CPL ; ADD A,$91 ; INC HL ; LD D,(HL) ; INC HL ; LD E,(HL) ; DEC HL ; DEC HL ; LD C,$00 ; BIT 7,D ; JR Z,L3252 ; to T-NUMERIC DEC C ; ;; T-NUMERIC L3252: SET 7,D ; LD B,$08 ; SUB B ; ADD A,B ; JR C,L325E ; to T-TEST LD E,D ; LD D,$00 ; SUB B ; ;; T-TEST L325E: JR Z,L3267 ; to T-STORE LD B,A ; ;; T-SHIFT L3261: SRL D ; RR E ; DJNZ L3261 ; to T-SHIFT ;; T-STORE L3267: CALL L2D8E ; routine INT-STORE POP DE ; RET ; ; --- ;; T-EXPNENT L326C: LD A,(HL) ; ;; X-LARGE L326D: SUB $A0 ; RET P ; NEG ; Negate ;; NIL-BYTES L3272: PUSH DE ; EX DE,HL ; DEC HL ; LD B,A ; SRL B ; SRL B ; SRL B ; JR Z,L3283 ; to BITS-ZERO ;; BYTE-ZERO L327E: LD (HL),$00 ; DEC HL ; DJNZ L327E ; to BYTE-ZERO ;; BITS-ZERO L3283: AND $07 ; JR Z,L3290 ; to IX-END LD B,A ; LD A,$FF ; ;; LESS-MASK L328A: SLA A ; DJNZ L328A ; to LESS-MASK AND (HL) ; LD (HL),A ; ;; IX-END L3290: EX DE,HL ; POP DE ; RET ; ; ---------------------------------- ; Storage of numbers in 5 byte form. ; ================================== ; Both integers and floating-point numbers can be stored in five bytes. ; Zero is a special case stored as 5 zeros. ; For integers the form is ; Byte 1 - zero, ; Byte 2 - sign byte, $00 +ve, $FF -ve. ; Byte 3 - Low byte of integer. ; Byte 4 - High byte ; Byte 5 - unused but always zero. ; ; it seems unusual to store the low byte first but it is just as easy either ; way. Statistically it just increases the chances of trailing zeros which ; is an advantage elsewhere in saving ROM code. ; ; zero sign low high unused ; So +1 is 00000000 00000000 00000001 00000000 00000000 ; ; and -1 is 00000000 11111111 11111111 11111111 00000000 ; ; much of the arithmetic found in BASIC lines can be done using numbers ; in this form using the Z80's 16 bit register operation ADD. ; (multiplication is done by a sequence of additions). ; ; Storing -ve integers in two's complement form, means that they are ready for ; addition and you might like to add the numbers above to prove that the ; answer is zero. If, as in this case, the carry is set then that denotes that ; the result is positive. This only applies when the signs don't match. ; With positive numbers a carry denotes the result is out of integer range. ; With negative numbers a carry denotes the result is within range. ; The exception to the last rule is when the result is -65536 ; ; Floating point form is an alternative method of storing numbers which can ; be used for integers and larger (or fractional) numbers. ; ; In this form 1 is stored as ; 10000001 00000000 00000000 00000000 00000000 ; ; When a small integer is converted to a floating point number the last two ; bytes are always blank so they are omitted in the following steps ; ; first make exponent +1 +16d (bit 7 of the exponent is set if positive) ; 10010001 00000000 00000001 ; 10010000 00000000 00000010 <- now shift left and decrement exponent ; ... ; 10000010 01000000 00000000 <- until a 1 abuts the imaginary point ; 10000001 10000000 00000000 to the left of the mantissa. ; ; however since the leftmost bit of the mantissa is always set then it can ; be used to denote the sign of the mantissa and put back when needed by the ; PREP routines which gives ; ; 10000001 00000000 00000000 ; ---------------------------------------------- ; THE 'RE-STACK TWO "SMALL" INTEGERS' SUBROUTINE ; ---------------------------------------------- ; This routine is called to re-stack two numbers in full floating point form ; e.g. from mult when integer multiplication has overflowed. ;; RE-ST-TWO L3293: CALL L3296 ; routine RESTK-SUB below and continue ; into the routine to do the other one. ;; RESTK-SUB L3296: EX DE,HL ; swap pointers ; --------------------------------------------- ; THE 'RE-STACK ONE "SMALL" INTEGER' SUBROUTINE ; --------------------------------------------- ; (offset: $3D 're-stack') ; This routine re-stacks an integer, usually on the calculator stack, in full ; floating point form. HL points to first byte. ;; re-stack L3297: LD A,(HL) ; Fetch Exponent byte to A AND A ; test it RET NZ ; return if not zero as already in full ; floating-point form. PUSH DE ; preserve DE. CALL L2D7F ; routine INT-FETCH ; integer to DE, sign to C. ; HL points to 4th byte. XOR A ; clear accumulator. INC HL ; point to 5th. LD (HL),A ; and blank. DEC HL ; point to 4th. LD (HL),A ; and blank. LD B,$91 ; set exponent byte +ve $81 ; and imaginary dec point 16 bits to right ; of first bit. ; we could skip to normalize now but it's quicker to avoid normalizing ; through an empty D. LD A,D ; fetch the high byte D AND A ; is it zero ? JR NZ,L32B1 ; skip to RS-NRMLSE if not. OR E ; low byte E to A and test for zero LD B,D ; set B exponent to 0 JR Z,L32BD ; forward to RS-STORE if value is zero. LD D,E ; transfer E to D LD E,B ; set E to 0 LD B,$89 ; reduce the initial exponent by eight. ;; RS-NRMLSE L32B1: EX DE,HL ; integer to HL, addr of 4th byte to DE. ;; RSTK-LOOP L32B2: DEC B ; decrease exponent ADD HL,HL ; shift DE left JR NC,L32B2 ; loop back to RSTK-LOOP ; until a set bit pops into carry RRC C ; now rotate the sign byte $00 or $FF ; into carry to give a sign bit RR H ; rotate the sign bit to left of H RR L ; rotate any carry into L EX DE,HL ; address 4th byte, normalized int to DE ;; RS-STORE L32BD: DEC HL ; address 3rd byte LD (HL),E ; place E DEC HL ; address 2nd byte LD (HL),D ; place D DEC HL ; address 1st byte LD (HL),B ; store the exponent POP DE ; restore initial DE. RET ; return. ;**************************************** ;** Part 10. FLOATING-POINT CALCULATOR ** ;**************************************** ; As a general rule the calculator avoids using the IY register. ; exceptions are val, 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' ; ------------------------ ; ; ; used 11 times ;; stk-zero 00 00 00 00 00 L32C5: DEFB $00 ;;Bytes: 1 DEFB $B0 ;;Exponent $00 DEFB $00 ;;(+00,+00,+00) ; used 19 times ;; stk-one 00 00 01 00 00 L32C8: DEFB $40 ;;Bytes: 2 DEFB $B0 ;;Exponent $00 DEFB $00,$01 ;;(+00,+00) ; used 9 times ;; stk-half 80 00 00 00 00 L32CC: DEFB $30 ;;Exponent: $80, Bytes: 1 DEFB $00 ;;(+00,+00,+00) ; used 4 times. ;; stk-pi/2 81 49 0F DA A2 L32CE: DEFB $F1 ;;Exponent: $81, Bytes: 4 DEFB $49,$0F,$DA,$A2 ;; ; used 3 times. ;; stk-ten 00 00 0A 00 00 L32D3: DEFB $40 ;;Bytes: 2 DEFB $B0 ;;Exponent $00 DEFB $00,$0A ;;(+00,+00) ; ------------------------ ; THE 'TABLE OF ADDRESSES' ; ------------------------ ; "Each problem that I solved became a rule which served afterwards to solve ; other problems" - Rene Descartes 1596 - 1650. ; ; Starts with binary operations which have two operands and one result. ; Three pseudo binary operations first. ;; tbl-addrs L32D7: DEFW L368F ; $00 Address: $368F - jump-true DEFW L343C ; $01 Address: $343C - exchange DEFW L33A1 ; $02 Address: $33A1 - delete ; True binary operations. DEFW L300F ; $03 Address: $300F - subtract DEFW L30CA ; $04 Address: $30CA - multiply DEFW L31AF ; $05 Address: $31AF - division DEFW L3851 ; $06 Address: $3851 - to-power DEFW L351B ; $07 Address: $351B - or DEFW L3524 ; $08 Address: $3524 - no-&-no DEFW L353B ; $09 Address: $353B - no-l-eql DEFW L353B ; $0A Address: $353B - no-gr-eql DEFW L353B ; $0B Address: $353B - nos-neql DEFW L353B ; $0C Address: $353B - no-grtr DEFW L353B ; $0D Address: $353B - no-less DEFW L353B ; $0E Address: $353B - nos-eql DEFW L3014 ; $0F Address: $3014 - addition DEFW L352D ; $10 Address: $352D - str-&-no DEFW L353B ; $11 Address: $353B - str-l-eql DEFW L353B ; $12 Address: $353B - str-gr-eql DEFW L353B ; $13 Address: $353B - strs-neql DEFW L353B ; $14 Address: $353B - str-grtr DEFW L353B ; $15 Address: $353B - str-less DEFW L353B ; $16 Address: $353B - strs-eql DEFW L359C ; $17 Address: $359C - strs-add ; Unary follow. DEFW L35DE ; $18 Address: $35DE - val$ DEFW L34BC ; $19 Address: $34BC - usr-$ DEFW L3645 ; $1A Address: $3645 - read-in DEFW L346E ; $1B Address: $346E - negate DEFW L3669 ; $1C Address: $3669 - code DEFW L35DE ; $1D Address: $35DE - val DEFW L3674 ; $1E Address: $3674 - len DEFW L37B5 ; $1F Address: $37B5 - sin DEFW L37AA ; $20 Address: $37AA - cos DEFW L37DA ; $21 Address: $37DA - tan DEFW L3833 ; $22 Address: $3833 - asn DEFW L3843 ; $23 Address: $3843 - acs DEFW L37E2 ; $24 Address: $37E2 - atn DEFW L3713 ; $25 Address: $3713 - ln DEFW L36C4 ; $26 Address: $36C4 - exp DEFW L36AF ; $27 Address: $36AF - int DEFW L384A ; $28 Address: $384A - sqr DEFW L3492 ; $29 Address: $3492 - sgn DEFW L346A ; $2A Address: $346A - abs DEFW L34AC ; $2B Address: $34AC - peek DEFW L34A5 ; $2C Address: $34A5 - in DEFW L34B3 ; $2D Address: $34B3 - usr-no DEFW L361F ; $2E Address: $361F - str$ DEFW L35C9 ; $2F Address: $35C9 - chrs DEFW L3501 ; $30 Address: $3501 - not ; End of true unary. DEFW L33C0 ; $31 Address: $33C0 - duplicate DEFW L36A0 ; $32 Address: $36A0 - n-mod-m DEFW L3686 ; $33 Address: $3686 - jump DEFW L33C6 ; $34 Address: $33C6 - stk-data DEFW L367A ; $35 Address: $367A - dec-jr-nz DEFW L3506 ; $36 Address: $3506 - less-0 DEFW L34F9 ; $37 Address: $34F9 - greater-0 DEFW L369B ; $38 Address: $369B - end-calc DEFW L3783 ; $39 Address: $3783 - get-argt DEFW L3214 ; $3A Address: $3214 - truncate DEFW L33A2 ; $3B Address: $33A2 - fp-calc-2 DEFW L2D4F ; $3C Address: $2D4F - e-to-fp DEFW L3297 ; $3D Address: $3297 - re-stack ; The following are just the next available slots for the 128 compound ; literals which are in range $80 - $FF. DEFW L3449 ; Address: $3449 - series-xx $80 - $9F. DEFW L341B ; Address: $341B - stk-const-xx $A0 - $BF. DEFW L342D ; Address: $342D - st-mem-xx $C0 - $DF. DEFW L340F ; Address: $340F - get-mem-xx $E0 - $FF. ; Aside: 3E - 3F are therefore unused calculator literals. ; If the literal has to be also usable as a function then bits 6 and 7 are ; used to show type of arguments and result. ; -------------- ; The Calculator ; -------------- ; "A good calculator does not need artificial aids" ; Lao Tze 604 - 531 B.C. ;; CALCULATE L335B: CALL L35BF ; routine STK-PNTRS 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 L335E: LD A,B ; fetch the Z80 B register to A LD ($5C67),A ; and store value in system variable BREG. ; 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 L3362: 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 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 L3365: LD ($5C65),DE ; save end of stack in system variable STKEND EXX ; switch to alt LD A,(HL) ; get next literal INC HL ; increase pointer' ; single operation jumps back to here ;; SCAN-ENT L336C: PUSH HL ; save pointer on stack AND A ; now test the literal JP P,L3380 ; forward to FIRST-3D 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,$7C ; add ($3E * 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 L338E ; forward to ENT-TABLE ; --- ; the branch was here with simple literals. ;; FIRST-3D L3380: CP $18 ; compare with first unary operations. JR NC,L338C ; to DOUBLE-A with unary operations ; it is binary so adjust pointers. EXX ; LD BC,$FFFB ; the value -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 L338C: RLCA ; double the literal LD L,A ; and store in L for indexing ;; ENT-TABLE L338E: LD DE,L32D7 ; 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,L3365 ; Address: RE-ENTRY EX (SP),HL ; goes to stack PUSH DE ; now address of routine EXX ; main set ; avoid using IY register. LD BC,($5C66) ; STKEND_hi ; nothing much goes to C but BREG to B ; and continue into next ret instruction ; which has a dual identity ; ------------------ ; Handle delete (02) ; ------------------ ; 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 L33A1: RET ; return - indirect jump if from above. ; --------------------- ; Single operation (3B) ; --------------------- ; 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 L33A2: POP AF ; drop return address. LD A,($5C67) ; load accumulator from system variable BREG ; value will be literal e.g. 'tan' EXX ; switch to alt JR L336C ; back to SCAN-ENT ; next literal will be end-calc at L2758 ; --------------------------------- ; THE 'TEST FIVE 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 L33A9: PUSH DE ; save PUSH HL ; registers LD BC,$0005 ; an overhead of five bytes CALL L1F05 ; routine 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 'STACK NUMBER' SUBROUTINE ; ----------------------------- ; This routine is called to stack a hidden floating point number found in ; a BASIC line. It is also called to stack a numeric variable value, and ; from BEEP, to stack an entry in the semi-tone table. It is not part of the ; calculator suite of routines. On entry, HL points to the number to be ; stacked. ;; STACK-NUM L33B4: LD DE,($5C65) ; Load destination from STKEND system variable. CALL L33C0 ; Routine MOVE-FP puts on calculator stack ; with a memory check. LD ($5C65),DE ; Set STKEND to next free location. RET ; Return. ; --------------------------------- ; Move a floating point number (31) ; --------------------------------- ; 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 L33C0: CALL L33A9 ; routine 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. ; ------------------- ; Stack literals ($34) ; ------------------- ; 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 integer or floating point form as succinctly as is possible. ;; stk-data L33C6: LD H,D ; transfer STKEND LD L,E ; to HL for result. ;; STK-CONST L33C8: CALL L33A9 ; routine 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,L33DE ; 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 L33DE: 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 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 L33F1: 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 L33F1 ; loop back to STK-ZEROS 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 ROM. The destination DE normally points to the ; end of the calculator stack which might be in the normal place or in the ; system variables area during E-LINE-NO; INT-TO-FP; stk-ten. In any case, ; it would be simpler all round if the routine just shoved unwanted values ; where it is going to stick the wanted value. The instruction LD DE, $0000 ; can be removed. ;; SKIP-CONS L33F7: AND A ; test if initially zero. ;; SKIP-NEXT L33F8: 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. ; e.g. with Expandor Systems 'Soft ROM'. ; Better still, write to the normal place. CALL L33C8 ; routine STK-CONST works through variable ; length records. POP DE ; restore real STKEND POP AF ; restore count DEC A ; decrease JR L33F8 ; loop back to SKIP-NEXT ; ------------------------------ ; THE 'LOCATE MEMORY' 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 reading the semi-tone table and addressing ; floating-point numbers in the calculator's memory area. ; It is not possible to use this routine for the table of constants as these ; six values are held in compressed format. ;; LOC-MEM L3406: LD C,A ; store the original number $00-$1F. RLCA ; X2 - double. RLCA ; X4 - quadruple. ADD A,C ; X5 - now add original to multiply by five. LD C,A ; place the result in the low byte. LD B,$00 ; set high byte to zero. ADD HL,BC ; add to form address of start of number in HL. RET ; return. ; ------------------------------ ; Get from memory area ($E0 etc.) ; ------------------------------ ; Literals $E0 to $FF ; A holds $00-$1F offset. ; The calculator stack increases by 5 bytes. ;; get-mem-xx L340F: PUSH DE ; save STKEND LD HL,($5C68) ; MEM is base address of the memory cells. CALL L3406 ; routine LOC-MEM so that HL = first byte CALL L33C0 ; routine MOVE-FP moves 5 bytes with memory ; check. ; DE now points to new STKEND. POP HL ; original STKEND is now RESULT pointer. RET ; return. ; -------------------------- ; Stack a constant (A0 etc.) ; -------------------------- ; 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 used for semi-tone table values. ;; stk-const-xx L341B: LD H,D ; save STKEND - required for result LD L,E ; EXX ; swap PUSH HL ; save pointer to next literal LD HL,L32C5 ; Address: stk-zero - start of table of ; constants EXX ; CALL L33F7 ; routine SKIP-CONS CALL L33C8 ; routine STK-CONST EXX ; POP HL ; restore pointer to next literal. EXX ; RET ; return. ; -------------------------------- ; Store in a memory area ($C0 etc.) ; -------------------------------- ; Offsets $C0 to $DF ; 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. Spectrum 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 the derived offset $00-$1F. ; This is a unary operation, so on entry HL points to the last value and DE ; points to STKEND. ;; st-mem-xx L342D: PUSH HL ; save the result pointer. EX DE,HL ; transfer to DE. LD HL,($5C68) ; fetch MEM the base of memory area. CALL L3406 ; routine LOC-MEM sets HL to the destination. EX DE,HL ; swap - HL is start, DE is destination. CALL L33C0 ; routine 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 swaps 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 L343C: LD B,$05 ; there are five bytes to be swapped ; start of loop. ;; SWAP-BYTE L343E: 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 L343E ; loop back to SWAP-BYTE until all 5 done. EX DE,HL ; even up the exchanges so that DE addresses ; STKEND. RET ; return. ; ------------------------------ ; THE 'SERIES GENERATOR' ROUTINE ; ------------------------------ ; (offset: $86 'series-06') ; (offset: $88 'series-08') ; (offset: $8C 'series-0C') ; The Spectrum 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 L3449: LD B,A ; parameter $00 - $1F to B counter CALL L335E ; routine GEN-ENT-1 is called. ; A recursive call to a special entry point ; in the calculator that puts the B register ; in the system variable BREG. 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 $31 ;;duplicate x,x DEFB $0F ;;addition x+x DEFB $C0 ;;st-mem-0 x+x DEFB $02 ;;delete . DEFB $A0 ;;stk-zero 0 DEFB $C2 ;;st-mem-2 0 ; a loop is now entered to perform the algebraic calculation for each of ; the numbers in the series ;; G-LOOP L3453: DEFB $31 ;;duplicate v,v. DEFB $E0 ;;get-mem-0 v,v,x+2 DEFB $04 ;;multiply v,v*x+2 DEFB $E2 ;;get-mem-2 v,v*x+2,v DEFB $C1 ;;st-mem-1 DEFB $03 ;;subtract DEFB $38 ;;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 L33C6 ; routine STK-DATA is called directly to ; push a value and advance H'L'. CALL L3362 ; routine GEN-ENT-2 recursively re-enters ; the calculator without disturbing ; system variable BREG ; H'L' value goes on the machine stack and is ; then loaded as usual with the next address. DEFB $0F ;;addition DEFB $01 ;;exchange DEFB $C2 ;;st-mem-2 DEFB $02 ;;delete DEFB $35 ;;dec-jr-nz DEFB $EE ;;back to L3453, G-LOOP ; when the counted loop is complete the final subtraction yields the result ; for example SIN X. DEFB $E1 ;;get-mem-1 DEFB $03 ;;subtract DEFB $38 ;;end-calc RET ; return with H'L' pointing to location ; after last number in series. ; --------------------------------- ; THE 'ABSOLUTE MAGNITUDE' FUNCTION ; --------------------------------- ; (offset: $2A 'abs') ; This calculator literal finds the absolute value of the last value, ; integer or floating point, on calculator stack. ;; abs L346A: LD B,$FF ; signal abs JR L3474 ; forward to NEG-TEST ; --------------------------- ; THE 'UNARY MINUS' OPERATION ; --------------------------- ; (offset: $1B 'negate') ; Unary so on entry HL points to last value, DE to STKEND. ;; NEGATE ;; negate L346E: CALL L34E9 ; call routine TEST-ZERO and RET C ; return if so leaving zero unchanged. LD B,$00 ; signal negate required before joining ; common code. ;; NEG-TEST L3474: LD A,(HL) ; load first byte and AND A ; test for zero JR Z,L3483 ; forward to INT-CASE if a small integer ; for floating point numbers a single bit denotes the sign. INC HL ; address the first byte of mantissa. LD A,B ; action flag $FF=abs, $00=neg. AND $80 ; now $80 $00 OR (HL) ; sets bit 7 for abs RLA ; sets carry for abs and if number negative CCF ; complement carry flag RRA ; and rotate back in altering sign LD (HL),A ; put the altered adjusted number back DEC HL ; HL points to result RET ; return with DE unchanged ; --- ; for integer numbers an entire byte denotes the sign. ;; INT-CASE L3483: PUSH DE ; save STKEND. PUSH HL ; save pointer to the last value/result. CALL L2D7F ; routine INT-FETCH puts integer in DE ; and the sign in C. POP HL ; restore the result pointer. LD A,B ; $FF=abs, $00=neg OR C ; $FF for abs, no change neg CPL ; $00 for abs, switched for neg LD C,A ; transfer result to sign byte. CALL L2D8E ; routine INT-STORE to re-write the integer. POP DE ; restore STKEND. RET ; return. ; --------------------- ; THE 'SIGNUM' FUNCTION ; --------------------- ; (offset: $29 'sgn') ; This routine replaces the last value on the calculator stack, ; which may be in floating point or integer form, with the integer values ; zero if zero, with one if positive and with -minus one if negative. ;; sgn L3492: CALL L34E9 ; call routine TEST-ZERO and RET C ; exit if so as no change is required. PUSH DE ; save pointer to STKEND. LD DE,$0001 ; the result will be 1. INC HL ; skip over the exponent. RL (HL) ; rotate the sign bit into the carry flag. DEC HL ; step back to point to the result. SBC A,A ; byte will be $FF if negative, $00 if positive. LD C,A ; store the sign byte in the C register. CALL L2D8E ; routine INT-STORE to overwrite the last ; value with 0001 and sign. POP DE ; restore STKEND. RET ; return. ; ----------------- ; THE 'IN' FUNCTION ; ----------------- ; (offset: $2C 'in') ; This function reads a byte from an input port. ;; in L34A5: CALL L1E99 ; Routine FIND-INT2 puts port address in BC. ; All 16 bits are put on the address line. IN A,(C) ; Read the port. JR L34B0 ; exit to STACK-A (via IN-PK-STK to save a byte ; of instruction code). ; ------------------- ; THE 'PEEK' FUNCTION ; ------------------- ; (offset: $2B 'peek') ; This function returns the contents of a memory address. ; The entire address space can be peeked including the ROM. ;; peek L34AC: CALL L1E99 ; routine FIND-INT2 puts address in BC. LD A,(BC) ; load contents into A register. ;; IN-PK-STK L34B0: JP L2D28 ; exit via STACK-A to put the value on the ; calculator stack. ; ------------------ ; THE 'USR' FUNCTION ; ------------------ ; (offset: $2d 'usr-no') ; The USR function followed by a number 0-65535 is the method by which ; the Spectrum invokes machine code programs. This function returns the ; contents of the BC register pair. ; Note. that STACK-BC re-initializes the IY register if a user-written ; program has altered it. ;; usr-no L34B3: CALL L1E99 ; routine FIND-INT2 to fetch the ; supplied address into BC. LD HL,L2D2B ; 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. ; ------------------------- ; THE 'USR STRING' FUNCTION ; ------------------------- ; (offset: $19 'usr-$') ; The user function with a one-character string argument, calculates the ; address of the User Defined Graphic character that is in the string. ; As an alternative, the ASCII equivalent, upper or lower case, ; may be supplied. This provides a user-friendly method of redefining ; the 21 User Definable Graphics e.g. ; POKE USR "a", BIN 10000000 will put a dot in the top left corner of the ; character 144. ; Note. the curious double check on the range. With 26 UDGs the first check ; only is necessary. With anything less the second check only is required. ; It is highly likely that the first check was written by Steven Vickers. ;; usr-$ L34BC: CALL L2BF1 ; routine STK-FETCH fetches the string ; parameters. DEC BC ; decrease BC by LD A,B ; one to test OR C ; the length. JR NZ,L34E7 ; to REPORT-A if not a single character. LD A,(DE) ; fetch the character CALL L2C8D ; routine ALPHA sets carry if 'A-Z' or 'a-z'. JR C,L34D3 ; forward to USR-RANGE if ASCII. SUB $90 ; make UDGs range 0-20d JR C,L34E7 ; to REPORT-A if too low. e.g. usr " ". CP $15 ; Note. this test is not necessary. JR NC,L34E7 ; to REPORT-A if higher than 20. INC A ; make range 1-21d to match LSBs of ASCII ;; USR-RANGE L34D3: DEC A ; make range of bits 0-4 start at zero ADD A,A ; multiply by eight ADD A,A ; and lose any set bits ADD A,A ; range now 0 - 25*8 CP $A8 ; compare to 21*8 JR NC,L34E7 ; to REPORT-A if originally higher ; than 'U','u' or graphics U. LD BC,($5C7B) ; fetch the UDG system variable value. ADD A,C ; add the offset to character LD C,A ; and store back in register C. JR NC,L34E4 ; forward to USR-STACK if no overflow. INC B ; increment high byte. ;; USR-STACK L34E4: JP L2D2B ; jump back and exit via STACK-BC to store ; --- ;; REPORT-A L34E7: RST 08H ; ERROR-1 DEFB $09 ; Error Report: Invalid argument ; ------------------------------ ; THE 'TEST FOR ZERO' SUBROUTINE ; ------------------------------ ; Test if top value on calculator stack is zero. The carry flag is set if ; the last value is zero but no registers are altered. ; All five bytes will be zero but first four only need be tested. ; On entry, HL points to the exponent the first byte of the value. ;; TEST-ZERO L34E9: PUSH HL ; preserve HL which is used to address. PUSH BC ; preserve BC which is used as a store. LD B,A ; preserve A in B. LD A,(HL) ; load first byte to accumulator INC HL ; advance. OR (HL) ; OR with second byte and clear carry. INC HL ; advance. OR (HL) ; OR with third byte. INC HL ; advance. OR (HL) ; OR with fourth byte. LD A,B ; restore A without affecting flags. POP BC ; restore the saved POP HL ; registers. RET NZ ; return if not zero and with carry reset. SCF ; set the carry flag. RET ; return with carry set if zero. ; -------------------------------- ; THE 'GREATER THAN ZERO' OPERATOR ; -------------------------------- ; (offset: $37 'greater-0' ) ; 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 ;; greater-0 L34F9: CALL L34E9 ; routine TEST-ZERO RET C ; return if was zero as this ; is also the Boolean 'false' value. LD A,$FF ; prepare XOR mask for sign bit JR L3507 ; 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. ; ------------------ ; THE 'NOT' FUNCTION ; ------------------ ; (offset: $30 'not') ; 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 ;; not L3501: CALL L34E9 ; routine TEST-ZERO sets carry if zero JR L350B ; to FP-0/1 to overwrite operand with ; 1 if carry is set else to overwrite with zero. ; ------------------------------ ; THE 'LESS THAN ZERO' OPERATION ; ------------------------------ ; (offset: $36 'less-0' ) ; Destructively test if last value on calculator stack is less than zero. ; Bit 7 of second byte will be set if so. ;; less-0 L3506: 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 L3507: 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. ; ---------------------------- ; THE 'ZERO OR ONE' SUBROUTINE ; ---------------------------- ; This routine places an integer value of zero or one at the addressed ; location of the calculator stack or MEM area. The value one is written if ; carry is set on entry else zero. ;; FP-0/1 L350B: PUSH HL ; save pointer to the first byte LD A,$00 ; load accumulator with zero - without ; disturbing flags. LD (HL),A ; zero to first byte INC HL ; address next LD (HL),A ; zero to 2nd byte INC HL ; address low byte of integer RLA ; carry to bit 0 of A LD (HL),A ; load one or zero to low byte. RRA ; restore zero to accumulator. INC HL ; address high byte of integer. LD (HL),A ; put a zero there. INC HL ; address fifth byte. LD (HL),A ; put a zero there. POP HL ; restore pointer to the first byte. RET ; return. ; ----------------- ; THE 'OR' OPERATOR ; ----------------- ; (offset: $07 'or' ) ; The Boolean OR operator. e.g. 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 L351B: EX DE,HL ; make HL point to second number CALL L34E9 ; routine TEST-ZERO EX DE,HL ; restore pointers RET C ; return if result was zero - first operand, ; now the last value, is the result. SCF ; set carry flag JR L350B ; back to FP-0/1 to overwrite the first operand ; with the value 1. ; --------------------------------- ; THE 'NUMBER AND NUMBER' OPERATION ; --------------------------------- ; (offset: $08 'no-&-no') ; 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. ;; no-&-no L3524: EX DE,HL ; make HL address second operand. CALL L34E9 ; routine TEST-ZERO sets carry if zero. EX DE,HL ; restore pointers. RET NC ; return if second non-zero, first is result. ; AND A ; else clear carry. JR L350B ; back to FP-0/1 to overwrite first operand ; with zero for return value. ; --------------------------------- ; THE 'STRING AND NUMBER' OPERATION ; --------------------------------- ; (offset: $10 'str-&-no') ; e.g. "You Win" AND score>99 will return the string if condition is true ; or the null string if false. ;; str-&-no L352D: EX DE,HL ; make HL point to the number. CALL L34E9 ; routine TEST-ZERO. EX DE,HL ; restore pointers. RET NC ; 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. ; --------------------------- ; THE 'COMPARISON' OPERATIONS ; --------------------------- ; (offset: $0A 'no-gr-eql') ; (offset: $0B 'nos-neql') ; (offset: $0C 'no-grtr') ; (offset: $0D 'no-less') ; (offset: $0E 'nos-eql') ; (offset: $11 'str-l-eql') ; (offset: $12 'str-gr-eql') ; (offset: $13 'strs-neql') ; (offset: $14 'str-grtr') ; (offset: $15 'str-less') ; (offset: $16 'strs-eql') ; 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 ; ========= ==== == ======== === ======== ======== ==== === = === === === ; no-l-eql x<=y 09 00000001 dec 00000000 00000000 ---- x-y ? --- >0? NOT ; no-gr-eql x>=y 0A 00000010 dec 00000001 10000000c swap y-x ? --- >0? NOT ; nos-neql x<>y 0B 00000011 dec 00000010 00000001 ---- x-y ? NOT --- NOT ; no-grtr x>y 0C 00000100 - 00000100 00000010 ---- x-y ? --- >0? --- ; no-less x<y 0D 00000101 - 00000101 10000010c swap y-x ? --- >0? --- ; nos-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. ;; no-l-eql,etc. L353B: LD A,B ; transfer literal to accumulator. SUB $08 ; subtract eight - which is not useful. BIT 2,A ; isolate '>', '<', '='. JR NZ,L3543 ; skip to EX-OR-NOT with these. DEC A ; else make $00-$02, $08-$0A to match bits 0-2. ;; EX-OR-NOT L3543: RRCA ; the first RRCA sets carry for a swap. JR NC,L354E ; forward to NU-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 L343C ; 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. ;; NU-OR-STR L354E: BIT 2,A ; test if a string comparison. JR NZ,L3559 ; 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 L300F ; routine subtract leaves result on stack. JR L358C ; forward to END-TESTS ; --- ;; STRINGS L3559: RRCA ; 2nd RRCA causes eql/neql to set carry. PUSH AF ; save A and carry. CALL L2BF1 ; routine STK-FETCH gets 2nd string params PUSH DE ; save start2 *. PUSH BC ; and the length. CALL L2BF1 ; routine 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 L3564: 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,L3575 ; forward to SEC-PLUS if second not null. OR C ; test length of first string. ;; SECND-LOW L356B: POP BC ; pop the second length off stack. JR Z,L3572 ; forward to BOTH-NULL if first string is also ; of zero length. ; the true condition - first is longer than second (SECND-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 L3588 ; forward to leave via STR-TEST ; --- ; the branch was here with a match ;; BOTH-NULL L3572: POP AF ; restore carry - set for eql/neql JR L3588 ; 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 L3575: OR C ; test the length of first string. JR Z,L3585 ; 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,L3585 ; forward to FRST-LESS if carry set JR NZ,L356B ; back to SECND-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 L3564 ; back to BYTE-COMP ; --- ; the false condition. ;; FRST-LESS L3585: 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 L3588: PUSH AF ; save A and carry RST 28H ;; FP-CALC DEFB $A0 ;;stk-zero an initial false value. DEFB $38 ;;end-calc ; both numeric and string paths converge here. ;; END-TESTS L358C: POP AF ; pop carry - will be set if eql/neql PUSH AF ; save it again. CALL C,L3501 ; routine NOT sets true(1) if equal(0) ; or, for strings, applies true result. POP AF ; pop carry and PUSH AF ; save A CALL NC,L34F9 ; routine GREATER-0 tests numeric subtraction ; result but also needlessly tests the string ; value for zero - it must be. POP AF ; pop A RRCA ; the third RRCA - test for '<=', '>=' or '<>'. CALL NC,L3501 ; apply a terminal NOT if so. RET ; return. ; ------------------------------------ ; THE 'STRING CONCATENATION' OPERATION ; ------------------------------------ ; (offset: $17 'strs-add') ; 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 L359C: CALL L2BF1 ; routine STK-FETCH fetches string parameters ; and deletes calculator stack entry. PUSH DE ; save start address. PUSH BC ; and length. CALL L2BF1 ; routine 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 30H ; BC-SPACES in workspace. ; DE points to start of space. CALL L2AB2 ; routine STK-STO-$ 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,L35B7 ; to OTHER-STR if null string LDIR ; copy string to workspace. ;; OTHER-STR L35B7: POP BC ; now second length POP HL ; and start of string LD A,B ; test this one OR C ; for zero length JR Z,L35BF ; skip forward to STK-PNTRS if so as complete. LDIR ; else copy the bytes. ; and continue into next routine which ; sets the calculator stack pointers. ; ----------------------------------- ; THE 'SET STACK POINTERS' SUBROUTINE ; ----------------------------------- ; 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 and READ-IN routines for ; the same reason and to initialize the calculator stack at the start of ; the CALCULATE routine. ;; STK-PNTRS L35BF: LD HL,($5C65) ; fetch STKEND value from system variable. LD DE,$FFFB ; the value -5 PUSH HL ; push STKEND value. ADD HL,DE ; subtract 5 from HL. POP DE ; pop STKEND to DE. RET ; return. ; ------------------- ; THE 'CHR$' FUNCTION ; ------------------- ; (offset: $2f 'chr$') ; 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$ 65 = "A". ;; chrs L35C9: CALL L2DD5 ; routine FP-TO-A puts the number in A. JR C,L35DC ; forward to REPORT-Bd if overflow JR NZ,L35DC ; forward to REPORT-Bd if negative PUSH AF ; save the argument. LD BC,$0001 ; one space required. RST 30H ; BC-SPACES makes DE point to start POP AF ; restore the number. LD (DE),A ; and store in workspace CALL L2AB2 ; routine STK-STO-$ stacks descriptor. EX DE,HL ; make HL point to result and DE to STKEND. RET ; return. ; --- ;; REPORT-Bd L35DC: RST 08H ; ERROR-1 DEFB $0A ; Error Report: Integer out of range ; ---------------------------- ; THE 'VAL and VAL$' FUNCTIONS ; ---------------------------- ; (offset: $1d 'val') ; (offset: $18 'val$') ; 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$ treats the characters in a string as a string expression. ; e.g. VAL$ (z$+"(2)") = a$(2) if z$ happens to be "a$". ;; val ;; val$ L35DE: LD HL,($5C5D) ; fetch value of system variable CH_ADD PUSH HL ; and save on the machine stack. LD A,B ; fetch the literal (either $1D or $18). ADD A,$E3 ; add $E3 to form $00 (setting carry) or $FB. SBC A,A ; now form $FF bit 6 = numeric result ; or $00 bit 6 = string result. PUSH AF ; save this mask on the stack CALL L2BF1 ; routine 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 30H ; BC-SPACES creates the space in workspace. POP HL ; restore start of string to HL. LD ($5C5D),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),$0D ; insert a carriage return at end. RES 7,(IY+$01) ; update FLAGS - signal checking syntax. CALL L24FB ; routine SCANNING evaluates string ; expression and result. RST 18H ; GET-CHAR fetches next character. CP $0D ; is it the expected carriage return ? JR NZ,L360C ; forward to V-RPORT-C if not ; 'Nonsense in BASIC'. POP HL ; restore start of string in workspace. POP AF ; restore expected result flag (bit 6). XOR (IY+$01) ; xor with FLAGS now updated by SCANNING. AND $40 ; test bit 6 - should be zero if result types ; match. ;; V-RPORT-C L360C: JP NZ,L1C8A ; jump back to REPORT-C with a result mismatch. LD ($5C5D),HL ; set CH_ADD to the start of the string again. SET 7,(IY+$01) ; update FLAGS - signal running program. CALL L24FB ; routine SCANNING evaluates the string ; in full leaving result on calculator stack. POP HL ; restore saved character address in program. LD ($5C5D),HL ; and reset the system variable CH_ADD. JR L35BF ; back to exit via STK-PNTRS. ; resetting the calculator stack pointers ; HL and DE from STKEND as it wasn't possible ; to preserve them during this routine. ; ------------------- ; THE 'STR$' FUNCTION ; ------------------- ; (offset: $2e 'str$') ; This function produces a string comprising the characters that would appear ; if the numeric argument were printed. ; e.g. STR$ (1/10) produces "0.1". ;; str$ L361F: LD BC,$0001 ; create an initial byte in workspace RST 30H ; using BC-SPACES restart. LD ($5C5B),HL ; set system variable K_CUR to new location. PUSH HL ; and save start on machine stack also. LD HL,($5C51) ; fetch value of system variable CURCHL PUSH HL ; and save that too. LD A,$FF ; select system channel 'R'. CALL L1601 ; routine CHAN-OPEN opens it. CALL L2DE3 ; routine PRINT-FP outputs the number to ; workspace updating K-CUR. POP HL ; restore current channel. CALL L1615 ; routine CHAN-FLAG resets flags. POP DE ; fetch saved start of string to DE. LD HL,($5C5B) ; load HL with end of string from K_CUR. AND A ; prepare for true subtraction. SBC HL,DE ; subtract start from end to give length. LD B,H ; transfer the length to LD C,L ; the BC register pair. CALL L2AB2 ; routine STK-STO-$ stores string parameters ; on the calculator stack. EX DE,HL ; HL = last value, DE = STKEND. RET ; return. ; ------------------------ ; THE 'READ-IN' SUBROUTINE ; ------------------------ ; (offset: $1a 'read-in') ; This is the calculator literal used by the INKEY$ function when a '#' ; is encountered after the keyword. ; INKEY$ # does not interact correctly with the keyboard, #0 or #1, and ; its uses are for other channels. ;; read-in L3645: CALL L1E94 ; routine FIND-INT1 fetches stream to A CP $10 ; compare with 16 decimal. JP NC,L1E9F ; JUMP to REPORT-Bb if not in range 0 - 15. ; 'Integer out of range' ; (REPORT-Bd is within range) LD HL,($5C51) ; fetch current channel CURCHL PUSH HL ; save it CALL L1601 ; routine CHAN-OPEN opens channel CALL L15E6 ; routine INPUT-AD - the channel must have an ; input stream or else error here from stream ; stub. LD BC,$0000 ; initialize length of string to zero JR NC,L365F ; forward to R-I-STORE if no key detected. INC C ; increase length to one. RST 30H ; BC-SPACES creates space for one character ; in workspace. LD (DE),A ; the character is inserted. ;; R-I-STORE L365F: CALL L2AB2 ; routine STK-STO-$ stacks the string ; parameters. POP HL ; restore current channel address CALL L1615 ; routine CHAN-FLAG resets current channel ; system variable and flags. JP L35BF ; jump back to STK-PNTRS ; ------------------- ; THE 'CODE' FUNCTION ; ------------------- ; (offset: $1c 'code') ; Returns the ASCII code of a character or first character of a string ; e.g. CODE "Aardvark" = 65, CODE "" = 0. ;; code L3669: CALL L2BF1 ; routine STK-FETCH to 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,L3671 ; skip to STK-CODE with zero if the null string. LD A,(DE) ; else fetch the first character. ;; STK-CODE L3671: JP L2D28 ; jump back to STACK-A (with memory check) ; ------------------ ; THE 'LEN' FUNCTION ; ------------------ ; (offset: $1e '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 L3674: CALL L2BF1 ; Routine STK-FETCH to fetch and delete the ; string parameters from the calculator stack. ; Register BC now holds the length of string. JP L2D2B ; Jump back to STACK-BC to save result on the ; calculator stack (with memory check). ; ------------------------------------- ; THE 'DECREASE THE COUNTER' SUBROUTINE ; ------------------------------------- ; (offset: $35 '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 L367A: EXX ; switch in set that addresses code PUSH HL ; save pointer to offset byte LD HL,$5C67 ; address BREG in system variables DEC (HL) ; decrement it POP HL ; restore pointer JR NZ,L3687 ; 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+$2d) - three bytes instead of six. ; --------------------- ; THE 'JUMP' SUBROUTINE ; --------------------- ; (offset: $33 'jump') ; This enables the calculator to perform relative jumps just like the Z80 ; chip's JR instruction. ;; jump ;; JUMP L3686: EXX ; switch in pointer set ;; JUMP-2 L3687: LD E,(HL) ; the jump byte 0-127 forward, 128-255 back. LD A,E ; transfer to accumulator. RLA ; if backward jump, carry is set. SBC A,A ; will be $FF if backward or $00 if forward. LD D,A ; transfer to high byte. ADD HL,DE ; advance calculator pointer forward or back. EXX ; switch back. RET ; return. ; -------------------------- ; THE 'JUMP-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. ;; jump-true L368F: INC DE ; Collect the INC DE ; third byte LD A,(DE) ; of the test DEC DE ; result and DEC DE ; backtrack. AND A ; Is result 0 or 1 ? JR NZ,L3686 ; 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 'END-CALC' SUBROUTINE ; ------------------------- ; (offset: $38 'end-calc') ; The end-calc literal terminates a mini-program written in the Spectrum's ; internal language. ;; end-calc L369B: 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 ; Switch back to main set. RET ; Return. ; ------------------------ ; THE 'MODULUS' SUBROUTINE ; ------------------------ ; (offset: $32 'n-mod-m') ; (n1,n2 -- r,q) ; Similar to FORTH's 'divide mod' /MOD ; On the Spectrum, this is only used internally by the RND function and could ; have been implemented inline. On the ZX81, this calculator routine was also ; used by PRINT-FP. ;; n-mod-m L36A0: RST 28H ;; FP-CALC 17, 3. DEFB $C0 ;;st-mem-0 17, 3. DEFB $02 ;;delete 17. DEFB $31 ;;duplicate 17, 17. DEFB $E0 ;;get-mem-0 17, 17, 3. DEFB $05 ;;division 17, 17/3. DEFB $27 ;;int 17, 5. DEFB $E0 ;;get-mem-0 17, 5, 3. DEFB $01 ;;exchange 17, 3, 5. DEFB $C0 ;;st-mem-0 17, 3, 5. DEFB $04 ;;multiply 17, 15. DEFB $03 ;;subtract 2. DEFB $E0 ;;get-mem-0 2, 5. DEFB $38 ;;end-calc 2, 5. RET ; return. ; ------------------ ; THE 'INT' FUNCTION ; ------------------ ; (offset $27: '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, +-3.4 as examples. ;; int L36AF: RST 28H ;; FP-CALC x. (= 3.4 or -3.4). DEFB $31 ;;duplicate x, x. DEFB $36 ;;less-0 x, (1/0) DEFB $00 ;;jump-true x, (1/0) DEFB $04 ;;to L36B7, X-NEG DEFB $3A ;;truncate trunc 3.4 = 3. DEFB $38 ;;end-calc 3. RET ; return with + int x on stack. ; --- ;; X-NEG L36B7: DEFB $31 ;;duplicate -3.4, -3.4. DEFB $3A ;;truncate -3.4, -3. DEFB $C0 ;;st-mem-0 -3.4, -3. DEFB $03 ;;subtract -.4 DEFB $E0 ;;get-mem-0 -.4, -3. DEFB $01 ;;exchange -3, -.4. DEFB $30 ;;not -3, (0). DEFB $00 ;;jump-true -3. DEFB $03 ;;to L36C2, EXIT -3. DEFB $A1 ;;stk-one -3, 1. DEFB $03 ;;subtract -4. ;; EXIT L36C2: DEFB $38 ;;end-calc -4. RET ; return. ; ------------------ ; THE 'EXP' FUNCTION ; ------------------ ; (offset $26: 'exp') ; The exponential function EXP x is equal to e^x, where e is the mathematical ; name for a number approximated to 2.718281828. ; ERROR 6 if argument is more than about 88. ;; EXP ;; exp L36C4: RST 28H ;; FP-CALC DEFB $3D ;;re-stack (not required - mult will do) DEFB $34 ;;stk-data DEFB $F1 ;;Exponent: $81, Bytes: 4 DEFB $38,$AA,$3B,$29 ;; DEFB $04 ;;multiply DEFB $31 ;;duplicate DEFB $27 ;;int DEFB $C3 ;;st-mem-3 DEFB $03 ;;subtract DEFB $31 ;;duplicate DEFB $0F ;;addition DEFB $A1 ;;stk-one DEFB $03 ;;subtract DEFB $88 ;;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 $38 ;;end-calc CALL L2DD5 ; routine FP-TO-A JR NZ,L3705 ; to N-NEGTV JR C,L3703 ; to REPORT-6b ; 'Number too big' ADD A,(HL) ; JR NC,L370C ; to RESULT-OK ;; REPORT-6b L3703: RST 08H ; ERROR-1 DEFB $05 ; Error Report: Number too big ; --- ;; N-NEGTV L3705: JR C,L370E ; to RSLT-ZERO SUB (HL) ; JR NC,L370E ; to RSLT-ZERO NEG ; Negate ;; RESULT-OK L370C: LD (HL),A ; RET ; return. ; --- ;; RSLT-ZERO L370E: RST 28H ;; FP-CALC DEFB $02 ;;delete DEFB $A0 ;;stk-zero DEFB $38 ;;end-calc RET ; return. ; -------------------------------- ; THE 'NATURAL LOGARITHM' FUNCTION ; -------------------------------- ; (offset $25: 'ln') ; Function to calculate the natural logarithm (to the base e ). ; Natural logarithms 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 machines ; like the ZX Spectrum 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. ; Error A if the argument is 0 or negative. ;; ln L3713: RST 28H ;; FP-CALC DEFB $3D ;;re-stack DEFB $31 ;;duplicate DEFB $37 ;;greater-0 DEFB $00 ;;jump-true DEFB $04 ;;to L371C, VALID DEFB $38 ;;end-calc ;; REPORT-Ab L371A: RST 08H ; ERROR-1 DEFB $09 ; Error Report: Invalid argument ;; VALID L371C: DEFB $A0 ;;stk-zero Note. not DEFB $02 ;;delete necessary. DEFB $38 ;;end-calc LD A,(HL) ; LD (HL),$80 ; CALL L2D28 ; routine STACK-A RST 28H ;; FP-CALC DEFB $34 ;;stk-data DEFB $38 ;;Exponent: $88, Bytes: 1 DEFB $00 ;;(+00,+00,+00) DEFB $03 ;;subtract DEFB $01 ;;exchange DEFB $31 ;;duplicate DEFB $34 ;;stk-data DEFB $F0 ;;Exponent: $80, Bytes: 4 DEFB $4C,$CC,$CC,$CD ;; DEFB $03 ;;subtract DEFB $37 ;;greater-0 DEFB $00 ;;jump-true DEFB $08 ;;to L373D, GRE.8 DEFB $01 ;;exchange DEFB $A1 ;;stk-one DEFB $03 ;;subtract DEFB $01 ;;exchange DEFB $38 ;;end-calc INC (HL) ; RST 28H ;; FP-CALC ;; GRE.8 L373D: DEFB $01 ;;exchange DEFB $34 ;;stk-data DEFB $F0 ;;Exponent: $80, Bytes: 4 DEFB $31,$72,$17,$F8 ;; DEFB $04 ;;multiply DEFB $01 ;;exchange DEFB $A2 ;;stk-half DEFB $03 ;;subtract DEFB $A2 ;;stk-half DEFB $03 ;;subtract DEFB $31 ;;duplicate DEFB $34 ;;stk-data DEFB $32 ;;Exponent: $82, Bytes: 1 DEFB $20 ;;(+00,+00,+00) DEFB $04 ;;multiply DEFB $A2 ;;stk-half DEFB $03 ;;subtract DEFB $8C ;;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 $04 ;;multiply DEFB $0F ;;addition DEFB $38 ;;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 $39: '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 L3783: RST 28H ;; FP-CALC X. DEFB $3D ;;re-stack (not rquired done by mult) DEFB $34 ;;stk-data DEFB $EE ;;Exponent: $7E, ;;Bytes: 4 DEFB $22,$F9,$83,$6E ;; X, 1/(2*PI) DEFB $04 ;;multiply X/(2*PI) = fraction DEFB $31 ;;duplicate DEFB $A2 ;;stk-half DEFB $0F ;;addition DEFB $27 ;;int DEFB $03 ;;subtract now range -.5 to .5 DEFB $31 ;;duplicate DEFB $0F ;;addition now range -1 to 1. DEFB $31 ;;duplicate DEFB $0F ;;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 $31 ;;duplicate Y, Y. DEFB $2A ;;abs Y, abs(Y). range 1 to 2 DEFB $A1 ;;stk-one Y, abs(Y), 1. DEFB $03 ;;subtract Y, abs(Y)-1. range 0 to 1 DEFB $31 ;;duplicate Y, Z, Z. DEFB $37 ;;greater-0 Y, Z, (1/0). DEFB $C0 ;;st-mem-0 store as possible sign ;; for cosine function. DEFB $00 ;;jump-true DEFB $04 ;;to L37A1, ZPLUS with quadrants II and III. ; else the angle lies in quadrant I or IV and value Y is already correct. DEFB $02 ;;delete Y. delete the test value. DEFB $38 ;;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. ;; ZPLUS L37A1: DEFB $A1 ;;stk-one Y, Z, 1. DEFB $03 ;;subtract Y, Z-1. Q3 = 0 to -1 DEFB $01 ;;exchange Z-1, Y. DEFB $36 ;;less-0 Z-1, (1/0). DEFB $00 ;;jump-true Z-1. DEFB $02 ;;to L37A8, YNEG ;;if angle in quadrant III ; else angle is within quadrant II (-1 to 0) DEFB $1B ;;negate range +1 to 0. ;; YNEG L37A8: DEFB $38 ;;end-calc quadrants II and III correct. RET ; return. ; --------------------- ; THE 'COSINE' FUNCTION ; --------------------- ; (offset $20: '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 L37AA: RST 28H ;; FP-CALC angle in radians. DEFB $39 ;;get-argt X reduce -1 to +1 DEFB $2A ;;abs ABS X. 0 to 1 DEFB $A1 ;;stk-one ABS X, 1. DEFB $03 ;;subtract now opposite angle ;; although sign is -ve. DEFB $E0 ;;get-mem-0 fetch the sign indicator DEFB $00 ;;jump-true DEFB $06 ;;fwd to L37B7, C-ENT ;;forward to common code if in QII or QIII. DEFB $1B ;;negate else make sign +ve. DEFB $33 ;;jump DEFB $03 ;;fwd to L37B7, C-ENT ;; with quadrants I and IV. ; ------------------- ; THE 'SINE' FUNCTION ; ------------------- ; (offset $1F: '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 L37B5: RST 28H ;; FP-CALC angle in radians DEFB $39 ;;get-argt reduce - sign now correct. ;; C-ENT L37B7: DEFB $31 ;;duplicate DEFB $31 ;;duplicate DEFB $04 ;;multiply DEFB $31 ;;duplicate DEFB $0F ;;addition DEFB $A1 ;;stk-one DEFB $03 ;;subtract DEFB $86 ;;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 $04 ;;multiply DEFB $38 ;;end-calc RET ; return. ; ---------------------- ; THE 'TANGENT' FUNCTION ; ---------------------- ; (offset $21: '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 L37DA: RST 28H ;; FP-CALC x. DEFB $31 ;;duplicate x, x. DEFB $1F ;;sin x, sin x. DEFB $01 ;;exchange sin x, x. DEFB $20 ;;cos sin x, cos x. DEFB $05 ;;division sin x/cos x (= tan x). DEFB $38 ;;end-calc tan x. RET ; return. ; --------------------- ; THE 'ARCTAN' FUNCTION ; --------------------- ; (Offset $24: '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 L37E2: CALL L3297 ; routine re-stack LD A,(HL) ; fetch exponent byte. CP $81 ; compare to that for 'one' JR C,L37F8 ; forward, if less, to SMALL RST 28H ;; FP-CALC DEFB $A1 ;;stk-one DEFB $1B ;;negate DEFB $01 ;;exchange DEFB $05 ;;division DEFB $31 ;;duplicate DEFB $36 ;;less-0 DEFB $A3 ;;stk-pi/2 DEFB $01 ;;exchange DEFB $00 ;;jump-true DEFB $06 ;;to L37FA, CASES DEFB $1B ;;negate DEFB $33 ;;jump DEFB $03 ;;to L37FA, CASES ;; SMALL L37F8: RST 28H ;; FP-CALC DEFB $A0 ;;stk-zero ;; CASES L37FA: DEFB $01 ;;exchange DEFB $31 ;;duplicate DEFB $31 ;;duplicate DEFB $04 ;;multiply DEFB $31 ;;duplicate DEFB $0F ;;addition DEFB $A1 ;;stk-one DEFB $03 ;;subtract DEFB $8C ;;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 $04 ;;multiply DEFB $0F ;;addition DEFB $38 ;;end-calc RET ; return. ; --------------------- ; THE 'ARCSIN' FUNCTION ; --------------------- ; (Offset $22: '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 ; ; GEOMETRIC PROOF. ; ; ; . /| ; . 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+c = 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 L3833: RST 28H ;; FP-CALC x. DEFB $31 ;;duplicate x, x. DEFB $31 ;;duplicate x, x, x. DEFB $04 ;;multiply x, x*x. DEFB $A1 ;;stk-one x, x*x, 1. DEFB $03 ;;subtract x, x*x-1. DEFB $1B ;;negate x, 1-x*x. DEFB $28 ;;sqr x, sqr(1-x*x) = y DEFB $A1 ;;stk-one x, y, 1. DEFB $0F ;;addition x, y+1. DEFB $05 ;;division x/y+1. DEFB $24 ;;atn a/2 (half the angle) DEFB $31 ;;duplicate a/2, a/2. DEFB $0F ;;addition a. DEFB $38 ;;end-calc a. RET ; return. ; --------------------- ; THE 'ARCCOS' FUNCTION ; --------------------- ; (Offset $23: '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 L3843: RST 28H ;; FP-CALC x. DEFB $22 ;;asn asn(x). DEFB $A3 ;;stk-pi/2 asn(x), pi/2. DEFB $03 ;;subtract asn(x) - pi/2. DEFB $1B ;;negate pi/2 -asn(x) = acs(x). DEFB $38 ;;end-calc acs(x). RET ; return. ; -------------------------- ; THE 'SQUARE ROOT' FUNCTION ; -------------------------- ; (Offset $28: 'sqr') ; This routine is remarkable for its brevity - 7 bytes. ; It wasn't written here but in the ZX81 where the programmers had to squeeze ; a bulky operating system into an 8K ROM. It simply calculates ; the square root by stacking the value .5 and continuing into the 'to-power' ; routine. With more space available the much faster Newton-Raphson method ; could have been used as on the Jupiter Ace. ;; sqr L384A: RST 28H ;; FP-CALC DEFB $31 ;;duplicate DEFB $30 ;;not DEFB $00 ;;jump-true DEFB $1E ;;to L386C, LAST DEFB $A2 ;;stk-half DEFB $38 ;;end-calc ; ------------------------------ ; 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 L3851: RST 28H ;; FP-CALC X, Y. DEFB $01 ;;exchange Y, X. DEFB $31 ;;duplicate Y, X, X. DEFB $30 ;;not Y, X, (1/0). DEFB $00 ;;jump-true DEFB $07 ;;to L385D, XIS0 if X is zero. ; else X is non-zero. Function 'ln' will catch a negative value of X. DEFB $25 ;;ln Y, LN X. DEFB $04 ;;multiply Y * LN X. DEFB $38 ;;end-calc JP L36C4 ; 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. ;; XIS0 L385D: DEFB $02 ;;delete Y. DEFB $31 ;;duplicate Y, Y. DEFB $30 ;;not Y, (1/0). DEFB $00 ;;jump-true DEFB $09 ;;to L386A, ONE if Y is zero. DEFB $A0 ;;stk-zero Y, 0. DEFB $01 ;;exchange 0, Y. DEFB $37 ;;greater-0 0, (1/0). DEFB $00 ;;jump-true 0. DEFB $06 ;;to L386C, 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 $A1 ;;stk-one 0, 1. DEFB $01 ;;exchange 1, 0. DEFB $05 ;;division 1/0 ouch! ; --- ;; ONE L386A: DEFB $02 ;;delete . DEFB $A1 ;;stk-one 1. ;; LAST L386C: DEFB $38 ;;end-calc last value is 1 or 0. RET ; return. ; "Everything should be made as simple as possible, but not simpler" ; - Albert Einstein, 1879-1955. ; --------------------- ; THE 'SPARE' LOCATIONS ; --------------------- ;; spare L386E: DEFB $FF, $FF ; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; DEFB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF; ORG $3D00 ; ------------------------------- ; THE 'ZX SPECTRUM CHARACTER SET' ; ------------------------------- ;; char-set ; $20 - Character: ' ' CHR$(32) L3D00: DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 ; $21 - Character: '!' CHR$(33) DEFB %00000000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00000000 DEFB %00010000 DEFB %00000000 ; $22 - Character: '"' CHR$(34) DEFB %00000000 DEFB %00100100 DEFB %00100100 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 ; $23 - Character: '#' CHR$(35) DEFB %00000000 DEFB %00100100 DEFB %01111110 DEFB %00100100 DEFB %00100100 DEFB %01111110 DEFB %00100100 DEFB %00000000 ; $24 - Character: '$' CHR$(36) DEFB %00000000 DEFB %00001000 DEFB %00111110 DEFB %00101000 DEFB %00111110 DEFB %00001010 DEFB %00111110 DEFB %00001000 ; $25 - Character: '%' CHR$(37) DEFB %00000000 DEFB %01100010 DEFB %01100100 DEFB %00001000 DEFB %00010000 DEFB %00100110 DEFB %01000110 DEFB %00000000 ; $26 - Character: '&' CHR$(38) DEFB %00000000 DEFB %00010000 DEFB %00101000 DEFB %00010000 DEFB %00101010 DEFB %01000100 DEFB %00111010 DEFB %00000000 ; $27 - Character: ''' CHR$(39) DEFB %00000000 DEFB %00001000 DEFB %00010000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 ; $28 - Character: '(' CHR$(40) DEFB %00000000 DEFB %00000100 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00000100 DEFB %00000000 ; $29 - Character: ')' CHR$(41) DEFB %00000000 DEFB %00100000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00100000 DEFB %00000000 ; $2A - Character: '*' CHR$(42) DEFB %00000000 DEFB %00000000 DEFB %00010100 DEFB %00001000 DEFB %00111110 DEFB %00001000 DEFB %00010100 DEFB %00000000 ; $2B - Character: '+' CHR$(43) DEFB %00000000 DEFB %00000000 DEFB %00001000 DEFB %00001000 DEFB %00111110 DEFB %00001000 DEFB %00001000 DEFB %00000000 ; $2C - Character: ',' CHR$(44) DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00001000 DEFB %00001000 DEFB %00010000 ; $2D - Character: '-' CHR$(45) DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00111110 DEFB %00000000 DEFB %00000000 DEFB %00000000 ; $2E - Character: '.' CHR$(46) DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00011000 DEFB %00011000 DEFB %00000000 ; $2F - Character: '/' CHR$(47) DEFB %00000000 DEFB %00000000 DEFB %00000010 DEFB %00000100 DEFB %00001000 DEFB %00010000 DEFB %00100000 DEFB %00000000 ; $30 - Character: '0' CHR$(48) DEFB %00000000 DEFB %00111100 DEFB %01000110 DEFB %01001010 DEFB %01010010 DEFB %01100010 DEFB %00111100 DEFB %00000000 ; $31 - Character: '1' CHR$(49) DEFB %00000000 DEFB %00011000 DEFB %00101000 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00111110 DEFB %00000000 ; $32 - Character: '2' CHR$(50) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %00000010 DEFB %00111100 DEFB %01000000 DEFB %01111110 DEFB %00000000 ; $33 - Character: '3' CHR$(51) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %00001100 DEFB %00000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $34 - Character: '4' CHR$(52) DEFB %00000000 DEFB %00001000 DEFB %00011000 DEFB %00101000 DEFB %01001000 DEFB %01111110 DEFB %00001000 DEFB %00000000 ; $35 - Character: '5' CHR$(53) DEFB %00000000 DEFB %01111110 DEFB %01000000 DEFB %01111100 DEFB %00000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $36 - Character: '6' CHR$(54) DEFB %00000000 DEFB %00111100 DEFB %01000000 DEFB %01111100 DEFB %01000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $37 - Character: '7' CHR$(55) DEFB %00000000 DEFB %01111110 DEFB %00000010 DEFB %00000100 DEFB %00001000 DEFB %00010000 DEFB %00010000 DEFB %00000000 ; $38 - Character: '8' CHR$(56) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %00111100 DEFB %01000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $39 - Character: '9' CHR$(57) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %01000010 DEFB %00111110 DEFB %00000010 DEFB %00111100 DEFB %00000000 ; $3A - Character: ':' CHR$(58) DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00010000 DEFB %00000000 DEFB %00000000 DEFB %00010000 DEFB %00000000 ; $3B - Character: ';' CHR$(59) DEFB %00000000 DEFB %00000000 DEFB %00010000 DEFB %00000000 DEFB %00000000 DEFB %00010000 DEFB %00010000 DEFB %00100000 ; $3C - Character: '<' CHR$(60) DEFB %00000000 DEFB %00000000 DEFB %00000100 DEFB %00001000 DEFB %00010000 DEFB %00001000 DEFB %00000100 DEFB %00000000 ; $3D - Character: '=' CHR$(61) DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00111110 DEFB %00000000 DEFB %00111110 DEFB %00000000 DEFB %00000000 ; $3E - Character: '>' CHR$(62) DEFB %00000000 DEFB %00000000 DEFB %00010000 DEFB %00001000 DEFB %00000100 DEFB %00001000 DEFB %00010000 DEFB %00000000 ; $3F - Character: '?' CHR$(63) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %00000100 DEFB %00001000 DEFB %00000000 DEFB %00001000 DEFB %00000000 ; $40 - Character: '@' CHR$(64) DEFB %00000000 DEFB %00111100 DEFB %01001010 DEFB %01010110 DEFB %01011110 DEFB %01000000 DEFB %00111100 DEFB %00000000 ; $41 - Character: 'A' CHR$(65) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %01000010 DEFB %01111110 DEFB %01000010 DEFB %01000010 DEFB %00000000 ; $42 - Character: 'B' CHR$(66) DEFB %00000000 DEFB %01111100 DEFB %01000010 DEFB %01111100 DEFB %01000010 DEFB %01000010 DEFB %01111100 DEFB %00000000 ; $43 - Character: 'C' CHR$(67) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %01000000 DEFB %01000000 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $44 - Character: 'D' CHR$(68) DEFB %00000000 DEFB %01111000 DEFB %01000100 DEFB %01000010 DEFB %01000010 DEFB %01000100 DEFB %01111000 DEFB %00000000 ; $45 - Character: 'E' CHR$(69) DEFB %00000000 DEFB %01111110 DEFB %01000000 DEFB %01111100 DEFB %01000000 DEFB %01000000 DEFB %01111110 DEFB %00000000 ; $46 - Character: 'F' CHR$(70) DEFB %00000000 DEFB %01111110 DEFB %01000000 DEFB %01111100 DEFB %01000000 DEFB %01000000 DEFB %01000000 DEFB %00000000 ; $47 - Character: 'G' CHR$(71) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %01000000 DEFB %01001110 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $48 - Character: 'H' CHR$(72) DEFB %00000000 DEFB %01000010 DEFB %01000010 DEFB %01111110 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %00000000 ; $49 - Character: 'I' CHR$(73) DEFB %00000000 DEFB %00111110 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00111110 DEFB %00000000 ; $4A - Character: 'J' CHR$(74) DEFB %00000000 DEFB %00000010 DEFB %00000010 DEFB %00000010 DEFB %01000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $4B - Character: 'K' CHR$(75) DEFB %00000000 DEFB %01000100 DEFB %01001000 DEFB %01110000 DEFB %01001000 DEFB %01000100 DEFB %01000010 DEFB %00000000 ; $4C - Character: 'L' CHR$(76) DEFB %00000000 DEFB %01000000 DEFB %01000000 DEFB %01000000 DEFB %01000000 DEFB %01000000 DEFB %01111110 DEFB %00000000 ; $4D - Character: 'M' CHR$(77) DEFB %00000000 DEFB %01000010 DEFB %01100110 DEFB %01011010 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %00000000 ; $4E - Character: 'N' CHR$(78) DEFB %00000000 DEFB %01000010 DEFB %01100010 DEFB %01010010 DEFB %01001010 DEFB %01000110 DEFB %01000010 DEFB %00000000 ; $4F - Character: 'O' CHR$(79) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $50 - Character: 'P' CHR$(80) DEFB %00000000 DEFB %01111100 DEFB %01000010 DEFB %01000010 DEFB %01111100 DEFB %01000000 DEFB %01000000 DEFB %00000000 ; $51 - Character: 'Q' CHR$(81) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %01000010 DEFB %01010010 DEFB %01001010 DEFB %00111100 DEFB %00000000 ; $52 - Character: 'R' CHR$(82) DEFB %00000000 DEFB %01111100 DEFB %01000010 DEFB %01000010 DEFB %01111100 DEFB %01000100 DEFB %01000010 DEFB %00000000 ; $53 - Character: 'S' CHR$(83) DEFB %00000000 DEFB %00111100 DEFB %01000000 DEFB %00111100 DEFB %00000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $54 - Character: 'T' CHR$(84) DEFB %00000000 DEFB %11111110 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00000000 ; $55 - Character: 'U' CHR$(85) DEFB %00000000 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $56 - Character: 'V' CHR$(86) DEFB %00000000 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %00100100 DEFB %00011000 DEFB %00000000 ; $57 - Character: 'W' CHR$(87) DEFB %00000000 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %01011010 DEFB %00100100 DEFB %00000000 ; $58 - Character: 'X' CHR$(88) DEFB %00000000 DEFB %01000010 DEFB %00100100 DEFB %00011000 DEFB %00011000 DEFB %00100100 DEFB %01000010 DEFB %00000000 ; $59 - Character: 'Y' CHR$(89) DEFB %00000000 DEFB %10000010 DEFB %01000100 DEFB %00101000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00000000 ; $5A - Character: 'Z' CHR$(90) DEFB %00000000 DEFB %01111110 DEFB %00000100 DEFB %00001000 DEFB %00010000 DEFB %00100000 DEFB %01111110 DEFB %00000000 ; $5B - Character: '[' CHR$(91) DEFB %00000000 DEFB %00001110 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00001110 DEFB %00000000 ; $5C - Character: '\' CHR$(92) DEFB %00000000 DEFB %00000000 DEFB %01000000 DEFB %00100000 DEFB %00010000 DEFB %00001000 DEFB %00000100 DEFB %00000000 ; $5D - Character: ']' CHR$(93) DEFB %00000000 DEFB %01110000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %01110000 DEFB %00000000 ; $5E - Character: '^' CHR$(94) DEFB %00000000 DEFB %00010000 DEFB %00111000 DEFB %01010100 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00000000 ; $5F - Character: '_' CHR$(95) DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %11111111 ; $60 - Character: ' £ ' CHR$(96) DEFB %00000000 DEFB %00011100 DEFB %00100010 DEFB %01111000 DEFB %00100000 DEFB %00100000 DEFB %01111110 DEFB %00000000 ; $61 - Character: 'a' CHR$(97) DEFB %00000000 DEFB %00000000 DEFB %00111000 DEFB %00000100 DEFB %00111100 DEFB %01000100 DEFB %00111100 DEFB %00000000 ; $62 - Character: 'b' CHR$(98) DEFB %00000000 DEFB %00100000 DEFB %00100000 DEFB %00111100 DEFB %00100010 DEFB %00100010 DEFB %00111100 DEFB %00000000 ; $63 - Character: 'c' CHR$(99) DEFB %00000000 DEFB %00000000 DEFB %00011100 DEFB %00100000 DEFB %00100000 DEFB %00100000 DEFB %00011100 DEFB %00000000 ; $64 - Character: 'd' CHR$(100) DEFB %00000000 DEFB %00000100 DEFB %00000100 DEFB %00111100 DEFB %01000100 DEFB %01000100 DEFB %00111100 DEFB %00000000 ; $65 - Character: 'e' CHR$(101) DEFB %00000000 DEFB %00000000 DEFB %00111000 DEFB %01000100 DEFB %01111000 DEFB %01000000 DEFB %00111100 DEFB %00000000 ; $66 - Character: 'f' CHR$(102) DEFB %00000000 DEFB %00001100 DEFB %00010000 DEFB %00011000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00000000 ; $67 - Character: 'g' CHR$(103) DEFB %00000000 DEFB %00000000 DEFB %00111100 DEFB %01000100 DEFB %01000100 DEFB %00111100 DEFB %00000100 DEFB %00111000 ; $68 - Character: 'h' CHR$(104) DEFB %00000000 DEFB %01000000 DEFB %01000000 DEFB %01111000 DEFB %01000100 DEFB %01000100 DEFB %01000100 DEFB %00000000 ; $69 - Character: 'i' CHR$(105) DEFB %00000000 DEFB %00010000 DEFB %00000000 DEFB %00110000 DEFB %00010000 DEFB %00010000 DEFB %00111000 DEFB %00000000 ; $6A - Character: 'j' CHR$(106) DEFB %00000000 DEFB %00000100 DEFB %00000000 DEFB %00000100 DEFB %00000100 DEFB %00000100 DEFB %00100100 DEFB %00011000 ; $6B - Character: 'k' CHR$(107) DEFB %00000000 DEFB %00100000 DEFB %00101000 DEFB %00110000 DEFB %00110000 DEFB %00101000 DEFB %00100100 DEFB %00000000 ; $6C - Character: 'l' CHR$(108) DEFB %00000000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00001100 DEFB %00000000 ; $6D - Character: 'm' CHR$(109) DEFB %00000000 DEFB %00000000 DEFB %01101000 DEFB %01010100 DEFB %01010100 DEFB %01010100 DEFB %01010100 DEFB %00000000 ; $6E - Character: 'n' CHR$(110) DEFB %00000000 DEFB %00000000 DEFB %01111000 DEFB %01000100 DEFB %01000100 DEFB %01000100 DEFB %01000100 DEFB %00000000 ; $6F - Character: 'o' CHR$(111) DEFB %00000000 DEFB %00000000 DEFB %00111000 DEFB %01000100 DEFB %01000100 DEFB %01000100 DEFB %00111000 DEFB %00000000 ; $70 - Character: 'p' CHR$(112) DEFB %00000000 DEFB %00000000 DEFB %01111000 DEFB %01000100 DEFB %01000100 DEFB %01111000 DEFB %01000000 DEFB %01000000 ; $71 - Character: 'q' CHR$(113) DEFB %00000000 DEFB %00000000 DEFB %00111100 DEFB %01000100 DEFB %01000100 DEFB %00111100 DEFB %00000100 DEFB %00000110 ; $72 - Character: 'r' CHR$(114) DEFB %00000000 DEFB %00000000 DEFB %00011100 DEFB %00100000 DEFB %00100000 DEFB %00100000 DEFB %00100000 DEFB %00000000 ; $73 - Character: 's' CHR$(115) DEFB %00000000 DEFB %00000000 DEFB %00111000 DEFB %01000000 DEFB %00111000 DEFB %00000100 DEFB %01111000 DEFB %00000000 ; $74 - Character: 't' CHR$(116) DEFB %00000000 DEFB %00010000 DEFB %00111000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00001100 DEFB %00000000 ; $75 - Character: 'u' CHR$(117) DEFB %00000000 DEFB %00000000 DEFB %01000100 DEFB %01000100 DEFB %01000100 DEFB %01000100 DEFB %00111000 DEFB %00000000 ; $76 - Character: 'v' CHR$(118) DEFB %00000000 DEFB %00000000 DEFB %01000100 DEFB %01000100 DEFB %00101000 DEFB %00101000 DEFB %00010000 DEFB %00000000 ; $77 - Character: 'w' CHR$(119) DEFB %00000000 DEFB %00000000 DEFB %01000100 DEFB %01010100 DEFB %01010100 DEFB %01010100 DEFB %00101000 DEFB %00000000 ; $78 - Character: 'x' CHR$(120) DEFB %00000000 DEFB %00000000 DEFB %01000100 DEFB %00101000 DEFB %00010000 DEFB %00101000 DEFB %01000100 DEFB %00000000 ; $79 - Character: 'y' CHR$(121) DEFB %00000000 DEFB %00000000 DEFB %01000100 DEFB %01000100 DEFB %01000100 DEFB %00111100 DEFB %00000100 DEFB %00111000 ; $7A - Character: 'z' CHR$(122) DEFB %00000000 DEFB %00000000 DEFB %01111100 DEFB %00001000 DEFB %00010000 DEFB %00100000 DEFB %01111100 DEFB %00000000 ; $7B - Character: '{' CHR$(123) DEFB %00000000 DEFB %00001110 DEFB %00001000 DEFB %00110000 DEFB %00001000 DEFB %00001000 DEFB %00001110 DEFB %00000000 ; $7C - Character: '|' CHR$(124) DEFB %00000000 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00000000 ; $7D - Character: '}' CHR$(125) DEFB %00000000 DEFB %01110000 DEFB %00010000 DEFB %00001100 DEFB %00010000 DEFB %00010000 DEFB %01110000 DEFB %00000000 ; $7E - Character: '~' CHR$(126) DEFB %00000000 DEFB %00010100 DEFB %00101000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 ; $7F - Character: ' © ' CHR$(127) DEFB %00111100 DEFB %01000010 DEFB %10011001 DEFB %10100001 DEFB %10100001 DEFB %10011001 DEFB %01000010 DEFB %00111100 #end ; generic cross-assembler directive ; Acknowledgements ; ----------------- ; Sean Irvine for default list of section headings ; Dr. Ian Logan for labels and functional disassembly. ; Dr. Frank O'Hara for labels and functional disassembly. ; ; Credits ; ------- ; Alex Pallero Gonzales for corrections. ; Mike Dailly for comments. ; Alvin Albrecht for comments. ; Andy Styles for full relocatability implementation and testing. testing. ; Andrew Owen for ZASM compatibility and format improvements. ; For other assemblers you may have to add directives like these near the ; beginning - see accompanying documentation. ; ZASM (MacOs) cross-assembler directives. (uncomment by removing ';' ) ; #target rom ; declare target file format as binary. ; #code 0,$4000 ; declare code segment. ; Also see notes at Address Labels 0609 and 1CA5 if your assembler has ; trouble with expressions. ; ; Note. The Sinclair Interface 1 ROM written by Dr. Ian Logan and Martin ; Brennan calls numerous routines in this ROM. ; Non-standard entry points have a label beginning with X.