org 0 ; THE RESTART ROUTINES and THE TABLES ; THE 'START' ; The maskable interrupt is disabled and the DE register pair set to hold the ; 'top of possible RAM'. START: ; 0000 di ; Disable the 'keyboard interrupt'. xor a ; +00 for start (but +FF for 'NEW'). ld de,0xffff ; Top of possible RAM. jp START_NEW ; Jump forward. repeat 8 - $ db 0xff endrepeat ; THE 'ERROR' RESTART ; The error pointer is made to point to the position of the error. ERROR_1: ; 0008 ld hl,(CH_ADD) ; The address reached by the ld (X_PTR),hl ; interpreter is copied to the error jr ERROR_2 ; pointer before proceeding. repeat 16 - $ db 0xff endrepeat ; THE 'PRINT A CHARACTER' RESTART ; The A register holds the code of the character that is to be printed. PRINT_A_1: ; 0010 jp PRINT_A_2 ; Jump forward immediately. repeat 24 - $ db 0xff endrepeat ; 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 printable character, otherwise ; CH-ADD ; is incremented and the tests repeated. GET_CHAR: ; 0018 ld hl,(CH_ADD) ; Fetch the value that is addressed ld a,(hl) ; by CH-ADD. TEST_CHAR: ; 001c call SKIP_OVER ; Find out if the character is ret nc ; printable. Return if it is so. repeat 32 - $ db 0xff endrepeat ; THE 'COLLECT NEXT CHARACTER' RESTART ; As a BASIC line is interpreted, this routine is called repeatedly to step ; along the line. NEXT_CHAR: ; 0020 call CH_ADD_1 ; CH-ADD needs to be incremented. jr TEST_CHAR ; Jump back to test the new value. repeat 40 - $ db 0xff endrepeat ; THE 'CALCULATOR' RESTART ; The floating point calculator is entered at 335B. FP_CALC: ; 0028 jp CALCULATE ; Jump forward immediately. repeat 48 - $ db 0xff endrepeat ; THE 'MAKE BC SPACES' RESTART ; This routine creates free locations in the work space. The number of ; locations is determined by the current contents of the BC register pair. BC_SPACES: ; 0030 push bc ; Save the 'number'. ld hl,(WORKSP) ; Fetch the present address of the push hl ; start of the work space and save jp RESERVE ; that also before proceeding. repeat 56 - $ db 0xff endrepeat ; THE 'MASKABLE INTERRUPT' ROUTINE ; The real time clock is incremented and the keyboard scanned whenever a ; maskable interrupt occurs. MASK_INT: ; 0038 push af ; Save the current values held in push hl ; these registers. ld hl,(FRAMES) ; The lower two bytes of the inc hl ; frame counter are incremented ld (FRAMES),hl ; every 20 ms. (U.K.) The highest ld a,h ; byte of the frame counter is or l ; only incremented when the jr nz,KEY_INT ; value of the lower two bytes inc (iy+d_FRAMES+2) ; is zero. KEY_INT: ; 0048 push bc ; Save the current values held push de ; in these registers. call KEYBOARD ; Now scan the keyboard. pop de ; Restore the values. pop bc pop hl pop af ei ; The maskable interrupt is en- ret ; abled before returning. ; THE 'ERROR-2' ROUTINE ; The return address to the interpreter points to the 'DEFB' that signifies ; which error has occurred. This 'DEFB' is fetched and transferred to ERR-NR. ; The machine stack is cleared before jumping forward to clear the calculator ; stack. ERROR_2: ; 0053 pop hl ; The address on the stack points ld l,(hl) ; to the error code. ERROR_3: ; 0055 ld (iy+d_ERR_NR),l ; It is transferred to ERR-NR. ld sp,(ERR_SP) ; The machine is cleared before jp SET_STK ; exiting via SET-STK. repeat 102 - $ db 0xff endrepeat ; THE 'NON-MASKABLE INTERRUPT' ROUTINE ; This routine is not used in the standard Spectrum but the code allows for a ; system reset to occur following activation of the NMI line. The system ; variable at 5CB0, named here NMIADD, has to have the value zero for the reset ; to occur. RESET: ; 0066 push af ; Save the current values held push hl ; in these registers. ld hl,(NMIADD) ; The two bytes of NMIADD ld a,h ; must both be zero for the reset or l ; to occur. jr nz,NO_RESET ; Note: This should have been 'JR Z'! jp hl ; Jump to START. NO_RESET: ; 0070 pop hl ; Restore the current values to pop af ; these registers and return. retn ; THE 'CH-ADD+1' SUBROUTINE ; The address held in CH-ADD is fetched, incremented and restored. The contents ; of the location now addressed by CH-ADD is fetched. The entry points of ; TEMP-PTR1 and TEMP-PTR2 are used to set CH-ADD for a temporary period. CH_ADD_1: ; 0074 ld hl,(CH_ADD) ; Fetch the address. TEMP_PTR1: ; 0077 inc hl ; Increment the pointer. TEMP_PTR2: ; 0078 ld (CH_ADD),hl ; Set CH-ADD. ld a,(hl) ; Fetch he addressed value and ret ; then return. ; THE 'SKIP-OVER' SUBROUTINE ; The value brought to the subroutine in the A register is tested to see if it ; is printable. Various special codes lead to HL being incremented once, or ; twice, and CH-ADD amended accordingly. SKIP_OVER: ; 007d cp 0x21 ; Return with the carry flag reset ret nc ; if ordinary character code. cp 0x0d ; Return if the end of the line ret z ; has been reached. cp 0x10 ; Return with codes +00 to +0F ret c ; but with carry set. cp 0x18 ; Return with codes +18 to +20 ccf ; again with carry set. ret c inc hl ; Skip-over once. cp 0x16 ; Jump forward with codes +10 jr c,SKIPS ; to +15 (INK to OVER). inc hl ; Skip-over once more (AT & TAB). SKIPS: ; 0090 scf ; Return with the carry flag set ld (CH_ADD),hl ; and CH-ADD holding the ret ; appropriate address. ; THE TOKEN TABLE ; All the tokens used by the Spectrum are expanded by reference to this table. ; The last code of each token is 'inverted' by having its bit 7 set. TOKEN_TABLE: ; 0095 db 0xbf db "RN", 0xc4 db "INKEY", 0xa4 db "P", 0xc9 db "F", 0xce db "POIN", 0xd4 db "SCREEN", 0xa4 db "ATT", 0xd2 db "A", 0xd4 db "TA", 0xc2 db "VAL", 0xa4 db "COD", 0xc5 db "VA", 0xcc db "LE", 0xce db "SI", 0xce db "CO", 0xd3 db "TA", 0xce db "AS", 0xce db "AC", 0xd3 db "AT", 0xce db "L", 0xce db "EX", 0xd0 db "IN", 0xd4 db "SQ", 0xd2 db "SG", 0xce db "AB", 0xd3 db "PEE", 0xcb db "I", 0xce db "US", 0xd2 db "STR", 0xa4 db "CHR", 0xa4 db "NO", 0xd4 db "BI", 0xce db "O", 0xd2 db "AN", 0xc4 db "<", 0xbd db ">", 0xbd db "<", 0xbe db "LIN", 0xc5 db "THE", 0xce db "T", 0xcf db "STE", 0xd0 db "DEF F", 0xce db "CA", 0xd4 db "FORMA", 0xd4 db "MOV", 0xc5 db "ERAS", 0xc5 db "OPEN ", 0xa3 db "CLOSE ", 0xa3 db "MERG", 0xc5 db "VERIF", 0xd9 db "BEE", 0xd0 db "CIRCL", 0xc5 db "IN", 0xcb db "PAPE", 0xd2 db "FLAS", 0xc8 db "BRIGH", 0xd4 db "INVERS", 0xc5 db "OVE", 0xd2 db "OU", 0xd4 db "LPRIN", 0xd4 db "LLIS", 0xd4 db "STO", 0xd0 db "REA", 0xc4 db "DAT", 0xc1 db "RESTOR", 0xc5 db "NE", 0xd7 db "BORDE", 0xd2 db "CONTINU", 0xc5 db "DI", 0xcd db "RE", 0xcd db "FO", 0xd2 db "GO T", 0xcf db "GO SU", 0xc2 db "INPU", 0xd4 db "LOA", 0xc4 db "LIS", 0xd4 db "LE", 0xd4 db "PAUS", 0xc5 db "NEX", 0xd4 db "POK", 0xc5 db "PRIN", 0xd4 db "PLO", 0xd4 db "RU", 0xce db "SAV", 0xc5 db "RANDOMIZ", 0xc5 db "I", 0xc6 db "CL", 0xd3 db "DRA", 0xd7 db "CLEA", 0xd2 db "RETUR", 0xce db "COP", 0xd9 ; THE KEY TABLES ; There are six separate key tables. The final character code obtained depends ; on the particular key pressed and the 'mode' being used. ; (a) The main key table - L mode and CAPS SHIFT. KEY_TABLE_A: ; 0205 db 0x42,0x48,0x59,0x36,0x35,0x54,0x47,0x56; B H Y 6 5 T G V db 0x4e,0x4a,0x55,0x37,0x34,0x52,0x46,0x43; N J U 7 4 R F C db 0x4d,0x4b,0x49,0x38,0x33,0x45,0x44,0x58; M K I 8 3 E D X db 0x0e,0x4c,0x4f,0x39,0x32,0x57,0x53,0x5a; SYMBOL L 0 9 2 W S Z SHIFT db 0x20,0x0d,0x50,0x30,0x31,0x51,0x41; SPACE ENTER P 0 1 Q A ; (b) Extended mode. Letter keys and unshifted KEY_TABLE_B: ; 022c db 0xe3,0xc4,0xe0,0xe4 ; READ BIN LPRINT DATA db 0xb4,0xbc,0xbd,0xbb ; TAN SGN ABS SQR db 0xaf,0xb0,0xb1,0xc0 ; CODE VAL LEN USR db 0xa7,0xa6,0xbe,0xad ; PI INKEY$ PEEK TAB db 0xb2,0xba,0xe5,0xa5 ; SIN INT RESTORE RND db 0xc2,0xe1,0xb3,0xb9 ; CHR$ LLIST COS EXP db 0xc1,0xb8 ; STR$ LN ; (c) Extended mode. Letter keys and either shift. KEY_TABLE_C: ; 0246 db 0x7e,0xdc,0xda,0x5c ; ~ BRIGHT PAPER \ db 0xb7,0x7b,0x7d,0xd8 ; ATN { } CIRCLE db 0xbf,0xae,0xaa,0xab ; IN VAL$ SCREEN$ ATTR db 0xdd,0xde,0xdf,0x7f ; INVERSE OVER OUT © db 0xb5,0xd6,0x7c,0xd5 ; ASN VERIFY | MERGE db 0x5d,0xdb,0xb6,0xd9 ; ] FLASH ACS INK db 0x5b,0xd7 ; [ BEEP ; (d) Control codes. Digit keys and CAPS SHIFT. KEY_TABLE_D: ; 0260 db 0x0c,0x07,0x06,0x04 ; DELETE EDIT CAPS LOCK ; TRUE VIDEO db 0x05,0x08,0x0a,0x0b ; INV VIDEO Cursor left Cursor down ; Cursor up db 0x09,0x0f ; Cursor right GRAPHICS ; (e) Symbol code. Letter keys and symbol shift. KEY_TABLE_E: ; 026a db 0xe2,0x2a,0x3f,0xcd ; STOP * ? STEP db 0xc8,0xcc,0xcb,0x5e ; >= TO THEN ^ db 0xac,0x2d,0x2b,0x3d ; AT - + = db 0x2e,0x2c,0x3b,0x22 ; . , ; " db 0xc7,0x3c,0xc3,0x3e ; <= < NOT > db 0xc5,0x2f,0xc9,0x60 ; OR / <> £ db 0xc6,0x3a ; AND : ; (f) Extended mode. Digit keys and symbol shift. KEY_TABLE_F: ; 0284 db 0xd0,0xce,0xa8,0xca ; FORMAT DEF FN FN LINE db 0xd3,0xd4,0xd1,0xd2 ; OPEN CLOSE MOVE ERASE db 0xa9,0xcf ; POINT CAT ; THE KEYBOARD ROUTINES ; THE 'KEYBOARD SCANNING' SUBROUTINE ; This very important subroutine is called by both the main keyboard subroutine ; and the INKEY$ routine (in SCANNING). ; In all instances the E register is returned with a value in the range of +00 ; to +27, the value being different for each of the forty keys of the keyboard, ; or the value +FF, for no-key. ; The D register is returned with a value that indicates which single shift key ; is being pressed. If both shift keys are being pressed then the D and E ; registers are returned with the values for the CAPS SHIFT and SYMBOL SHIFT ; keys respectively. ; If no keys is being pressed then the DE register pair is returned holding ; +FFFF. ; The zero flag is returned reset if more than two keys are being pressed, or ; neither key of a pair of keys is a shift key. KEY_SCAN: ; 028e ld l,0x2f ; The initial key value for each line ; will be +2F, +2E,...,+28. ; (Eight lines.) ld de,0xffff ; Initialise DE to 'no-key'. ld bc,0xfefe ; C = port address, B = counter. ; Now enter a loop. Eight passes are made with each pass having a different ; initial key value and scanning a different line of five keys. (The first line ; is CAPS SHIFT, Z, X, C, V.) KEY_LINE: ; 0296 in a,(c) ; Read from the port specified. cpl ; A pressed key in the line will set and 0x1f ; its respective bit (from bit 0 - ; outer key, to bit 4 - inner key). jr z,KEY_DONEb ; Jump forward if none of the five keys ; in the line are being ; pressed. ld h,a ; The key-bits go to the H register ld a,l ; whilst the initial key value is ; fetched. KEY_3KEYS: ; 029f inc d ; If three keys are being pressed ret nz ; on the keyboard then the D register ; will no longer hold +FF ; - so return if this happens. KEY_BITS: ; 02a1 sub 0x08 ; Repeatedly subtract '8' from srl h ; the present key value until a jr nc,KEY_BITS ; key-bit is found. ld d,e ; Copy any earlier key value to the D ; register. ld e,a ; Pass the new key value to the E ; register. jr nz,KEY_3KEYS ; If there is a second, or possibly a ; third, pressed key in this line ; then jump back. KEY_DONEb: ; 02ab dec l ; The line has been scanned so the ; initial key value is reduced for ; the next pass. rlc b ; The counter is shifted and the jr c,KEY_LINE ; jump taken if there are still lines ; to be scanned. ; Four tests are now made. ld a,d ; Accept any key value which still inc a ; has the D register holding +FF. ret z ; i.e. A single key pressed or ; 'no-key'. cp 0x28 ; Accept the key value for a pair ret z ; of keys if the 'D' key is CAPS SHIFT. cp 0x19 ; Accept the key value for a pair ret z ; of keys if the 'D' key is SYMBOL ; SHIFT. ld a,e ; It is however possible for the 'E' ld e,d ; key of a pair to be SYMBOL ld d,a ; SHIFT - so this has to be cp 0x18 ; considered. ret ; Return with the zero flag set if it ; was SYMBOL SHIFT and ; 'another key'; otherwise reset. ; THE 'KEYBOARD' SUBROUTINE ; This subroutine is called on every occasion that a maskable interrupt occurs. ; In normal operation this will happen once every 20 ms. The purpose of this ; subroutine is to scan the keyboard and decode the key value. The code ; produced will, if the 'repeat' status allows it, be passed to the system ; variable LAST-K. When a code is put into this system variable bit 5 of FLAGS ; is set to show that a 'new' key has been pressed. KEYBOARD: ; 02bf call KEY_SCAN ; Fetch a key value in the DE ret nz ; register pair but return immediately ; if the zero pair flag is reset. ; A double system of 'KSTATE system variables' (KSTATE0 - KSTATE3 and KSTATE4 - ; KSTATE7) is used from now on. ; The two sets allow for the detection of a new key being pressed (using one ; set) whilst still within the 'repeat period' of the previous key to have been ; pressed (details in the other set). ; A set will only become free to handle a new key if the key is held down for ; about 1/10 th. of a second. i.e. Five calls to KEYBOARD. ld hl,KSTATE ; Start with KSTATE0. K_ST_LOOP: ; 02c6 bit 7,(hl) ; Jump forward if a 'set is free'; jr nz,K_CH_SET ; i.e. KSTATE0/4 holds +FF. inc hl ; However if the set is not free dec (hl) ; decrease its '5 call counter' dec hl ; and when it reaches zero signal jr nz,K_CH_SET ; the set as free. ld (hl),0xff ; After considering the first set change the pointer and consider the second ; set. K_CH_SET: ; 02d1 ld a,l ; Fetch the low byte of the ld hl,KSTATE+4 ; address and jump back if the cp l ; second set has still to be jr nz,K_ST_LOOP ; considered. ; Return now if the key value indicates 'no-key' or a shift key only. call K_TEST ; Make the necessary tests and ret nc ; return if needed. Also change the key ; value to a 'main code'. ; A key stroke that is being repeated (held down) is now separated from a new ; key stroke. ld hl,KSTATE ; Look first at KSTATE0. cp (hl) ; Jump forward if the codes jr z,K_REPEAT ; match - indicating a repeat. ex de,hl ; Save the address of KSTATE0. ld hl,KSTATE+4 ; Now look at KSTATE4. cp (hl) ; Jump forward if the codes jr z,K_REPEAT ; match - indicating a repeat. ; But a new key will not be accepted unless one of the sets of KSTATE system ; variables is 'free'. bit 7,(hl) ; Consider the second set. jr nz,K_NEW ; Jump forward if 'free'. ex de,hl ; Now consider the first set. bit 7,(hl) ; Continue if the set is 'free' but ret z ; exit from the KEYBOARD subroutine if ; not. ; The new key is to be accepted. But before the system variable LAST-K can be ; filled, the KSTATE system variables, of the set being used, have to be ; initialised to handle any repeats and the key's code has to be decoded. K_NEW: ; 02f1 ld e,a ; The code is passed to the ld (hl),a ; E register and to KSTATE0/4. inc hl ; The '5 call counter' for this ld (hl),0x05 ; set is reset to '5'. inc hl ; The third system variable of ld a,(REPDEL) ; the set holds the REPDEL value ld (hl),a ; (normally 0.7 secs.). inc hl ; Point to KSTATE3/7. ; The decoding of a 'main code' depends upon the present state of MODE, bit 3 ; of FLAGS and the 'shift byte'. ld c,(iy+d_MODE) ; Fetch MODE. ld d,(iy+d_FLAGS) ; Fetch FLAGS. push hl ; Save the pointer whilst the call K_DECODE ; 'main code' is decoded. pop hl ld (hl),a ; The final code value is saved in ; KSTATE3/7; from where it is ; collected in case of a repeat. ; The next three instruction lines are common to the handling of both 'new ; keys' and 'repeat keys'. K_END: ; 0308 ld (LAST_K),a ; Enter the final code value into set 5,(iy+d_FLAGS) ; LAST-K and signal 'a new key'. ret ; Finally return. ; THE 'REPEATING KEY' SUBROUTINE ; A key will 'repeat' on the first occasion after the delay period - REPDEL ; (normally 0.7 secs.) and on subsequent occasions after the delay period - ; REPPER (normally 0.1 secs.). K_REPEAT: ; 0310 inc hl ; Point to the '5 call counter' ld (hl),0x05 ; of the set being used and reset it to ; '5'. inc hl ; Point to the third system vari- dec (hl) ; able - the REPDEL/REPPER value, and ; decrement it. ret nz ; Exit from the KEYBOARD subroutine if ; the delay period ; has not passed. ld a,(REPPER) ; However once it has passed the ld (hl),a ; delay period for the next repeat is ; to be REPPER. inc hl ; The repeat has been accepted ld a,(hl) ; so the final code value is fetched ; from KSTATE3/7 and passed jr K_END ; to K-END. ; THE 'K-TEST' SUBROUTINE ; The key value is tested and a return made if 'no-key' or 'shift-only'; ; otherwise the 'main code' for that key is found. K_TEST: ; 031e ld b,d ; Copy the shift byte. ld d,0x00 ; Clear the D register for later. ld a,e ; Move the key number. cp 0x27 ; Return now if the key was ret nc ; 'CAPS SHIFT' only or 'no-key'. cp 0x18 ; Jump forward unless the 'E' jr nz,K_MAIN ; key was SYMBOL SHIFT. bit 7,b ; However accept SYMBOL SHIFT ret nz ; and another key; return with SYMBOL ; SHIFT only. ; The 'main code' is found by indexing into the main key table. K_MAIN: ; 032c ld hl,KEY_TABLE_A ; The base address of the table. add hl,de ; Index into the table and fetch ld a,(hl) ; the 'main code'. scf ; Signal 'valid keystroke' ret ; before returning. ; THE 'KEYBOARD DECODING' SUBROUTINE ; This subroutine is entered with the 'main code' in the E register, the value ; of FLAGS in the D register, the value of MODE in the C register and the ; 'shift byte' in the B register. ; By considering these four values and referring, as necessary, to the six key ; tables a 'final code' is produced. This is returned in the A register. K_DECODE: ; 0333 ld a,e ; Copy the 'main code'. cp 0x3a ; Jump forward if a digit key is jr c,K_DIGIT ; being considered; also SPACE, ENTER & ; both shifts. dec c ; Decrement the MODE value. jp m,K_KLC_LET ; Jump forward, as needed, for jr z,K_E_LET ; modes 'K', 'L', 'C' & 'E'. ; Only 'graphics' mode remains and the 'final code' for letter keys in graphics ; mode is computed from the 'main code'. add a,0x4f ; Add the offset. ret ; Return with the 'final code'. ; Letter keys in extended mode are considered next. K_E_LET: ; 0341 ld hl,KEY_TABLE_B-0x0041 ; The base address for table 'b'. inc b ; Jump forward to use this table jr z,K_LOOK_UP ; if neither shift key is being ; pressed. ld hl,KEY_TABLE_C-0x0041 ; Otherwise use the base address for ; table 'c'. ; Key tables 'b-f' are all served by the following look-up routine. In all ; cases a 'final code' is found and returned. K_LOOK_UP: ; 034a ld d,0x00 ; Clear the D register. add hl,de ; Index the required table ld a,(hl) ; and fetch the 'final code'. ret ; Then return. ; Letter keys in 'K', 'L' or 'C' modes are now considered. But first the ; special SYMBOL SHIFT codes have to be dealt with. K_KLC_LET: ; 034f ld hl,KEY_TABLE_E-0x0041 ; The base address for table 'e' bit 0,b ; Jump back if using the SYMBOL jr z,K_LOOK_UP ; SHIFT key and a letter key. bit 3,d ; Jump forward if currently in jr z,K_TOKENS ; 'K' mode. bit 3,(iy+d_FLAGS2) ; If CAPS LOCK is set then ret nz ; return with the 'main code' inc b ; Also return in the same manner ret nz ; if CAPS SHIFT is being pressed. add a,0x20 ; However if lower case codes are ret ; required then +20 has to be added to ; the 'main code' to give ; the correct 'final code'. ; The 'final code' values for tokens are found by adding +A5 to the 'main ; code'. K_TOKENS: ; 0364 add a,0xa5 ; Add the required offset and ret ; return. ; Next the digit keys; and SPACE, ENTER & both shifts; are considered. K_DIGIT: ; 0367 cp 0x30 ; Proceed only with the digit keys. ret c ; i.e. Return with SPACE (+20), ENTER ; (+0D) & both shifts ; (+0E). dec c ; Now separate the digit keys into ; three groups - according to the ; mode. jp m,K_KLC_DGT ; Jump with 'K', 'L' & 'C' modes; jr nz,K_GRA_DGT ; and also with 'G' mode. Continue with ; 'E' mode. ld hl,KEY_TABLE_F-0x0030 ; The base address for table 'f'. bit 5,b ; Use this table for SYMBOL jr z,K_LOOK_UP ; SHIFT & a digit key in extended mode. cp 0x38 ; Jump forward with digit keys jr nc,K_8_N_9 ; '8' and '9'. ; The digit keys '0' to '7' in extended mode are to give either a 'paper colour ; code' or an 'ink colour code' depending on the use of the CAPS SHIFT. sub 0x20 ; Reduce the range +30 to +37 giving ; +10 to +17. inc b ; Return with this 'paper colour ret z ; code' if the CAPS SHIFT is not being ; used. add a,0x08 ; But if it is then the range is to ret ; be +18 to +1F instead - indicating an ; 'ink colour code'. ; The digit keys '8' and '9' are to give 'BRIGHT' & 'FLASH' codes. K_8_N_9: ; 0382 sub 0x36 ; +38 & +39 go to +02 & +03. inc b ; Return with these codes if CAPS ret z ; SHIFT is not being used. (These are ; 'BRIGHT' codes.) add a,0xfe ; Subtract '2' is CAPS SHIFT is ret ; being used; giving +00 & +01 (as ; 'FLASH' codes). ; The digit keys in graphics mode are to give the block graphic characters (+80 ; to +8F), the GRAPHICS code (+0F) and the DELETE code (+0C). K_GRA_DGT: ; 0389 ld hl,KEY_TABLE_D-0x0030 ; The base address of table 'd'. cp 0x39 ; Use this table directly for jr z,K_LOOK_UP ; both digit key '9' that is to give cp 0x30 ; GRAPHICS, and digit key '0' jr z,K_LOOK_UP ; that is to give DELETE. and 0x07 ; For keys '1' to '8' make the add a,0x80 ; range +80 to +87. inc b ; Return with a value from this ret z ; range if neither shift key is being ; pressed. xor 0x0f ; But if 'shifted' make the range ret ; +88 to +8F. ; Finally consider the digit keys in 'K', 'L' & 'C' modes. K_KLC_DGT: ; 039d inc b ; Return directly if neither shift ret z ; key is being used. (Final codes +30 ; to +39.) bit 5,b ; Use table 'd' if the CAPS ld hl,KEY_TABLE_D-0x0030 ; SHIFT key is also being jr nz,K_LOOK_UP ; pressed. ; The codes for the various digit keys and SYMBOL SHIFT can now be found. sub 0x10 ; Reduce the range to give +20 to +29. cp 0x22 ; Separate the '@' character jr z,K___CHAR ; from the others. cp 0x20 ; The '-' character has also to be ; separated. ret nz ; Return now with the 'final codes' ; +21, +23 to +29. ld a,0x5f ; Give the '-' character a ret ; code of +5F. K___CHAR: ; 03b2 ld a,0x40 ; Give the '@' character a code ret ; of +40. ; THE LOUDSPEAKER ROUTINES ; The two subroutines in this section are the BEEPER subroutine, that actually ; controls the loudspeaker, and the BEEP command routine. ; The loudspeaker is activated by having D4 low during an OUT instruction that ; is using port '254'. When D4 is high in a similar situation the loudspeaker ; is deactivated. A 'beep' can therefore be produced by regularly changing the ; level of D4. ; Consider now the note 'middle C' which has the frequency 261.63 hz. In order ; to get this note the loudspeaker will have to be alternately activated and ; deactivated every 1/523.26 th. of a second. In the SPECTRUM the system clock ; is set to run at 3.5 mhz. and the note of 'middle C' will require that the ; requisite OUT instruction be executed as close as possible to every 6,689 T ; states. This last value, when reduced slightly for unavoidable overheads, ; represents the 'length of the timing loop' in the BEEPER subroutine. ; THE 'BEEPER' SUBROUTINE ; This subroutine is entered with the DE register pair holding the value 'f*t', ; where a note of given frequency 'f' is to have a duration of 't' seconds, and ; the HL register pair holding a value equal to the number of T states in the ; 'timing loop' divided by '4'. ; i.e. For the note 'middle C' to be produced for one second DE holds +0105 ; (INT(261.3 * 1)) and HL holds +066A (derived from 6,689/4 - 30.125). BEEPER: ; 03b5 di ; Disable the interrupt for the ; duration of a 'beep'. ld a,l ; Save L temporarily. srl l ; Each '1' in the L register is srl l ; to count '4' T states, but take INT ; (L/4) and count '16' T ; states instead. cpl ; Go back to the original value and 0x03 ; in L and find how many were ld c,a ; lost by taking INT (L/4). ld b,0x00 ld ix,BE_IX_3 ; The base address of the timing loop. add ix,bc ; Alter the length of the timing loop. ; Use an earlier starting ; point for each '1' lost by taking INT ; (L/4). ld a,(BORDCR) ; Fetch the present border and 0x38 ; colour and move it to bits rrca ; 2, 1 & 0 of the A register. rrca rrca or 0x08 ; Ensure the MIC output is 'off'. ; Now enter the sound generation loop. 'DE' complete passes are made, i.e. a ; pass for each cycle of the note. ; The HL register holds the 'length of the timing loop' with '16' T states ; being used for each '1' in the L register and '1,024' T states for each '1' ; in the H register. BE_IX_3: ; 03d1 nop ; Add '4' T states for each BE_IX_2: ; 03d2 nop ; earlier entry port BE_IX_1: ; 03d3 nop ; that is used. BE_IX_0: ; 03d4 inc b ; The values in the B & C registers inc c ; will come from H & L registers - see ; below. BE_HNL_LP: ; 03d6 dec c ; The 'timing loop'. jr nz,BE_HNL_LP ; i.e. 'BC' * '4' T states. ld c,0x3f ; (But note that at the half-cycle dec b ; point - C will be equal to jp nz,BE_HNL_LP ; 'L+1'.) ; The loudspeaker is now alternately activated and deactivated. xor 0x10 ; Flip bit 4. out (0xfe),a ; Perform the OUT operation; leaving ; the border unchanged. ld b,h ; Reset the B register. ld c,a ; Save the A register. bit 4,a ; Jump if at the half-cycle jr nz,BE_AGAIN ; point. ; After a full cycle the DE register pair is tested. ld a,d ; Jump forward if the last or e ; complete pass has been jr z,BE_END ; made already. ld a,c ; Fetch the saved value. ld c,l ; Reset the C register. dec de ; Decrease the pass counter. jp ix ; Jump back to the required starting ; location of the loop. ; The parameters for the second half-cycle are set up. BE_AGAIN: ; 03f2 ld c,l ; Reset the C register. inc c ; Add '16' T states as this path is ; shorter. jp ix ; Jump back. ; Upon completion of the 'beep' the maskable interrupt has to be enabled. BE_END: ; 03f6 ei ; Enable interrupt. ret ; Finally return. ; THE 'BEEP' COMMAND ROUTINE ; The subroutine is entered with two numbers on the calculator stack. The ; topmost ; number represents the 'pitch' of the note and the number underneath it ; represents the 'duration'. BEEP: ; 03f8 rst FP_CALC ; The floating-point calculator is used ; to manipulate the two ; values - t & P. db C_duplicate ; t, P, P db C_int ; t, P, i (where i = INT P) db C_st_mem_0 ; t, P, i (mem-0 holds i) db C_subtract ; t, P (where p is the fractional part ; of P) db C_stk_data ; Stack the decimal value 'K'. db 4-1<<6|0x7c-0x50 ; 0.0577622606 (which is a db 0x6c,0x98,0x1f,0xf5 ; little below 12*√2 − 1) db C_multiply ; t, pK db C_stk_one ; t, pK, 1 db C_addition ; t, pK+1 db C_end_calc ; Now perform several tests on i, the integer part of the 'pitch'. ld hl,MEMBOT ; This is 'mem-0-1st' (MEMBOT). ld a,(hl) ; Fetch the exponent of i. and a ; Give an error if i is not in the jr nz,REPORT_Ba ; integral (short) form. inc hl ; Copy the sign byte to the ld c,(hl) ; C register. inc hl ; Copy the low-byte to the ld b,(hl) ; B register; and to the A ld a,b ; register. rla ; Again give report B if i does not sbc a,a ; satisfy the test: cp c ; -128 <= i <= +127 jr nz,REPORT_Ba inc hl cp (hl) jr nz,REPORT_Ba ld a,b ; Fetch the low-byte and test it ; further. add a,0x3c jp p,BE_I_OK ; Accept -60<=i<=67. jp po,REPORT_Ba ; Reject -128 to -61. ; Note: The range +70 to +127 will be rejected later on. ; The correct frequency for the 'pitch' i can now be found. BE_I_OK: ; 0425 ld b,0xfa ; Start '6' octaves below middle C. BE_OCTAVE: ; 0427 inc b ; Repeatedly reduce i in order to sub 0x0c ; find the correct octave. jr nc,BE_OCTAVE add a,0x0c ; Ass back the last subtraction. push bc ; Save the octave number. ld hl,SEMI_TONE_TABLE ; The base address of the 'semitone ; table'. call LOC_MEM ; Consider the table and pass the call STACK_NUM ; 'A th.' value to the calculator ; stack. (Call it C.) ; Now the fractional part of the 'pitch' can be taken into consideration. rst FP_CALC ; t, pK+1, C db C_multiply ; t, C(pK+1) db C_end_calc ; The final frequency f is found by modifying the 'last value' according to the ; octave number. pop af ; Fetch the octave number. add a,(hl) ; Multiply the 'last value' by ld (hl),a ; '2 to the power of the octave ; number'. rst FP_CALC ; t, f db C_st_mem_0 ; The frequency is put aside for db C_delete ; the moment in mem-0. ; Attention is now turned to the 'duration'. db C_duplicate ; t, t db C_end_calc call FIND_INT1 ; The value 'INT t' must be in cp 0x0b ; the range +00 to +0A. jr nc,REPORT_Ba ; The number of complete cycles in the 'beep' is given by 'f*t' so this value ; is now found. rst FP_CALC ; t db C_get_mem_0 ; t, f db C_multiply ; f*t ; The result is left on the calculator stack whilst the length of the 'timing ; loop' required for the 'beep' is computed; db C_get_mem_0 ; f*t, f db C_stk_data ; The value '3.5 * 10^6/8' db 0x80 ; is formed on the top of db 2-1<<6|0x53-0x50 ; the calculator stack. db 0x55,0x9f,0x80 ; f*t, f, 437,500 (dec.) db C_exchange ; f*t, 437,500, f db C_division ; f*t, 437,500/f db C_stk_data db 1-1<<6|0x85-0x50 db 0x71 ; f*t, 437,500/f, 30.125 (dec.) db C_subtract ; f*t, 437,500/f - 30.125 db C_end_calc ; Note: The value '437,500/f' gives the 'half-cycle' length of the note and ; reducing it by '30.125' allows for '120.5' T states in which to actually ; produce the note and adjust the counters etc. ; The values can now be transferred to the required registers. call FIND_INT2 ; The 'timing loop' value is compressed ; into the BC push bc ; register pair; and saved. ; Note: If the timing loop value is too large then an error will occur ; (returning via ERROR-1); thereby excluding 'pitch' values of '+70 to +127'. call FIND_INT2 ; The 'f*t' value is compressed into ; the BC register pair. pop hl ; Move the 'timing loop' value to the ; HL register pair. ld d,b ; Move the 'f*t' value to the ld e,c ; DE register pair. ; However before making the 'beep' test the value 'f*t'. ld a,d ; Return if 'f*t' has given the or e ; result of 'no cycles' ret z ; required. dec de ; Decrease the cycle number and jp BEEPER ; jump to the BEEPER subroutine ; (making, at least, one pass). ; Report B - interget out of range REPORT_Ba: ; 046c rst ERROR_1 ; Call the error handling db 0x0a ; routine. ; THE 'SEMI-TONE' TABLE ; This table holds the frequencies of the twelve semi-tones in an octave. ; frequency hz. note SEMI_TONE_TABLE: ; 046e db 0x89,0x02,0xd0,0x12,0x86 ; 261.63 C db 0x89,0x0a,0x97,0x60,0x75 ; 277.18 C# db 0x89,0x12,0xd5,0x17,0x1f ; 293.66 D db 0x89,0x1b,0x90,0x41,0x02 ; 311.12 D# db 0x89,0x24,0xd0,0x53,0xca ; 329.63 E db 0x89,0x2e,0x9d,0x36,0xb1 ; 349.23 F db 0x89,0x38,0xff,0x49,0x3e ; 369.99 F# db 0x89,0x43,0xff,0x6a,0x73 ; 392 G db 0x89,0x4f,0xa7,0x00,0x54 ; 415.30 G# db 0x89,0x5c,0x00,0x00,0x00 ; 440 A db 0x89,0x69,0x14,0xf6,0x24 ; 466.16 A# db 0x89,0x76,0xf1,0x10,0x05 ; 493.88 B ; THE 'PROGRAM NAME' SUBROUTINE (ZX81) ; The following subroutine applies to the ZX81 and was not removed when the ; program was rewritten for the SPECTRUM. db 0xcd,0xfb,0x24,0x3a db 0x3b,0x5c,0x87,0xfa db 0x8a,0x1c,0xe1,0xd0 db 0xe5,0xcd,0xf1,0x2b db 0x62,0x6b,0x0d,0xf8 db 0x09,0xcb,0xfe,0xc9 ; THE CASSETTE HANDLING ROUTINES ; The 16K monitor program has an extensive set of routines for handling the ; cassette interface. In effect these routines form the SAVE, LOAD, VERIFY & ; MERGE command routines. ; The entry point to the routines is at SAVE-ETC (0605). However before this ; point are the subroutines concerned with the actual SAVEing and LOADing (or ; VERIFYing) of bytes. ; In all cases the bytes to be handled by these subroutines are described by ; the DE register pair holding the 'length' of the block, the IX register pair ; holding the 'base address' and the A register holding +00 for a header block, ; or +FF for a program/data block. ; THE 'SA-BYTES' SUBROUTINE ; This subroutine is called to SAVE the header information (from 09BA) and ; later the actual program/data block (from 099E). SA_BYTES: ; 04c2 ld hl,SA_LD_RET ; Pre-load the machine stack with push hl ; the address - SA/LD-RET. ld hl,0x1f80 ; This constant will give a leader of ; about 5 secs. for a 'header'. bit 7,a ; Jump forward if SAVEing a jr z,SA_FLAG ; header. ld hl,0x0c98 ; This constant will give a leader of ; about 2 secs. for a program/ ; data block. SA_FLAG: ; 04d0 ex af,af' ; The flag is saved. inc de ; The 'length' is incremented dec ix ; and the 'base address' reduced to ; allow for the flag. di ; The maskable interrupt is disabled ; during the SAVE. ld a,0x02 ; Signal 'MIC on' and border to be RED. ld b,a ; Give a value to B. ; A loop is now entered to create the pulses of the leader. Both the 'MIC on' ; and the 'MIC off' pulses are 2,168 T states in length. The colour of the ; border changes from RED to CYAN with each 'edge'. ; Note: An 'edge' will be a transition either from 'on' to 'off', ; or from 'off' to 'on'. SA_LEADER: ; 04d8 djnz SA_LEADER ; The main timing period. out (0xfe),a ; MIC on/off, border RED/CYAN, xor 0x0f ; on each pass. ld b,0xa4 ; The main timing constant. dec l ; Decrease the low counter. jr nz,SA_LEADER ; Jump back for another pulse. dec b ; Allow for the longer path (-reduce by ; 13 T states). dec h ; Decrease the high counter. jp p,SA_LEADER ; Jump back for another pulse until ; completion of the leader. ; A sync pulse is now sent. ld b,0x2f SA_SYNC_1: ; 04ea djnz SA_SYNC_1 ; MIC off for 667 T states from 'OUT to ; OUT'. out (0xfe),a ; MIC on and RED. ld a,0x0d ; Signal 'MIC off & CYAN'. ld b,0x37 ; MIC on for 735 T States from SA_SYNC_2: ; 04f2 djnz SA_SYNC_2 ; 'OUT to OUT'. out (0xfe),a ; Now MIC off & border CYAN. ; The header v. program/data flag will be the first byte to be SAVEd. ld bc,0x3b0e ; +3B is a timing constant; +0E signals ; 'MIC off & YELLOW'. ex af,af' ; Fetch the flag and pass it to the ld l,a ; L register for 'sending'. jp SA_START ; Jump forward into the SAVEing loop. ; The byte SAVEing loop is now entered. The first byte to be SAVEd is the flag; ; this is followed by the actual data byte and the final byte sent is the ; parity byte that is built up by considering the values of all the earlier ; bytes. SA_LOOP: ; 04fe ld a,d ; The 'length' counter is tested or e ; and the jump taken when it jr z,SA_PARITY ; has reached zero. ld l,(ix+0) ; Fetch the next byte that is to be ; SAVEd. SA_LOOP_P: ; 0505 ld a,h ; Fetch the current 'parity'. xor l ; Include the present byte. SA_START: ; 0507 ld h,a ; Restore the 'parity'. Note that on ; entry here the 'flag' value ; initialises 'parity'. ld a,0x01 ; Signal 'MIC on & BLUE'. scf ; Set the carry flag. This will act as ; a 'marker' for the 8 bits of a ; byte. jp SA_8_BITS ; Jump forward. ; When it is time to send the 'parity' byte then it is transferred to the L ; register for SAVEing. SA_PARITY: ; 050e ld l,h ; Get final 'parity' value. jr SA_LOOP_P ; Jump back. ; The following inner loop produces the actual pulses. The loop is entered at ; SA-BIT-1 with the type of the bit to be SAVEd indicated by the carry flag. ; Two passes of the loop are made for each bit thereby making an 'off pulse' ; and an 'on pulse'. The pulses for a reset bit are shorter by 855 T states. SA_BIT_2: ; 0511 ld a,c ; Come here on the second pass and ; fetch 'MIC off & YELLOW'. bit 7,b ; Set the zero flag to show 'second ; pass'. SA_BIT_1: ; 0514 djnz SA_BIT_1 ; The main timing loop; always 801 T ; states on a 2nd. pass. jr nc,SA_OUT ; Jump, taking the shorter path, if ; SAVEing a '0'. ld b,0x42 ; However if SAVEing a '1' then SA_SET: ; 051a djnz SA_SET ; add 855 T states. SA_OUT: ; 051c out (0xfe),a ; On the 1st. pass 'MIC on & BLUE' and ; on the 2nd. pass ; 'MIC off & YELLOW'. ld b,0x3e ; Set the timing constant for the ; second pass. jr nz,SA_BIT_2 ; Jump back at the end of the dec b ; first pass; otherwise reclaim 13 T ; states. xor a ; Clear the carry flag and set inc a ; A to hold +01 (MIC on & BLUE} before ; continuing into the ; '8 bit loop'. ; The '8 bit loop' is entered initially with the whole byte in the L register ; and the carry flag set. However it is re-entered after each bit has been ; SAVEd until the point is reached when the 'marker' passes to the carry flag ; leaving the L register empty. SA_8_BITS: ; 0525 rl l ; Move bit 7 to the carry and the ; 'marker' leftwards. jp nz,SA_BIT_1 ; SAVE the bit unless finished with the ; byte. dec de ; Decrease the 'counter'. inc ix ; Advance the 'base address'. ld b,0x31 ; Set the timing constant for the first ; bit of the next byte. ld a,0x7f ; Return (to SA/LD-RET) if the in a,(0xfe) ; BREAK key is being pressed. rra ret nc ld a,d ; Otherwise test the 'counter' inc a ; and jump back even if it has jp nz,SA_LOOP ; reached zero (so as to send the ; 'parity' byte). ld b,0x3b ; Exit when the 'counter' SA_DELAY: ; 053c djnz SA_DELAY ; reaches +FFFF. But first ret ; give a short delay. ; Note: A reset bit will give a 'MIC off' pulse of 855 T states followed by a ; 'MIC on' pulse of 855 T states. Whereas a Set bit will give pulses of exactly ; twice as long. Note also that there are no gaps either between the sync pulse ; and the first bit of the flag, or between bytes. ; THE 'SA/LD-RET' SUBROUTINE ; This subroutine is common to both SAVEing and LOADing. ; The border is set to its original colour and the BREAK key tested for a last ; time. SA_LD_RET: ; 053f push af ; Save the carry flag. (It is reset ; after a LOADing error.) ld a,(BORDCR) ; Fetch the original border colour and 0x38 ; from its system variable. rrca ; Move the border colour rrca ; to bits 2, 1 & 0. rrca out (0xfe),a ; Set the border to its original ; colour. ld a,0x7f ; Read the BREAK key for a in a,(0xfe) ; last time. rra ei ; Enable the maskable interrupt. jr c,SA_LD_END ; Jump unless a break is to be made. ; Report D - BREAK-CONT repeats REPORT_Da: ; 0552 rst ERROR_1 ; Call the error handling db 0x0c ; routine. ; Continue here. SA_LD_END: ; 0554 pop af ; Retrieve the carry flag. ret ; Return to the calling routine. ; THE 'LD-BYTES' SUBROUTINE ; This subroutine is called to LOAD the header information (from 07BE) and ; later LOAD, or VERIFY, an actual block of data (from 0802). LD_BYTES: ; 0556 inc d ; This resets the zero flag. (D cannot ; hold +FF.) ex af,af' ; The A register holds +00 for a header ; and +FF for a block of ; data. The carry flag is reset for ; VERIFYing and set for LOADing. dec d ; Restore D to its original value. di ; The maskable interrupt is now ; disabled. ld a,0x0f ; The border is made WHITE. out (0xfe),a ld hl,SA_LD_RET ; Preload the machine stack push hl ; with the address - SA/LD-RET. in a,(0xfe) ; Make an initial read of port '254' rra ; Rotate the byte obtained but and 0x20 ; keep only the EAR bit, or 0x02 ; Signal 'RED' border. ld c,a ; Store the value in the C register. - ; (+22 for 'off' and +02 for 'on' ; - the present EAR state.) cp a ; Set the zero flag. ; The first stage of reading a tape involves showing that a pulsing signal ; actually exist (i.e. 'On/off' or 'off/on' edges.) LD_BREAK: ; 056b ret nz ; Return if the BREAK key is being ; pressed. LD_START: ; 056c call LD_EDGE_1 ; Return with the carry flag reset jr nc,LD_BREAK ; if there is no 'edge' within approx. ; 14,000 T states. But if ; an 'edge' is found the border will go ; CYAN. ; The next stage involves waiting a while and then showing that the signal is ; still pulsing. ld hl,0x0415 ; The length of this waiting LD_WAIT: ; 0574 djnz LD_WAIT ; period will be almost one dec hl ; second in duration. ld a,h or l jr nz,LD_WAIT call LD_EDGE_2 ; Continue only if two edges are jr nc,LD_BREAK ; found within the allowed time period. ; Now accept only a 'leader signal'. LD_LEADER: ; 0580 ld b,0x9c ; The timing constant, call LD_EDGE_2 ; Continue only if two edges are jr nc,LD_BREAK ; found within the allowed time period. ld a,0xc6 ; However the edges must have cp b ; been found within about jr nc,LD_START ; 3,000 T states of each other inc h ; Count the pair of edges in the H jr nz,LD_LEADER ; register until '256' pairs have been ; found. ; After the leader come the 'off' and 'on' part's of the sync pulse. LD_SYNC: ; 058f ld b,0xc9 ; The timing constant. call LD_EDGE_1 ; Every edge is considered until jr nc,LD_BREAK ; two edges are found close ld a,b ; together - these will be the cp 0xd4 ; start and finishing edges of jr nc,LD_SYNC ; the 'off' sync pulse. call LD_EDGE_1 ; The finishing edge of the ret nc ; 'on' pulse must exist. (Return carry ; flag reset.) ; The bytes of the header or the program/data block can now be LOADed or ; VERIFied. But the first byte is the type flag. ld a,c ; The border colours from now xor 0x03 ; on will be BLUE & YELLOW. ld c,a ld h,0x00 ; Initialise the 'parity matching' byte ; to zero. ld b,0xb0 ; Set the timing constant for the flag ; byte. jr LD_MARKER ; Jump forward into the byte LOADING ; loop. ; The byte LOADing loop is used to fetch the bytes one at a time. The flag byte ; is first. This is followed by the data bytes and the last byte is the ; 'parity' byte. LD_LOOP: ; 05a9 ex af,af' ; Fetch the flags. jr nz,LD_FLAG ; Jump forward only when handling the ; first byte. jr nc,LD_VERIFY ; Jump forward if VERIFYing a tape. ld (ix+0),l ; Make the actual LOAD when required. jr LD_NEXT ; Jump forward to LOAD the next byte. LD_FLAG: ; 05b3 rl c ; Keep the carry flag in a safe place ; temporarily. xor l ; Return now if the type flag does ret nz ; not match the first byte on the tape. ; (Carry flag reset.) ld a,c ; Restore the carry flag now. rra ld c,a inc de ; Increase the counter to jr LD_DEC ; compensate for its 'decrease' after ; the jump. ; If a data block is being verified then the freshly loaded byte is tested ; against the original byte. LD_VERIFY: ; 05bd ld a,(ix+0) ; Fetch the original byte. xor l ; Match it against the new byte. ret nz ; Return if 'no match'. (Carry flag ; reset.) ; A new byte can now be collected from the tape. LD_NEXT: ; 05c2 inc ix ; Increase the 'destination'. LD_DEC: ; 05c4 dec de ; Decrease the 'counter'. ex af,af' ; Save the flags. ld b,0xb2 ; Set the timing constant. LD_MARKER: ; 05c8 ld l,0x01 ; Clear the 'object' register apart ; from a 'marker' bit. ; The 'LD-8-BITS' loop is used to build up a byte in the L register. LD_8_BITS: ; 05ca call LD_EDGE_2 ; Find the length of the 'off' and 'on' ; pulses of the next bit. ret nc ; Return if the time period is ; exceeded. (Carry flag reset.) ld a,0xcb ; Compare the length against approx. ; 2,400 T states; resetting cp b ; the carry flag for a '0' and setting ; it for a '1'. rl l ; Include the new bit in the L ; register. ld b,0xb0 ; Set the timing constant for the next ; bit. jp nc,LD_8_BITS ; Jump back whilst there are still bits ; to be fetched. ; The 'parity matching' byte has to be updated with each new byte. ld a,h ; Fetch the 'parity matching' xor l ; byte and include the new byte. ld h,a ; Save it once again. ; Passes round the loop are made until the 'counter' reaches zero. At that ; point the 'parity matching' byte should be holding zero. ld a,d ; Make a further pass if the DE or e ; register pair does not hold jr nz,LD_LOOP ; zero. ld a,h ; Fetch the 'parity matching' byte. cp 0x01 ; Return with the carry flat set ret ; if the value is zero. (Carry flag ; reset if in error.) ; THE 'LD-EDGE-2' AND 'LD-EDGE-1' SUBROUTINES ; These two subroutines form the most important part of the LOAD/VERIFY ; operation. ; The subroutines are entered with a timing constant in the B register, ; and the previous border colour and 'edge-type' in the C register. ; The subroutines return with the carry flag set if the required number of ; 'edges' have been found in the time allowed; and the change to the value in ; the B register shows just how long it took to find the 'edge(s)'. ; The carry flag will be reset if there is an error. The zero flag then ; signals 'BREAK pressed' by being reset, or 'time-up' by being set. ; The entry point LD-EDGE-2 is used when the length of a complete pulse is ; required and LD-EDGE-1 is used to find the time before the next 'edge'. LD_EDGE_2: ; 05e3 call LD_EDGE_1 ; In effect call LD-EDGE-1 twice; ret nc ; returning in between if there is an ; error. LD_EDGE_1: ; 05e7 ld a,0x16 ; Wait 358 T states before LD_DELAY: ; 05e9 dec a ; entering the sampling loop. jr nz,LD_DELAY and a ; The sampling loop is now entered. The value in the B register is incremented ; for each pass; 'time-up' is given when B reaches zero. LD_SAMPLE: ; 05ed inc b ; Count each pass. ret z ; Return carry reset & zero set if ; 'time-up'. ld a,0x7f ; Read from port +7FFE. in a,(0xfe) ; i.e. BREAK & EAR. rra ; Shift the byte. ret nc ; Return carry reset & zero reset if ; BREAK was pressed. xor c ; Now test the byte against the and 0x20 ; 'last edge-type'; jump back jr z,LD_SAMPLE ; unless it has changed. ; A new 'edge' has been found within the time period allowed for the search. So ; change the border colour and set the carry flag. ld a,c ; Change the 'last edge-type' cpl ; and border colour. ld c,a and 0x07 ; Keep only the border colour. or 0x08 ; Signal 'MIC off'. out (0xfe),a ; Change the border colour (RED/ CYAN ; or BLUE/YELLOW). scf ; Signal the successful search ret ; before returning. ; Note: The LD-EDGE-1 subroutine takes 465 T states, plus an additional 58 T ; states for each unsuccessful pass around the sampling loop. ; For example, therefore, when awaiting the sync pulse (see LD-SYNC at 058F) ; allowance is made for ten additional passes through the sampling loop. The ; search is thereby for the next edge to be found within, roughly, 1,100 T ; states (465 + 10 * 58 + overhead). This will prove successful for the sync ; 'off' pulse that comes after the long 'leader pulses'. ; THE 'SAVE, LOAD, VERIFY & MERGE' COMMAND ROUTINES ; The entry point SAVE-ETC is used for all four commands. The value held in ; T-ADDR however distinguishes between the four commands. The first part of the ; following routine is concerned with the construction of the 'header ; information' in the work space. SAVE_ETC: ; 0605 pop af ; Drop the address - SCAN-LOOP. ld a,(T_ADDR) ; Reduce T-ADDR-lo by +E0; sub 0xe0 ; giving +00 for SAVE, +01 for ld (T_ADDR),a ; LOAD, +02 for VERIFY and +03 for ; MERGE. call EXPT_EXP ; Pass the parameters of the 'name' to ; the calculator stack. call SYNTAX_Z ; Jump forward if checking jr z,SA_DATA ; syntax. ld bc,0x0011 ; Allow seventeen locations ld a,(T_ADDR) ; for the header of a SAVE but and a ; thirty four for the other jr z,SA_SPACE ; commands. ld c,0x22 SA_SPACE: ; 0621 rst BC_SPACES ; The required amount of space is made ; in the work space. push de ; Copy the start address to the pop ix ; IX register pair. ld b,0x0b ; A program name can have ld a,0x20 ; up to ten characters but SA_BLANK: ; 0629 ld (de),a ; first enter eleven space inc de ; characters into the prepared djnz SA_BLANK ; area. ld (ix+1),0xff ; A null name is +FF only. call STK_FETCH ; The parameters of the name are ; fetched and its length is ; tested. ld hl,0xfff6 ; This is '-10'. dec bc ; In effect jump forward if the add hl,bc ; length of the name is not inc bc ; too long. (i.e. No more than jr nc,SA_NAME ; ten characters.) ld a,(T_ADDR) ; But allow for the LOADing, and a ; VERIFYing and MERGEing of jr nz,SA_NULL ; programs with 'null' names or extra ; long names. ; Report F - Invalid file name REPORT_Fa: ; 0642 rst ERROR_1 ; Call the error handling db 0x0e ; routine. ; Continue to handle the name of the program. SA_NULL: ; 0644 ld a,b ; Jump forward if the name or c ; has a 'null' length. jr z,SA_DATA ld bc,0x000a ; But truncate longer names. ; The name is now transferred to the work space (second location onwards). SA_NAME: ; 064b push ix ; Copy the start address to the pop hl ; HL register pair. inc hl ; Step to the second location. ex de,hl ; Switch the pointers over and ldir ; copy the name. ; The many different parameters, if any, that follow the command are now ; considered. Start by handling 'xxx "name" DATA'. SA_DATA: ; 0652 rst GET_CHAR ; Is the present code the cp 0xe4 ; token 'DATA'? jr nz,SA_SCR_ ; Jump if not. ld a,(T_ADDR) ; However it is not possible cp 0x03 ; to have 'MERGE name DATA'. jp z,REPORT_Cb rst NEXT_CHAR ; Advance CH-ADD. call LOOK_VARS ; Look in the variables area for the ; array. set 7,c ; Set bit 7 of the array's name. jr nc,SA_V_OLD ; Jump if handling an existing array. ld hl,0x0000 ; Signal 'using a new array'. ld a,(T_ADDR) ; Consider the value in T-ADDR dec a ; and give an error if trying to jr z,SA_V_NEW ; SAVE or VERIFY a new array. ; Report 2 - Variable not found REPORT_2a: ; 0670 rst ERROR_1 ; Call the error handling db 0x01 ; routine. ; Continue with the handling of an existing array. SA_V_OLD: ; 0672 jp nz,REPORT_Cb ; Note: This fails to exclude simple ; strings. call SYNTAX_Z ; Jump forward if checking jr z,SA_DATA_1 ; syntax. inc hl ; Point to the 'low length' of the ; variable. ld a,(hl) ; The low length byte goes into ld (ix+11),a ; the work space; followed by inc hl ; the high length byte. ld a,(hl) ld (ix+12),a inc hl ; Step past the length bytes. ; The next part is common to both 'old' and 'new' arrays. Note: Syntax path ; error. SA_V_NEW: ; 0685 ld (ix+14),c ; Copy the array's name. ld a,0x01 ; Assume an array of numbers. bit 6,c ; Jump if it is so. jr z,SA_V_TYPE inc a ; It is an array of characters. SA_V_TYPE: ; 068f ld (ix+0),a ; Save the 'type' in the first location ; of the header area. ; The last part of the statement is examined before joining the other pathways. SA_DATA_1: ; 0692 ex de,hl ; Save the pointer in DE. rst NEXT_CHAR ; Is the next character cp 0x29 ; a ')' ? jr nz,SA_V_OLD ; Give report C if it is not. rst NEXT_CHAR ; Advance CH-ADD. call CHECK_END ; Move on to the next statement if ; checking syntax. ex de,hl ; Return the pointer to the HL jp SA_ALL ; register pair before jumping forward. ; (The pointer indicates ; the start of an existing array's ; contents.) ; Now consider 'SCREEN$'. SA_SCR_: ; 06a0 cp 0xaa ; Is the present code the token ; SCREEN$'. jr nz,SA_CODE ; Jump if not. ld a,(T_ADDR) ; However it is not possible to cp 0x03 ; have 'MERGE name SCREEN$'. jp z,REPORT_Cb rst NEXT_CHAR ; Advance CH-ADD. call CHECK_END ; Move on to the next statement if ; checking syntax. ld (ix+11),0x00 ; The display area and the ld (ix+12),0x1b ; attribute area occupy +1800 locations ; and these locations ld hl,0x4000 ; start at +4000; these details ld (ix+13),l ; are passed to the header area ld (ix+14),h ; in the work space. jr SA_TYPE_3 ; Jump forward. ; Now consider 'CODE'. SA_CODE: ; 06c3 cp 0xaf ; Is the present code the token 'CODE'? jr nz,SA_LINE ; Jump if not. ld a,(T_ADDR) ; However it is not possible to cp 0x03 ; have 'MERGE name CODE'. jp z,REPORT_Cb rst NEXT_CHAR ; Advance CH-ADD. call PR_ST_END ; Jump forward if the statement jr nz,SA_CODE_1 ; has not finished. ld a,(T_ADDR) ; However it is not possible to and a ; have 'SAVE name CODE' by jp z,REPORT_Cb ; itself. call USE_ZERO ; Put a zero on the calculator stack - ; for the 'start'. jr SA_CODE_2 ; Jump forward. ; Look for a 'starting address'. SA_CODE_1: ; 06e1 call EXPT_1NUM ; Fetch the first number. rst GET_CHAR ; Is the present character a ',' cp 0x2c ; or not? jr z,SA_CODE_3 ; Jump if it is - the number was a ; 'starting address'. ld a,(T_ADDR) ; However refuse 'SAVE name and a ; CODE' that does not have a jp z,REPORT_Cb ; 'start' and a 'length'. SA_CODE_2: ; 06f0 call USE_ZERO ; Put a zero on the calculator stack - ; for the 'length'. jr SA_CODE_4 ; Jump forward. ; Fetch the 'length' as it was specified. SA_CODE_3: ; 06f5 rst NEXT_CHAR ; Advance CH-ADD. call EXPT_1NUM ; Fetch the 'length'. ; The parameters are now stored in the header area of the work space. SA_CODE_4: ; 06f9 call CHECK_END ; But move on to the next statement now ; if checking syntax. call FIND_INT2 ; Compress the 'length' into ld (ix+11),c ; the BC register pair and ld (ix+12),b ; store it. call FIND_INT2 ; Compress the 'starting address' ld (ix+13),c ; into the BC register pair ld (ix+14),b ; and store it. ld h,b ; Transfer the 'pointer' to the ld l,c ; HL register pair as usual. ; 'SCREEN$' and 'CODE' are both of type 3. SA_TYPE_3: ; 0710 ld (ix+0),0x03 ; Enter the 'type' number. jr SA_ALL ; Rejoin the other pathways. ; Now consider 'LINE'; and 'no further parameters'. SA_LINE: ; 0716 cp 0xca ; Is the present code the token 'LINE'? jr z,SA_LINE_1 ; Jump if it is. call CHECK_END ; Move on to the next statement if ; checking syntax. ld (ix+14),0x80 ; When there are no further parameters ; an +80 is entered. jr SA_TYPE_0 ; Jump forward. ; Fetch the 'line number' that must follow 'LINE'. SA_LINE_1: ; 0723 ld a,(T_ADDR) ; However only allow 'SAVE and a ; name LINE number'. jp nz,REPORT_Cb rst NEXT_CHAR ; Advance CH-ADD. call EXPT_1NUM ; Pass the number to the calculator ; stack. call CHECK_END ; Move on to the next statement if ; checking syntax. call FIND_INT2 ; Compress the 'line number' ld (ix+13),c ; into the BC register pair ld (ix+14),b ; and store it. ; 'LINE' and 'no further parameters' are both of type 0. SA_TYPE_0: ; 073a ld (ix+0),0x00 ; Enter the 'type' number. ; The parameters that describe the program, and its variables, are found and ; stored in the header area of the work space. ld hl,(E_LINE) ; The pointer to the end of the ; variables area. ld de,(PROG) ; The pointer to the start of the BASIC ; program. scf ; Now perform the subtraction sbc hl,de ; to find the length of the ld (ix+11),l ; 'program + variables'; store ld (ix+12),h ; the result. ld hl,(VARS) ; Repeat the operation but this sbc hl,de ; time storing the length of the ld (ix+15),l ; 'program' only. ld (ix+16),h ex de,hl ; Transfer the 'pointer' to the HL ; register pair as usual. ; In all cases the header information has now been prepared. ; The location 'IX+00' holds the type number. ; Locations 'IX+01 to IX+0A' holds the name (+FF in 'IX+01' if null). ; Locations 'IX+0B & IX+0C' hold the number of bytes that are to be found ; in the 'data block'. ; Locations 'IX+0D to IX+10' hold a variety of parameters whose exact ; interpretation depends on the 'type'. ; The routine continues with the first task being to separate SAVE from LOAD, ; VERIFY and MERGE. SA_ALL: ; 075a ld a,(T_ADDR) ; Jump forward when handling and a ; a SAVE command. jp z,SA_CONTRL ; In the case of a LOAD, VERIFY or MERGE command the first seventeen bytes of ; the 'header area' in the work space hold the prepared information, as ; detailed above; and it is now time to fetch a 'header' from the tape. push hl ; Save the 'destination' pointer. ld bc,0x0011 ; Form in the IX register pair add ix,bc ; the base address of the 'second ; header area'. ; Now enter a loop; leaving it only when a 'header' has been LOADed. LD_LOOK_H: ; 0767 push ix ; Make a copy of the base address. ld de,0x0011 ; LOAD seventeen bytes. xor a ; Signal 'header'. scf ; Signal 'LOAD'. call LD_BYTES ; Now look for a header. pop ix ; Retrieve the base address. jr nc,LD_LOOK_H ; Go round the loop until successful. ; The new 'header' is now displayed on the screen but the routine will only ; proceed if the 'new' header matches the 'old' header. ld a,0xfe ; Ensure that channel 'S' call CHAN_OPEN ; is open. ld (iy+d_SCR_CT),0x03 ; Set the scroll counter. ld c,0x80 ; Signal 'names do not match'. ld a,(ix+0) ; Compare the 'new' type cp (ix-17) ; against the 'old' type. jr nz,LD_TYPE ; Jump if the 'types' do not match. ld c,0xf6 ; But if they do; signal 'ten ; characters are to match'. LD_TYPE: ; 078a cp 0x04 ; Clearly the 'header' is jr nc,LD_LOOK_H ; nonsense if 'type 4 or more'. ; The appropriate message - 'Program:', 'Number array:', 'Character array:' or ; 'Bytes:' is printed. ld de,TAPE_MESSAGE_TABLE ; The base address of the message ; block. push bc ; Save the C register whilst call PO_MSG ; the appropriate message is pop bc ; printed. ; The 'new name' is printed and as this is done the 'old' and the 'new' names ; are compared. push ix ; Make the DE register pair pop de ; point to the 'new type' and ld hl,0xfff0 ; the HL register pair to the add hl,de ; 'old name'. ld b,0x0a ; Ten characters are to be considered. ld a,(hl) ; Jump forward if the match is inc a ; to be against an actual name. jr nz,LD_NAME ld a,c ; But if the 'old name' is 'null' add a,b ; then signal 'ten characters ld c,a ; already match'. ; A loop is entered to print the characters of the 'new name'. The name will be ; accepted if the 'counter' reaches zero, at least. LD_NAME: ; 07a6 inc de ; Consider each character of the ld a,(de) ; 'new name' in turn. cp (hl) ; Match it against the appropriate inc hl ; character of the 'old name'. jr nz,LD_CH_PR ; Do not count it if it does not inc c ; does not match. LD_CH_PR: ; 07ad rst PRINT_A_1 ; Print the 'new' character. djnz LD_NAME ; Loop for ten characters. bit 7,c ; Accept the name only if the jr nz,LD_LOOK_H ; counter has reached zero. ld a,0x0d ; Follow the 'new name' with rst PRINT_A_1 ; a 'carriage return'. ; The correct header has been found and the time has come to consider the three ; commands LOAD, VERIFY, & MERGE separately. pop hl ; Fetch the pointer. ld a,(ix+0) ; 'SCREEN$ and CODE' are cp 0x03 ; handled with VERIFY. jr z,VR_CONTRL ld a,(T_ADDR) ; Jump forward if using a dec a ; LOAD command. jp z,LD_CONTRL cp 0x02 ; Jump forward if using a MERGE jp z,ME_CONTRL ; command; continue with a VERIFY ; command. ; THE 'VERIFY' CONTROL ROUTINE ; The verification process involves the LOADing of a block of data, a byte at a ; time, but the bytes are not stored - only checked. This routine is also used ; to LOAD blocks of data that have been described with 'SCREEN$ & CODE'. VR_CONTRL: ; 07cb push hl ; Save the 'pointer'. ld l,(ix-6) ; Fetch the 'number of bytes' ld h,(ix-5) ; as described in the 'old' header. ld e,(ix+11) ; Fetch also the number from the ld d,(ix+12) ; 'new' header. ld a,h ; Jump forward if the 'length' is or l ; unspecified. jr z,VR_CONT_1 ; e.g. 'LOAD name CODE' only. sbc hl,de ; Give report R if attempting jr c,REPORT_Ra ; to LOAD a larger block than has been ; requested. jr z,VR_CONT_1 ; Accept equal 'lengths'. ld a,(ix+0) ; Also give report R if trying cp 0x03 ; to VERIFY blocks that are of jr nz,REPORT_Ra ; unequal size. ('Old length' greater ; than 'new length'.) ; The routine continues by considering the 'destination pointer'. VR_CONT_1: ; 07e9 pop hl ; Fetch the 'pointer', i.e. the ; 'start'. ld a,h ; This 'pointer' will be used or l ; unless it is zero, in which jr nz,VR_CONT_2 ; case the 'start' found in ld l,(ix+13) ; the 'new' header will be used ld h,(ix+14) ; instead. ; The VERIFY/LOAD flag is now considered and the actual LOAD made. VR_CONT_2: ; 07f4 push hl ; Move the 'pointer' to the pop ix ; IX register pair. ld a,(T_ADDR) ; Jump forward unless using cp 0x02 ; the VERIFY command; with scf ; the carry flag signalling jr nz,VR_CONT_3 ; 'LOAD' and a ; Signal 'VERIFY'. VR_CONT_3: ; 0800 ld a,0xff ; Signal 'accept data block only' ; before LOADing the block. ; THE 'LOAD A DATA BLOCK' SUBROUTINE ; This subroutine is common to all the 'LOADing' routines. In the case of LOAD ; & VERIFY it acts as a full return from the cassette handling routines but in ; the case of MERGE the data block has yet to be 'MERGEd'. LD_BLOCK: ; 0802 call LD_BYTES ; LOAD/VERIFY a data block. ret c ; Return unless an error. ; Report R - Tape loading error REPORT_Ra: ; 0806 rst ERROR_1 ; Call the error handling db 0x1a ; routine. ; THE 'LOAD' CONTROL ROUTINE ; This routine controls the LOADing of a BASIC program, and its variables, or ; an array. LD_CONTRL: ; 0808 ld e,(ix+11) ; Fetch the 'number of bytes' ld d,(ix+12) ; as given in the 'new header'. push hl ; Save the 'destination pointer'. ld a,h ; Jump forward unless trying or l ; to LOAD a previously jr nz,LD_CONT_1 ; undeclared array. inc de ; Add three bytes to the inc de ; length - for the name, the inc de ; low length & the high length ex de,hl ; of a new variable. jr LD_CONT_2 ; Jump forward. ; Consider now if there is enough room in memory for the new data block. LD_CONT_1: ; 0819 ld l,(ix-6) ; Fetch the size of the existing ld h,(ix-5) ; 'program+variables or array'. ex de,hl scf ; Jump forward if no extra sbc hl,de ; room will be required; taking jr c,LD_DATA ; into account the reclaiming of the ; presently used memory. ; Make the actual test for room. LD_CONT_2: ; 0825 ld de,0x0005 ; Allow an overhead of five add hl,de ; bytes. ld b,h ; Move the result to the ld c,l ; BC register pair and make call TEST_ROOM ; the test. ; Now deal with the LOADing of arrays. LD_DATA: ; 082e pop hl ; Fetch the 'pointer' anew. ld a,(ix+0) ; Jump forward if LOADing and a ; a BASIC program. jr z,LD_PROG ld a,h ; Jump forward if LOADing a or l ; new array. jr z,LD_DATA_1 dec hl ; Fetch the 'length' of the ld b,(hl) ; existing array by collecting dec hl ; the length bytes from the ld c,(hl) ; variables area. dec hl ; Point to its old name. inc bc ; Add three bytes to the inc bc ; length - one for the name inc bc ; and two for the 'length'. ld (X_PTR),ix ; Save the IX register pair call RECLAIM_2 ; temporarily whilst the old ld ix,(X_PTR) ; array is reclaimed. ; Space is now made available for the new array - at the end of the present ; variables area. LD_DATA_1: ; 084c ld hl,(E_LINE) ; Find the pointer to the dec hl ; end-marker of the variables area - ; the '80-byte'. ld c,(ix+11) ; Fetch the 'length' of the ld b,(ix+12) ; new array. push bc ; Save this 'length'. inc bc ; Add three bytes - one for inc bc ; the name and two for the inc bc ; 'length'. ld a,(ix-3) ; 'IX+0E' of the old header gives the ; name of the array. push af ; The name is saved whilst the call MAKE_ROOM ; appropriate amount of room is inc hl ; made available. In effect 'BC' pop af ; spaces before the 'new 80-byte'. ld (hl),a ; The name is entered. pop de ; The 'length' is fetched and inc hl ; its two bytes are also ld (hl),e ; entered. inc hl ld (hl),d inc hl ; HL now points to the first location ; that is to be filled ; with data from the tape. push hl ; This address is moved to the pop ix ; IX register pair; the carry scf ; flag set; 'data block' is ld a,0xff ; signalled; and the block jp LD_BLOCK ; LOADed. ; Now deal with the LOADing of a BASIC program and its variables LD_PROG: ; 0873 ex de,hl ; Save the 'destination pointer'. ld hl,(E_LINE) ; Find the address of the dec hl ; end-marker of the current variables ; area - the '80-byte'. ld (X_PTR),ix ; Save IX temporarily. ld c,(ix+11) ; Fetch the 'length' of the ld b,(ix+12) ; new data block. push bc ; Keep a copy of the 'length' call RECLAIM_1 ; whilst the present program and pop bc ; variables areas are reclaimed. push hl ; Save the pointer to the program push bc ; area and the length of the new data ; block. call MAKE_ROOM ; Make sufficient room available for ; the new program and its ; variables. ld ix,(X_PTR) ; Restore the IX register pair. inc hl ; The system variable VARS ld c,(ix+15) ; has also to be set for the ld b,(ix+16) ; new program. add hl,bc ld (VARS),hl ld h,(ix+14) ; If a line number was ld a,h ; specified then it too has to and 0xc0 ; be considered. jr nz,LD_PROG_1 ; Jump if 'no number'; otherwise ld l,(ix+13) ; set NEWPPC & NSPPC. ld (NEWPPC),hl ld (iy+d_NSPPC),0x00 ; The data block can now be LOADed. LD_PROG_1: ; 08ad pop de ; Fetch the 'length'. pop ix ; Fetch the 'start'. scf ; Signal 'LOAD'. ld a,0xff ; Signal 'data block' only. jp LD_BLOCK ; Now LOAD it. ; THE 'MERGE' CONTROL ROUTINE ; There are three main parts to this routine. ; i. LOAD the data block into the work space. ; ii. MERGE the lines of the new program into the old program. ; iii. MERGE the new variables into the old variables. ; Start therefore with the LOADing of the data block. ME_CONTRL: ; 08b6 ld c,(ix+11) ; Fetch the 'length' of the ld b,(ix+12) ; data block. push bc ; Save a copy of the 'length'. inc bc ; Now made 'length+1' locations rst BC_SPACES ; available in the work space. ld (hl),0x80 ; Place an end-marker in the extra ; location. ex de,hl ; Move the 'start' pointer to the HL ; register pair. pop de ; Fetch the original 'length'. push hl ; Save a copy of the 'start'. push hl ; Now set the IX register pair pop ix ; for the actual LOAD. scf ; Signal 'LOAD'. ld a,0xff ; Signal 'data block only'. call LD_BLOCK ; LOAD the data block. ; The lines of the new program are MERGEd with the lines of the old program. pop hl ; Fetch the 'start' of the new program. ld de,(PROG) ; Initialise DE to the 'start' of the ; old program. ; Enter a loop to deal with the lines of the new program. ME_NEW_LP: ; 08d2 ld a,(hl) ; Fetch a line number and test and 0xc0 ; it. jr nz,ME_VAR_LP ; Jump when finished with all the ; lines. ; Now enter an inner loop to deal with the lines of the old program. ME_OLD_LP: ; 08d7 ld a,(de) ; Fetch the high line number inc de ; byte and compare it. cp (hl) ; Jump forward if it does not inc hl ; match but in any case advance jr nz,ME_OLD_L1 ; both pointers. ld a,(de) ; Repeat the comparison for the cp (hl) ; low line number bytes. ME_OLD_L1: ; 08df dec de ; Now retreat the pointers. dec hl jr nc,ME_NEW_L2 ; Jump forward if the correct place has ; been found for a line ; of the new program. push hl ; Otherwise find the address of ex de,hl ; the start of the next old line. call NEXT_ONE pop hl jr ME_OLD_LP ; Go round the loop for each of the ; 'old lines'. ME_NEW_L2: ; 08eb call ME_ENTER ; Enter the 'new line' and go jr ME_NEW_LP ; round the outer loop again. ; In a similar manner the variables of the new program are MERGEd with the ; variables of the old program. ; A loop is entered to deal with each of the new variables in turn. ME_VAR_LP: ; 08f0 ld a,(hl) ; Fetch each variable name in ld c,a ; turn and test it. cp 0x80 ; Return when all the variables ret z ; have been considered. push hl ; Save the current new pointer. ld hl,(VARS) ; Fetch VARS (for the old program). ; Now enter an inner loop to search the existing variables area. ME_OLD_VP: ; 08f9 ld a,(hl) ; Fetch each variable name and cp 0x80 ; test it. jr z,ME_VAR_L2 ; Jump forward once the end marker is ; found. (Make an ; 'addition'.) cp c ; Compare the names (1st. bytes). jr z,ME_OLD_V2 ; Jump forward to consider it further; ; returning here if it ; proves not to match fully. ME_OLD_V1: ; 0901 push bc ; Save the new variable's name call NEXT_ONE ; whilst the next 'old variable' pop bc ; is located. ex de,hl ; Restore the pointer to the jr ME_OLD_VP ; DE register pair and go round the ; loop again. ; The old and new variables match with respect to their first bytes but ; variables with long names will need to be matched fully. ME_OLD_V2: ; 0909 and 0xe0 ; Consider bits 7, 6 & 5 only. cp 0xa0 ; Accept all the variable types jr nz,ME_VAR_L1 ; except 'long named variables'. pop de ; Make DE point to the first push de ; character of the 'new name'. push hl ; Save the pointer to the 'old name'. ; Enter a loop to compare the letters of the long names. ME_OLD_V3: ; 0912 inc hl ; Update both the 'old' and the inc de ; 'new' pointers. ld a,(de) ; Compare the two letters cp (hl) jr nz,ME_OLD_V4 ; Jump forward if the match fails. rla ; Go round the loop until the jr nc,ME_OLD_V3 ; 'last character' is found. pop hl ; Fetch the pointer to the start of the ; 'old' name and jr ME_VAR_L1 ; jump forward - successful. ME_OLD_V4: ; 091e pop hl ; Fetch the pointer and jump jr ME_OLD_V1 ; back - unsuccessful. ; Come here if the match was found. ME_VAR_L1: ; 0921 ld a,0xff ; Signal 'replace' variable. ; And here if not. (A holds +80 - variable to be 'added'.) ME_VAR_L2: ; 0923 pop de ; Fetch pointer to 'new' name. ex de,hl ; Switch over the registers. inc a ; The zero flag is to be set if there ; is to be a 'replacement'; reset ; for an 'addition'. scf ; Signal 'handling variables'. call ME_ENTER ; Now make the entry. jr ME_VAR_LP ; Go round the loop to consider the ; next new variable. ; THE 'MERGE A LINE OR A VARIABLE' SUBROUTINE ; This subroutine is entered with the following parameters: ; Carry flag reset - MERGE a BASIC line. ; set - MERGE a variable. ; Zero reset - It will be an 'addition'. ; set - It is a 'replacement'. ; HL register pair - Points to the start of the new entry. ; DE register pair - Points to where it is to MERGE. ME_ENTER: ; 092c jr nz,ME_ENT_1 ; Jump if handling an 'addition'. ex af,af' ; Save the flags. ld (X_PTR),hl ; Save the 'new' pointer whilst ex de,hl ; the 'old' line or variable call NEXT_ONE ; is reclaimed. call RECLAIM_2 ex de,hl ld hl,(X_PTR) ex af,af' ; Restore the flags. ; The new entry can now be made. ME_ENT_1: ; 093e ex af,af' ; Save the flags. push de ; Make a copy of the 'destination' ; pointer. call NEXT_ONE ; Find the length of the 'new' ; variable/line. ld (X_PTR),hl ; Save the pointer to the 'new' ; variable/line. ld hl,(PROG) ; Fetch PROG - to avoid corruption. ex (sp),hl ; Save PROG on the stack and fetch the ; 'new' pointer. push bc ; Save the length. ex af,af' ; Retrieve the flags. jr c,ME_ENT_2 ; Jump forward if adding a new ; variable. dec hl ; A new line is added before the ; 'destination' location. call MAKE_ROOM ; Make the room for the new line. inc hl jr ME_ENT_3 ; Jump forward. ME_ENT_2: ; 0955 call MAKE_ROOM ; Make the room for the new variable. ME_ENT_3: ; 0958 inc hl ; Point to the l st new location. pop bc ; Retrieve the length. pop de ; Retrieve PROG and store it ld (PROG),de ; in its correct place. ld de,(X_PTR) ; Also fetch the 'new' pointer. push bc ; Again save the length and the push de ; new' pointer. ex de,hl ; Switch the pointers and copy ldir ; the 'new' variable/line into the room ; made for it. ; The 'new' variable/line has now to be removed from the work space. pop hl ; Fetch the 'new' pointer. pop bc ; Fetch the length. push de ; Save the 'old' pointer. (Points to ; the location after the 'added' ; variable/line.) call RECLAIM_2 ; Remove the variable/line from the ; work space. pop de ; Return with the 'old' pointer ret ; in the DE register pair. ; THE 'SAVE' CONTROL ROUTINE ; The operation of SAVing a program or a block of data is very straightforward. SA_CONTRL: ; 0970 push hl ; Save the 'pointer'. ld a,0xfd ; Ensure that channel 'K' call CHAN_OPEN ; is open. xor a ; Signal 'first message'. ld de,START_TAPE_MESSAGE ; Print the message - 'Start tape, call PO_MSG ; then press any key.'. set 5,(iy+d_TV_FLAG) ; Signal 'screen will require to be ; cleared'. call WAIT_KEY ; Wait for a key to be pressed. ; Upon receipt of a keystroke the 'header' is saved. push ix ; Save the base address of the 'header' ; on the machine stack. ld de,0x0011 ; Seventeen bytes are to be SAVEd. xor a ; Signal 'it is a header'. call SA_BYTES ; Send the 'header'; with a leading ; 'type' byte and a trailing 'parity' ; byte. ; There follows a short delay before the program/data block is SAVEd. pop ix ; Retrieve the pointer to the 'header'. ld b,0x32 ; The delay is for fifty SA_1_SEC: ; 0991 halt ; interrupts, i.e. one second. djnz SA_1_SEC ld e,(ix+11) ; Fetch the length of the ld d,(ix+12) ; data block that is to be SAVEd. ld a,0xff ; Signal 'data block'. pop ix ; Fetch the 'start of block jp SA_BYTES ; pointer' and SAVE the block. ; THE CASSETTE MESSAGES ; Each message is given with the last character inverted (+80 hex.). START_TAPE_MESSAGE: ; 09a1 db 0x80 db "Start tape, then press any key" TAPE_MESSAGE_TABLE: ; 09c0 db 0xae db 0x0d, "Program:", 0xa0 db 0x0d, "Number array:", 0xa0 db 0x0d, "Character array:", 0xa0 db 0x0d, "Bytes:", 0xa0 ; THE SCREEN & PRINTER HANDLING ROUTINES ; THE 'PRINT-OUT' ROUTINES ; All of the printing to the main part of the screen, the lower part of the ; screen and the printer is handled by this set of routines. ; The PRINT-OUT routine is entered with the A register holding the code for a ; control character, a printable character or a token. PRINT_OUT: ; 09f4 call PO_FETCH ; The current print position. cp 0x20 ; If the code represents a jp nc,PO_ABLE ; printable character then jump. cp 0x06 ; Print a question mark for jr c,PO_QUEST ; codes in the range +00 - +05. cp 0x18 ; And also for codes +18 - +1F. jr nc,PO_QUEST ld hl,CONTROL_CHARACTER_OT-6 ; Base of 'control' table. ld e,a ; Move the code to the ld d,0x00 ; DE register pair. add hl,de ; Index into the table and ld e,(hl) ; fetch the offset. add hl,de ; Add the offset and make push hl ; an indirect jump to the jp PO_FETCH ; appropriate subroutine. ; THE 'CONTROL CHARACTER' TABLE CONTROL_CHARACTER_OT: ; 0a11 db PO_COMMA - $ db PO_QUEST - $ db PO_BACK_1 - $ db PO_RIGHT - $ db PO_QUEST - $ db PO_QUEST - $ db PO_QUEST - $ db PO_ENTER - $ db PO_QUEST - $ db PO_QUEST - $ db PO_1_OPER - $ db PO_1_OPER - $ db PO_1_OPER - $ db PO_1_OPER - $ db PO_1_OPER - $ db PO_1_OPER - $ db PO_2_OPER - $ db PO_2_OPER - $ ; THE 'CURSOR LEFT' SUBROUTINE ; The subroutine is entered with the B register holding the current line number ; and the C register with the current column number. PO_BACK_1: ; 0a23 inc c ; Move leftwards by one column. ld a,0x22 ; Accept the change unless cp c ; up against the lefthand side. jr nz,PO_BACK_3 bit 1,(iy+d_FLAGS) ; If dealing with the printer jr nz,PO_BACK_2 ; jump forward. inc b ; Go up one line. ld c,0x02 ; Set column value. ld a,0x18 ; Test against top line. cp b ; Note: This ought to be +19. jr nz,PO_BACK_3 ; Accept the change unless at the top ; of the screen. dec b ; Unacceptable so down a line. PO_BACK_2: ; 0a38 ld c,0x21 ; Set to lefthand column. PO_BACK_3: ; 0a3a jp CL_SET ; Make an indirect return via CL-SET & ; PO-STORE. ; THE 'CURSOR RIGHT' SUBROUTINE ; This subroutine performs an operation identical to the BASIC statement - ; PRINT OVER 1;CHR$ 32; -. PO_RIGHT: ; 0a3d ld a,(P_FLAG) ; Fetch P-FLAG and save it on push af ; the machine stack. ld (iy+d_P_FLAG),0x01 ; Set P-FLAG to OVER 1. ld a,0x20 ; A 'space'. call PO_CHAR ; Print the character. pop af ; Fetch the old value of ld (P_FLAG),a ; P-FLAG. ret ; Finished. Note: The programmer has ; forgotten to exit via PO-STORE. ; THE 'CARRIAGE RETURN' SUBROUTINE ; If the printing being handled is going to the printer then a carriage return ; character leads to the printer buffer being emptied. If the printing is to ; the screen then a test for 'scroll?' is made before decreasing the line ; number. PO_ENTER: ; 0a4f bit 1,(iy+d_FLAGS) ; Jump forward if handling jp nz,COPY_BUFF ; the printer. ld c,0x21 ; Set to lefthand column. call PO_SCR ; Scroll if necessary. dec b ; Now down a line. jp CL_SET ; Make an indirect return via CL-SET & ; PO-STORE. ; THE 'PRINT COMMA' SUBROUTINE ; The current column value is manipulated and the A register set to hold +00 ; (for TAB 0) or +10 (for TAB 16). PO_COMMA: ; 0a5f call PO_FETCH ; Why again? ld a,c ; Current column number. dec a ; Move rightwards by two dec a ; columns and then test. and 0x10 ; The A register will be +00 or +10. jr PO_FILL ; Exit via PO-FILL. ; THE 'PRINT A QUESTION MARK' SUBROUTINE ; A question mark is printed whenever an attempt is made to print an ; unprintable code. PO_QUEST: ; 0a69 ld a,0x3f ; The character '?'. jr PO_ABLE ; Now print this character instead. ; THE 'CONTROL CHARACTERS WITH OPERANDS' ROUTINE ; The control characters from INK to OVER require a single operand whereas the ; control characters AT & TAB are required to be followed by two operands. ; The present routine leads to the control character code being saved in ; TVDATA-lo, the first operand in TVDATA-hi or the A register if there is only ; a single operand required, and the second operand in the A register. PO_TV_2: ; 0a6d ld de,PO_CONT ; Save the first operand in ld (TVDATA+1),a ; TVDATA-hi and change the jr PO_CHANGE ; address of the 'output' routine to ; PO-CONT (+0A87). ; Enter here when handling the characters AT & TAB. PO_2_OPER: ; 0a75 ld de,PO_TV_2 ; The character code will be jr PO_TV_1 ; saved in TVDATA-lo and the address of ; the 'output' routine ; changed to PO-TV-2 (+0A6D). ; Enter here when handling the colour items - INK to OVER. PO_1_OPER: ; 0a7a ld de,PO_CONT ; The 'output' routine is to be changed ; to PO-CONT (+0A87). PO_TV_1: ; 0a7d ld (TVDATA),a ; Save the control character code. ; The current 'output' routine address is changed temporarily. PO_CHANGE: ; 0a80 ld hl,(CURCHL) ; HL will point to the 'output' routine ; address. ld (hl),e ; Enter the new 'output' inc hl ; routine address and thereby ld (hl),d ; force the next character code ret ; to be considered as an operand. ; Once the operands have been collected the routine continues. PO_CONT: ; 0a87 ld de,PRINT_OUT ; Restore the original address call PO_CHANGE ; for PRINT-OUT (+09F4). ld hl,(TVDATA) ; Fetch the control code and the first ; operand if there are indeed ; two operands. ld d,a ; The 'last' operand and the ld a,l ; control code are moved. cp 0x16 ; Jump forward if handling jp c,CO_TEMP_5 ; INK to OVER. jr nz,PO_TAB ; Jump forward if handling TAB. ; Now deal with the AT control character. ld b,h ; The line number. ld c,d ; The column number. ld a,0x1f ; Reverse the column number; sub c ; i.e. +00 - +1F becomes +1F - +00. jr c,PO_AT_ERR ; Must be in range. add a,0x02 ; Add in the offset to give ld c,a ; C holding +21 - +22. bit 1,(iy+d_FLAGS) ; Jump forward if handling the jr nz,PO_AT_SET ; printer. ld a,0x16 ; Reverse the line number; sub b ; i.e. +00 - +15 becomes +16 - +01. PO_AT_ERR: ; 0aac jp c,REPORT_Bd ; If appropriate jump forward. inc a ; The range +16 - +01 becomes ld b,a ; +17 - +02. inc b ; And now +18 - +03. bit 0,(iy+d_TV_FLAG) ; If printing in the lower part jp nz,PO_SCR ; of the screen then consider whether ; scrolling is needed. cp (iy+d_DF_SZ) ; Give report 5 - Out of screen, jp c,REPORT_5a ; if required. PO_AT_SET: ; 0abf jp CL_SET ; Return via CL-SET & PO-STORE. ; And the TAB control character. PO_TAB: ; 0ac2 ld a,h ; Fetch the first operand. PO_FILL: ; 0ac3 call PO_FETCH ; The current print position. add a,c ; Add the current column value. dec a ; Find how many 'spaces', modulo and 0x1f ; 32, are required and return ret z ; if the result is zero. ld d,a ; Use 0 as the counter. set 0,(iy+d_FLAGS) ; Suppress 'leading space'. PO_SPACE: ; 0ad0 ld a,0x20 ; Print 'D number' of call PO_SAVE ; spaces. dec d jr nz,PO_SPACE ret ; Now finished. ; PRINTABLE CHARACTER CODES. ; The required character (or characters) is printed by calling PO-ANY followed ; by PO-STORE. PO_ABLE: ; 0ad9 call PO_ANY ; Print the character(s) and continue ; into PO-STORE. ; THE 'POSITION STORE' SUBROUTINE ; The new position's 'line & column' values and the 'pixel' address are stored ; in the appropriate system variables. PO_STORE: ; 0adc bit 1,(iy+d_FLAGS) ; Jump forward if handling jr nz,PO_ST_PR ; the printer. bit 0,(iy+d_TV_FLAG) ; Jump forward if handling the jr nz,PO_ST_E ; lower part of the screen. ld (S_POSN),bc ; Save the values that relate ld (DF_CC),hl ; to the main part of the ret ; screen. Then return. PO_ST_E: ; 0af0 ld (S_POSNL),bc ; Save the values that relate ld (ECHO_E),bc ; to the lower part of the ld (DF_CCL),hl ; screen. ret ; Then return. PO_ST_PR: ; 0afc ld (iy+d_P_POSN),c ; Save the values that relate ld (PR_CC),hl ; to the printer buffer. ret ; Then return. ; THE 'POSITION FETCH' SUBROUTINE ; The current position's parameters are fetched from the appropriate system ; variables. PO_FETCH: ; 0b03 bit 1,(iy+d_FLAGS) ; Jump forward if handling jr nz,PO_F_PR ; the printer. ld bc,(S_POSN) ; Fetch the values relating ld hl,(DF_CC) ; to the main part of the bit 0,(iy+d_TV_FLAG) ; screen and return if this ret z ; was the intention. ld bc,(S_POSNL) ; Otherwise fetch the values ld hl,(DF_CCL) ; relating to the lower part ret ; of the screen. PO_F_PR: ; 0b1d ld c,(iy+d_P_POSN) ; Fetch the values relating ld hl,(PR_CC) ; to the printer buffer. ret ; THE 'PRINT ANY CHARACTER(S)' SUBROUTINE ; Ordinary character codes, token codes and user-defined graphic codes, and ; graphic codes are dealt with separately. PO_ANY: ; 0b24 cp 0x80 ; Jump forward with ordinary jr c,PO_CHAR ; character codes. cp 0x90 ; Jump forward with token jr nc,PO_TNUDG ; codes and UDG codes. ld b,a ; Move the graphic code. call PO_GR_1 ; Construct the graphic form. call PO_FETCH ; HL has been disturbed so 'fetch' ; again. ld de,MEMBOT ; Make DE point to the start of the ; graphic form; i.e. MEMBOT. jr PR_ALL ; Jump forward to print the graphic ; character. ; Graphic characters are constructed in an Ad Hoc manner in the calculator's ; memory area; i.e. MEM-0 & MEM-1. PO_GR_1: ; 0b38 ld hl,MEMBOT ; This is MEMBOT. call PO_GR_2 ; In effect call the following ; subroutine twice. PO_GR_2: ; 0b3e rr b ; Determine bit 0 (and later bit 2) sbc a,a ; of the graphic code. and 0x0f ; The A register will hold +00 or +0F ; depending on the value of ; the bit in the code. ld c,a ; Save the result in C. rr b ; Determine bit 1 (and later bit 3) sbc a,a ; of the graphic code. and 0xf0 ; The A register will hold +00 or +F0. or c ; The two results are combined. ld c,0x04 ; The A register holds half the PO_GR_3: ; 0b4c ld (hl),a ; character form and has to be inc hl ; used four times. dec c ; This is done for the upper jr nz,PO_GR_3 ; half of the character form ret ; and then the lower. ; Token codes and user-defined graphic codes are now separated. PO_TNUDG: ; 0b52 sub 0xa5 ; Jump forward with token codes jr nc,PO_T add a,0x15 ; UDG codes are now +00 - +0F. push bc ; Save the current position values on ; the machine stack. ld bc,(UDG) ; Fetch the base address of the jr PO_CHAR_2 ; UDG area and jump forward. PO_T: ; 0b5f call PO_TOKENS ; Now print the token and return jp PO_FETCH ; via PO-FETCH. ; The required character form is identified. PO_CHAR: ; 0b65 push bc ; The current position is saved. ld bc,(CHARS) ; The base address of the character ; area is fetched. PO_CHAR_2: ; 0b6a ex de,hl ; The print address is saved. ld hl,FLAGS ; This is FLAGS. res 0,(hl) ; Allow for a leading space cp 0x20 ; Jump forward if the character jr nz,PO_CHAR_3 ; is not a 'space'. set 0,(hl) ; But 'suppress' if it is. PO_CHAR_3: ; 0b76 ld h,0x00 ; Now pass the character code ld l,a ; to the HL register pair. add hl,hl ; The character code is in add hl,hl ; effect multiplied by 8. add hl,hl add hl,bc ; The base address of the character ; form is found. pop bc ; The current position is fetched ex de,hl ; and the base address passed to the DE ; register pair. ; THE 'PRINT ALL CHARACTERS' SUBROUTINE ; This subroutine is used to print all '8*8' bit characters. On entry the DE ; register pair holds the base address of the character form, the HL register ; the destination address and the BC register pair the current 'line & column' ; values. PR_ALL: ; 0b7f ld a,c ; Fetch the column number. dec a ; Move one column rightwards. ld a,0x21 ; Jump forward unless a new jr nz,PR_ALL_1 ; line is indicated. dec b ; Move down one line. ld c,a ; Column number is +21. bit 1,(iy+d_FLAGS) ; Jump forward if handling jr z,PR_ALL_1 ; the screen. push de ; Save the base address whilst call COPY_BUFF ; the printer buffer is pop de ; emptied. ld a,c ; Copy the new column number. PR_ALL_1: ; 0b93 cp c ; Test whether a new line is push de ; being used. If it is call z,PO_SCR ; see if the display requires pop de ; to be scrolled. ; Now consider the present state of INVERSE & OVER push bc ; Save the position values push hl ; and the destination address on the ; machine stack. ld a,(P_FLAG) ; Fetch P-FLAG and read bit 0. ld b,0xff ; Prepare the 'OVER-mask' in rra ; the B register; i.e. OVER 0 jr c,PR_ALL_2 ; = +00 & OVER 1 - +FF. inc b PR_ALL_2: ; 0ba4 rra ; Read bit 2 of P-FLAG and rra ; prepare the 'INVERSE-mask' sbc a,a ; in the C register; i.e. ld c,a ; INVERSE 0 = +00 & INVERSE 1 = +FF. ld a,0x08 ; Set the A register to hold and a ; the 'pixel-line' counter and clear ; the carry flag. bit 1,(iy+d_FLAGS) ; Jump forward if handling jr z,PR_ALL_3 ; the screen. set 1,(iy+d_FLAGS2) ; Signal 'printer buffer no longer ; empty'. scf ; Set the carry flag to show that the ; printer is being used. PR_ALL_3: ; 0bb6 ex de,hl ; Exchange the destination address with ; the base address ; before entering the loop. ; The character can now be printed. Eight passes of the loop are made - one for ; each 'pixel-line'. PR_ALL_4: ; 0bb7 ex af,af' ; The carry flag is set when using the ; printer. Save this flag in F'. ld a,(de) ; Fetch the existing 'pixel-line'. and b ; Use the 'OVER-mask' and then xor (hl) ; XOR the result with the 'pixel-line' ; of the character form. xor c ; Finally consider the 'INVERSE-mask'. ld (de),a ; Enter the result. ex af,af' ; Fetch the printer flag and jr c,PR_ALL_6 ; jump forward if required. inc d ; Update the destination address PR_ALL_5: ; 0bc1 inc hl ; Update the 'pixel-line' of the ; character form. dec a ; Decrease the counter and loop jr nz,PR_ALL_4 ; back unless it is zero. ; Once the character has been printed the attribute byte is to set as required. ex de,hl ; Make the H register hold a dec h ; correct high-address for the ; character area. bit 1,(iy+d_FLAGS) ; Set the attribute byte only if call z,PO_ATTR ; handling the screen. pop hl ; Restore the original pop bc ; destination address and the position ; values. dec c ; Decrease the column number inc hl ; and increase the destination ret ; address before returning. ; When the printer is being used the destination address has to be updated in ; increments of +20. PR_ALL_6: ; 0bd3 ex af,af' ; Save the printer flag again. ld a,0x20 ; The required increment value. add a,e ; Add the value and pass the ld e,a ; result back to the E register. ex af,af' ; Fetch the flag. jr PR_ALL_5 ; Jump back into the loop. ; THE 'SET ATTRIBUTE BYTE' SUBROUTINE ; The appropriate attribute byte is identified and fetched. The new value is ; formed by manipulating the old value, ATTR-T, MASK-T and P-FLAG. Finally this ; new value is copied to the attribute area. PO_ATTR: ; 0bdb ld a,h ; The high byte of the rrca ; destination address is rrca ; divided by eight and ANDed rrca ; with +03 to determine which and 0x03 ; third of the screen is being ; addressed; i.e. 00, 01 or 02. or 0x58 ; The high byte for the ld h,a ; attribute area is then formed. ld de,(ATTR_T) ; D holds ATTR-T, and E holds MASK-T. ld a,(hl) ; The old attribute value. xor e ; The values of MASK-T and and d ; ATTR-R are taken into xor e ; account. bit 6,(iy+d_P_FLAG) ; Jump forward unless dealing jr z,PO_ATTR_1 ; with PAPER 9. and 0xc7 ; The old paper colour is ignored and ; depending on whether the bit 2,a ; ink colour is light or dark jr nz,PO_ATTR_1 ; the new paper colour will be xor 0x38 ; black (000) or white (111). PO_ATTR_1: ; 0bfa bit 4,(iy+d_P_FLAG) ; Jump forward unless dealing jr z,PO_ATTR_2 ; with INK 9. and 0xf8 ; The old ink colour is ignored and ; depending on whether the paper bit 5,a ; colour is light or dark the new jr nz,PO_ATTR_2 ; ink colour will be black (000) xor 0x07 ; or white (111). PO_ATTR_2: ; 0c08 ld (hl),a ; Enter the new attribute value ret ; and return. ; THE 'MESSAGE PRINTING' SUBROUTINE ; This subroutine is used to print messages and tokens. The A register holds ; the 'entry number' of the message or token in a table. The DE register pair ; holds the base address of the table. PO_MSG: ; 0c0a push hl ; The high byte of the last ld h,0x00 ; entry on the machine stack is ex (sp),hl ; made zero so as to suppress trailing ; spaces (see below). jr PO_TABLE ; Jump forward. ; Enter here when expanding token codes. PO_TOKENS: ; 0c10 ld de,TOKEN_TABLE ; The base address of the token table. push af ; Save the code on the stack. (Range ; +00 - +5A; RND - COPY). ; The table is searched and the correct entry printed. PO_TABLE: ; 0c14 call PO_SEARCH ; Locate the required entry. jr c,PO_EACH ; Print the message/token. ld a,0x20 ; A 'space' will be printed bit 0,(iy+d_FLAGS) ; before the message/token call z,PO_SAVE ; if required. ; The characters of the message/token are printed in turn. PO_EACH: ; 0c22 ld a,(de) ; Collect a code. and 0x7f ; Cancel any 'inverted bit'. call PO_SAVE ; Print the character. ld a,(de) ; Collect the code again. inc de ; Advance the pointer. add a,a ; The 'inverted bit' goes to jr nc,PO_EACH ; the carry flag and signals the end of ; the message/token; ; otherwise jump back. ; Now consider whether a 'trailing space' is required. pop de ; For messages - D holds +00; for ; tokens - D holds +00 - +5A. cp 0x48 ; Jump forward if the last jr z,PO_TR_SP ; character was a '$'. cp 0x82 ; Return if the last character ret c ; was any other before 'A'. PO_TR_SP: ; 0c35 ld a,d ; Examine the value in D and cp 0x03 ; return if it indicates a ret c ; message, RND, INKEY$ or PI. ld a,0x20 ; All other cases will require a ; 'trailing space'. ; THE 'PO-SAVE' SUBROUTINE ; This subroutine allows for characters to be printed 'recursively'. The ; appropriate registers are saved whilst 'PRINT-OUT' is called. PO_SAVE: ; 0c3b push de ; Save the DE register pair. exx ; Save HL & BC. rst PRINT_A_1 ; Print the single character. exx ; Restore HL & BC. pop de ; Restore DE. ret ; Finished. ; THE 'TABLE SEARCH' SUBROUTINE ; The subroutine returns with the DE register pair pointing to the initial ; character of the required entry and the carry flag reset if a 'leading space' ; is to be considered. PO_SEARCH: ; 0c41 push af ; Save the 'entry number'. ex de,hl ; HL now holds the base address. inc a ; Make the range +01 - ?. PO_STEP: ; 0c44 bit 7,(hl) ; Wait for an 'inverted inc hl ; character'. jr z,PO_STEP dec a ; Count through the entries jr nz,PO_STEP ; until the correct one is found. ex de,hl ; DE points to the initial character. pop af ; Fetch the 'entry number' and cp 0x20 ; return with carry set for the ret c ; first thirty two entries. ld a,(de) ; However if the intial sub 0x41 ; character is a letter then a ret ; leading space may be needed. ; THE 'TEST FOR SCROLL' SUBROUTINE ; This subroutine is called whenever there might be the need to scroll the ; display. This occurs on three occasions; i. when handling a 'carriage return' ; character; ii. when using AT in an INPUT line; & iii. when the current line ; is full and the next line has to be used. ; On entry the B register holds the line number under test. PO_SCR: ; 0c55 bit 1,(iy+d_FLAGS) ; Return immediately if the ret nz ; printer is being used. ld de,CL_SET ; Pre-load the machine stack push de ; with the address of 'CL-SET'. ld a,b ; Transfer the line number. bit 0,(iy+d_TV_FLAG) ; Jump forward if considering jp nz,PO_SCR_4 ; 'INPUT ... AT ..'. cp (iy+d_DF_SZ) ; Return, via CL-SET, if the line jr c,REPORT_5a ; number is greater than the value ret nz ; of DF-SZ; give report 5 if it is ; less; otherwise continue. bit 4,(iy+d_TV_FLAG) ; Jump forward unless dealing jr z,PO_SCR_2 ; with an 'automatic listing'. ld e,(iy+d_BREG) ; Fetch the line counter. dec e ; Decrease this counter. jr z,PO_SCR_3 ; Jump forward if the listing is to be ; scrolled. ld a,0x00 ; Otherwise open channel 'K', call CHAN_OPEN ; restore the stack pointer, ld sp,(LIST_SP) ; flag that the automatic res 4,(iy+d_TV_FLAG) ; listing has finished and ret ; return via CL-SET. ; Report 5 - Out of screen REPORT_5a: ; 0c86 rst ERROR_1 ; Call the error handling db 0x04 ; routine. ; Now consider if the prompt 'scroll?' is required. PO_SCR_2: ; 0c88 dec (iy+d_SCR_CT) ; Decrease the scroll counter jr nz,PO_SCR_3 ; and proceed to give the prompt only ; if is becomes zero. ; Proceed to give the prompt message. ld a,0x18 ; The counter is reset. sub b ld (SCR_CT),a ld hl,(ATTR_T) ; The current values of ATTR-T push hl ; and MASK-T are saved. ld a,(P_FLAG) ; The current value of P-FLAG push af ; is saved. ld a,0xfd ; Channel 'K' is opened. call CHAN_OPEN xor a ; The message 'scroll?' is ld de,SCROLL_MESSAGE ; message '0'. This message is call PO_MSG ; now printed. set 5,(iy+d_TV_FLAG) ; Signal 'clear the lower screen after ; a keystroke'. ld hl,FLAGS ; This is FLAGS. set 3,(hl) ; Signal 'L mode'. res 5,(hl) ; Signal 'no key yet'. exx ; Note: DE should be pushed also. call WAIT_KEY ; Fetch a single key code. exx ; Restore the registers. cp 0x20 ; There is a jump forward to jr z,REPORT_Db ; REPORT-D - 'BREAK - CONT cp 0xe2 ; repeats' - if the keystroke jr z,REPORT_Db ; was 'BREAK', 'STOP', 'N' or or 0x20 ; 'n'; otherwise accept the cp 0x6e ; keystroke as indicating the jr z,REPORT_Db ; need to scroll the display. ld a,0xfe ; Open channel 'S'. call CHAN_OPEN pop af ; Restore the value of ld (P_FLAG),a ; P-FLAG. pop hl ; Restore the values of ATTR-T ld (ATTR_T),hl ; and MASK-T. ; The display is now scrolled. PO_SCR_3: ; 0cd2 call CL_SC_ALL ; The whole display is scrolled. ld b,(iy+d_DF_SZ) ; The line and column numbers inc b ; for the start of the line ld c,0x21 ; above the lower part of the push bc ; display are found and saved. call CL_ADDR ; The corresponding attribute ld a,h ; byte for this character area is rrca ; then found. The HL register pair rrca ; holds the address of the rrca ; byte. and 0x03 or 0x58 ld h,a ; The line in question will have 'lower part' attribute values and the new line ; at the bottom of the display may have 'ATTR-P' values so the attribute values ; are exchanged. ld de,0x5ae0 ; DE points to the first attribute byte ; of the bottom line. ld a,(de) ; The value is fetched. ld c,(hl) ; The 'lower part' value. ld b,0x20 ; There are thirty two bytes. ex de,hl ; Exchange the pointers. PO_SCR_3A: ; 0cf0 ld (de),a ; Make the first exchange ld (hl),c ; and then proceed to use the inc de ; same values for the thirty inc hl ; two attribute bytes of the djnz PO_SCR_3A ; two lines being handled. pop bc ; The line and column numbers of the ; bottom line of the 'upper ; part' are fetched before ret ; returning. ; The 'scroll?' message SCROLL_MESSAGE: ; 0cf8 db 0x80 ; Initial marker - stepped over. db 0x73,0x63,0x72,0x6f ; s - c - r - o db 0x6c,0x6c,0xbf ; l - l - ? (inverted). ; Report 0 - BREAK - CONT repeats REPORT_Db: ; 0d00 rst ERROR_1 ; Call the error handling db 0x0c ; routine. ; The lower part of the display is handled as follows: PO_SCR_4: ; 0d02 cp 0x02 ; The 'out of screen' error is jr c,REPORT_5a ; given if the lower part is add a,(iy+d_DF_SZ) ; going to be 'too large' and a sub 0x19 ; return made if scrolling is ret nc ; unnecessary. neg ; The A register will now hold 'the ; number of scrolls to be ; made'. push bc ; The line and column numbers are now ; saved. ld b,a ; The 'scroll number', ATTR-T ld hl,(ATTR_T) ; MASK-T & P-FLAG are all push hl ; saved. ld hl,(P_FLAG) push hl call TEMPS ; The 'permanent' colour items are to ; be used. ld a,b ; The 'scroll number' is fetched. ; The lower part of the screen is now scrolled 'A' number of times. PO_SCR_4A: ; 0d1c push af ; Save the 'number'. ld hl,DF_SZ ; This is DF-SZ. ld b,(hl) ; The value in DF-SZ is ld a,b ; incremented; the B register inc a ; set to hold the former value and ld (hl),a ; the A register the new value. ld hl,S_POSN+1 ; This is S-POSN-hi. cp (hl) ; The jump is taken if only the jr c,PO_SCR_4B ; lower part of the display is to be ; scrolled. (B = old DF-SZ). inc (hl) ; Otherwise S-POSN-hi is ld b,0x18 ; incremented and the whole display ; scrolled. (B = +18) PO_SCR_4B: ; 0d2d call CL_SCROLL ; Scroll 'B' lines. pop af ; Fetch and decrement the dec a ; scroll number'. jr nz,PO_SCR_4A ; Jump back until finished. pop hl ; Restore the value of ld (iy+d_P_FLAG),l ; P-FLAG. pop hl ; Restore the values of ATTR-T ld (ATTR_T),hl ; and MASK-T. ld bc,(S_POSN) ; In case S-POSN has been res 0,(iy+d_TV_FLAG) ; changed CL-SET is called to call CL_SET ; give a matching value to DF-CC. set 0,(iy+d_TV_FLAG) ; Reset the flag to indicate that pop bc ; the lower screen is being ret ; handled, fetch the line and column ; numbers, and then ; return. ; THE 'TEMPORARY COLOUR ITEMS' SUBROUTINE ; This is a most important subroutine. It is used whenever the 'permanent' ; details are required to be copied to the 'temporary' system variables. First ; ATTR-T & MASK-T are considered TEMPS: ; 0d4d xor a ; A is set to hold +00. ld hl,(ATTR_P) ; The current values of ATTR-P bit 0,(iy+d_TV_FLAG) ; and MASK-P are fetched. jr z,TEMPS_1 ; Jump forward if handing the main part ; of the screen. ld h,a ; Otherwise use +00 and the ld l,(iy+d_BORDCR) ; value in BORDCR instead. TEMPS_1: ; 0d5b ld (ATTR_T),hl ; Now set ATTR-T & MASK-T. ; Next P-FLAG is considered. ld hl,P_FLAG ; This is P-FLAG. jr nz,TEMPS_2 ; Jump forward if dealing with the ; lower part of the screen ; (A = +00). ld a,(hl) ; Otherwise fetch the value of rrca ; P-FLAG and move the odd bits to the ; even bits. TEMPS_2: ; 0d65 xor (hl) ; Proceed to copy the even bits and 0x55 ; of A to P-FLAG. xor (hl) ld (hl),a ret ; THE 'CLS COMMAND' ROUTINE ; In the first instance the whole of the display is 'cleared' - the 'pixels' ; are all reset and the attribute bytes are set to equal the value in ATTR-P - ; then the lower part of the display is reformed. CLS: ; 0d6b call CL_ALL ; The whole of the display is ; 'cleared'. CLS_LOWER: ; 0d6e ld hl,TV_FLAG ; This is TV-FLAG. res 5,(hl) ; Signal 'do not clear the lower screen ; after keystroke'. set 0,(hl) ; Signal 'lower part'. call TEMPS ; Use the permanent values. i.e. ATTR-T ; is copied from BORDCR. ld b,(iy+d_DF_SZ) ; The lower part of the screen is call CL_LINE ; now 'cleared' with these values. ; With the exception of the attribute bytes for lines '22' & '23' the attribute ; bytes for the lines in the lower part of the display will need to be made ; equal to ATTR-P. ld hl,0x5ac0 ; Attribute byte at start of line '22'. ld a,(ATTR_P) ; Fetch ATTR-P. dec b ; The line counter. jr CLS_3 ; Jump forward into the loop. CLS_1: ; 0d87 ld c,0x20 ; +20 characters per line. CLS_2: ; 0d89 dec hl ; Go back along the line setting ld (hl),a ; the attribute bytes. dec c jr nz,CLS_2 CLS_3: ; 0d8e djnz CLS_1 ; Loop back until finished. ; The size of the lower part of the display can now be fixed. ld (iy+d_DF_SZ),0x02 ; It will be two lines in size. ; It now remains for the following 'house keeping' tasks to be performed. CL_CHAN: ; 0d94 ld a,0xfd ; Open channel 'K'. call CHAN_OPEN ld hl,(CURCHL) ; Fetch the address of the ld de,PRINT_OUT ; current channel and make and a ; the output address +09F4 CL_CHAN_A: ; 0da0 ld (hl),e ; (= PRINT-OUT) and the inc hl ; input address +10A8 ld (hl),d ; (= KEY-INPUT). inc hl ld de,KEY_INPUT ccf ; First the output address jr c,CL_CHAN_A ; then the input address. ld bc,0x1721 ; As the lower part of the display is ; being handled the ; 'lower print line' will be line '23'. jr CL_SET ; Return via CL-SET. ; THE 'CLEARING THE WHOLE DISPLAY AREA' SUBROUTINE ; This subroutine is called from; i. the CLS command routine. ii. the main ; execution routine, and iii. the automatic listing routine. CL_ALL: ; 0daf ld hl,0x0000 ; The system variable COORDS ld (COORDS),hl ; is reset to zero. res 0,(iy+d_FLAGS2) ; Signal 'the screen is clear'. call CL_CHAN ; Perform the 'house keeping' tasks. ld a,0xfe ; Open channel 'S'. call CHAN_OPEN call TEMPS ; Use the 'permanent' values. ld b,0x18 ; Now 'clear' the 24 lines call CL_LINE ; of the display. ld hl,(CURCHL) ; Ensure that the current ld de,PRINT_OUT ; output address is +09F4 ld (hl),e ; (PRINT-OUT). inc hl ld (hl),d ld (iy+d_SCR_CT),0x01 ; Reset the scroll counter. ld bc,0x1821 ; As the upper part of the display is ; being handled the 'upper print ; line' will be Line '0'. Continue into ; CL-SET. ; THE 'CL-SET' SUBROUTINE ; This subroutine is entered with the BC register pair holding the line and ; column numbers of a character areas, or the C register holding the column ; number within the printer buffer. The appropriate address of the first ; character bit is then found. The subroutine returns via PO-STORE so as to ; store all the values in the required system variables. CL_SET: ; 0dd9 ld hl,PRINTER_BUFFER ; The start of the printer buffer. bit 1,(iy+d_FLAGS) ; Jump forward if handling the jr nz,CL_SET_2 ; printer buffer. ld a,b ; Transfer the line number. bit 0,(iy+d_TV_FLAG) ; Jump forward if handling the jr z,CL_SET_1 ; main part of the display. add a,(iy+d_DF_SZ) ; The top line of the lower sub 0x18 ; part of the display is called 'line ; +18' and this has to be ; converted. CL_SET_1: ; 0dee push bc ; The line & column numbers are saved. ld b,a ; The line number is moved. call CL_ADDR ; The address for the start of the line ; is formed in HL. pop bc ; The line & column numbers are fetched ; back. CL_SET_2: ; 0df4 ld a,0x21 ; The column number is now sub c ; reversed and transferred to ld e,a ; the DE register pair. ld d,0x00 add hl,de ; The required address is now jp PO_STORE ; formed; and the address and the line ; and column numbers ; are stored by jumping to PO-STORE. ; THE 'SCROLLING' SUBROUTINE ; The number of lines of the display that are to be scrolled has to be held on ; entry to the main subroutine in the B register. CL_SC_ALL: ; 0dfe ld b,0x17 ; The entry point after 'scroll?' ; The main entry point - from above and when scrolling for INPUT..AT. CL_SCROLL: ; 0e00 call CL_ADDR ; Find the starting address of the ; line. ld c,0x08 ; There are eight pixel lines to a ; complete line. ; Now enter the main scrolling loop. The B register holds the number of the top ; line to be scrolled, the HL register pair the starting address in the display ; area of this line and the C register the pixel line counter. CL_SCR_1: ; 0e05 push bc ; Save both counters. push hl ; Save the starting address. ld a,b ; Jump forward unless and 0x07 ; dealing at the present ld a,b ; moment with a 'third' of jr nz,CL_SCR_3 ; the display. ; The pixel lines of the top lines of the 'thirds' of the display have to be ; moved across the 2K boundaries. (Each 'third' = 2K.) CL_SCR_2: ; 0e0d ex de,hl ; The result of this ld hl,0xf8e0 ; manipulation is to leave HL add hl,de ; unchanged and DE pointing to ex de,hl ; the required destination. ld bc,NEXT_CHAR ; There are +20 characters. dec a ; Decrease the counter as one line is ; being dealt with. ldir ; Now move the thirty two bytes. ; The pixel lines within the 'thirds' can now be scrolled. The A register ; holds, on the first pass, +01 - +07, +09 - +0F or +11 - +17. CL_SCR_3: ; 0e19 ex de,hl ; Again DE is made to point ld hl,0xffe0 ; to the required destination. add hl,de ; This time only thirty two ex de,hl ; locations away. ld b,a ; Save the line number in B. and 0x07 ; Now find how many characters rrca ; there are remaining in the rrca ; 'third'. rrca ld c,a ; Pass the 'character total' to the C ; register. ld a,b ; Fetch the line number. ld b,0x00 ; BC holds the 'character total' ldir ; and a pixel line from each of the ; characters is 'scrolled'. ld b,0x07 ; Now prepare to increment the address ; to jump across a 'third' ; boundary. add hl,bc ; Increase HL by +0700. and 0xf8 ; Jump back if there are any jr nz,CL_SCR_2 ; 'thirds' left to consider. ; Now find if the loop has been used eight times - once for each pixel line. pop hl ; Fetch the original address. inc h ; Address the next pixel line. pop bc ; Fetch the counters. dec c ; Decrease the pixel line counter jr nz,CL_SCR_1 ; and jump back unless eight lines have ; been moved. ; Next the attribute bytes are scrolled. Note that the B register still holds ; the number of lines to be scrolled and the C register holds zero. call CL_ATTR ; The required address in the attribute ; area and the number ; of characters in 'B' lines are found. ld hl,0xffe0 ; The displacement for all add hl,de ; the attribute bytes is ex de,hl ; thirty two locations away. ldir ; The attribute bytes are 'scrolled'. ; It remains now to clear the bottom line of the display. ld b,0x01 ; The B register is loaded with +01 and ; CL-LINE is entered. ; THE 'CLEAR LINES' SUBROUTINE ; This subroutine will clear the bottom 'B' lines of the display. CL_LINE: ; 0e44 push bc ; The line number is saved for the ; duration of the subroutine. call CL_ADDR ; The starting address for the line is ; formed in HL. ld c,0x08 ; Again there are eight pixel lines to ; be considered. ; Now enter a loop to clear all the pixel lines. CL_LINE_1: ; 0e4a push bc ; Save the line number and the pixel ; line counter. push hl ; Save the address. ld a,b ; Save the line number in A. CL_LINE_2: ; 0e4d and 0x07 ; Find how many characters are rrca ; involved in 'B mod 8' lines. rrca ; Pass the result to the rrca ; C register. (C will hold +00 ld c,a ; i.e. 256 dec. for a 'third'.) ld a,b ; Fetch the line number. ld b,0x00 ; Make the BC register pair dec c ; hold 'one less' than the number of ; characters. ld d,h ; Make DE point to the first ld e,l ; character. ld (hl),0x00 ; Clear the pixel-byte of the first ; character. inc de ; Make DE point to the second ldir ; character and then clear the ; pixel-bytes of all the other ; characters. ld de,0x0701 ; For each 'third' of the add hl,de ; display HL has to be increased by ; +0701. dec a ; Now decrease the line number. and 0xf8 ; Discard any extra lines and ld b,a ; pass the 'third' count to B. jr nz,CL_LINE_2 ; Jump back if there are still 'thirds' ; to be dealt with. ; Now find if the loop has been used eight times. pop hl ; Update the address for each inc h ; pixel line. pop bc ; Fetch the counters. dec c ; Decrease the pixel line jr nz,CL_LINE_1 ; counter and jump back unless ; finished. ; Next the attribute bytes are set as required. The value in ATTR-P will be ; used when handling the main part of the display and the value in BORDCR when ; handling the lower part. call CL_ATTR ; The address of the first attribute ; byte and the number ; of bytes are found. ld h,d ; HL will point to the first ld l,e ; attribute byte and DE the inc de ; second. ld a,(ATTR_P) ; Fetch the value in ATTR-P. bit 0,(iy+d_TV_FLAG) ; Jump forward if handling the jr z,CL_LINE_3 ; main part of the screen. ld a,(BORDCR) ; Otherwise use BORDCR instead. CL_LINE_3: ; 0e80 ld (hl),a ; Set the attribute byte. dec bc ; One byte has been done. ldir ; Now copy the value to all the ; attribute bytes. pop bc ; Restore the line number. ld c,0x21 ; Set the column number to the ret ; lefthand column and return. ; THE 'CL-ATTR' SUBROUTINE ; This subroutine has two separate functions. ; i. For a given display area address the appropriate attribute address is ; returned in the DE register pair. Note that the value on entry points to the ; 'ninth' line of a character. ; ii. For a given line number, in the B register, the number of character areas ; in the display from the start of that line onwards is returned in the BC ; register pair. CL_ATTR: ; 0e88 ld a,h ; Fetch the high byte. rrca ; Multiply this value by rrca ; thirty two. rrca dec a ; Go back to the 'eight' line. or 0x50 ; Address the attribute area. ld h,a ; Restore to the high byte and ex de,hl ; transfer the address to DE. ld h,c ; This is always zero. ld l,b ; The line number. add hl,hl ; Multiply by thirty two. add hl,hl add hl,hl add hl,hl add hl,hl ld b,h ; Move the result to the ld c,l ; BC register pair before ret ; returning. ; THE 'CL-ADDR' SUBROUTINE ; For a given line number, in the B register, the appropriate display file ; address is formed in the HL register pair. CL_ADDR: ; 0e9b ld a,0x18 ; The line number has to be sub b ; reversed. ld d,a ; The result is saved in D. rrca ; In effect '(A mod 8) * 32'. rrca ; In a 'third' of the display rrca ; the low byte for the: and 0xe0 ; 1st. line = +00, 2nd. line = +20, ; etc. ld l,a ; The low byte goes into L. ld a,d ; The true line number is fetched. and 0x18 ; In effect '64 +8 * INT (A/8)' or 0x40 ; For the upper 'third' of the display ; the high byte = +40, ; middle 'third' = +48, and the lower ; 'third' = +50. ld h,a ; The high byte goes to H. ret ; Finished. ; THE 'COPY' COMMAND ROUTINE ; The one hundred and seventy six pixel lines of the display are dealt with one ; by one. COPY: ; 0eac di ; The maskable interrupt is disabled ; during COPY. ld b,0xb0 ; The '176' lines. ld hl,0x4000 ; The base address of the display. ; The following loop is now entered. COPY_1: ; 0eb2 push hl ; Save the base address and push bc ; the number of the line. call COPY_LINE ; It is called '176' times. pop bc ; Fetch the line number and pop hl ; the base address. inc h ; The base address is updated by '256' ; locations for each line of ; pixels. ld a,h ; Jump forward and hence round and 0x07 ; the loop again directly for the jr nz,COPY_2 ; eight pixel lines of a character ; line. ; For each new line of characters the base address has to be updated. ld a,l ; Fetch the low byte. add a,0x20 ; Update it by +20 bytes. ld l,a ; The carry flag will be reset when ; 'within thirds' of the display. ccf ; Change the carry flag. sbc a,a ; The A register will hold +F8 and 0xf8 ; when within a 'third' but +00 when a ; new 'third' is reached. add a,h ; The high byte of the ld h,a ; address is now updated. COPY_2: ; 0ec9 djnz COPY_1 ; Jump back until '176' lines have been ; printed. jr COPY_END ; Jump forward to the end routine. ; THE 'COPY-BUFF' SUBROUTINE ; This subroutine is called whenever the printer buffer is to have its contents ; passed to the printer. COPY_BUFF: ; 0ecd di ; Disable the maskable interrupt. ld hl,PRINTER_BUFFER ; The base address of the printer ; buffer. ld b,0x08 ; There are eight pixel lines. COPY_3: ; 0ed3 push bc ; Save the line number. call COPY_LINE ; It is called '8' times. pop bc ; Fetch the line number. djnz COPY_3 ; Jump back until '8' lines have been ; printed. ; Continue into the COPY-END routine. COPY_END: ; 0eda ld a,0x04 ; Stop the printer motor. out (0xfb),a ei ; Enable the maskable interrupt and ; continue into CLEAR-PRB. ; THE 'CLEAR PRINTER BUFFER' SUBROUTINE ; The printer buffer is cleared by calling this subroutine. CLEAR_PRB: ; 0edf ld hl,PRINTER_BUFFER ; The base address of the printer ; buffer. ld (iy+d_PR_CC),l ; Reset the printer 'column'. xor a ; Clear the A register. ld b,a ; Also clear the B register (in effect ; B holds dec.256). PRB_BYTES: ; 0ee7 ld (hl),a ; The '256' bytes of the inc hl ; printer buffer are all djnz PRB_BYTES ; cleared in turn. res 1,(iy+d_FLAGS2) ; Signal 'the buffer is empty'. ld c,0x21 ; Set the printer position and jp CL_SET ; return via CL-SET & PO-STORE. ; THE 'COPY-LINE' SUBROUTINE ; The subroutine is entered with the HL register pair holding the base address ; of the thirty two bytes that form the pixel-line and the B register holding ; the pixel-line number. COPY_LINE: ; 0ef4 ld a,b ; Copy the pixel-line number. cp 0x03 ; The A register will hold sbc a,a ; +00 until the last two lines and 0x02 ; are being handled. out (0xfb),a ; Slow the motor for the last two pixel ; lines only. ld d,a ; The D register will hold either +00 ; or +02. ; There are three tests to be made before doing any 'printing'. COPY_L_1: ; 0efd call BREAK_KEY ; Jump forward unless the jr c,COPY_L_2 ; BREAK key is being pressed. ld a,0x04 ; But if it is then; out (0xfb),a ; stop the motor, ei ; enable the maskable interrupt, call CLEAR_PRB ; clear the printer buffer and exit rst ERROR_1 ; via the error handling routine db 0x0c ; – 'BREAK-CONT repeats'. COPY_L_2: ; 0f0c in a,(0xfb) ; Fetch the status of the add a,a ; printer. ret m ; Make an immediate return if the ; printer is not present. jr nc,COPY_L_1 ; Wait for the stylus. ld c,0x20 ; There are thirty two bytes. ; Now enter a loop to handle these bytes. COPY_L_3: ; 0f14 ld e,(hl) ; Fetch a byte. inc hl ; Update the pointer. ld b,0x08 ; Eight bits per byte. COPY_L_4: ; 0f18 rl d ; Move D left. rl e ; Move each bit into the carry. rr d ; Move D back again, picking up the ; carry from E. COPY_L_5: ; 0f1e in a,(0xfb) ; Again fetch the status of the rra ; printer and wait for the jr nc,COPY_L_5 ; signal from the encoder. ld a,d ; Now go ahead and pass the out (0xfb),a ; 'bit' to the printer Note: bit 2 - ; low starts the ; motor, bit 1 - high slows the motor ; and bit 7 is high for the ; actual 'printing'. djnz COPY_L_4 ; 'Print' each bit. dec c ; Decrease the byte counter. jr nz,COPY_L_3 ; Jump back whilst there are ret ; still bytes; otherwise return. ; THE 'EDITOR' ROUTINES ; The editor is called on two occasions: ; i.From the main execution routine so that the user can enter a BASIC line ; into the system. ; ii. From the INPUT command routine. ; First the 'error stack pointer' is saved and an alternative address provided. EDITOR: ; 0f2c ld hl,(ERR_SP) ; The current value is saved on push hl ; the machine stack. ED_AGAIN: ; 0f30 ld hl,ED_ERROR ; This is ED-ERROR. push hl ; Any event that leads to the ld (ERR_SP),sp ; error handling routine being used ; will come back to ; ED-ERROR. ; A loop is now entered to handle each keystroke. ED_LOOP: ; 0f38 call WAIT_KEY ; Return once a key has been pressed. push af ; Save the code temporarily. ld d,0x00 ; Fetch the duration of the ld e,(iy+d_PIP) ; keyboard click. ld hl,0x00c8 ; And the pitch. call BEEPER ; Now make the 'pip'. pop af ; Restore the code. ld hl,ED_LOOP ; Pre-load the machine stack push hl ; with the address of ED-LOOP. ; Now analyse the code obtained. cp 0x18 ; Accept all character codes, jr nc,ADD_CHAR ; graphic codes and tokens. cp 0x07 ; Also accept ','. jr c,ADD_CHAR cp 0x10 ; Jump forward if the code jr c,ED_KEYS ; represents an editing key. ; The control keys - INK to TAB -are now considered. ld bc,0x0002 ; INK & PAPER will require two ; locations. ld d,a ; Copy the code to D. cp 0x16 ; Jump forward with INK & jr c,ED_CONTR ; PAPER ; AT & TAB would be handled as follows: inc bc ; Three locations required. bit 7,(iy+d_FLAGX) ; Jump forward unless dealing jp z,ED_IGNORE ; with INPUT LINE... . call WAIT_KEY ; Get the second code. ld e,a ; and put it in E. ; The other bytes for the control characters are now fetched. ED_CONTR: ; 0f6c call WAIT_KEY ; Get another code. push de ; Save the previous codes. ld hl,(K_CUR) ; Fetch K-CUR. res 0,(iy+d_MODE) ; Signal 'K mode'. call MAKE_ROOM ; Make two or three spaces. pop bc ; Restore the previous codes. inc hl ; Point to the first location. ld (hl),b ; Enter first code. inc hl ; Then enter the second code ld (hl),c ; which will be overwritten if there ; are only two codes - i.e. ; with INK & PAPER. jr ADD_CH_1 ; Jump forward. ; THE 'ADDCHAR' SUBROUTINE ; This subroutine actually adds a code to the current EDIT or INPUT line. ADD_CHAR: ; 0f81 res 0,(iy+d_MODE) ; Signal 'K mode'. ld hl,(K_CUR) ; Fetch the cursor position. call ONE_SPACE ; Make a single space. ADD_CH_1: ; 0f8b ld (de),a ; Enter the code into the space inc de ; and signal that the cursor is to ld (K_CUR),de ; occur at the location after. Then ret ; return indirectly to ED-LOOP. ; The editing keys are dealt with as follows: ED_KEYS: ; 0f92 ld e,a ; The code is transferred to ld d,0x00 ; the DE register pair. ld hl,EDITING_KEYS_OT-7 ; The base address of the editing key ; table. add hl,de ; The entry is addressed and ld e,(hl) ; then fetched into E. add hl,de ; The address of the handling push hl ; routine is saved on the machine ; stack. ld hl,(K_CUR) ; The HL register pair is set and ret ; an indirect jump made to the required ; routine. ; THE 'EDITING KEYS' TABLE EDITING_KEYS_OT: ; 0fa0 db ED_EDIT - $ db ED_LEFT - $ db ED_RIGHT - $ db ED_DOWN - $ db ED_UP - $ db ED_DELETE - $ db ED_ENTER - $ db ED_SYMBOL - $ db ED_GRAPH - $ ; THE 'EDIT KEY' SUBROUTINE ; When in 'editing mode' pressing the EDIT key will bring down the 'current ; BASIC line'. However in 'INPUT mode' the action of the EDIT key is to clear ; the current reply and allow a fresh one. ED_EDIT: ; 0fa9 ld hl,(E_PPC) ; Fetch the current line number. bit 5,(iy+d_FLAGX) ; But jump forward if in jp nz,CLEAR_SP ; 'INPUT mode'. call LINE_ADDR ; Find the address of the start of call LINE_NO ; the current line and hence its ; number. ld a,d ; If the line number returned is or e ; zero then simply clear the jp z,CLEAR_SP ; editing area. push hl ; Save the address of the line. inc hl ; Move on to collect the ld c,(hl) ; length of the line. inc hl ld b,(hl) ld hl,0x000a ; Add +0A to the length and test add hl,bc ; that there is sufficient room ld b,h ; for a copy of the line. ld c,l call TEST_ROOM call CLEAR_SP ; Now clear the editing area. ld hl,(CURCHL) ; Fetch the current channel ex (sp),hl ; address and exchange it for the ; address of the line. push hl ; Save it temporarily. ld a,0xff ; Open channel 'R' so that the call CHAN_OPEN ; line will be copied to the editing ; area. pop hl ; Fetch the address of the line. dec hl ; Goto before the line. dec (iy+d_E_PPC) ; Decrement the current line number so ; as to avoid ; printing the cursor. call OUT_LINE ; Print the BASIC line inc (iy+d_E_PPC) ; Increment the current line number. ; Note: The decrementing of the line ; number does not always ; stop the cursor from being printed. ld hl,(E_LINE) ; Fetch the start of the line in inc hl ; the editing area and step past inc hl ; the line number and the inc hl ; length to find the address inc hl ; for K-CUR. ld (K_CUR),hl pop hl ; Fetch the former channel call CHAN_FLAG ; address and set the ret ; appropriate flags before returning to ; ED-LOOP. ; THE 'CURSOR DOWN EDITING' SUBROUTINE ED_DOWN: ; 0ff3 bit 5,(iy+d_FLAGX) ; Jump forward if in jr nz,ED_STOP ; 'INPUT mode'. ld hl,E_PPC ; This is E-PPC. call LN_FETCH ; The next line number is found jr ED_LIST ; and a new automatic listing produced. ED_STOP: ; 1001 ld (iy+d_ERR_NR),0x10 ; 'STOP in INPUT' report. jr ED_ENTER ; Jump forward. ; THE 'CURSOR LEFT EDITING' SUBROUTINE ED_LEFT: ; 1007 call ED_EDGE ; The cursor is moved. jr ED_CUR ; Jump forward. ; THE 'CURSOR RIGHT EDITING' SUBROUTINE ED_RIGHT: ; 100c ld a,(hl) ; The current character is tested cp 0x0d ; and if it is 'carriage return' ret z ; then return. inc hl ; Otherwise make the cursor come after ; the character. ED_CUR: ; 1011 ld (K_CUR),hl ; Set the system variable K-CUR. ret ; THE 'DELETE EDITING' SUBROUTINE ED_DELETE: ; 1015 call ED_EDGE ; Move the cursor leftwards. ld bc,0x0001 ; Reclaim the current jp RECLAIM_2 ; character. ; THE 'ED-IGNORE' SUBROUTINE ED_IGNORE: ; 101e call WAIT_KEY ; The next two codes from the call WAIT_KEY ; key-input routine are ignored. ; THE 'ENTER EDITING' SUBROUTINE ED_ENTER: ; 1024 pop hl ; The address of ED-LOOP and pop hl ; ED-ERROR are discarded. ED_END: ; 1026 pop hl ; The old value of ERR-SP ld (ERR_SP),hl ; is restored. bit 7,(iy+d_ERR_NR) ; Now return if there were ret nz ; no errors. ld sp,hl ; Otherwise make an indirect ret ; jump to the error routine. ; THE 'ED-EDGE' SUBROUTINE ; The address of the cursor is in the HL register pair and will be decremented ; unless the cursor is already at the start of the line. Care is taken not to ; put the cursor between control characters and their parameters. ED_EDGE: ; 1031 scf ; DE will hold either E-LINE call SET_DE ; (for editing) or WORKSP (for ; INPUTing). sbc hl,de ; The carry flag will become set if the ; cursor is already to be at add hl,de ; the start of the line. inc hl ; Correct for the subtraction. pop bc ; Drop the return address. ret c ; Return via ED-LOOP if the carry flag ; is set. push bc ; Restore the return address. ld b,h ; Move the current address of ld c,l ; the cursor to BC. ; Now enter a loop to check that control characters are not split from their ; parameters. ED_EDGE_1: ; 103e ld h,d ; HL will point to the ld l,e ; character in the line after inc hl ; that addressed by DE. ld a,(de) ; Fetch a character code. and 0xf0 ; Jump forward if the code cp 0x10 ; does not represent jr nz,ED_EDGE_2 ; INK to TAB. inc hl ; Allow for one parameter. ld a,(de) ; Fetch the code anew. sub 0x17 ; Carry is reset for TAB. adc a,0x00 ; Note: This splits off AT & TAB but AT ; & TAB in this form are ; not implemented anyway so it makes no ; difference. jr nz,ED_EDGE_2 ; Jump forward unless dealing inc hl ; with AT & TAB which would have two ; parameters, if used. ED_EDGE_2: ; 1051 and a ; Prepare for true subtraction. sbc hl,bc ; The carry flag will be reset add hl,bc ; when the 'updated pointer' reaches ; K-CUR. ex de,hl ; For the next loop use the jr c,ED_EDGE_1 ; 'updated pointer', but if ret ; exiting use the 'present pointer' for ; K-CUR. ; Note: It is the control character ; that is deleted when using ; DELETE. ; THE 'CURSOR UP EDITING' SUBROUTINE ED_UP: ; 1059 bit 5,(iy+d_FLAGX) ; Return if in 'INPUT mode'. ret nz ld hl,(E_PPC) ; Fetch the current line call LINE_ADDR ; number and its start address. ex de,hl ; HL now points to the previous line. call LINE_NO ; This line's number is fetched. ld hl,E_PPC+1 ; This is E-PPC-hi. call LN_STORE ; The line number is stored. ED_LIST: ; 106e call AUTO_LIST ; A new automatic listing is ld a,0x00 ; now produced and channel 'K' jp CHAN_OPEN ; re-opened before returning to ; ED-LOOP. ; THE 'ED-SYMBOL' SUBROUTINE ; If SYMBOL & GRAPHICS codes were used they would be handled as follows: ED_SYMBOL: ; 1076 bit 7,(iy+d_FLAGX) ; Jump back unless dealing with jr z,ED_ENTER ; INPUT LINE. ED_GRAPH: ; 107c jp ADD_CHAR ; Jump back. ; THE 'ED-ERROR' SUBROUTINE ; Come here when there has been some kind of error. ED_ERROR: ; 107f bit 4,(iy+d_FLAGS2) ; Jump back if using other than jr z,ED_END ; channel 'K'. ld (iy+d_ERR_NR),0xff ; Cancel the error number and ld d,0x00 ; give a 'rasp' before going ld e,(iy+d_RASP) ; around the editor again. ld hl,P_FOR call BEEPER jp ED_AGAIN ; THE 'CLEAR-SP' SUBROUTINE ; The editing area or the work space is cleared as directed. CLEAR_SP: ; 1097 push hl ; Save the pointer to the space. call SET_HL ; DE will point to the first character ; and HL the last. dec hl ; The correct amount is now call RECLAIM_1 ; reclaimed. ld (K_CUR),hl ; The system variables K-CUR ld (iy+d_MODE),0x00 ; and MODE ('K mode') are pop hl ; initialised before fetching ret ; the pointer and returning. ; THE 'KEYBOARD INPUT' SUBROUTINE ; This important subroutine returns the code of the last key to have bean ; pressed but note that CAPS LOCK, the changing of the mode and the colour ; control parameters are handled within the subroutine. KEY_INPUT: ; 10a8 bit 3,(iy+d_TV_FLAG) ; Copy the edit-linear the call nz,ED_COPY ; INPUT-line to the screen if the mode ; has changed. and a ; Return with both carry bit 5,(iy+d_FLAGS) ; and zero flags reset if no ret z ; new key has been pressed ld a,(LAST_K) ; Otherwise fetch the code and res 5,(iy+d_FLAGS) ; signal that it has been taken push af ; Save the code temporarily. bit 5,(iy+d_TV_FLAG) ; Clear the lower part of the call nz,CLS_LOWER ; display if necessary; e.g. after ; 'scroll?'; pop af ; Fetch the code. cp 0x20 ; Accept all characters and jr nc,KEY_DONEa ; token codes. cp 0x10 ; Jump forward with most of jr nc,KEY_CONTR ; the control character codes. cp 0x06 ; Jump forward with the 'mode'. jr nc,KEY_MNCL ; codes and the CAPS LOCK code. ; Now deal with the FLASH, BRIGHT& INVERSE codes. ld b,a ; Save the code. and 0x01 ; Keep only bit 0. ld c,a ; C holds +00 (= OFF) or C holds +01 (= ; ON). ld a,b ; Fetch the code. rra ; Rotate it once (losing bit 0). add a,0x12 ; Increase it by +12 giving for jr KEY_DATA ; FLASH - +12, BRIGHT - +13 and INVERSE ; - +14. ; The CAPS LOCK code and the mode codes are dealt with 'locally'. KEY_MNCL: ; 10db jr nz,KEY_MODE ; Jump forward with 'mode' codes. ld hl,FLAGS2 ; This is FLAGS2. ld a,0x08 ; Flip bit 3 of FLAGS2. This is xor (hl) ; the CAPS LOCK flag. ld (hl),a jr KEY_FLAG ; Jump forward. KEY_MODE: ; 10e6 cp 0x0e ; Check the lower limit. ret c sub 0x0d ; Reduce the range. ld hl,MODE ; This is MODE. cp (hl) ; Has it been changed? ld (hl),a ; Enter the new 'mode' code. jr nz,KEY_FLAG ; Jump if it has changed; ld (hl),0x00 ; otherwise make it 'L mode'. KEY_FLAG: ; 10f4 set 3,(iy+d_TV_FLAG) ; Signal 'the mode might have changed. cp a ; Reset the carry flag and ret ; return. ; The control key codes (apart from FLASH, BRIGHT & INVERSE) are manipulated. KEY_CONTR: ; 10fa ld b,a ; Save the code. and 0x07 ; Make the C register hold the ld c,a ; parameter. (+00 to +07) ld a,0x10 ; A now holds the INK code. bit 3,b ; But if the code was an jr nz,KEY_DATA ; 'unshifted' code then make A inc a ; hold the PAPER code. ; The parameter is saved in K-DATA and the channel address changed from ; KEY-INPUT to KEY-NEXT. KEY_DATA: ; 1105 ld (iy+d_K_DATA),c ; Save the parameter. ld de,KEY_NEXT ; This is KEY-NEXT. jr KEY_CHAN ; Jump forward. ; Note: On the first pass entering at KEY-INPUT the A register is ; returned holding a control code' and then on the next pass, entering at ; KEY-NEXT, it is the parameter that is returned. KEY_NEXT: ; 110d ld a,(K_DATA) ; Fetch the parameter. ld de,KEY_INPUT ; This is KEY-INPUT. ; Now set the input address in the first channel area. KEY_CHAN: ; 1113 ld hl,(CHANS) ; Fetch the channel address. inc hl inc hl ld (hl),e ; Now set the input address. inc hl ld (hl),d ; Finally exit with the required code in the A register. KEY_DONEa: ; 111b scf ; Show a code has been found ret ; and return. ; THE 'LOWER SCREEN COPYING' SUBROUTINE ; This subroutine is called whenever the line in the editing area or the INPUT ; area is to be printed in the lower part of the screen. ED_COPY: ; 111d call TEMPS ; Use the permanent colours. res 3,(iy+d_TV_FLAG) ; Signal that the 'mode is to be res 5,(iy+d_TV_FLAG) ; considered unchanged' and the 'lower ; screen does not need ; clearing'. ld hl,(S_POSNL) ; Save the current value of push hl ; S-POSNL. ld hl,(ERR_SP) ; Keep the current value of push hl ; ERR-SP. ld hl,ED_FULL ; This is ED-FULL. push hl ; Push this address on to the ld (ERR_SP),sp ; machine stack to make ED-FULL the ; entry point following an ; error. ld hl,(ECHO_E) ; Push the value of ECHO-E push hl ; on to the stack. scf ; Make HL point to the start call SET_DE ; of the space and DE the end. ex de,hl call OUT_LINE2 ; Now print the line. ex de,hl ; Exchange the pointers and call OUT_CURS ; print the cursor. ld hl,(S_POSNL) ; Next fetch the Current value ex (sp),hl ; of S-POSNL and exchange it with ; ECHO-E. ex de,hl ; Pass ECHO-E to DE. call TEMPS ; Again fetch the permanent colours. ; The remainder of any line that has been started is now completed with spaces ; printed with the 'permanent' PAPER colour. ED_BLANK: ; 1150 ld a,(S_POSNL+1) ; Fetch the current line number sub d ; and subtract the old line number. jr c,ED_C_DONE ; Jump forward if no 'blanking' of ; lines required. jr nz,ED_SPACES ; Jump forward if not on the same line. ld a,e ; Fetch the old column number sub (iy+d_S_POSNL) ; and subtract the new column number. jr nc,ED_C_DONE ; Jump if no spaces required. ED_SPACES: ; 115e ld a,0x20 ; A 'space'. push de ; Save the old values, call PRINT_OUT ; Print it. pop de ; Fetch the old values. jr ED_BLANK ; Back again. ; New deal with any errors. ED_FULL: ; 1167 ld d,0x00 ; Give out a 'rasp'. ld e,(iy+d_RASP) ld hl,P_FOR call BEEPER ld (iy+d_ERR_NR),0xff ; Cancel the error number. ld de,(S_POSNL) ; Fetch the current value of jr ED_C_END ; S-POSNL and jump forward. ; The normal exit upon completion of the copying over of the editor the INPUT ; line. ED_C_DONE: ; 117c pop de ; The new position value. pop hl ; The 'error address'. ; But come here after an error. ED_C_END: ; 117e pop hl ; The old value of ERR-SP is ld (ERR_SP),hl ; restored. pop bc ; Fetch the old value of S-POSNL. push de ; Save the new position values. call CL_SET ; Set the system variables. pop hl ; The old value of S-POSNL ld (ECHO_E),hl ; goes into ECHO-E. ld (iy+d_X_PTR+1),0x00 ; X-PTR is cleared in a ret ; suitable manner and the return made. ; THE 'SET-HL' AND 'SET-DE' SUBROUTINES ; These subroutines return with HL pointing to the first location and DE the ; 'last' location of either the editing area or the work space. SET_HL: ; 1190 ld hl,(WORKSP) ; Point to the last location dec hl ; of the editing area. and a ; Clear the carry flag. SET_DE: ; 1195 ld de,(E_LINE) ; Point to the start of the bit 5,(iy+d_FLAGX) ; editing area and return if ret z ; in 'editing mode'. ld de,(WORKSP) ; Otherwise change DE. ret c ; Return if now intended. ld hl,(STKBOT) ; Fetch STKBOT and then ret ; return. ; THE 'REMOVE-FP' SUBROUTINE ; This subroutine removes the hidden floating-point forms in a BASIC line. REMOVE_FP: ; 11a7 ld a,(hl) ; Each character in turn is examined. cp 0x0e ; Is it a number marker? ld bc,0x0006 ; It will occupy six locations. call z,RECLAIM_2 ; Reclaim the F-P number. ld a,(hl) ; Fetch the code again. inc hl ; Update the pointer. cp 0x0d ; 'Carriage return'? jr nz,REMOVE_FP ; Back if not. But make a ret ; simple return if it is. ; THE EXECUTIVE ROUTINES ; THE 'INITIALISATION' ROUTINE ; The main entry point to this routine is at START/NEW (11CB). When entered ; from START (0000), as when power is first applied to the system, the A ; register holds zero and the DE register the value +FFFF. However the main ; entry point can also be reached following the execution of the NEW command ; routine. ; THE 'NEW COMMAND' ROUTINE NEW: ; 11b7 di ; Disable the maskable interrupt. ld a,0xff ; The NEW flag. ld de,(RAMTOP) ; The existing value of RAMTOP is ; preserved. exx ; Load the alternate registers ld bc,(P_RAMT) ; with the following system ld de,(RASP) ; variables. All of which will ld hl,(UDG) ; also be preserved. exx ; The main entry point. START_NEW: ; 11cb ld b,a ; Save the flag for later. ld a,0x07 ; Make the border white in out (0xfe),a ; colour. ld a,0x3f ; Set the I register to hold ld i,a ; the value of +3F. db 0x00,0x00,0x00 ; Wait 24 T states. db 0x00,0x00,0x00 ; Now the memory is checked. RAM_CHECK: ; 11da ld h,d ; Transfer the value in DE ld l,e ; (START = +FFFF, NEW = RAMTOP). RAM_FILL: ; 11dc ld (hl),0x02 ; Enter the value of +02 into dec hl ; every location above +3FFF. cp h jr nz,RAM_FILL RAM_READ: ; 11e2 and a ; Prepare for true subtraction. sbc hl,de ; The carry flag will become add hl,de ; reset when the top is reached. inc hl ; Update the pointer. jr nc,RAM_DONE ; Jump when at top. dec (hl) ; +02 goes to +01. jr z,RAM_DONE ; But if zero then RAM is faulty. Use ; current HL as top. dec (hl) ; +01 goes to +00. jr z,RAM_READ ; Step to the next test unless it ; fails. RAM_DONE: ; 11ef dec hl ; HL points to the last actual location ; in working order. ; Next restore the 'preserved' system variables. (Meaningless when coming from ; START.) exx ; Switch registers. ld (P_RAMT),bc ; Restore P-RAMT,RASP/PIP ld (RASP),de ; &UDG ld (UDG),hl exx inc b ; Test the START/NEW flag. jr z,RAM_SET ; Jump forward if coming from the NEW ; command routine. ; Overwrite the system variables when coming from START and initialise the ; user-defined graphics area. ld (P_RAMT),hl ; Top of physical RAM. ld de,FONT+0x35*8+7 ; Last byte of 'U' in character set. ld bc,0x00a8 ; There are this number of bytes in ; twenty one letters. ex de,hl ; Switch the pointers. lddr ; Now copy the character forms of the ; letter 'A' to 'U'. ex de,hl ; Switch the pointers back. inc hl ; Point to the first byte. ld (UDG),hl ; Now set UDG. dec hl ; Down one location. ld bc,0x0040 ; Set the system variables ld (RASP),bc ; RASP & PIP. ; The remainder of the routine is common to both the START and the NEW ; operations. RAM_SET: ; 1219 ld (RAMTOP),hl ; Set RAMTOP. ld hl,FONT-0x20*8 ; Initialise the system variable ld (CHARS),hl ; CHARS. ; Next the machine stack is set up. ld hl,(RAMTOP) ; The top location is made to ld (hl),0x3e ; hold +3E. dec hl ; The next location is left holding ; zero. ld sp,hl ; These two locations represent the ; 'last entry'. dec hl ; Step down two locations to dec hl ; find the correct value for ld (ERR_SP),hl ; ERR-SP. ; The initialisation routine continues with: im 1 ; Interrupt mode 1 is used. ld iy,ERR_NR ; IY holds +ERR-NR always. ei ; The maskable interrupt can now be ; enabled. The real-time clock ; will be updated and the keyboard ; scanned every 1/50th of a ; second. ld hl,AFTER_SYSVARS ; The base address of the ld (CHANS),hl ; channel information area. ld de,CHANS_INIT ; The initial channel data ld bc,0x0015 ; is moved from the table ex de,hl ; (15AF) to the channel ldir ; information area. ex de,hl ; The system variable DATADD dec hl ; is made to point to the last ld (DATADD),hl ; location of the channel data. inc hl ; And PROG & VARS to the ld (PROG),hl ; the location after that. ld (VARS),hl ld (hl),0x80 ; The end-marker of the variables area. inc hl ; Move on one location to find ld (E_LINE),hl ; the value for E-LINE. ld (hl),0x0d ; Make the edit-line be a single inc hl ; 'carriage return' character. ld (hl),0x80 ; Now enter an end-marker. inc hl ; Move on one location to find ld (WORKSP),hl ; the value for WORKSP, STKBOT ld (STKBOT),hl ; & STKEND. ld (STKEND),hl ld a,0x38 ; Initialise the colour system ld (ATTR_P),a ; variables to : FLASH 0, ld (ATTR_T),a ; BRIGHT 0, PAPER 7, & INK 0. ld (BORDCR),a ld hl,0x0523 ; Initialise the system ld (REPDEL),hl ; variables REPDEL & REPPER. dec (iy+d_KSTATE) ; Make KSTATE0 hold +FF dec (iy+d_KSTATE+4) ; Make KSTATE4 hold +FF ld hl,STREAMS_INIT ; Next move the initial stream ld de,STRMS ; data from its table to the ld bc,0x000e ; streams area. ldir set 1,(iy+d_FLAGS) ; Signal 'printer in use' call CLEAR_PRB ; and clear the printer buffer. ld (iy+d_DF_SZ),0x02 ; Set the size of the lower call CLS ; part of the display and clear the ; whole display. xor a ; Now print the message ld de,COPYRIGHT_MESSAGE-1 ; '© 1982 Sinclair Research Ltd' call PO_MSG ; on the bottom line. set 5,(iy+d_TV_FLAG) ; Signal 'the lower part will required ; to be cleared. jr MAIN_1 ; Jump forward into the main execution ; loop. ; THE 'MAIN EXECUTION' LOOP ; The main loop extends from location 12A2 to location 15AE and it controls the ; 'editing mode', the execution of direct commands and the production of ; reports. MAIN_EXEC: ; 12a2 ld (iy+d_DF_SZ),0x02 ; The lower part of the screen is to be ; two lines in size. call AUTO_LIST ; Produce an automatic listing. MAIN_1: ; 12a9 call SET_MIN ; All the areas from E-LINE onwards are ; given their ; minimum configurations. MAIN_2: ; 12ac ld a,0x00 ; Channel 'K' is opened before call CHAN_OPEN ; calling the EDITOR. call EDITOR ; The EDITOR is called to allow the ; user to build up a BASIC line. call LINE_SCAN ; The current line is scanned for ; correct syntax. bit 7,(iy+d_ERR_NR) ; Jump forward if the syntax is jr nz,MAIN_3 ; correct. bit 4,(iy+d_FLAGS2) ; Jump forward if other than jr z,MAIN_4 ; channel 'K' is being used. ld hl,(E_LINE) ; Point to the start of the line with ; the error. call REMOVE_FP ; Remove the floating-point forms from ; this line. ld (iy+d_ERR_NR),0xff ; Reset ERR-NR and jump back jr MAIN_2 ; to MAIN-2 leaving the listing ; unchanged. ; The 'edit-line' has passed syntax and the three types of line that are ; possible have to be distinguished from each other. MAIN_3: ; 12cf ld hl,(E_LINE) ; Point to the start of the line. ld (CH_ADD),hl ; Set CH-ADD to the start also. call E_LINE_NO ; Fetch any line number into BC. ld a,b ; Is the line number a valid or c ; one? jp nz,MAIN_ADD ; Jump if it is so, and add the new ; line to the existing program. rst GET_CHAR ; Fetch the first character of cp 0x0d ; the line and see if the line is ; 'carriage return only'. jr z,MAIN_EXEC ; If it is then jump back. ; The 'edit-line' must start with a direct BASIC command so this line becomes ; the first line to be interpreted. bit 0,(iy+d_FLAGS2) ; Clear the whole display unless call nz,CL_ALL ; the flag says it is unnecessary. call CLS_LOWER ; Clear the lower part anyway. ld a,0x19 ; Set the appropriate value sub (iy+d_S_POSN+1) ; for the scroll counter. ld (SCR_CT),a set 7,(iy+d_FLAGS) ; Signal 'line execution'. ld (iy+d_ERR_NR),0xff ; Ensure ERR-NR is correct. ld (iy+d_NSPPC),0x01 ; Deal with the first statement in the ; line. call LINE_RUN ; Now the line is interpreted. Note: ; The address 1303 goes on ; to the machine stack and is addressed ; by ERR-SP. ; After the line has been interpreted and all the actions consequential to it ; have been completed a return is made to MAIN-4, so that a report can be made. MAIN_4: ; 1303 halt ; The maskable interrupt must be ; enabled. res 5,(iy+d_FLAGS) ; Signal 'ready for a new key'. bit 1,(iy+d_FLAGS2) ; Empty the printer buffer if call nz,COPY_BUFF ; it has been used. ld a,(ERR_NR) ; Fetch the error number and inc a ; increment it. MAIN_G: ; 1313 push af ; Save the new value. ld hl,0x0000 ; The system variables ld (iy+d_FLAGX),h ; FLAGX, X-PTR-hi & ld (iy+d_X_PTR+1),h ; DEFADD are all set to zero. ld (DEFADD),hl ld hl,0x0001 ; Ensure that stream +00 ld (STRMS+6),hl ; points to channel 'K' call SET_MIN ; Clear all the work areas and the ; calculator stack. res 5,(iy+d_FLAGX) ; Signal 'editing mode'. call CLS_LOWER ; Clear the lower screen. set 5,(iy+d_TV_FLAG) ; Signal 'the lower screen will require ; clearing'. pop af ; Fetch the report value. ld b,a ; Make a copy in B. cp 0x0a ; Jump forward with report jr c,MAIN_5 ; numbers '0 to 9'. add a,0x07 ; Add the ASCII letter offset value. MAIN_5: ; 133c call OUT_CODE ; Print the report code and ld a,0x20 ; follow it with a 'space'. rst PRINT_A_1 ld a,b ; Fetch the report value and ld de,REPORT_MESSAGES ; use it to identify the call PO_MSG ; required report message. xor a ; Print the message and follow ld de,COMMA_AND_SPACE-1 ; it by a 'comma' and a 'space'. call PO_MSG ld bc,(PPC) ; Now fetch the current line call OUT_NUM_1 ; number and print it as well. ld a,0x3a ; Follow it by a ':' rst PRINT_A_1 ld c,(iy+d_SUBPPC) ; Fetch the current statement ld b,0x00 ; number into the BC register call OUT_NUM_1 ; pair and print it. call CLEAR_SP ; Clear the editing area. ld a,(ERR_NR) ; Fetch the error number again. inc a ; Increment it as usual. jr z,MAIN_9 ; If the program was completed ; successfully there cannot be ; any 'CONTinuing' so jump. cp 0x09 ; If the program halted with jr z,MAIN_6 ; 'STOP statement' or 'BREAK cp 0x15 ; into program' CONTinuing will jr nz,MAIN_7 ; be from the next statement; MAIN_6: ; 1373 inc (iy+d_SUBPPC) ; otherwise SUBPPC is unchanged. MAIN_7: ; 1376 ld bc,0x0003 ; The system variables OLDPPC ld de,OSPCC ; & OSPCC have now to be made to hold ; the CONTinuing line ; and statement numbers. ld hl,NSPPC ; The values used will be those in bit 7,(hl) ; PPC & SUBPPC unless NSPPC jr z,MAIN_8 ; indicates that the 'break' add hl,bc ; occurred before a 'jump'. MAIN_8: ; 1384 lddr ; (i.e. after a GO TO statement etc.) MAIN_9: ; 1386 ld (iy+d_NSPPC),0xff ; NSPPC is reset to indicate 'no jump'. res 3,(iy+d_FLAGS) ; 'K mode' is selected. jp MAIN_2 ; And finally the jump back is made but ; no program listing ; will appear until requested. ; THE REPORT MESSAGES ; Each message is given with the last character inverted (+80 hex.). REPORT_MESSAGES: ; 1391 db 0x80 REPORT_0a: ; 1392 db "O", 0xcb REPORT_1a: ; 1394 db "NEXT without FO", 0xd2 REPORT_2b: ; 13a4 db "Variable not foun", 0xe4 REPORT_3b: ; 13b6 db "Subscript wron", 0xe7 REPORT_4a: ; 13c5 db "Out of memor", 0xf9 REPORT_5b: ; 13d2 db "Out of scree", 0xee REPORT_6c: ; 13df db "Number too bi", 0xe7 REPORT_7a: ; 13ed db "RETURN without GOSU", 0xc2 REPORT_8a: ; 1401 db "End of fil", 0xe5 REPORT_9: ; 140c db "STOP statemen", 0xf4 REPORT_Ac: ; 141a db "Invalid argumen", 0xf4 REPORT_Bc: ; 142a db "Integer out of rang", 0xe5 REPORT_Ca: ; 143e db "Nonsense in BASI", 0xc3 REPORT_Dc: ; 144f db "BREAK - CONT repeat", 0xf3 REPORT_Ea: ; 1463 db "Out of DAT", 0xc1 REPORT_Fb: ; 146e db "Invalid file nam", 0xe5 REPORT_Ga: ; 147f db "No room for lin", 0xe5 REPORT_Ha: ; 148f db "STOP in INPU", 0xd4 REPORT_Ia: ; 149c db "FOR without NEX", 0xd4 REPORT_Ja: ; 14ac db "Invalid I/O devic", 0xe5 REPORT_Ka: ; 14be db "Invalid colou", 0xf2 REPORT_La: ; 14cc db "BREAK into progra", 0xed REPORT_Ma: ; 14de db "RAMTOP no goo", 0xe4 REPORT_Na: ; 14ec db "Statement los", 0xf4 REPORT_Oa: ; 14fa db "Invalid strea", 0xed REPORT_Pb: ; 1508 db "FN without DE", 0xc6 REPORT_Qb: ; 1516 db "Parameter erro", 0xf2 REPORT_Rb: ; 1525 db "Tape loading erro", 0xf2 COMMA_AND_SPACE: ; 1537 db ",", 0xa0 COPYRIGHT_MESSAGE: ; 1539 db 0x7f, " 1982 Sinclair Research Lt", 0xe4 REPORT_Gb: ; 1555 db ">", 0x10, 0x01, 0x00, 0x00, 0xc3 db 0x13, 0x13 ; THE 'MAIN-ADD' SUBROUTINE ; This subroutine allows for a new BASIC line to be added to the existing BASIC ; program in the program area. If a line has both an old and a new version ; then the old one is 'reclaimed'. A new line that consists of only a line ; number does not go into the program area. MAIN_ADD: ; 155d ld (E_PPC),bc ; Make the new line number the 'current ; line'. ld hl,(CH_ADD) ; Fetch CH-ADD and save the ex de,hl ; address in DE. ld hl,REPORT_Gb ; Push the address of REPORT-G push hl ; on to the machine stack. ERR-SP will ; now point to ; REPORT-G. ld hl,(WORKSP) ; Fetch WORKSP. scf ; Find the length of the line sbc hl,de ; from after the line number to the ; 'carriage return' character ; inclusively. push hl ; Save the length. ld h,b ; Move the line number to the ld l,c ; HL register pair. call LINE_ADDR ; Is there an existing line with this ; number? jr nz,MAIN_ADD1 ; Jump if there was not. call NEXT_ONE ; Find the length of the 'old' call RECLAIM_2 ; line and reclaim it. MAIN_ADD1: ; 157d pop bc ; Fetch the length of the ld a,c ; 'new' line and jump forward dec a ; if it is only a 'line number or b ; and a carriage return'. jr z,MAIN_ADD2 push bc ; Save the length. inc bc ; Four extra locations will be inc bc ; needed. inc bc ; i.e. two for the number & inc bc ; two for the length. dec hl ; Make HL point to the location before ; the 'destination'. ld de,(PROG) ; Save the current value of push de ; PROG to avoid corruption when adding ; a first line. call MAKE_ROOM ; Space for the new line is created. pop hl ; The old value of PROG is ld (PROG),hl ; fetched and restored. pop bc ; A copy of the line length push bc ; (without parameters) is taken. inc de ; Make DE point to the end location of ; the new area ld hl,(WORKSP) ; and HL to the 'carriage dec hl ; return' character of the new dec hl ; line in the editing area. lddr ; Now copy over the line. ld hl,(E_PPC) ; Fetch the line's number. ex de,hl ; Destination into HL & number into DE. pop bc ; Fetch the new line's length. ld (hl),b ; The high length byte. dec hl ld (hl),c ; The low length byte. dec hl ld (hl),e ; The low line number byte. dec hl ld (hl),d ; The high line number byte. MAIN_ADD2: ; 15ab pop af ; Drop the address of REPORT-G. jp MAIN_EXEC ; Jump back and this time do produce ; and automatic listing. ; THE 'INITIAL CHANNEL INFORMATION' ; Initially there are four channels - 'K', 'S', 'R', & 'P' - for communicating ; with the 'keyboard', 'screen', 'work space' and 'printer'. For each channel ; the output routine address comes before the input routine address and the ; channel's code. CHANS_INIT: ; 15af dw PRINT_OUT dw KEY_INPUT db 'K' dw PRINT_OUT dw REPORT_Jb db 'S' dw ADD_CHAR dw REPORT_Jb db 'R' dw PRINT_OUT dw REPORT_Jb db 'P' db 0x80 REPORT_Jb: ; 15c4 rst ERROR_1 db 0x12 ; THE 'INITIAL STREAM DATA' ; Initially there are seven streams - +FD to +03. STREAMS_INIT: ; 15c6 db 0x01, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x01, 0x00, 0x01, 0x00, 0x06, 0x00, 0x10, 0x00 ; THE 'WAIT-KEY' SUBROUTINE ; This subroutine is the controlling subroutine for calling the current input ; subroutine. WAIT_KEY: ; 15d4 bit 5,(iy+d_TV_FLAG) ; Jump forward if the flag jr nz,WAIT_KEY1 ; indicates the lower screen does not ; require clearing. set 3,(iy+d_TV_FLAG) ; Otherwise signal 'consider the mode ; as having changed'. WAIT_KEY1: ; 15de call INPUT_AD ; Call the input subroutine indirectly ; via INPUT-AD. ret c ; Return with acceptable codes. jr z,WAIT_KEY1 ; Both the carry flag and the zero flag ; are reset if 'no key is ; being pressed'; otherwise signal an ; error. ; Report 8 - End of file REPORT_8b: ; 15e4 rst ERROR_1 ; Call the error handling db 0x07 ; routine. ; THE 'INPUT-AD' SUBROUTINE ; The registers are saved and HL made to point to the input address. INPUT_AD: ; 15e6 exx ; Save the registers. push hl ld hl,(CURCHL) ; Fetch the base address for the ; current channel information. inc hl ; Step past the output address. inc hl jr CALL_SUB ; Jump forward. ; THE 'MAIN PRINTING' SUBROUTINE ; The subroutine is called with either an absolute value or a proper character ; code in the A register. OUT_CODE: ; 15ef ld e,0x30 ; Increase the value in the add a,e ; A register by +30. PRINT_A_2: ; 15f2 exx ; Again save the registers. push hl ld hl,(CURCHL) ; Fetch the base address for the ; current channel. This will point ; to an output address. ; Now call the actual subroutine. HL points to the output or the input address ; as directed. CALL_SUB: ; 15f7 ld e,(hl) ; Fetch the low byte. inc hl ld d,(hl) ; Fetch the high byte. ex de,hl ; Move the address to the HL register ; pair. call CALL_JUMP ; Call the actual subroutine. pop hl ; Restore the registers. exx ret ; Return will be from here unless an ; error occurred. ; THE 'CHAN-OPEN' SUBROUTINE ; This subroutine is called with the A register holding a valid stream number - ; normally +FD to +03. Then depending on the stream data a particular channel ; will be made the current channel. CHAN_OPEN: ; 1601 add a,a ; The value in the A register is add a,0x16 ; doubled and then increased by ld l,a ; +16. The result is moved to L. ld h,0x5c ; The address 5C16 is the base address ; for stream +00. ld e,(hl) ; Fetch the first byte of the inc hl ; required stream's data; then ld d,(hl) ; the second byte. ld a,d ; Give an error if both bytes or e ; are zero; otherwise jump jr nz,CHAN_OP_1 ; forward. ; Report O - Invalid stream REPORT_Ob: ; 160e rst ERROR_1 ; Call the error handling db 0x17 ; routine. ; Using the stream data now find the base address of the channel information ; associated with that stream. CHAN_OP_1: ; 1610 dec de ; Reduce the stream data. ld hl,(CHANS) ; The base address of the whole channel ; information area. add hl,de ; Form the required address in this ; area. ; THE 'CHAN-FLAG' SUBROUTINE ; The appropriate flags for the different channels are set by this subroutine. CHAN_FLAG: ; 1615 ld (CURCHL),hl ; The HL register pair holds the base ; address for a particular ; channel. res 4,(iy+d_FLAGS2) ; Signal 'using other than channel ; 'K''. inc hl ; Step past the output inc hl ; and the input addresses and inc hl ; make HL point to the inc hl ; channel code. ld c,(hl) ; Fetch the code. ld hl,CHANNEL_LU ; The base address of the 'channel code ; look-up table'. call INDEXER ; Index into this table and locate the ; required offset; but return if ret nc ; there is not a matching channel code. ld d,0x00 ; Pass the offset to the ld e,(hl) ; DE register pair. add hl,de ; Jump forward to the appropriate CALL_JUMP: ; 162c jp hl ; flag setting routine. ; THE 'CHANNEL CODE LOOK-UP' TABLE CHANNEL_LU: ; 162d db 0x4b, CHAN_K - $ - 1 db 0x53, CHAN_S - $ - 1 db 0x50, CHAN_P - $ - 1 db 0x00 ; THE 'CHANNEL 'K' FLAG' SUBROUTINE CHAN_K: ; 1634 set 0,(iy+d_TV_FLAG) ; Signal 'using lower screen'. res 5,(iy+d_FLAGS) ; Signal 'ready for a key'. set 4,(iy+d_FLAGS2) ; Signal 'using channel 'K''. jr CHAN_S_1 ; Jump forward. ; THE 'CHANNEL 'S' FLAG' SUBROUTINE CHAN_S: ; 1642 res 0,(iy+d_TV_FLAG) ; Signal 'using main screen'. CHAN_S_1: ; 1646 res 1,(iy+d_FLAGS) ; Signal 'printer not being used'. jp TEMPS ; Exit via TEMPS so as to set the ; colour system variables. ; THE 'CHANNEL 'P' FLAG' SUBROUTINE CHAN_P: ; 164d set 1,(iy+d_FLAGS) ; Signal 'printer in use'. ret ; THE 'MAKE-ROOM' SUBROUTINE ; This is a very important subroutine. It is called on many occasions to 'open ; up' an area. In all cases the HL register pair points to the location after ; the place where 'room' is required and the BC register pair holds the length ; of the 'room' needed. When a single space only is required then the ; subroutine is entered at ONE-SPACE. ONE_SPACE: ; 1652 ld bc,0x0001 ; Just the single extra location is ; required. MAKE_ROOM: ; 1655 push hl ; Save the pointer. call TEST_ROOM ; Make sure that there is sufficient ; memory available for the task ; being undertaken. pop hl ; Restore the pointer. call POINTERS ; Alter all the pointers before making ; the 'room'. ld hl,(STKEND) ; Make HL hold the new STKEND. ex de,hl ; Switch 'old' and 'new'. lddr ; Now make the 'room' ret ; and return. ; Note: This subroutine returns with the HL register pair pointing to the ; location before the new 'room' and the DE register pair pointing to the last ; of the new locations. The new 'room' therefore has the description: '(HL)+1' ; to '(DE)' inclusive. ; However as the 'new locations' still retain their 'old values' it is also ; possible to consider the new 'room' as having been made after the original ; location '(HL)' and it thereby has the description '(HL)+2' to (DE)+1'. ; In fact the programmer appears to have a preference for the 'second ; description' and this can be confusing. ; THE 'POINTERS' SUBROUTINE ; Whenever an area has to be 'made' or 'reclaimed' the system variables that ; address locations beyond the 'position' of the change have to be amended as ; required. On entry the BC register pair holds the number of bytes involved ; and the HL register pair addresses the location before the 'position'. POINTERS: ; 1664 push af ; These registers are saved. push hl ; Copy the address of the 'position'. ld hl,VARS ; This is VARS, the first of the ld a,0x0e ; fourteen system pointers. ; A loop is now entered to consider each pointer in turn. Only those pointers ; that point beyond the 'position' are changed. PTR_NEXT: ; 166b ld e,(hl) ; Fetch the two bytes of the inc hl ; current pointer. ld d,(hl) ex (sp),hl ; Exchange the system variable with the ; address of the 'position'. and a ; The carry flag will become sbc hl,de ; set if the system variable's add hl,de ; address is to be updated. ex (sp),hl ; Restore the 'position'. jr nc,PTR_DONE ; Jump forward if the pointer is to be ; left; otherwise change it. push de ; Save the old value. ex de,hl ; Now add the value in BC add hl,bc ; to the old value. ex de,hl ld (hl),d ; Enter the new value into the dec hl ; system variable - high byte ld (hl),e ; before low byte. inc hl ; Point again to the high byte. pop de ; Fetch the old value. PTR_DONE: ; 167f inc hl ; Point to the next system dec a ; variable and jump back until all jr nz,PTR_NEXT ; fourteen have been considered. ; Now find the size of the block to be moved. ex de,hl ; Put the old value of STKEND in pop de ; HL and restore the other pop af ; registers. and a ; Now find the difference sbc hl,de ; between the old value of ld b,h ; STKEND and the 'position'. ld c,l ; Transfer the result to BC inc bc ; and add '1' for the inclusive byte. add hl,de ; Reform the old value of ex de,hl ; STKEND and pass it to DE ret ; before returning. ; THE 'COLLECT A LINE NUMBER' SUBROUTINE ; On entry the HL register pair points to the location under consideration. If ; the location holds a value that constitutes a suitable high byte for a line ; number then the line number is returned in DE. However if this is not so ; then the location addressed by DE is tried instead; and should this also be ; unsuccessful line number zero is returned. LINE_ZERO: ; 168f db 0x00 ; Line number zero. db 0x00 LINE_NO_A: ; 1691 ex de,hl ; Consider the other pointer. ld de,LINE_ZERO ; Use line number zero. ; The usual entry point is at LINE-NO. LINE_NO: ; 1695 ld a,(hl) ; Fetch the high byte and and 0xc0 ; test it. jr nz,LINE_NO_A ; Jump back if not suitable. ld d,(hl) ; Fetch the high byte. inc hl ld e,(hl) ; Fetch the low byte and ret ; return. ; THE 'RESERVE' SUBROUTINE ; This subroutine is normally called by using RST 0030,BC-SPACES. ; On entry here the last value on the machine stack is WORKSP and the value ; above it is the number of spaces that is to be 'reserved'. ; This subroutine always makes 'room' between the existing work space and the ; calculator stack. RESERVE: ; 169e ld hl,(STKBOT) ; Fetch the current value of dec hl ; STKBOT and decrement it to get the ; last location of the ; work space. call MAKE_ROOM ; Now make 'BC spaces'. inc hl ; Point to the first new space inc hl ; and then the second. pop bc ; Fetch the old value of ld (WORKSP),bc ; WORKSP and restore it. pop bc ; Restore BC - number of spaces. ex de,hl ; Switch the pointers, inc hl ; Make HL point to the first of the ; displaced bytes. ret ; Now return. ; Note: It can also be considered that the subroutine returns with the DE ; register pair pointing to a 'first extra byte' and the HL register pair ; pointing to a 'last extra byte', these extra bytes having been added after ; the original '(HL)+1' location. ; THE 'SET-MIN' SUBROUTINE ; This subroutine resets the editing area and the areas after it to their ; minimum sizes. In effect it 'clears' the areas. SET_MIN: ; 16b0 ld hl,(E_LINE) ; Fetch E-LINE. ld (hl),0x0d ; Make the editing area hold ld (K_CUR),hl ; only the 'carriage return' inc hl ; character and the end marker. ld (hl),0x80 inc hl ; Move on to clear the work ld (WORKSP),hl ; space. ; Entering here will 'clear' the work space and the calculator stack. SET_WORK: ; 16bf ld hl,(WORKSP) ; Fetch the WORKSP. ld (STKBOT),hl ; This clears the work space. ; Entering here will 'clear' only the calculator stack. SET_STK: ; 16c5 ld hl,(STKBOT) ; Fetch STKBOT. ld (STKEND),hl ; This clears the stack. ; In all cases make MEM address the calculator's memory area. push hl ; Save STKEND. ld hl,MEMBOT ; The base of the memory area. ld (MEM),hl ; Set MEM to this address. pop hl ; Restore STKEND to the HL ret ; register pair before returning. ; THE 'RECLAIM THE EDIT-LINE' SUBROUTINE' REC_EDIT: ; 16d4 ld de,(E_LINE) ; Fetch E-LINE. jp RECLAIM_1 ; Reclaim the memory. ; THE 'INDEXER' SUBROUTNE ; This subroutine is used on several occasions to look through tables. The ; entry point is at INDEXER. INDEXER_1: ; 16db inc hl ; Move on to consider the next pair of ; entries. INDEXER: ; 16dc ld a,(hl) ; Fetch the first of a pair of and a ; entries but return if it is ret z ; zero - the end marker. cp c ; Compare it to the supplied code. inc hl ; Point to the second entry. jr nz,INDEXER_1 ; Jump back if the correct entry has ; not been found. scf ; The carry flag is set upon a ret ; successful search. ; THE 'CLOSE #' COMMAND ROUTINE ; This command allows the user to CLOSE streams. However for streams +00 to ; +03 the 'initial' stream data is restored and these streams cannot therefore ; be CLOSEd. CLOSE: ; 16e5 call STR_DATA ; The existing data for the stream is ; fetched. call CLOSE_2 ; Check the code in that stream's ; channel. ld bc,0x0000 ; Prepare to make the stream's data ; zero. ld de,0xa3e2 ; Prepare to identify the use of ex de,hl ; streams +00 to +03. add hl,de ; The carry flag will be set with ; streams +04 to +0F. jr c,CLOSE_1 ; Jump forward with these ld bc,WAIT_KEY ; streams; otherwise find the add hl,bc ; correct entry in the 'initial stream ; data' table. ld c,(hl) ; Fetch the initial data inc hl ; for streams +00 to +03. ld b,(hl) CLOSE_1: ; 16fc ex de,hl ; Now enter the data; either ld (hl),c ; zero & zero, or the initial inc hl ; values. ld (hl),b ret ; THE 'CLOSE-2' SUBROUTINE ; The code of the channel associated with the stream being closed has to be ; 'K', 'S', or 'P'. CLOSE_2: ; 1701 push hl ; Save the address of the stream's ; data. ld hl,(CHANS) ; Fetch the base address of the add hl,bc ; channel information area and find the ; channel data for the ; stream being CLOSEd. inc hl ; Step past the subroutine inc hl ; addresses and pick up inc hl ; the code for that channel. ld c,(hl) ex de,hl ; Save the pointer. ld hl,CLOSE_LU ; The base address of the 'CLOSE stream ; look-up' table. call INDEXER ; Index into this table and locate the ; required offset. ld c,(hl) ; Pass the offset to the BC ld b,0x00 ; register pair. add hl,bc ; Jump forward to the jp hl ; appropriate routine. ; THE 'CLOSE STREAM LOOK-UP' TABLE CLOSE_LU: ; 1716 db 0x4b, CLOSE_STR - $ - 1 db 0x53, CLOSE_STR - $ - 1 db 0x50, CLOSE_STR - $ - 1 ; Note: There is no end marker at the end of this table. ; THE 'CLOSE STREAM' SUBROUTINE CLOSE_STR: ; 171c pop hl ; Fetch the channel information ret ; pointer and return. ; THE 'STREAM DATA' SUBROUTINE ; This subroutine returns in the BC register pair the stream data for a given ; stream. STR_DATA: ; 171e call FIND_INT1 ; The given stream number is taken off ; the calculator stack. cp 0x10 ; Give an error if the stream jr c,STR_DATA1 ; number is greater than +0F. ; Report O - Invalid stream REPORT_Oc: ; 1725 rst ERROR_1 ; Call the error handling db 0x17 ; routine. ; Continue with valid stream numbers. STR_DATA1: ; 1727 add a,0x03 ; Range now +03 to +12; rlca ; and now +06 to +24. ld hl,STRMS ; The base address of the stream data ; area. ld c,a ; Move the stream code to the ld b,0x00 ; BC register pair. add hl,bc ; Index into the data area ld c,(hl) ; and fetch the the two data bytes inc hl ; into the BC register pair. ld b,(hl) dec hl ; Make the pointer address the ret ; first of the data bytes before ; returning. ; THE 'OPEN #' COMMAND ROUTINE ; This command allows the user to OPEN streams. A channel code must be ; supplied and it must be 'K', 'k', 'S', 's', 'P', or 'p'. ; Note that no attempt is made to give streams +00 to +03 their initial data. OPEN: ; 1736 rst FP_CALC ; Use the CALCULATOR. db C_exchange ; Exchange the stream number db C_end_calc ; and the channel code. call STR_DATA ; Fetch the data for the stream. ld a,b ; Jump forward if both bytes of or c ; the data are zero, i.e. the jr z,OPEN_1 ; stream was in a closed state. ex de,hl ; Save DE. ld hl,(CHANS) ; Fetch CHANS - the base add hl,bc ; address of the channel inc hl ; information and find the inc hl ; code of the channel inc hl ; associated with the stream ld a,(hl) ; being OPENed. ex de,hl ; Return DE. cp 0x4b ; The code fetched from the jr z,OPEN_1 ; channel information area cp 0x53 ; must be 'K', 'S' or 'P'; jr z,OPEN_1 ; give an error if it is not. cp 0x50 jr nz,REPORT_Oc OPEN_1: ; 1756 call OPEN_2 ; Collect the appropriate data in DE. ld (hl),e ; Enter the data into the inc hl ; two bytes in the stream ld (hl),d ; information area. ret ; Finally return. ; THE 'OPEN-2' SUBROUTINE ; The appropriate stream data bytes for the channel that is associated with the ; stream being OPENed are found. OPEN_2: ; 175d push hl ; Save HL call STK_FETCH ; Fetch the parameters of the channel ; code. ld a,b ; Give an error if the or c ; expression supplied is a null jr nz,OPEN_3 ; expression; i.e. OPEN #5,"". ; Report F - Invalid file name REPORT_Fc: ; 1765 rst ERROR_1 ; Call the error handling db 0x0e ; routine. ; Continue if no error occurred. OPEN_3: ; 1767 push bc ; The length of the expression is ; saved. ld a,(de) ; Fetch the first character. and 0xdf ; Convert lower case codes to upper ; case ones. ld c,a ; Move code to the C register. ld hl,OPEN_LU ; The base address of the 'OPEN stream ; look-up' table. call INDEXER ; Index into this table and locate the ; required offset. jr nc,REPORT_Fc ; Jump back if not found. ld c,(hl) ; Pass the offset to the BC ld b,0x00 ; register pair. add hl,bc ; Make HL point to the start of the ; appropriate subroutine. pop bc ; Fetch the length of the jp hl ; expression before jumping to the ; subroutine. ; THE 'OPEN STREAM LOOK-UP' TABLE OPEN_LU: ; 177a db 0x4b, OPEN_K - $ - 1 db 0x53, OPEN_S - $ - 1 db 0x50, OPEN_P - $ - 1 db 0x00 ; THE 'OPEN-K' SUBROUTINE OPEN_K: ; 1781 ld e,0x01 ; The data bytes will be +01 jr OPEN_END ; & +00. ; THE 'OPEN-S' SUBROUTINE OPEN_S: ; 1785 ld e,0x06 ; The data bytes will be +06 jr OPEN_END ; & +00. ; THE 'OPEN-P' SUBROUTINE OPEN_P: ; 1789 ld e,0x10 ; The data bytes will be +10 & +00. OPEN_END: ; 178b dec bc ; Decrease the length of the ld a,b ; expression and give an error or c ; if it was not a single jr nz,REPORT_Fc ; character; otherwise clear the ld d,a ; D register, fetch HL and pop hl ; return. ret ; THE 'CAT, ERASE, FORMAT & MOVE' COMMAND ROUTINES ; In the standard SPECTRUM system the use of these commands leads to the ; production of report O - Invalid stream. CAT_ETC_: ; 1793 jr REPORT_Oc ; Give this report. ; THE 'LIST & LLIST' COMMAND ROUTINES ; The routines in this part of the 16K program are used to produce listings of ; the current BASIC program. Each line has to have its line number evaluated, ; its tokens expanded and the appropriate cursors positioned. ; The entry point AUTO-LIST is used by both the MAIN EXECUTION routine and the ; EDITOR to produce a single page of the listing. AUTO_LIST: ; 1795 ld (LIST_SP),sp ; The stack pointer is saved allowing ; the machine stack to ; be reset when the listing is ; finished. (see PO-SCR,0C55) ld (iy+d_TV_FLAG),0x10 ; Signal 'automatic listing in the main ; screen'. call CL_ALL ; Clear this part of the screen. set 0,(iy+d_TV_FLAG) ; Switch to the editing area. ld b,(iy+d_DF_SZ) ; Now clear the the lower part call CL_LINE ; of the screen as well. res 0,(iy+d_TV_FLAG) ; Then switch back. set 0,(iy+d_FLAGS2) ; Signal 'screen is clear'. ld hl,(E_PPC) ; Now fetch the the 'current' line ld de,(S_TOP) ; number and the 'automatic' line ; number. and a ; If the 'current' number is sbc hl,de ; less than the 'automatic' add hl,de ; number then jump forward to jr c,AUTO_L_2 ; update the 'automatic' number. ; The 'automatic' number has now to be altered to give a listing with the ; 'current' line appearing near the bottom of the screen. push de ; Save the 'automatic' number. call LINE_ADDR ; Find the address of the ld de,0x02c0 ; start of the 'current' line ex de,hl ; and produce an address roughly sbc hl,de ; a 'screen before it' (negated). ex (sp),hl ; Save the 'result' on the machine call LINE_ADDR ; stack whilst the 'automatic' line ; address is also found (in HL). pop bc ; The 'result' goes to the BC register ; pair. ; A loop is now entered. The 'automatic' line number is increased on each pass ; until it is likely that the 'current' line will show on a listing. AUTO_L_1: ; 17ce push bc ; Save the 'result'. call NEXT_ONE ; Find the address of the start of the ; line after the present ; 'automatic' line (in DE). pop bc ; Restore the 'result'. add hl,bc ; Perform the computation and jr c,AUTO_L_3 ; jump forward if finished. ex de,hl ; Move the next line's address ld d,(hl) ; to the HL register pair and inc hl ; collect its line number. ld e,(hl) dec hl ld (S_TOP),de ; Now S-TOP can be updated and jr AUTO_L_1 ; the test repeated with the new line. ; Now the 'automatic' listing can be made. AUTO_L_2: ; 17e1 ld (S_TOP),hl ; When E-PPC is less than S-TOP. AUTO_L_3: ; 17e4 ld hl,(S_TOP) ; Fetch the top line's number call LINE_ADDR ; and hence its address. jr z,AUTO_L_4 ; If the line cannot be found ex de,hl ; use DE instead. AUTO_L_4: ; 17ed call LIST_ALL ; The listing is produced. res 4,(iy+d_TV_FLAG) ; The return will be to here ret ; unless scrolling was needed to show ; the current line. ; THE 'LLIST' ENTRY POINT ; The printer channel will need to be opened. LLIST: ; 17f5 ld a,0x03 ; Use stream +03. jr LIST_1 ; Jump forward. ; THE 'LIST' ENTRY POINT ; The 'main screen' channel will need to be opened. LIST: ; 17f9 ld a,0x02 ; Use stream +02. LIST_1: ; 17fb ld (iy+d_TV_FLAG),0x00 ; Signal 'an ordinary listing in the ; main part of the screen'. call SYNTAX_Z ; Open the channel unless call nz,CHAN_OPEN ; checking syntax. rst GET_CHAR ; With the present character in call STR_ALTER ; the A register see if the stream is ; to be changed. jr c,LIST_4 ; Jump forward if unchanged. rst GET_CHAR ; Is the present character cp 0x3b ; a ';'? jr z,LIST_2 ; Jump if it is. cp 0x2c ; Is it a ','? jr nz,LIST_3 ; Jump if it is not. LIST_2: ; 1814 rst NEXT_CHAR ; A numeric expression must call EXPT_1NUM ; follow, e.g. LIST #5,20 jr LIST_5 ; Jump forward with it. LIST_3: ; 181a call USE_ZERO ; Otherwise use zero and jr LIST_5 ; also jump forward. ; Come here if the stream was unaltered. LIST_4: ; 181f call FETCH_NUM ; Fetch any line or use zero if none ; supplied. LIST_5: ; 1822 call CHECK_END ; If checking the syntax of the ; edit-line move on to the next ; statement. call FIND_INT2 ; Line number to BC. ld a,b ; High byte to A. and 0x3f ; Limit the high byte to the ld h,a ; correct range and pass the ld l,c ; whole line number to HL. ld (E_PPC),hl ; Set E-PPC and find the address call LINE_ADDR ; of the start of this line or the ; first line after it if the actual ; line does not exist. LIST_ALL: ; 1833 ld e,0x01 ; Flag 'before the current line'. ; Now the controlling loop for printing a series of lines is entered. LIST_ALL_1: ; 1835 call OUT_LINE ; Print the whole of a BASIC line. rst PRINT_A_1 ; This will be a 'carriage return'. bit 4,(iy+d_TV_FLAG) ; Jump back unless dealing jr z,LIST_ALL_1 ; with an automatic listing. ld a,(DF_SZ) ; Also jump back if there is sub (iy+d_S_POSN+1) ; still part of the main screen jr nz,LIST_ALL_1 ; that can be used. xor e ; A return can be made at this ret z ; point if the screen is full and the ; current line has been printed ; (E = +00) push hl ; However if the current line is push de ; missing from the listing ld hl,S_TOP ; then S-TOP has to be updated call LN_FETCH ; and a further line printed pop de ; (using scrolling). pop hl jr LIST_ALL_1 ; THE 'PRINT A WHOLE BASIC LINE' SUBROUTINE ; The HL register pair points to the start of the line - the location holding ; the high byte of the line number. ; Before the line number is printed it is tested to determine whether it comes ; before the 'current' line, is the 'current' line or comes after. OUT_LINE: ; 1855 ld bc,(E_PPC) ; Fetch the 'current' line call CP_LINES ; number and compare it. ld d,0x3e ; Pre-load the D register with the ; current line cursor. jr z,OUT_LINE1 ; Jump forward if printing the ; 'current' line. ld de,0x0000 ; Load the D register with zero (it is ; not the cursor) and rl e ; set E to hold +01 if the line is ; before the 'current' line and +00 ; if after. (The carry flag comes from ; CP-LINES.) OUT_LINE1: ; 1865 ld (iy+d_BREG),e ; Save the line marker. ld a,(hl) ; Fetch the high byte of the cp 0x40 ; line number and make a full pop bc ; return if the listing has been ret nc ; finished. push bc call OUT_NUM_2 ; The line number can now be printed - ; with leading spaces. inc hl ; Move the pointer on to address inc hl ; the first command code in inc hl ; the line. res 0,(iy+d_FLAGS) ; Signal 'leading space allowed' ld a,d ; Fetch the cursor code and and a ; jump forward unless the jr z,OUT_LINE3 ; cursor is to be printed. rst PRINT_A_1 ; So print the cursor now. OUT_LINE2: ; 187d set 0,(iy+d_FLAGS) ; Signal 'no leading space now'. OUT_LINE3: ; 1881 push de ; Save the registers. ex de,hl ; Move the pointer to DE. res 2,(iy+d_FLAGS2) ; Signal 'not in quotes'. ld hl,FLAGS ; This is FLAGS. res 2,(hl) ; Signal 'print in K-mode'. bit 5,(iy+d_FLAGX) ; Jump forward unless in jr z,OUT_LINE4 ; INPUT mode. set 2,(hl) ; Signal 'print in L-mode'. ; Now enter a loop to print all the codes in the rest of the BASIC line - ; jumping over floating-point forms as necessary. OUT_LINE4: ; 1894 ld hl,(X_PTR) ; Fetch the syntax error and a ; pointer and jump forward sbc hl,de ; unless it is time to print jr nz,OUT_LINE5 ; the error marker. ld a,0x3f ; Print the error marker now. call OUT_FLASH ; It is a flashing '?'. OUT_LINE5: ; 18a1 call OUT_CURS ; Consider whether to print the cursor. ex de,hl ; Move the pointer to HL now. ld a,(hl) ; Fetch each character in turn. call NUMBER ; If the character is a 'number marker' ; then the hidden floating- ; point form is not to be printed. inc hl ; Update the pointer for the next pass. cp 0x0d ; Is the character a 'carriage return'. jr z,OUT_LINE6 ; Jump if it is. ex de,hl ; Switch the pointer to DE. call OUT_CHAR ; Print the character. jr OUT_LINE4 ; Go around the loop for at least one ; further pass. ; The line has now been printed. OUT_LINE6: ; 18b4 pop de ; Restore the DE register pair ret ; and return. ; THE 'NUMBER' SUBROUTINE ; If the A register holds the 'number marker' then the HL register pair is ; advanced past the floating-point form. NUMBER: ; 18b6 cp 0x0e ; Is the character a 'number ret nz ; marker'. Return if not. inc hl ; Advance the pointer six inc hl ; times so as to step past the inc hl ; 'number marker' and the five inc hl ; locations holding the inc hl ; floating-point form. inc hl ld a,(hl) ; Fetch the current code before ret ; returning. ; THE 'PRINT A FLASHING CHARACTER' SUBROUTINE ; The 'error cursor' and the 'mode cursors' are printed using this subroutine. OUT_FLASH: ; 18c1 exx ; Save the current register. ld hl,(ATTR_T) ; Save the ATTR-T & MASK-T on push hl ; the machine stack. res 7,h ; Ensure that FLASH is set 7,l ; active. ld (ATTR_T),hl ; Use these modified values for ATTR-T ; & MASK-T. ld hl,P_FLAG ; This is P-FLAG. ld d,(hl) ; Save P-FLAG also on the push de ; machine stack. ld (hl),0x00 ; Ensure INVERSE 0, OVER 0, and not ; PAPER 9 nor INK 9. call PRINT_OUT ; The character is printed. pop hl ; The former value of P-FLAG ld (iy+d_P_FLAG),h ; is restored. pop hl ; The former values of ATTR-T ld (ATTR_T),hl ; & MASK-T are also restored exx ; before returning. ret ; THE 'PRINT THE CURSOR' SUBROUTINE ; A return is made if it is not the correct place to print the cursor but if it ; is then either 'C', 'E', 'G', 'K' or 'L' will be printed. OUT_CURS: ; 18e1 ld hl,(K_CUR) ; Fetch the address of the and a ; cursor but return if the sbc hl,de ; correct place is not being ret nz ; considered. ld a,(MODE) ; The current value of MODE is rlc a ; fetched and doubled. jr z,OUT_C_1 ; Jump forward unless dealing with ; Extended mode or Graphics. add a,0x43 ; Add the appropriate offset to give ; 'E' or 'G'. jr OUT_C_2 ; Jump forward to print it. OUT_C_1: ; 18f3 ld hl,FLAGS ; This is FLAGS. res 3,(hl) ; Signal 'K-mode'. ld a,0x4b ; The character 'K'. bit 2,(hl) ; Jump forward to print 'K'. jr z,OUT_C_2 ; If 'the printing is to be in K-mode'. set 3,(hl) ; The 'printing is to be in L-mode' so ; signal 'L-MODE'. inc a ; Form the character 'L'. bit 3,(iy+d_FLAGS2) ; Jump forward if not in jr z,OUT_C_2 ; 'C-mode'. ld a,0x43 ; The character 'C'. OUT_C_2: ; 1909 push de ; Save the DE register pair call OUT_FLASH ; whilst the cursor is printed pop de ; - FLASHing. ret ; Return once it has been done. ; Note: It is the action of considering which cursor-letter is to be printed ; that determines the mode - 'K' vs. 'L/C'. ; THE 'LN-FETCH' SUBROUTINE ; This subroutine is entered with the HL register pair addressing a system ; variable - S-TOP or E-PPC. ; The subroutine returns with the system variable holding the line number of ; the following line. LN_FETCH: ; 190f ld e,(hl) ; The line number held by the inc hl ; system variable is collected. ld d,(hl) push hl ; The pointer is saved. ex de,hl ; The line number is moved to the inc hl ; HL register pair and incremented. call LINE_ADDR ; The address of the start of this line ; is found, or the next line ; if the actual line number is not ; being used. call LINE_NO ; The number of that line is fetched. pop hl ; The pointer to the system variable is ; restored. ; The entry point LN-STORE is used by the EDITOR. LN_STORE: ; 191c bit 5,(iy+d_FLAGX) ; Return if in 'INPUT mode'; ret nz ; otherwise proceed to ld (hl),d ; enter the line number into dec hl ; the two locations of the ld (hl),e ; system variable. ret ; Return when it has been done. ; THE 'PRINTING CHARACTERS IN A BASIC LINE' SUBROUTINE ; All of the character/token codes in a BASIC line are printed by repeatedly ; calling this subroutine. ; The entry point OUT-SP-NO is used when printing line numbers which may ; require leading spaces. OUT_SP_2: ; 1925 ld a,e ; The A register will hold +20 for a ; space or +FF for no-space. and a ; Test the value and return if ret m ; there is not to be a space. jr OUT_CHAR ; Jump forward to print a space OUT_SP_NO: ; 192a xor a ; Clear the A register. ; The HL register pair holds the line number and the BC register the value for ; 'repeated subtraction'. (BC holds '-1000, -100 or -10'.) OUT_SP_1: ; 192b add hl,bc ; The 'trial subtraction'. inc a ; Count each 'trial'. jr c,OUT_SP_1 ; Jump back until exhausted. sbc hl,bc ; Restore last 'subtraction' dec a ; and discount it. jr z,OUT_SP_2 ; If no 'subtractions' were possible ; jump back to see if a space is to ; be printed. jp OUT_CODE ; Otherwise print the digit. ; The entry point OUT-CHAR is used for all characters, tokens and control ; characters. OUT_CHAR: ; 1937 call NUMERIC ; Return carry reset if handling a ; digit code. jr nc,OUT_CH_3 ; Jump forward to print the digit. cp 0x21 ; Also print the control jr c,OUT_CH_3 ; characters and 'space'. res 2,(iy+d_FLAGS) ; Signal 'print in K-mode'. cp 0xcb ; Jump forward if dealing jr z,OUT_CH_3 ; with the token 'THEN'. cp 0x3a ; Jump forward unless dealing jr nz,OUT_CH_1 ; with ':'. bit 5,(iy+d_FLAGX) ; Jump forward to print the jr nz,OUT_CH_2 ; ':' if in 'INPUT mode'. bit 2,(iy+d_FLAGS2) ; Jump forward if the ':' jr z,OUT_CH_3 ; is 'not in quotes', i.e. an ; inter-statement marker. jr OUT_CH_2 ; The ':' is inside quotes and can now ; be printed. OUT_CH_1: ; 195a cp 0x22 ; Accept for printing all jr nz,OUT_CH_2 ; characters except '"'. push af ; Save the character code whilst ; changing the 'quote mode'. ld a,(FLAGS2) ; Fetch FLAGS2 and flip xor 0x04 ; bit 2. ld (FLAGS2),a ; Enter the amended value and pop af ; restore the character code. OUT_CH_2: ; 1968 set 2,(iy+d_FLAGS) ; Signal 'the next character is to be ; printed in L-mode'. OUT_CH_3: ; 196c rst PRINT_A_1 ; The present character is ret ; printed before returning. ; Note: It is the consequence of the tests on the present character that ; determines whether the next character is to be "printed in 'K' or 'L' mode". ; Also note how the program does not cater for ':' in REM statements. ; THE 'LINE-ADDR' SUBROUTINE ; For a given line number, in the HL register pair, this subroutine returns the ; starting address of that line or the 'first line after', in the HL register ; pair, and the start of the previous line in the DE register pair. ; If the line number is being used the zero flag will be set. However if the ; 'first line after' is substituted then the zero flag is returned reset. LINE_ADDR: ; 196e push hl ; Save the given line number. ld hl,(PROG) ; Fetch the system variable ld d,h ; PROG and transfer the address ld e,l ; to the DE register pair. ; Now enter a loop to test the line number of each line of the program against ; the given line number until the line number is matched or exceeded. LINE_AD_1: ; 1974 pop bc ; The given line number. call CP_LINES ; Compare the given line number against ; the addressed line ret nc ; number. Return if carry reset; push bc ; otherwise address the next call NEXT_ONE ; line's number. ex de,hl ; Switch the pointers and jr LINE_AD_1 ; jump back to consider the next line ; of the program. ; THE 'COMPARE LINE NUMBERS' SUBROUTINE ; The given line number in the BC register pair is matched against the ; addressed line number. CP_LINES: ; 1980 ld a,(hl) ; Fetch the high byte of the cp b ; addressed line number and ret nz ; compare it. Return if they do not ; match. inc hl ; Next compare the low bytes. ld a,(hl) ; Return with the carry flag dec hl ; set if the addressed line cp c ; number has yet to reach the ret ; given line number. ; THE 'FIND EACH STATEMENT' SUBROUTINE ; This subroutine has two distinct functions. ; i. It can be used to find the 'D'th. statement in a BASIC line - returning ; with the HL register pair addressing the location before the start of the ; statement and the zero flag set. ; ii. Also the subroutine can be used to find a statement, if any, that starts ; with a given token code (in the E register). inc hl ; Not used. inc hl inc hl EACH_STMT: ; 198b ld (CH_ADD),hl ; Set CH-ADD to the current byte. ld c,0x00 ; Set a 'quotes off' flag. ; Enter a loop to handle each statement in the BASIC line. EACH_S_1: ; 1990 dec d ; Decrease 'D' and return if ret z ; the required statement has been ; found. rst NEXT_CHAR ; Fetch the next character code cp e ; and jump if it does not match jr nz,EACH_S_3 ; the given token code. and a ; But should it match then ret ; return with the carry and the zero ; flags both reset. ; Now enter another loop to consider the individual characters in the line to ; find where the statement ends. EACH_S_2: ; 1998 inc hl ; Update the pointer and fetch ld a,(hl) ; the new code. EACH_S_3: ; 199a call NUMBER ; Step over any number. ld (CH_ADD),hl ; Update CH-ADD. cp 0x22 ; Jump forward if the character jr nz,EACH_S_4 ; is not a '"'. dec c ; Otherwise set the 'quotes flag'. EACH_S_4: ; 19a5 cp 0x3a ; Jump forward if the character jr z,EACH_S_5 ; is a ':'. cp 0xcb ; Jump forward unless the code jr nz,EACH_S_6 ; is the token 'THEN'. EACH_S_5: ; 19ad bit 0,c ; Read the 'quotes flag' and jr z,EACH_S_1 ; jump back at the end of each ; statement (including after ; 'THEN'). EACH_S_6: ; 19b1 cp 0x0d ; Jump back unless at the end jr nz,EACH_S_2 ; of a BASIC line. dec d ; Decrease the statement scf ; counter and set the carry ret ; flag before returning. ; THE 'NEXT-ONE' SUBROUTINE ; This subroutine can be used to find the 'next line' in the program area or ; the 'next variable' in the variables area. The subroutine caters for the six ; different types of variable that are used in the SPECTRUM system. NEXT_ONE: ; 19b8 push hl ; Save the address of the current line ; or variable. ld a,(hl) ; Fetch the first byte. cp 0x40 ; Jump forward if searching jr c,NEXT_O_3 ; for a 'next line'. bit 5,a ; Jump forward if searching for jr z,NEXT_O_4 ; the next string or array variable. add a,a ; Jump forward with simple jp m,NEXT_O_1 ; numeric and FOR-NEXT variables. ccf ; Long name numeric variables only. NEXT_O_1: ; 19c7 ld bc,0x0005 ; A numeric variable will jr nc,NEXT_O_2 ; occupy five locations but a ld c,0x12 ; FOR-NEXT control variable will need ; eighteen locations. NEXT_O_2: ; 19ce rla ; The carry flag becomes reset for long ; named variables only; ; until the final character of the long ; name is reached. inc hl ; Increment the pointer and ld a,(hl) ; fetch the new code. jr nc,NEXT_O_2 ; Jump back unless the previous code ; was the last code of the ; variable's name. jr NEXT_O_5 ; Now jump forward (BC = +0005 or ; +0012). NEXT_O_3: ; 19d5 inc hl ; Step past the low byte of the line ; number. NEXT_O_4: ; 19d6 inc hl ; Now point to the low byte of the ; length. ld c,(hl) ; Fetch the length into the inc hl ; BC register pair. ld b,(hl) inc hl ; Allow for the inclusive byte. ; In all cases the address of the 'next' line or variable is found. NEXT_O_5: ; 19db add hl,bc ; Point to the first byte of the 'next' ; line or variable. pop de ; Fetch the address of the previous one ; and continue into ; the 'difference' subroutine. ; THE 'DIFFERENCE' SUBROUTINE ; The 'length' between two 'starts' is formed in the BC register pair. The ; pointers are reformed but returned exchanged. DIFFER: ; 19dd and a ; Prepare for a true subtraction. sbc hl,de ; Find the length from one ld b,h ; 'start' to the next and pass ld c,l ; it to the BC register pair. add hl,de ; Reform the address and ex de,hl ; exchange them before ret ; returning. ; THE 'RECLAIMING' SUBROUTINE ; The entry point RECLAIM-1 is used when the address of the first location to ; be reclaimed is in the DE register pair and the address of the first location ; to be left alone is in the HL register pair. The entry point RECLAIM-2 is ; used when the HL register pair points to the first location to be reclaimed ; and the BC register pair holds the number of the bytes that are to be ; reclaimed. RECLAIM_1: ; 19e5 call DIFFER ; Use the 'difference' subroutine to ; develop the appropriate ; values. RECLAIM_2: ; 19e8 push bc ; Save the number of bytes to be ; reclaimed. ld a,b ; All the system variable cpl ; pointers above the area ld b,a ; have to be reduced by 'BC' ld a,c ; so this number is 2's cpl ; complemented before the ld c,a ; pointers are altered. inc bc call POINTERS ex de,hl ; Return the 'first location' pop hl ; address to the DE register add hl,de ; pair and reform the address of the ; first location to the left. push de ; Save the 'first location' ldir ; whilst the actual reclamation pop hl ; occurs. ret ; Now return. ; THE 'E-LINE-NO' SUBROUTINE ; This subroutine is used to read the line number of the line in the editing ; area. If there is no line number, i.e. a direct BASIC line, then the line ; number is considered to be zero. ; In all cases the line number is returned in the BC register pair. E_LINE_NO: ; 19fb ld hl,(E_LINE) ; Pick up the pointer to the edit-line. dec hl ; Set the CH-ADD to point to the ld (CH_ADD),hl ; location before any number. rst NEXT_CHAR ; Pass the first code to the A ; register. ld hl,MEMBOT ; However before considering ld (STKEND),hl ; the code make the calculator's memory ; area a temporary ; calculator stack area. call INT_TO_FP ; Now read the digits of the line ; number. Return zero if no ; number exists. call FP_TO_BC ; Compress the line number into the BC ; register pair. jr c,E_L_1 ; Jump forward if the number exceeds ; '65,536'. ld hl,0xd8f0 ; Otherwise test it against add hl,bc ; '10,000'. E_L_1: ; 1a15 jp c,REPORT_Cb ; Give report C if over '9,999'. jp SET_STK ; Return via SET-STK that restores the ; calculator stack to ; its rightful place. ; THE 'REPORT AND LINE NUMBER PRINTING' SUBROUTINE ; The entry point OUT-NUM-1 will lead to the number in the BC register pair ; being printed. Any value over '9,999' will not however be printed correctly. ; The entry point OUT-NUM-2 will lead to the number indirectly addressed by the ; HL register pair being printed. This time any necessary leading spaces will ; appear. Again the limit of correctly printed numbers is '9,999'. OUT_NUM_1: ; 1a1b push de ; Save the other registers push hl ; throughout the subroutine. xor a ; Clear the A register. bit 7,b ; Jump forward to print a zero rather ; than '-2' when jr nz,OUT_NUM_4 ; reporting on the edit-line. ld h,b ; Move the number to the ld l,c ; HL register pair. ld e,0xff ; Flag 'no leading spaces'. jr OUT_NUM_3 ; Jump forward to print the number. OUT_NUM_2: ; 1a28 push de ; Save the DE register pair. ld d,(hl) ; Fetch the number into the inc hl ; DE register pair and save ld e,(hl) ; the pointer (updated). push hl ex de,hl ; Move the number to the HL ld e,0x20 ; register pair and flag 'leading space ; are to be printed'. ; Now the integer form of the number in the HL register pair is printed. OUT_NUM_3: ; 1a30 ld bc,0xfc18 ; This is '-1,000'. call OUT_SP_NO ; Print a first digit. ld bc,0xff9c ; This is '-100'. call OUT_SP_NO ; Print the second digit. ld c,0xf6 ; This is '-10'. call OUT_SP_NO ; Print the third digit. ld a,l ; Move any remaining part of the number ; to the A register. OUT_NUM_4: ; 1a42 call OUT_CODE ; Print the digit. pop hl ; Restore the registers pop de ; before returning. ret ; BASIC LINE AND COMMAND INTERPRETATION ; THE SYNTAX TABLES ; i. The offset table ; There is an offset value for each of the fifty BASIC commands. SYNTAX_OT: ; 1a48 db P_DEF_FN - $ db P_CAT - $ db P_FORMAT - $ db P_MOVE - $ db P_ERASE - $ db P_OPEN - $ db P_CLOSE - $ db P_MERGE - $ db P_VERIFY - $ db P_BEEP - $ db P_CIRCLE - $ db P_INK - $ db P_PAPER - $ db P_FLASH - $ db P_BRIGHT - $ db P_INVERSE - $ db P_OVER - $ db P_OUT - $ db P_LPRINT - $ db P_LLIST - $ db P_STOP - $ db P_READ - $ db P_DATA - $ db P_RESTORE - $ db P_NEW - $ db P_BORDER - $ db P_CONT - $ db P_DIM - $ db P_REM - $ db P_FOR - $ db P_GO_TO - $ db P_GO_SUB - $ db P_INPUT - $ db P_LOAD - $ db P_LIST - $ db P_LET - $ db P_PAUSE - $ db P_NEXT - $ db P_POKE - $ db P_PRINT - $ db P_PLOT - $ db P_RUN - $ db P_SAVE - $ db P_RANDOM - $ db P_IF - $ db P_CLS - $ db P_DRAW - $ db P_CLEAR - $ db P_RETURN - $ db P_COPY - $ ; ii. The parameter table ; For each of the fifty BASIC commands there are up to eight entries in the ; parameter table. These entries comprise command class details, required ; separators and, where appropriate, command routine addresses. P_LET: ; 1a7a db 0x01 ; CLASS-01 db '=' db 0x02 ; CLASS-02 P_GO_TO: ; 1a7d db 0x06 ; CLASS-06 db 0x00 ; CLASS-00 dw GO_TO P_IF: ; 1a81 db 0x06 ; CLASS-06 db 0xcb ; THEN db 0x05 ; CLASS-05 dw IF P_GO_SUB: ; 1a86 db 0x06 ; CLASS-06 db 0x00 ; CLASS-00 dw GO_SUB P_STOP: ; 1a8a db 0x00 ; CLASS-00 dw STOP P_RETURN: ; 1a8d db 0x00 ; CLASS-00 dw RETURN P_FOR: ; 1a90 db 0x04 ; CLASS-04 db '=' db 0x06 ; CLASS-06 db 0xcc ; TO db 0x06 ; CLASS-06 db 0x05 ; CLASS-05 dw FOR P_NEXT: ; 1a98 db 0x04 ; CLASS-04 db 0x00 ; CLASS-00 dw NEXT P_PRINT: ; 1a9c db 0x05 ; CLASS-05 dw PRINT P_INPUT: ; 1a9f db 0x05 ; CLASS-05 dw INPUT P_DIM: ; 1aa2 db 0x05 ; CLASS-05 dw DIM P_REM: ; 1aa5 db 0x05 ; CLASS-05 dw REM P_NEW: ; 1aa8 db 0x00 ; CLASS-00 dw NEW P_RUN: ; 1aab db 0x03 ; CLASS-03 dw RUN P_LIST: ; 1aae db 0x05 ; CLASS-05 dw LIST P_POKE: ; 1ab1 db 0x08 ; CLASS-08 db 0x00 ; CLASS-00 dw POKE P_RANDOM: ; 1ab5 db 0x03 ; CLASS-03 dw RANDOMIZE P_CONT: ; 1ab8 db 0x00 ; CLASS-00 dw CONTINUE P_CLEAR: ; 1abb db 0x03 ; CLASS-03 dw CLEAR P_CLS: ; 1abe db 0x00 ; CLASS-00 dw CLS P_PLOT: ; 1ac1 db 0x09 ; CLASS-09 db 0x00 ; CLASS-00 dw PLOT P_PAUSE: ; 1ac5 db 0x06 ; CLASS-06 db 0x00 ; CLASS-00 dw PAUSE P_READ: ; 1ac9 db 0x05 ; CLASS-05 dw READ P_DATA: ; 1acc db 0x05 ; CLASS-05 dw DATA P_RESTORE: ; 1acf db 0x03 ; CLASS-03 dw RESTORE P_DRAW: ; 1ad2 db 0x09 ; CLASS-09 db 0x05 ; CLASS-05 dw DRAW P_COPY: ; 1ad6 db 0x00 ; CLASS-00 dw COPY P_LPRINT: ; 1ad9 db 0x05 ; CLASS-05 dw LPRINT P_LLIST: ; 1adc db 0x05 ; CLASS-05 dw LLIST P_SAVE: ; 1adf db 0x0b ; CLASS-0B P_LOAD: ; 1ae0 db 0x0b ; CLASS-0B P_VERIFY: ; 1ae1 db 0x0b ; CLASS-0B P_MERGE: ; 1ae2 db 0x0b ; CLASS-0B P_BEEP: ; 1ae3 db 0x08 ; CLASS-08 db 0x00 ; CLASS-00 dw BEEP P_CIRCLE: ; 1ae7 db 0x09 ; CLASS-09 db 0x05 ; CLASS-05 dw CIRCLE P_INK: ; 1aeb db 0x07 ; CLASS-07 P_PAPER: ; 1aec db 0x07 ; CLASS-07 P_FLASH: ; 1aed db 0x07 ; CLASS-07 P_BRIGHT: ; 1aee db 0x07 ; CLASS-07 P_INVERSE: ; 1aef db 0x07 ; CLASS-07 P_OVER: ; 1af0 db 0x07 ; CLASS-07 P_OUT: ; 1af1 db 0x08 ; CLASS-08 db 0x00 ; CLASS-00 dw OUT P_BORDER: ; 1af5 db 0x06 ; CLASS-06 db 0x00 ; CLASS-00 dw BORDER P_DEF_FN: ; 1af9 db 0x05 ; CLASS-05 dw DEF_FN P_OPEN: ; 1afc db 0x06 ; CLASS-06 db ',' db 0x0a ; CLASS-0A db 0x00 ; CLASS-00 dw OPEN P_CLOSE: ; 1b02 db 0x06 ; CLASS-06 db 0x00 ; CLASS-00 dw CLOSE P_FORMAT: ; 1b06 db 0x0a ; CLASS-0A db 0x00 ; CLASS-00 dw CAT_ETC_ P_MOVE: ; 1b0a db 0x0a ; CLASS-0A db ',' db 0x0a ; CLASS-0A db 0x00 ; CLASS-00 dw CAT_ETC_ P_ERASE: ; 1b10 db 0x0a ; CLASS-0A db 0x00 ; CLASS-00 dw CAT_ETC_ P_CAT: ; 1b14 db 0x00 ; CLASS-00 dw CAT_ETC_ ; CLASS-00 - No further operands. ; CLASS-01 - Used in LET. A variable is required. ; CLASS-02 - Used in LET. An expression, numeric or string, must follow. ; CLASS-03 - A numeric expression may follow. Zero to be used in case of ; default. ; CLASS-04 - A single character variable must follow. ; CLASS-05 - A set of items may be given. ; CLASS-06 - A numeric expression must follow. ; CLASS-07 - Handles colour items. ; CLASS-08 - Two numeric expressions, separated by a comma, must follow. ; CLASS-09 - As for CLASS-08 but colour items may precede the expressions. ; CLASS-0A - A string expression must follow. ; CLASS-0B - Handles cassette routines. ; THE 'MAIN PARSER' OF THE BASIC INTERPRETER ; The parsing routine of the BASIC interpreter is entered at LINE-SCAN when ; syntax is being checked, and at LINE-RUN when a BASIC program of one or more ; statements is to be executed. ; Each statement is considered in turn and the system variable CH-ADD is used ; to point to each code of the statement as it occurs in the program area or ; the editing area. LINE_SCAN: ; 1b17 res 7,(iy+d_FLAGS) ; Signal 'syntax checking'. call E_LINE_NO ; CH-ADD is made to point to the first ; code after any line number. xor a ; The system variable SUBPPC ld (SUBPPC),a ; is initialised to +00 and dec a ; ERR-NR to +FF. ld (ERR_NR),a jr STMT_L_1 ; Jump forward to consider the first ; statement of the line. ; THE STATEMENT LOOP. ; Each statement is considered in turn until the end of the line is reached. STMT_LOOP: ; 1b28 rst NEXT_CHAR ; Advance CH-ADD along the line. STMT_L_1: ; 1b29 call SET_WORK ; The work space is cleared. inc (iy+d_SUBPPC) ; Increase SUBPPC on each passage ; around the loop. jp m,REPORT_Cb ; But only '127' statements are allowed ; in a single line. rst GET_CHAR ; Fetch a character. ld b,0x00 ; Clear the register for later. cp 0x0d ; Is the character a 'carriage jr z,LINE_END ; return'; jump if it is. cp 0x3a ; Go around the loop again if jr z,STMT_LOOP ; it is a ':'. ; A statement has been identified so, first, its initial command is considered. ld hl,STMT_RET ; Pre-load the machine stack push hl ; with the return address - STMT-RET. ld c,a ; Save the command temporarily rst NEXT_CHAR ; in the C register whilst ld a,c ; CH-ADD is advanced again. sub 0xce ; Reduce the command's code by +CE; ; giving the range +00 to ; +31 for the fifty commands. jp c,REPORT_Cb ; Give the appropriate error if not a ; command code. ld c,a ; Move the command code to the BC ; register pair (B holds +00). ld hl,SYNTAX_OT ; The base address of the syntax offset ; table. add hl,bc ; The required offset is passed to ld c,(hl) ; the C register and used to add hl,bc ; compute the base address for the ; command's entries in the ; parameter table. jr GET_PARAM ; Jump forward into the scanning loop ; with this address. ; Each of the command class routines applicable to the present command are ; executed in turn. Any required separators are also considered. SCAN_LOOP: ; 1b52 ld hl,(T_ADDR) ; The temporary pointer to the entries ; in the parameter table. GET_PARAM: ; 1b55 ld a,(hl) ; Fetch each entry in turn. inc hl ; Update the pointer to the ld (T_ADDR),hl ; entries for the next pass. ld bc,SCAN_LOOP ; Pre-load the machine stack push bc ; with the return address - SCAN-LOOP. ld c,a ; Copy the entry to the C register for ; later. cp 0x20 ; Jump forward if the entry is jr nc,SEPARATOR ; a 'separator'. ld hl,COMMAND_CLASS_OT ; The base address of the 'command ; class' table. ld b,0x00 ; Clear the B register and add hl,bc ; index into the table. ld c,(hl) ; Fetch the offset and compute add hl,bc ; the starting address of the required ; command class routine push hl ; Push the address on to the machine ; stack. rst GET_CHAR ; Before making an indirect dec b ; jump to the command class ret ; routine pass the command code to the ; A register and set the B ; register to +FF. ; THE 'SEPARATOR' SUBROUTINE ; The report - 'Nonsense in BASIC is given if the required separator is not ; present. But note that when syntax is being checked the actual report does ; not appear on the screen - only the 'error marker'. SEPARATOR: ; 1b6f rst GET_CHAR ; The current character is cp c ; fetched and compared to the entry in ; the parameter table. jp nz,REPORT_Cb ; Give the error report if there is not ; a match. rst NEXT_CHAR ; Step past a correct character ret ; and return. ; THE 'STMT-RET' SUBROUTINE ; After the correct interpretation of a statement a return is made to this ; entry point. STMT_RET: ; 1b76 call BREAK_KEY ; The BREAK key is tested after every ; statement. jr c,STMT_R_1 ; Jump forward unless it has been ; pressed. ; Report L - 'BREAK into program' REPORT_Lb: ; 1b7b rst ERROR_1 ; Call the error handling db 0x14 ; routine. ; Continue here as the BREAK key was not pressed. STMT_R_1: ; 1b7d bit 7,(iy+d_NSPPC) ; Jump forward if there is not jr nz,STMT_NEXT ; a 'jump' to be made. ld hl,(NEWPPC) ; Fetch the 'new line' number bit 7,h ; and jump forward unless dealing jr z,LINE_NEW ; with a further statement in the ; editing area. ; THE 'LINE-RUN' ENTRY POINT ; This entry point is used wherever a line in the editing area is to be 'run'. ; In such a case the syntax/run flag (bit 7 of FLAGS) will be set. ; The entry point is also used in the syntax checking of a line in the editing ; area that has more than one statement (bit 7 of FLAGS will be reset). LINE_RUN: ; 1b8a ld hl,0xfffe ; A line in the editing area ld (PPC),hl ; is considered as line '-2'. ld hl,(WORKSP) ; Make HL point to the end dec hl ; marker of the editing area ld de,(E_LINE) ; and DE to the location before dec de ; the start of that area. ld a,(NSPPC) ; Fetch the number of the next ; statement to be handled jr NEXT_LINE ; before jumping forward. ; THE 'LINE-NEW' SUBROUTINE ; There has been a jump in the program and the starting address of the new line ; has to be found. LINE_NEW: ; 1b9e call LINE_ADDR ; The starting address of the line, or ; the 'first line after' is found. ld a,(NSPPC) ; Collect the statement number. jr z,LINE_USE ; Jump forward if the required and a ; line was found; otherwise jr nz,REPORT_Nb ; check the validity of the statement ; number - must be zero. ld b,a ; Also check that the 'first ld a,(hl) ; line after' is not after the and 0xc0 ; actual 'end of program'. ld a,b jr z,LINE_USE ; Jump forward with valid addresses; ; otherwise signal the ; error 'OK'. ; Report 0 - 'OK' REPORT_0b: ; 1bb0 rst ERROR_1 ; Use the error handling db 0xff ; routine. ; Note: Obviously not an error in the normal sense - but rather a jump past the ; program. ; THE 'REM' COMMAND ROUTINE ; The return address to STMT-RET is dropped which has the effect of forcing the ; rest of the line to be ignored. REM: ; 1bb2 pop bc ; Drop the address - STMT-RET. ; THE 'LINE-END' ROUTINE ; If checking syntax a simple return is made but when 'running' the address ; held by NXTLIN has to be checked before it can be used. LINE_END: ; 1bb3 call SYNTAX_Z ; Return if syntax is being ret z ; checked; otherwise fetch ld hl,(NXTLIN) ; the address in NXTLIN. ld a,0xc0 ; Return also if the address is and (hl) ; after the end of the program ret nz ; - the 'run' is finished. xor a ; Signal 'statement zero' before ; proceeding. ; THE 'LINE-USE' ROUTINE ; This short routine has three functions; i. Change statement zero to statement ; '1'; ii. Find the number of the new line and enter it into PPC; & iii. Form ; the address of the start of the line after. LINE_USE: ; 1bbf cp 0x01 ; Statement zero becomes adc a,0x00 ; statement '1' ld d,(hl) ; The line number of the line inc hl ; to be used is collected and ld e,(hl) ; passed to PPC. ld (PPC),de inc hl ; Now find the 'length' ld e,(hl) ; of the line. inc hl ld d,(hl) ex de,hl ; Switch over the values. add hl,de ; Form the address of the start inc hl ; of the line after in HL and the ; location before the 'next' line's ; first character in DE. ; THE 'NEXT-LINE' ROUTINE ; On entry the HL register pair points to the location after the end of the ; 'next' line to be handled and the DE register pair to the location before the ; first character of the line. This applies to lines in the program area and ; also to a line in the editing area - where the next line will be the same ; line again whilst there are still statements to be interpreted. NEXT_LINE: ; 1bd1 ld (NXTLIN),hl ; Set NXTLIN for use once the current ; line has been completed. ex de,hl ; As usual CH-ADD points to the ld (CH_ADD),hl ; location before the first character ; to be considered. ld d,a ; The statement number is fetched. ld e,0x00 ; The E register is cleared in case ; EACH-STMT is used. ld (iy+d_NSPPC),0xff ; Signal 'no jump'. dec d ; The statement number minus ld (iy+d_SUBPPC),d ; one goes into SUBPPC. jp z,STMT_LOOP ; A first statement can now be ; considered. inc d ; However for later statements call EACH_STMT ; the 'starting address' has to be ; found. jr z,STMT_NEXT ; Jump forward unless the statement ; does not exist. ; Report N - 'Statement lost' REPORT_Nb: ; 1bec rst ERROR_1 ; Call the error handling db 0x16 ; routine. ; THE 'CHECK-END' SUBROUTINE ; This is an important routine and is called from many places in the monitor ; program when the syntax of the edit-line is being checked. The purpose of the ; routine is to give an error report if the end of a statement has not been ; reached and to move on to the next statement if the syntax is correct. CHECK_END: ; 1bee call SYNTAX_Z ; Do not proceed unless ret nz ; checking syntax. pop bc ; Drop the addresses of pop bc ; SCAN-LOOP & STMT-RET before ; continuing into ; STMT-NEXT. ; THE 'STMT-NEXT' ROUTINE ; If the present character is a 'carriage return' then the 'next statement' is ; on the 'next line'; if ' : ' it is on the same line; but if any other ; character is found then there is an error in syntax. STMT_NEXT: ; 1bf4 rst GET_CHAR ; Fetch the present character. cp 0x0d ; Consider the 'next line' if jr z,LINE_END ; it is a 'carriage return'. cp 0x3a ; Consider the 'next statement' jp z,STMT_LOOP ; if it is a ' : '. jp REPORT_Cb ; Otherwise there has been a syntax ; error. ; THE 'COMMAND CLASS' TABLE COMMAND_CLASS_OT: ; 1c01 db CLASS_00 - $ db CLASS_01 - $ db CLASS_02 - $ db CLASS_03 - $ db CLASS_04 - $ db CLASS_05 - $ db EXPT_1NUM - $ db PERMS - $ db EXPT_2NUM - $ db CLASS_09 - $ db EXPT_EXP - $ db CLASS_0B - $ ; THE 'COMMAND CLASSES - 00, 03 & 05' ; The commands of class-03 may, or may not, be followed by a number. e.g. RUN & ; RUN 200. CLASS_03: ; 1c0d call FETCH_NUM ; A number is fetched but zero is used ; in cases of default. ; The commands of class-00 must not have any operands. e.g. COPY & CONTINUE. CLASS_00: ; 1c10 cp a ; Set the zero flag for later. ; The commands of class-05 may be followed by a set of items. e.g. PRINT & ; PRINT "222". CLASS_05: ; 1c11 pop bc ; In all cases drop the address - ; SCAN-LOOP. call z,CHECK_END ; If handling commands of classes 00 & ; 03 AND syntax is being ; checked move on now to consider the ; next statement. ex de,hl ; Save the line pointer in the DE ; register pair. ; THE 'JUMP-C-R' ROUTINE ; After the command class entries and the separator entries in the parameter ; table have been considered the jump to the appropriate command routine is ; made. JUMP_C_R: ; 1c16 ld hl,(T_ADDR) ; Fetch the pointer to the ld c,(hl) ; entries in the parameter table inc hl ; and fetch the address of the ld b,(hl) ; required command routine. ex de,hl ; Exchange the pointers back push bc ; and make an indirect jump ret ; to the command routine. ; THE 'COMMAND CLASSES - 01, 02 & 04' ; These three command classes are used by the variable handling commands - LET, ; FOR & NEXT and indirectly by READ & INPUT. ; Command class 01 is concerned with the identification of the variable in ; a LET, READ or INPUT statement. CLASS_01: ; 1c1f call LOOK_VARS ; Look in the variables area to ; determine whether or not ; the variable has been used already. ; THE 'VARIABLE IN ASSIGNMENT' SUBROUTINE ; This subroutine develops the appropriate values for the system variables DEST ; & STRLEN. VAR_A_1: ; 1c22 ld (iy+d_FLAGX),0x00 ; Initialise FLAGX to +00. jr nc,VAR_A_2 ; Jump forward if the variable has been ; used before. set 1,(iy+d_FLAGX) ; Signal 'a new variable'. jr nz,VAR_A_3 ; Give an error if trying to use an ; 'undimensioned array'. ; Report 2 - Variable not found REPORT_2c: ; 1c2e rst ERROR_1 ; Call the error handling db 0x01 ; routine. ; Continue with the handling of existing variables. VAR_A_2: ; 1c30 call z,STK_VAR ; The parameters of simple string ; variables and all array variables ; are passed to the calculator stack. ; (STK-VARS will 'slice' a ; string if required.) bit 6,(iy+d_FLAGS) ; Jump forward if handling a jr nz,VAR_A_3 ; numeric variable. xor a ; Clear the A register. call SYNTAX_Z ; The parameters of the string of call nz,STK_FETCH ; string array variable are fetched ; unless syntax is being checked. ld hl,FLAGX ; This is FLAGX. or (hl) ; Bit 0 is set only when handling ld (hl),a ; complete 'simple strings' thereby ; signalling 'old copy to be ; deleted'. ex de,hl ; HL now points to the string or the ; element of the array. ; The pathways now come together to set STRLEN & DEST as required. For all ; numeric variables and 'new' string & string array variables STRLEN-lo holds ; the 'letter' of the variable's name. But for 'old' string & string array ; variables whether 'sliced' or complete it holds the 'length' in 'assignment'. VAR_A_3: ; 1c46 ld (STRLEN_),bc ; Set STRLEN as required. ; DEST holds the address for the 'destination of an 'old' variable but in ; effect the 'source' for a 'new' variable. ld (DEST),hl ; Set DEST as required and ret ; return. ; Command class 02 is concerned with the actual calculation of the value to be ; assigned in a LET statement. CLASS_02: ; 1c4e pop bc ; The address - SCAN-LOOP is dropped. call VAL_FET_1 ; The assignment is made. call CHECK_END ; Move on to the next statement either ; via CHECK-END if ret ; checking syntax, or STMT-RET if in ; 'run-time'. ; THE 'FETCH A VALUE' SUBROUTINE ; This subroutine is used by LET, READ & INPUT statements to first evaluate and ; then assign values to the previously designated variable. ; The entry point VAL-FET-1 is used by LET & READ and considers FLAGS ; whereas the entry point VAL-FET-2 is used by INPUT and considers FLAGX. VAL_FET_1: ; 1c56 ld a,(FLAGS) ; Use FLAGS. VAL_FET_2: ; 1c59 push af ; Save FLAGS or FLAGX. call SCANNING ; Evaluate the next expression. pop af ; Fetch the old FLAGS or FLAGX. ld d,(iy+d_FLAGS) ; Fetch the new FLAGS. xor d ; The nature - numeric or string and 0x40 ; of the variable and the expression ; must match. jr nz,REPORT_Cb ; Give report C if they do not. bit 7,d ; Jump forward to make the jp nz,LET ; actual assignment unless checking ; syntax when simply ret ; return. ; THE 'COMMAND CLASS 04' ROUTINE ; The command class 04 entry point is used by FOR & NEXT statements. CLASS_04: ; 1c6c call LOOK_VARS ; Look in the variables area for the ; variable being used. push af ; Save the AF register pair whilst ld a,c ; the discriminator byte is tested or 0x9f ; to ensure that the variable inc a ; is a FOR-NEXT control jr nz,REPORT_Cb ; variable. pop af ; Restore the flags register and jr VAR_A_1 ; jump back to make the variable that ; has been found the ; 'variable in assignment'. ; THE 'EXPECT NUMERIC/STRING EXPRESSIONS' SUBROUTINE ; There is a series of short subroutines that are used to fetch the result of ; evaluating the next expression. The result from a single expression is ; returned as a 'last value' on the calculator stack. ; The entry point NEXT-2NUM is used when CH-ADD needs updating to point to ; the start of the first expression. NEXT_2NUM: ; 1c79 rst NEXT_CHAR ; Advance CH-ADD. ; The entry point EXPT-2NUM (EQU. CLASS-08) allows for two numeric expressions, ; separated by a comma, to be evaluated. EXPT_2NUM: ; 1c7a call EXPT_1NUM ; Evaluate each expression in ; turn - so evaluate the first. cp 0x2c ; Give an error report if the jr nz,REPORT_Cb ; separator is not a comma. rst NEXT_CHAR ; Advance CH-ADD. ; The entry point EXPT-1NUM (EQU. CLASS-06) allows for a single numeric ; expression to be evaluated. EXPT_1NUM: ; 1c82 call SCANNING ; Evaluate the next expression. ; (CLASS-06) bit 6,(iy+d_FLAGS) ; Return as long as the result was ret nz ; numeric; otherwise it is an error. ; Report C - Nonsense in BASIC REPORT_Cb: ; 1c8a rst ERROR_1 ; Call the error handling db 0x0b ; routine. ; The entry point EXPT-EXP (EQU. CLASS-0A) allows for a single string ; expression to be evaluated. EXPT_EXP: ; 1c8c call SCANNING ; Evaluate the next expression. ; (CLASS-0A) bit 6,(iy+d_FLAGS) ; This time return if the result ret z ; indicates a string; otherwise jr REPORT_Cb ; give an error report. ; THE 'SET PERMANENT COLOURS' SUBROUTINE (EQU. CLASS-07) ; This subroutine allows for the current temporary colours to be made ; permanent. As command class 07 it is in effect the command routine for the ; six colour item commands. PERMS: ; 1c96 bit 7,(iy+d_FLAGS) ; The syntax/run flag is read. ; (CLASS-07) res 0,(iy+d_TV_FLAG) ; Signal 'main screen'. call nz,TEMPS ; Only during a 'run' call TEMPS to ; ensure the temporary colours ; are the main screen colours. pop af ; Drop the return address - SCAN-LOOP. ld a,(T_ADDR) ; Fetch the low byte of T-ADDR and ; subtract +13 to give the sub 0x13 ; range +D9 to +DE which are the token ; codes for INK to OVER. call CO_TEMP_4 ; Jump forward to change the temporary ; colours as directed ; by the BASIC statement. call CHECK_END ; Move on to the next statement if ; checking syntax. ld hl,(ATTR_T) ; Now the temporary colour ld (ATTR_P),hl ; values are made permanent (both ; ATTR-P & MASK-P). ld hl,P_FLAG ; This is P-FLAG; and that too ld a,(hl) ; has to be considered. ; The following instructions cleverly copy the even bits of the supplied byte ; to the odd bits. In effect making the permanent bits the same as the ; temporary ones. rlca ; Move the mask leftwards. xor (hl) ; Impress onto the mask and 0xaa ; only the even bits of the xor (hl) ; other byte. ld (hl),a ; Restore the result. ret ; THE 'COMMAND CLASS 09' ROUTINE ; This routine is used by PLOT, DRAW & CIRCLE statements in order to specify ; the default conditions of 'FLASH 8; BRIGHT 8; PAPER 8;' that are set up ; before any embedded colour items are considered. CLASS_09: ; 1cbe call SYNTAX_Z ; Jump forward if jr z,CL_09_1 ; checking syntax. res 0,(iy+d_TV_FLAG) ; Signal 'main screen'. call TEMPS ; Set the temporary colours for the ; main screen. ld hl,ATTR_T+1 ; This is MASK-T. ld a,(hl) ; Fetch its present value but or 0xf8 ; keep only its INK part 'unmasked'. ld (hl),a ; Restore the value which now indicates ; 'FLASH 8; BRIGHT 8; ; PAPER 8;'. res 6,(iy+d_P_FLAG) ; Also ensure NOT 'PAPER 9'. rst GET_CHAR ; Fetch the present character before ; continuing to deal with ; embedded colour items. CL_09_1: ; 1cd6 call CO_TEMP_2 ; Deal with the locally dominant colour ; items. jr EXPT_2NUM ; Now get the first two operands for ; PLOT, DRAW or CIRCLE. ; THE 'COMMAND CLASS 0B' ROUTINE ; This routine is used by SAVE, LOAD, VERIFY & MERGE statements. CLASS_0B: ; 1cdb jp SAVE_ETC ; Jump to the cassette handling ; routine. ; THE 'FETCH A NUMBER' SUBROUTINE ; This subroutine leads to a following numeric expression being evaluated but ; zero being used instead if there is no expression. FETCH_NUM: ; 1cde cp 0x0d ; Jump forward if at the end jr z,USE_ZERO ; of a line. cp 0x3a ; But jump to EXPT-1NUM unless jr nz,EXPT_1NUM ; at the end of a statement. ; The calculator is now used to add the value zero to the calculator stack. USE_ZERO: ; 1ce6 call SYNTAX_Z ; Do not perform the operation ret z ; if syntax is being checked. rst FP_CALC ; Use the calculator. db C_stk_zero ; The 'last value' is now zero. db C_end_calc ret ; Return with zero added to the stack. ; THE COMMAND ROUTINES ; The section of the 16K monitor program from 1CEE to 23FA contains most of the ; command routines of the BASIC interpreter. ; THE 'STOP' COMMAND ROUTINE ; The command routine for STOP contains only a call to the error handling ; routine. STOP: ; 1cee rst ERROR_1 ; Call the error handling db 0x08 ; routine. ; THE 'IF' COMMAND ROUTINE ; On entry the value of the expression between the IF and the THEN is the 'last ; value' on the calculator stack. If this is logically true then the next ; statement is considered; otherwise the line is considered to have been ; finished. IF: ; 1cf0 pop bc ; Drop the return address - STMT-RET. call SYNTAX_Z ; Jump forward if checking jr z,IF_1 ; syntax. ; Now use the calculator to 'delete' the last value on the calculator stack but ; leave the DE register pair addressing the first byte of the value. rst FP_CALC ; Use the calculator. db C_delete ; The present 'last value' is db C_end_calc ; deleted. ex de,hl ; Make HL point to the first call TEST_ZERO ; byte and call TEST-ZERO. jp c,LINE_END ; If the value was 'FALSE' jump to the ; next line. IF_1: ; 1d00 jp STMT_L_1 ; But if 'TRUE' jump to the next ; statement (after the THEN). ; THE 'FOR' COMMAND ROUTINE ; This command routine is entered with the VALUE and the LIMIT of the FOR ; statement already on the top of the calculator stack. FOR: ; 1d03 cp 0xcd ; Jump forward unless a 'STEP' jr nz,F_USE_1 ; is given. rst NEXT_CHAR ; Advance CH-ADD and fetch the call EXPT_1NUM ; value of the STEP. call CHECK_END ; Move on to the next statement jr F_REORDER ; if checking syntax; otherwise jump ; forward. ; There has not been a STEP supplied so the value '1' is to be used. F_USE_1: ; 1d10 call CHECK_END ; Move on to the next statement if ; checking syntax; otherwise rst FP_CALC ; use the calculator to place a '1' db C_stk_one ; on the calculator stack. db C_end_calc ; The three values on the calculator stack are the VALUE (v), the LIMIT (l) and ; the STEP (s). These values now have to be manipulated. F_REORDER: ; 1d16 rst FP_CALC ; v, l, s db C_st_mem_0 ; v, l, s (mem-0 = s) db C_delete ; v, l db C_exchange ; l, v db C_get_mem_0 ; l, v, s db C_exchange ; l, s, v db C_end_calc ; A FOR control variable is now established and treated as a temporary ; calculator memory area. call LET ; The variable is found, or created if ; needed (v is used). ld (MEM),hl ; Make it a 'memory area'. ; The variable that has been found may be a simple numeric variable using only ; six locations in which case it will need extending. dec hl ; Fetch the variable's single ld a,(hl) ; character name. set 7,(hl) ; Ensure bit 7 of the name is set. ld bc,0x0006 ; It will have six locations at least. add hl,bc ; Make HL point after them. rlca ; Rotate the name and jump if jr c,F_LNS ; it was already a FOR variable. ld c,0x0d ; Otherwise create thirteen call MAKE_ROOM ; more locations. inc hl ; Again make HL point to the LIMIT ; position. ; The initial values for the LIMIT and the STEP are now added. F_LNS: ; 1d34 push hl ; The pointer is saved. rst FP_CALC ; l, s db C_delete ; l db C_delete ; - db C_end_calc ; DE still points to 'l'. pop hl ; The pointer is restored and ex de,hl ; both pointers exchanged. ld c,0x0a ; The ten bytes of the LIMIT ldir ; and the STEP are moved. ; The looping line number and statement number are now entered. ld hl,(PPC) ; The current line number. ex de,hl ; Exchange the registers before ld (hl),e ; adding the line number to the inc hl ; FOR control variable. ld (hl),d ld d,(iy+d_SUBPPC) ; The looping statement is inc d ; always the next statement - inc hl ; whether it exists or not. ld (hl),d ; The NEXT-LOOP subroutine is called to test the possibility of a 'pass' and a ; return is made if one is possible; otherwise the statement after for FOR - ; NEXT loop has to be identified. call NEXT_LOOP ; Is a 'pass' possible? ret nc ; Return now if it is. ld b,(iy+d_STRLEN) ; Fetch the variable's name. ld hl,(PPC) ; Copy the present line number ld (NEWPPC),hl ; to NEWPPC. ld a,(SUBPPC) ; Fetch the current statement neg ; number and two's complement it. ld d,a ; Transfer the result to the D ; register. ld hl,(CH_ADD) ; Fetch the current value of CH-ADD. ld e,0xf3 ; The search will be for 'NEXT'. ; Now a search is made in the program area, from the present point onwards, for ; the first occurrence of NEXT followed by the correct variable. F_LOOP: ; 1d64 push bc ; Save the variable's name. ld bc,(NXTLIN) ; Fetch the current value of NXTLIN. call LOOK_PROG ; The program area is now searched and ; BC will change ; with each new line examined. ld (NXTLIN),bc ; Upon return save the pointer. pop bc ; Restore the variable's name. jr c,REPORT_Ib ; If there are no further NEXTs then ; give an error. rst NEXT_CHAR ; Advance past the NEXT that was found. or 0x20 ; Allow for upper and lower cp b ; case letters before the new variable ; name is tested. jr z,F_FOUND ; Jump forward if it matches. rst NEXT_CHAR ; Advance CH-ADD again and jr F_LOOP ; jump back if not the correct ; variable. ; NEWPPC holds the line number of the line in which the correct NEXT was found. ; Now the statement number has to be found and stored in NSPPC. F_FOUND: ; 1d7c rst NEXT_CHAR ; Advance CH-ADD. ld a,0x01 ; The statement counter in the sub d ; D register counted statements back ; from zero so it has to ; be subtracted from '1'. ld (NSPPC),a ; The result is stored. ret ; Now return - to STMT-RET. ; REPORT I - FOR without NEXT REPORT_Ib: ; 1d84 rst ERROR_1 ; Call the error handling db 0x11 ; routine. ; THE 'LOOK-PROG' SUBROUTINE ; This subroutine is used to find occurrences of either DATA, DEF FN or NEXT. ; On entry the appropriate token code is in the E register and the HL register ; pair points to the start of the search area. LOOK_PROG: ; 1d86 ld a,(hl) ; Fetch the present character. cp 0x3a ; Jump forward if it is a ' : ' jr z,LOOK_P_2 ; which will indicate there are more ; statements in the present ; line. ; Now a loop is entered to examine each further line in the program. LOOK_P_1: ; 1d8b inc hl ; Fetch the high byte of the ld a,(hl) ; line number and return with and 0xc0 ; carry set if there are no scf ; further lines in the program. ret nz ld b,(hl) ; The line number is fetched inc hl ; and passed to NEWPPC. ld c,(hl) ld (NEWPPC),bc inc hl ; Then the length is collected. ld c,(hl) inc hl ld b,(hl) push hl ; The pointer is saved whilst add hl,bc ; the address of the end of the ld b,h ; line is formed in the BC ld c,l ; register pair. pop hl ; The pointer is restored. ld d,0x00 ; Set the statement counter to zero. LOOK_P_2: ; 1da3 push bc ; The end-of-line pointer is call EACH_STMT ; saved whilst the statements pop bc ; of the line are examined. ret nc ; Make a return if there was jr LOOK_P_1 ; an 'occurrence'; otherwise consider ; the next line. ; THE 'NEXT' COMMAND ROUTINE ; The 'variable in assignment' has already been determined (see CLASS-04,1C6C); ; and it remains to change the VALUE as required. NEXT: ; 1dab bit 1,(iy+d_FLAGX) ; Jump to give the error report jp nz,REPORT_2c ; if the variable was not found. ld hl,(DEST) ; The address of the variable bit 7,(hl) ; is fetched and the name jr z,REPORT_1b ; tested further. ; Next the variable's VALUE and STEP are manipulated by the calculator. inc hl ; Step past the name. ld (MEM),hl ; Make the variable a temporary 'memory ; area'. rst FP_CALC ; - db C_get_mem_0 ; v db C_get_mem_2 ; v, s db C_addition ; v+s db C_st_mem_0 ; v+s db C_delete ; - db C_end_calc ; - ; The result of adding the VALUE and the STEP is now tested against the LIMIT ; by calling NEXT-LOOP. call NEXT_LOOP ; Test the new VALUE against the LIMIT ret c ; Return now if the FOR-NEXT loop has ; been completed. ; Otherwise collect the 'looping' line number and statement. ld hl,(MEM) ; Find the address of the ld de,0x000f ; low byte of the looping add hl,de ; line number. ld e,(hl) ; Now fetch this line number. inc hl ld d,(hl) inc hl ld h,(hl) ; Followed by the statement number. ex de,hl ; Exchange the numbers before jp GO_TO_2 ; jumping forward to treat them as the ; destination line of a ; GO TO command. ; Report 1 - NEXT without FOR REPORT_1b: ; 1dd8 rst ERROR_1 ; Call the error handling db 0x00 ; routine. ; THE 'NEXT-LOOP SUBROUTINE ; This subroutine is used to determine whether the LIMIT has been exceeded by ; the present VALUE. Note has to be taken of the sign of the STEP. ; The subroutine returns the carry flag set if the LIMIT is exceeded. NEXT_LOOP: ; 1dda rst FP_CALC ; - db C_get_mem_1 ; l db C_get_mem_0 ; l, v db C_get_mem_2 ; l, v, s db C_less_0 ; l, v, (1/0) db C_jump_true ; l, v, (1/0) db NEXT_1 - $ ; l, v, (1/0) db C_exchange ; v, l NEXT_1: ; 1de2 db C_subtract ; v-l or l-v db C_greater_0 ; (1/0) db C_jump_true ; (1/0) db NEXT_2 - $ ; - db C_end_calc ; - and a ; Clear the carry flag and ret ; return - loop is possible. ; However if the loop is impossible the carry flag has to be set. NEXT_2: ; 1de9 db C_end_calc ; - scf ; Set the carry flag and ret ; return. ; THE 'READ' COMMAND ROUTINE ; The READ command allows for the reading of a DATA list and has an effect ; similar to a series of LET statements. ; Each assignment within a single READ statement is dealt with in turn. ; The system variable X-PTR is used as a storage location for the pointer to ; the READ statement whilst CH-ADD is used to step along the DATA list. READ_3: ; 1dec rst NEXT_CHAR ; Come here on each pass, after the ; first, to move along the ; READ statement. READ: ; 1ded call CLASS_01 ; Consider whether the variable has ; been used before; find ; the existing entry if it has. call SYNTAX_Z ; Jump forward if checking jr z,READ_2 ; syntax. rst GET_CHAR ; Save the current pointer ld (X_PTR),hl ; CH-ADD in X-PTR. ld hl,(DATADD) ; Fetch the current DATA list ld a,(hl) ; pointer and jump forward cp 0x2c ; unless a new DATA statement jr z,READ_1 ; has to be found. ld e,0xe4 ; The search is for 'DATA'. call LOOK_PROG ; Jump forward if the search is jr nc,READ_1 ; successful. ; Report E - Out of DATA REPORT_Eb: ; 1e08 rst ERROR_1 ; Call the error handling db 0x0d ; routine. ; Continue - picking up a value from the DATA list. READ_1: ; 1e0a call TEMP_PTR1 ; Advance the pointer along the DATA ; list and set CH-ADD. call VAL_FET_1 ; Fetch the value and assign it to the ; variable. rst GET_CHAR ; Fetch the current value of ld (DATADD),hl ; CH-ADD and store it in DATADD. ld hl,(X_PTR) ; Fetch the pointer to the ld (iy+d_X_PTR+1),0x00 ; READ statement and clear X-PTR. call TEMP_PTR2 ; Make CH-ADD once again point to the ; READ statement. READ_2: ; 1e1e rst GET_CHAR ; GET the present character and cp 0x2c ; see if it is a ','. jr z,READ_3 ; If it is then jump back as there are ; further items; call CHECK_END ; otherwise return either via ret ; CHECK-END (if checking syntax) or the ; RET instruction ; (to STMT-RET). ; THE 'DATA' COMMAND ROUTINE ; During syntax checking a DATA statement is checked to ensure that it contains ; a series of valid expressions, separated by commas. But in 'run-time' the ; statement is passed by. DATA: ; 1e27 call SYNTAX_Z ; Jump forward unless checking jr nz,DATA_2 ; syntax. ; A loop is now entered to deal with each expression in the DATA statement. DATA_1: ; 1e2c call SCANNING ; Scan the next expression. cp 0x2c ; Check for the correct separator - a ; ','; call nz,CHECK_END ; but move on to the next statement if ; not matched. rst NEXT_CHAR ; Whilst there are still jr DATA_1 ; expressions to be checked go around ; the loop. ; The DATA statement has to be passed-by in 'run-time'. DATA_2: ; 1e37 ld a,0xe4 ; It is a 'DATA' statement that is to ; be passed-by. ; THE 'PASS-BY' SUBROUTINE ; On entry the A register will hold either the token 'DATA' or the token 'DEF ; FN' depending on the type of statement that is being 'passed-by'. PASS_BY: ; 1e39 ld b,a ; Make the BC register pair hold a very ; high number. cpdr ; Look back along the statement for the ; token. ld de,0x0200 ; Now look along the line jp EACH_STMT ; for the statement after. (The 'D-1'th ; statement from the ; current position). ; THE 'RESTORE' COMMAND ROUTINE ; The operand for a RESTORE command is taken as a line number, zero being used ; if no operand is given. ; The REST-RUN entry point is used by the RUN command routine. RESTORE: ; 1e42 call FIND_INT2 ; Compress the operand into the BC ; register pair. REST_RUN: ; 1e45 ld h,b ; Transfer the result to the ld l,c ; HL register pair. call LINE_ADDR ; Now find the address of that line or ; the 'first line after'. dec hl ; Make DATADD point to the ld (DATADD),hl ; location before. ret ; Return once it is done. ; THE 'RANDOMIZE' COMMAND ROUTINE ; Once again the operand is compressed into the BC register pair and ; transferred to the required system variable. However if the operand is zero ; the value in FRAMES1 and FRAMES2 is used instead. RANDOMIZE: ; 1e4f call FIND_INT2 ; Fetch the operand. ld a,b ; Jump forward unless the or c ; value of the operand is jr nz,RAND_1 ; zero. ld bc,(FRAMES) ; Fetch the two low order bytes of ; FRAMES instead. RAND_1: ; 1e5a ld (SEED),bc ; Now enter the result into the ret ; system variable SEED before ; returning. ; THE 'CONTINUE' COMMAND ROUTINE ; The required line number and statement number within that line are made the ; object of a jump. CONTINUE: ; 1e5f ld hl,(OLDPPC) ; The line number. ld d,(iy+d_OSPCC) ; The statement number. jr GO_TO_2 ; Jump forward. ; THE 'GO TO' COMMAND ROUTINE ; The operand of a GO TO ought to be a line number in the range '1' to '9999' ; but the actual test is against an upper value of '61439'. GO_TO: ; 1e67 call FIND_INT2 ; Fetch the operand and transfer ld h,b ; it to the HL register pair. ld l,c ld d,0x00 ; Set the statement number to zero. ld a,h ; Give the error message cp 0xf0 ; - Integer out of range - jr nc,REPORT_Bd ; with lines over '614139' ; The entry point GO-TO-2 is used to determine the line number of the next line ; to be handled in several instances. GO_TO_2: ; 1e73 ld (NEWPPC),hl ; Enter the line number and ld (iy+d_NSPPC),d ; then the statement number. ret ; Return; - to STMT-RET. ; THE 'OUT' COMMAND ROUTINE ; The two parameters for the OUT instruction are fetched from the calculator ; stack and used as directed. OUT: ; 1e7a call TWO_PARAM ; The operands are fetched. out (c),a ; The actual OUT instruction. ret ; Return; - to STMT-RET. ; THE 'POKE' COMMAND ROUTINE ; In a similar manner the POKE operation is performed. POKE: ; 1e80 call TWO_PARAM ; The operands are fetched. ld (bc),a ; The actual POKE operation. ret ; Return; - to STMT-RET. ; THE 'TWO-PARAM' SUBROUTINE ; The topmost parameter on the calculator stack must be compressible into a ; single register. It is two's complemented if it is negative. The second ; parameter must be compressible into a register pair. TWO_PARAM: ; 1e85 call FP_TO_A ; The parameter is fetched. jr c,REPORT_Bd ; Give an error if it is too high a ; number. jr z,TWO_P_1 ; Jump forward with positive neg ; numbers but two's complement negative ; numbers. TWO_P_1: ; 1e8e push af ; Save the first parameter call FIND_INT2 ; whilst the second is fetched. pop af ; The first parameter is ret ; restored before returning. ; THE 'FIND INTEGERS' SUBROUTINE ; The 'last value' on the calculator stack is fetched and compressed into a ; single register or a register pair by entering at FIND-INT1 AND FIND-INT2 ; respectively. FIND_INT1: ; 1e94 call FP_TO_A ; Fetch the 'last value'. jr FIND_I_1 ; Jump forward. FIND_INT2: ; 1e99 call FP_TO_BC ; Fetch the 'last value'. FIND_I_1: ; 1e9c jr c,REPORT_Bd ; In both cases overflow is indicated ; by a set carry flag. ret z ; Return with all positive numbers that ; are in range. ; Report B - Integer out of range REPORT_Bd: ; 1e9f rst ERROR_1 ; Call the error handling db 0x0a ; routine. ; THE 'RUN' COMMAND ROUTINE ; The parameter of the RUN command is passed to NEWPPC by calling the GO TO ; command routine. The operations of 'RESTORE 0' and 'CLEAR 0' are then ; performed before a return is made. RUN: ; 1ea1 call GO_TO ; Set NEWPPC as required. ld bc,0x0000 ; Now perform a 'RESTORE 0'. call REST_RUN jr CLEAR_RUN ; Exit via the CLEAR command routine. ; THE 'CLEAR' COMMAND ROUTINE ; This routine allows for the variables area to be cleared, the display area ; cleared and RAMTOP moved. In consequence of the last operation the machine ; stack is rebuilt thereby having the effect of also clearing the GO SUB stack. CLEAR: ; 1eac call FIND_INT2 ; Fetch the operand - using zero by ; default. CLEAR_RUN: ; 1eaf ld a,b ; Jump forward if the operand is or c ; other than zero. When called jr nz,CLEAR_1 ; from RUN there is no jump. ld bc,(RAMTOP) ; If zero use the existing value in ; RAMTOP. CLEAR_1: ; 1eb7 push bc ; Save the value. ld de,(VARS) ; Next reclaim all the bytes ld hl,(E_LINE) ; of the present variables dec hl ; area. call RECLAIM_1 call CLS ; Clear the display area. ; The value in the BC register pair which will be used as RAMTOP is tested to ; ensure it is neither too low nor too high. ld hl,(STKEND) ; The current value of STKEND. ld de,0x0032 ; is increased by '50' before add hl,de ; being tested. This forms the pop de ; lower limit. sbc hl,de jr nc,REPORT_Mb ; RAMTOP will be too low. ld hl,(P_RAMT) ; For the upper test the value and a ; for RAMTOP is tested against sbc hl,de ; P-RAMT. jr nc,CLEAR_2 ; Jump forward if acceptable. ; Report M - RAMTOP no good REPORT_Mb: ; 1eda rst ERROR_1 ; Call the error handling db 0x15 ; routine. ; Continue with the CLEAR operation. CLEAR_2: ; 1edc ex de,hl ; Now the value can actually be ld (RAMTOP),hl ; passed to RAMTOP. pop de ; Fetch the address - STMT-RET. pop bc ; Fetch the 'error address'. ld (hl),0x3e ; Enter a GO SUB stack end marker. dec hl ; Leave one location. ld sp,hl ; Make the stack pointer point to an ; empty GO SUB stack. push bc ; Next pass the 'error address' ld (ERR_SP),sp ; to the stack and save its address in ; ERR-SP. ex de,hl ; An indirect return is now jp hl ; made to STMT-RET. ; Note: When the routine is called from RUN the values of NEWPPC & NSPPC will ; have been affected and no statements coming after RUN can ever be found ; before the jump is taken. ; THE 'GO SUB' COMMAND ROUTINE ; The present value of PPC and the incremented value of SUBPPC are stored on ; the GO SUB stack. GO_SUB: ; 1eed pop de ; Save the address - STMT-RET. ld h,(iy+d_SUBPPC) ; Fetch the statement number inc h ; and increment it. ex (sp),hl ; Exchange the 'error address' with the ; statement number. inc sp ; Reclaim the use of a location. ld bc,(PPC) ; Next save the present line push bc ; number. push hl ; Return the 'error address' ld (ERR_SP),sp ; to the machine stack and reset ERR-SP ; to point to it. push de ; Return the address - STMT-RET. call GO_TO ; Now set NEWPPC & NSPPC to the ; required values. ld bc,0x0014 ; But before making the jump make a ; test for room. ; THE 'TEST-ROOM' SUBROUTINE ; A series of tests is performed to ensure that there is sufficient free memory ; available for the task being undertaken. TEST_ROOM: ; 1f05 ld hl,(STKEND) ; Increase the value taken from add hl,bc ; STKEND by the value carried into the ; routine by the BC ; register pair. jr c,REPORT_4b ; Jump forward if the result is over ; +FFFF. ex de,hl ; Try it again allowing for a ld hl,0x0050 ; further eighty bytes. add hl,de jr c,REPORT_4b sbc hl,sp ; Finally test the value against the ; address of the machine stack. ret c ; Return if satisfactory. ; Report 4 - Out of memory REPORT_4b: ; 1f15 ld l,0x03 ; This is a 'run-time' error and the jp ERROR_3 ; error marker is not to be used. ; THE 'FREE MEMORY' SUBROUTINE ; There is no BASIC command 'FRE' in the SPECTRUM but there is a subroutine for ; performing such a task. ; An estimate of the amount of free space can be found at any time by using: ; 'PRINT 65536-USR 7962' FREE_MEM: ; 1f1a ld bc,0x0000 ; Do not allow any overhead. call TEST_ROOM ; Make the test and pass the ld b,h ; result to the BC register ld c,l ; before returning. ret ; THE 'RETURN' COMMAND ROUTINE ; The line number and the statement number that are to be made the object of a ; 'return' are fetched from the GO SUB stack. RETURN: ; 1f23 pop bc ; Fetch the address - STMT-RET. pop hl ; Fetch the 'error address'. pop de ; Fetch the last entry on the GO SUB ; stack. ld a,d ; The entry is tested to see if cp 0x3e ; it is the GO SUB stack end jr z,REPORT_7b ; marker; jump if it is. dec sp ; The full entry uses three locations ; only. ex (sp),hl ; Exchange the statement number with ; the 'error address'. ex de,hl ; Move the statement number. ld (ERR_SP),sp ; Reset the error pointer. push bc ; Replace the address - STMT-RET. jp GO_TO_2 ; Jump back to change NEWPPC & NSPPC. ; Report 7 - RETURN without GOSUB REPORT_7b: ; 1f36 push de ; Replace the end marker and push hl ; the 'error address'. rst ERROR_1 ; Call the error handling db 0x06 ; routine. ; THE 'PAUSE' COMMAND ROUTINE ; The period of the PAUSE is determined by counting the number of maskable ; interrupts as they occur every 1/50 th. of a second. ; A PAUSE is finished either after the appropriate number of interrupts or by ; the system Variable FLAGS indicating that a key has been pressed. PAUSE: ; 1f3a call FIND_INT2 ; Fetch the operand. PAUSE_1: ; 1f3d halt ; Wait for a maskable interrupt. dec bc ; Decrease the counter. ld a,b ; If the counter is thereby or c ; reduced to zero the PAUSE jr z,PAUSE_END ; has come to an end. ld a,b ; If the operand was zero BC and c ; will now hold +FFFF and this inc a ; value will be returned to jr nz,PAUSE_2 ; zero. Jump will all other inc bc ; operand values. PAUSE_2: ; 1f49 bit 5,(iy+d_FLAGS) ; Jump back unless a key has jr z,PAUSE_1 ; been pressed. ; The period of the PAUSE has now finished. PAUSE_END: ; 1f4f res 5,(iy+d_FLAGS) ; Signal 'no key pressed'. ret ; Now return; - to STMT-RET. ; THE 'BREAK-KEY' SUBROUTINE ; This subroutine is called in several instances to read the BREAK key. The ; carry flag is returned reset only if the SHIFT and the BREAK keys are both ; being pressed. BREAK_KEY: ; 1f54 ld a,0x7f ; Form the port address in a,(0xfe) ; +7FFE and read in a byte. rra ; Examine only bit 0 by shifting it ; into the carry position. ret c ; Return if the BREAK key is not being ; pressed. ld a,0xfe ; Form the port address in a,(0xfe) ; +FEFE and read in a byte. rra ; Again examine bit 0. ret ; Return with carry reset if both keys ; are being pressed. ; THE 'DEF FN' COMMAND ROUTINE ; During syntax checking a DEF FN statement is checked to ensure that it has ; the correct form. Space is also made available for the result of evaluating ; the function. ; But in 'run-time' a DEF FN statement is passed-by. DEF_FN: ; 1f60 call SYNTAX_Z ; Jump forward if checking jr z,DEF_FN_1 ; syntax. ld a,0xce ; Otherwise bass-by the jp PASS_BY ; 'DEF FN' statement. ; First consider the variable of the function. DEF_FN_1: ; 1f6a set 6,(iy+d_FLAGS) ; Signal 'a numeric variable'. call ALPHA ; Check that the present code is a ; letter. jr nc,DEF_FN_4 ; Jump forward if not. rst NEXT_CHAR ; Fetch the next character. cp 0x24 ; Jump forward unless it is jr nz,DEF_FN_2 ; a '$'. res 6,(iy+d_FLAGS) ; Change bit 6 as it is a string ; variable. rst NEXT_CHAR ; Fetch the next character. DEF_FN_2: ; 1f7d cp 0x28 ; A '(' must follow the jr nz,DEF_FN_7 ; variable's name. rst NEXT_CHAR ; Fetch the next character. cp 0x29 ; Jump forward if it is a jr z,DEF_FN_6 ; ')' as there are no parameters of the ; function. ; A loop is now entered to deal with each parameter in turn. DEF_FN_3: ; 1f86 call ALPHA ; The present code must be DEF_FN_4: ; 1f89 jp nc,REPORT_Cb ; a letter. ex de,hl ; Save the pointer in DE. rst NEXT_CHAR ; Fetch the next character. cp 0x24 ; Jump forward unless it is jr nz,DEF_FN_5 ; a '$'. ex de,hl ; Otherwise save the new pointer in DE ; instead. rst NEXT_CHAR ; Fetch the next character. DEF_FN_5: ; 1f94 ex de,hl ; Move the pointer to the last ; character of the name to the HL ; register pair. ld bc,0x0006 ; Now make six locations after call MAKE_ROOM ; that last character and inc hl ; enter a 'number marker' into inc hl ; the first of the new ld (hl),0x0e ; locations. cp 0x2c ; If the present character is jr nz,DEF_FN_6 ; a ',' then jump back as rst NEXT_CHAR ; there should be a further jr DEF_FN_3 ; parameter; otherwise jump out of the ; loop. ; Next the definition of the function is considered. DEF_FN_6: ; 1fa6 cp 0x29 ; Check that the ')' does jr nz,DEF_FN_7 ; exist. rst NEXT_CHAR ; The next character is fetched. cp 0x3d ; It must be an '='. jr nz,DEF_FN_7 rst NEXT_CHAR ; Fetch the next character. ld a,(FLAGS) ; Save the nature - numeric or push af ; string - of the variable. call SCANNING ; Now consider the definition as an ; expression. pop af ; Fetch the nature of the xor (iy+d_FLAGS) ; variable and check that it and 0x40 ; is of the same type as found for the ; definition. DEF_FN_7: ; 1fbd jp nz,REPORT_Cb ; Give an error report if it is ; required. call CHECK_END ; Exit via the CHECK-END subroutine. ; (Thereby moving ; on to consider the next statement in ; the line.) ; THE 'UNSTACK-Z' SUBROUTINE ; This subroutine is called in several instances in order to 'return early' ; from a subroutine when checking syntax. The reason for this is to avoid ; actually printing characters or passing values to/from the calculator stack. UNSTACK_Z: ; 1fc3 call SYNTAX_Z ; Is syntax being checked? pop hl ; Fetch the return address but ret z ; ignore it in 'syntax-time'. jp hl ; In 'run-time' make a simple return to ; the calling routine. ; THE 'LPRINT & PRINT' COMMAND ROUTINES ; The appropriate channel is opened as necessary and the items to be printed ; are considered in turn. LPRINT: ; 1fc9 ld a,0x03 ; Prepare to open channel 'P'. jr PRINT_1 ; Jump forward. PRINT: ; 1fcd ld a,0x02 ; Prepare to open channel 'S'. PRINT_1: ; 1fcf call SYNTAX_Z ; Unless syntax is being call nz,CHAN_OPEN ; checked open a channel. call TEMPS ; Set the temporary colour system ; variables. call PRINT_2 ; Call the print controlling ; subroutine. call CHECK_END ; Move on to consider the next ret ; statement; via CHECK-END if checking ; syntax. ; The print controlling subroutine is called by the PRINT, LPRINT and INPUT ; command routines. PRINT_2: ; 1fdf rst GET_CHAR ; Get the first character. call PR_END_Z ; Jump forward if already at the jr z,PRINT_4 ; end of the item list. ; Now enter a loop to deal with the 'position controllers' and the print items. PRINT_3: ; 1fe5 call PR_POSN_1 ; Deal with any consecutive jr z,PRINT_3 ; position controllers. call PR_ITEM_1 ; Deal with a single print item. call PR_POSN_1 ; Check for further position jr z,PRINT_3 ; controllers and print items until ; there are none left. PRINT_4: ; 1ff2 cp 0x29 ; Return now if the present ret z ; character is a ')'; otherwise ; consider performing a 'carriage ; return'. ; THE 'PRINT A CARRIAGE RETURN' SUBROUTINE PRINT_CR: ; 1ff5 call UNSTACK_Z ; Return if changing syntax. ld a,0x0d ; Print a carriage return rst PRINT_A_1 ; character and then return. ret ; THE 'PRINT ITEMS' SUBROUTINE ; This subroutine is called from the PRINT, LPRINT and INPUT command routines. ; The various types of print item are identified and printed. PR_ITEM_1: ; 1ffc rst GET_CHAR ; The first character is fetched. cp 0xac ; Jump forward unless it is jr nz,PR_ITEM_2 ; an 'AT'. ; Now deal with an 'AT'. call NEXT_2NUM ; The two parameters are transferred to ; the calculator stack. call UNSTACK_Z ; Return now if checking syntax. call STK_TO_BC ; The parameters are compressed into ; the BC register pair. ld a,0x16 ; The A register is loaded with jr PR_AT_TAB ; the AT control character before the ; jump is taken. ; Next look for a 'TAB'. PR_ITEM_2: ; 200e cp 0xad ; Jump forward unless it is jr nz,PR_ITEM_3 ; a 'TAB'. ; Now deal with a 'TAB'. rst NEXT_CHAR ; Get the next character. call EXPT_1NUM ; Transfer one parameter to the ; calculator stack. call UNSTACK_Z ; Return now if checking syntax. call FIND_INT2 ; The value is compressed into the BC ; register pair. ld a,0x17 ; The A register is loaded with the TAB ; control character. ; The 'AT' and the 'TAB' print items are printed by making three calls to ; PRINT-OUT. PR_AT_TAB: ; 201e rst PRINT_A_1 ; Print the control character. ld a,c ; Follow it with the first rst PRINT_A_1 ; value. ld a,b ; Finally print the second rst PRINT_A_1 ; value; then return. ret ; Next consider embedded colour items. PR_ITEM_3: ; 2024 call CO_TEMP_3 ; Return with carry reset if a colour ; items was found. ret nc ; Continue if none were found. call STR_ALTER ; Next consider if the stream is to be ; changed. ret nc ; Continue unless it was altered. ; The print item must now be an expression, either numeric or string. call SCANNING ; Evaluate the expression but call UNSTACK_Z ; return now if checking syntax. bit 6,(iy+d_FLAGS) ; Test for the nature of the ; expression. call z,STK_FETCH ; If it is string then fetch the ; necessary parameters; but if it is jp nz,PRINT_FP ; numeric then exit via PRINT-FP. ; A loop is now set up to deal with each character in turn of the string. PR_STRING: ; 203c ld a,b ; Return now if there are or c ; no characters remaining dec bc ; in the string; otherwise ret z ; decease the counter. ld a,(de) ; Fetch the code and increment inc de ; the pointer. rst PRINT_A_1 ; The code is printed and a jump jr PR_STRING ; taken to consider any further ; characters. ; THE 'END OF PRINTING' SUBROUTINE ; The zero flag will be set if no further printing is to be done. PR_END_Z: ; 2045 cp 0x29 ; Return now if the character ret z ; is a ')'. PR_ST_END: ; 2048 cp 0x0d ; Return now if the character is ret z ; a 'carriage return'. cp 0x3a ; Make a final test against ':' ret ; before returning. ; THE 'PRINT POSITION' SUBROUTINE ; The various position controlling characters are considered by this ; subroutine. PR_POSN_1: ; 204e rst GET_CHAR ; Get the present character. cp 0x3b ; Jump forward if it is jr z,PR_POSN_3 ; a ';'. cp 0x2c ; Also jump forward with a jr nz,PR_POSN_2 ; character other than a ','; call SYNTAX_Z ; but do not actually print the jr z,PR_POSN_3 ; character if checking syntax. ld a,0x06 ; Load the A register with rst PRINT_A_1 ; the 'comma' control code and jr PR_POSN_3 ; print it; then jump forward. PR_POSN_2: ; 2061 cp 0x27 ; Is it a '''? ret nz ; Return now if not any of the position ; controllers. call PRINT_CR ; Print 'carriage return' unless ; checking syntax. PR_POSN_3: ; 2067 rst NEXT_CHAR ; Fetch the next character. call PR_END_Z ; If not at the end of a print jr nz,PR_POSN_4 ; statement then jump forward; pop bc ; otherwise return to the PR_POSN_4: ; 206e cp a ; calling routine. ret ; The zero flag will be reset if the ; end of the print statement has ; not been reached. ; THE 'ALTER STREAM' SUBROUTINE ; This subroutine is called whenever there is the need to consider whether the ; user wishes to use a different stream. STR_ALTER: ; 2070 cp 0x23 ; Unless the present character scf ; is a '#' return with the ret nz ; carry flag set. rst NEXT_CHAR ; Advance CH-ADD. call EXPT_1NUM ; Pass the parameter to the calculator ; stack. and a ; Clear the carry flag. call UNSTACK_Z ; Return now if checking syntax. call FIND_INT1 ; The value is passed to the A ; register. cp 0x10 ; Give report O if the value is jp nc,REPORT_Ob ; over +FF. call CHAN_OPEN ; Use the channel for the stream in ; question. and a ; Clear the carry flag and ret ; return. ; THE 'INPUT' COMMAND ROUTINE ; This routine allows for values entered from the keyboard to be assigned to ; variables. It is also possible to have print items embedded in the INPUT ; statement and these items are printed in the lower part of the display. INPUT: ; 2089 call SYNTAX_Z ; Jump forward if syntax is jr z,INPUT_1 ; being checked. ld a,0x01 ; Open channel 'K'. call CHAN_OPEN call CLS_LOWER ; The lower part of the display is ; cleared. INPUT_1: ; 2096 ld (iy+d_TV_FLAG),0x01 ; Signal that the lower screen is being ; handled. Reset all other bits. call IN_ITEM_1 ; Call the subroutine to deal with the ; INPUT items. call CHECK_END ; Move on to the next statement if ; checking syntax. ld bc,(S_POSN) ; Fetch the current print position. ld a,(DF_SZ) ; Jump forward if the current cp b ; position is above the lower jr c,INPUT_2 ; screen. ld c,0x21 ; Otherwise set the print position ld b,a ; to the top of the lower screen. INPUT_2: ; 20ad ld (S_POSN),bc ; Reset S-POSN. ld a,0x19 ; Now set the scroll counter. sub b ld (SCR_CT),a res 0,(iy+d_TV_FLAG) ; Signal 'main screen'. call CL_SET ; Set the system variables jp CLS_LOWER ; and exit via CLS-LOWER. ; The INPUT items and embedded PRINT items are dealt with in turn by the ; following loop. IN_ITEM_1: ; 20c1 call PR_POSN_1 ; Consider first any position jr z,IN_ITEM_1 ; control characters. cp 0x28 ; Jump forward if the present jr nz,IN_ITEM_2 ; character is not a '('. rst NEXT_CHAR ; Fetch the next character. call PRINT_2 ; Now call the PRINT command routine to ; handle the items ; inside the brackets. rst GET_CHAR ; Fetch the present character. cp 0x29 ; Give report C unless the jp nz,REPORT_Cb ; character is a ')'. rst NEXT_CHAR ; Fetch the next character and jp IN_NEXT_2 ; jump forward to see if there are any ; further INPUT items. ; Now consider whether INPUT LINE is being used. IN_ITEM_2: ; 20d8 cp 0xca ; Jump forward if it is not jr nz,IN_ITEM_3 ; 'LINE'. rst NEXT_CHAR ; Advance CH-ADD. call CLASS_01 ; Determine the destination address for ; the variable. set 7,(iy+d_FLAGX) ; Signal 'using INPUT LINE'. bit 6,(iy+d_FLAGS) ; Give report C unless using jp nz,REPORT_Cb ; a string variable. jr IN_PROMPT ; Jump forward to issue the prompt ; message. ; Proceed to handle simple INPUT variables. IN_ITEM_3: ; 20ed call ALPHA ; Jump to consider going round jp nc,IN_NEXT_1 ; the loop again if the present ; character is not a letter. call CLASS_01 ; Determine the destination address for ; the variable. res 7,(iy+d_FLAGX) ; Signal 'not INPUT LINE'. ; The prompt message is now built up in the work space. IN_PROMPT: ; 20fa call SYNTAX_Z ; Jump forward if only checking jp z,IN_NEXT_2 ; syntax. call SET_WORK ; The work space is set to null. ld hl,FLAGX ; This is FLAGX. res 6,(hl) ; Signal 'string result'. set 5,(hl) ; Signal 'INPUT mode'. ld bc,0x0001 ; Allow the prompt message only a ; single location. bit 7,(hl) ; Jump forward if using 'LINE'. jr nz,IN_PR_2 ld a,(FLAGS) ; Jump forward if awaiting and 0x40 ; a numeric entry. jr nz,IN_PR_1 ld c,0x03 ; A string entry will need three ; locations. IN_PR_1: ; 211a or (hl) ; Bit 6 of FLAGX will become ld (hl),a ; set for a numeric entry. IN_PR_2: ; 211c rst BC_SPACES ; The required number of locations is ; made available. ld (hl),0x0d ; A 'carriage return' goes into the ; last location. ld a,c ; Test bit 6 of the C register rrca ; and jump forward if only rrca ; one location was required. jr nc,IN_PR_3 ld a,0x22 ; A 'double quotes' character ld (de),a ; goes into the first and dec hl ; second locations. ld (hl),a IN_PR_3: ; 2129 ld (K_CUR),hl ; The position of the cursor can now be ; saved. ; In the case of INPUT LINE the EDITOR can be called without further ; preparation but for other types of INPUT the error stack has to be changed so ; as to trap errors. bit 7,(iy+d_FLAGX) ; Jump forward with INPUT jr nz,IN_VAR_3 ; LINE' ld hl,(CH_ADD) ; Save the current values of push hl ; CH-ADD & ERR-SP on the ld hl,(ERR_SP) ; machine stack. push hl IN_VAR_1: ; 213a ld hl,IN_VAR_1 ; This will be the 'return push hl ; point' in case of errors. bit 4,(iy+d_FLAGS2) ; Only change the error jr z,IN_VAR_2 ; stack pointer if using channel ld (ERR_SP),sp ; 'K'. IN_VAR_2: ; 2148 ld hl,(WORKSP) ; Set HL to the start of the call REMOVE_FP ; INPUT line and remove any ; floating-point forms. (There will ; not be any except perhaps after an ; error.) ld (iy+d_ERR_NR),0xff ; Signal 'no error yet'. call EDITOR ; Now get the INPUT and with res 7,(iy+d_FLAGS) ; the syntax/run flag indicating call IN_ASSIGN ; syntax, check the INPUT for jr IN_VAR_4 ; errors; jump if in order; return to ; IN-VAR-1 if not. IN_VAR_3: ; 215e call EDITOR ; Get a 'LINE'. ; All the system variables have to be reset before the actual assignment of a ; value can be made. IN_VAR_4: ; 2161 ld (iy+d_K_CUR+1),0x00 ; The cursor address is reset. call IN_CHAN_K ; The jump is taken if using jr nz,IN_VAR_5 ; other than channel 'K'. call ED_COPY ; The input-line is copied to ld bc,(ECHO_E) ; the display and the position call CL_SET ; in ECHO-E made the current position ; in the lower screen. IN_VAR_5: ; 2174 ld hl,FLAGX ; This is FLAGX. res 5,(hl) ; Signal 'edit mode'. bit 7,(hl) ; Jump forward if handling an res 7,(hl) ; INPUT LINE. jr nz,IN_VAR_6 pop hl ; Drop the address IN-VAR-1. pop hl ; Reset the ERR-SP to its ld (ERR_SP),hl ; original address. pop hl ; Save the original CH-ADD ld (X_PTR),hl ; address in X-PTR. set 7,(iy+d_FLAGS) ; Now with the syntax/run flag call IN_ASSIGN ; indicating 'run' make the assignment. ld hl,(X_PTR) ; Restore the original address ld (iy+d_X_PTR+1),0x00 ; to CH-ADD and clear X-PTR. ld (CH_ADD),hl jr IN_NEXT_2 ; Jump forward to see if there are ; further INPUT items. IN_VAR_6: ; 219b ld hl,(STKBOT) ; The length of the 'LINE' in ld de,(WORKSP) ; the work space is found. scf sbc hl,de ld b,h ; DE points to the start and ld c,l ; BC holds the length. call STK_STO__ ; These parameters are stacked call LET ; and the actual assignment made. jr IN_NEXT_2 ; Also jump forward to consider further ; items. ; Further items in the INPUT statement are considered. IN_NEXT_1: ; 21af call PR_ITEM_1 ; Handle any print items. IN_NEXT_2: ; 21b2 call PR_POSN_1 ; Handle any position controllers. jp z,IN_ITEM_1 ; Go around the loop again if there are ; further items; ret ; otherwise return. ; THE 'IN-ASSIGN' SUBROUTINE ; This subroutine is called twice for each INPUT value. Once with the ; syntax/run flag reset (syntax) and once with it set (run). IN_ASSIGN: ; 21b9 ld hl,(WORKSP) ; Set CH-ADD to point to the ld (CH_ADD),hl ; first location of the work rst GET_CHAR ; space and fetch the character. cp 0xe2 ; Is it a 'STOP'? jr z,IN_STOP ; Jump if it is. ld a,(FLAGX) ; Otherwise make the assignment call VAL_FET_2 ; of the 'value' to the variable. rst GET_CHAR ; Get the present character cp 0x0d ; and check it is a 'carriage ret z ; return'. Return if it is. ; Report C - Nonsense in BASIC REPORT_Cc: ; 21ce rst ERROR_1 ; Call the error handling db 0x0b ; routine. ; Come here if the INPUT line starts with 'STOP'. IN_STOP: ; 21d0 call SYNTAX_Z ; But do not give the error ret z ; report on the syntax-pass. ; Report H - STOP in INPUT REPORT_Hb: ; 21d4 rst ERROR_1 ; Call the error handling db 0x10 ; routine. ; THE 'IN-CHAN-K' SUBROUTINE ; This subroutine returns with the zero flag reset only if channel 'K' is being ; used. IN_CHAN_K: ; 21d6 ld hl,(CURCHL) ; The base address of the inc hl ; channel information for the inc hl ; current channel is fetched inc hl ; and the channel code compared inc hl ; to the character 'K'. ld a,(hl) cp 0x4b ret ; Return afterwards. ; THE 'COLOUR ITEM' ROUTINES ; This set of routines can be readily divided into two parts: ; i. The embedded colour item' handler. ; ii. The 'colour system variable' handler. ; i. Embedded colour items are handled by calling the PRINT-OUT subroutine as ; required. ; A loop is entered to handle each item in turn. The entry point is at ; CO-TEMP-2. CO_TEMP_1: ; 21e1 rst NEXT_CHAR ; Consider the next character in the ; BASIC statement. CO_TEMP_2: ; 21e2 call CO_TEMP_3 ; Jump forward to see if the present ; code represents an ; embedded 'temporary' colour ret c ; item. Return carry set if not a ; colour item. rst GET_CHAR ; Fetch the present character. cp 0x2c ; Jump back if it is either a jr z,CO_TEMP_1 ; ',' or a ';'; otherwise cp 0x3b ; there has been an error. jr z,CO_TEMP_1 jp REPORT_Cb ; Exit via 'report C'. CO_TEMP_3: ; 21f2 cp 0xd9 ; Return with the carry flag ret c ; Set if the code is not in the cp 0xdf ; range +D9 to +DE (INK to OVER). ccf ret c push af ; The colour item code is rst NEXT_CHAR ; preserved whilst CH-ADD is pop af ; advanced to address the parameter ; that follows it. ; The colour item code and the parameter are now 'printed' by calling PRINT-OUT ; on two occasions. CO_TEMP_4: ; 21fc sub 0xc9 ; The token range (+D9 to +DE) is ; reduced to the control ; character range (+10 to +15). push af ; The control character code is call EXPT_1NUM ; preserved whilst the parameter pop af ; is moved to the calculator stack. and a ; A return is made at this point call UNSTACK_Z ; if syntax is being checked. push af ; The control character code is call FIND_INT1 ; preserved whilst the parameter ld d,a ; is moved to the D register. pop af rst PRINT_A_1 ; The control character is sent out. ld a,d ; Then the parameter is fetched rst PRINT_A_1 ; and sent out before ret ; returning. ; ii. The colour system variables - ATTR-T, MASK-T & P-FLAG - are altered as ; required. This subroutine is called by PRINT-OUT. On entry the control ; character code is in the A register and the parameter is in the D register. ; Note that all changes are to the 'temporary' system variables. CO_TEMP_5: ; 2211 sub 0x11 ; Reduce the range and jump adc a,0x00 ; forward with INK & PAPER. jr z,CO_TEMP_7 sub 0x02 ; Reduce the range once again adc a,0x00 ; and jump forward with FLASH jr z,CO_TEMP_C ; & BRIGHT. ; The colour control code will now be +01 for INVERSE and +02 for OVER and the ; system variable P-FLAG is altered accordingly. cp 0x01 ; Prepare to jump with OVER. ld a,d ; Fetch the parameter. ld b,0x01 ; Prepare the mask for OVER. jr nz,CO_TEMP_6 ; Now jump. rlca ; Bit 2 of the A register is to be rlca ; reset for INVERSE 0 and set for ld b,0x04 ; INVERSE 1; the mask is to have bit 2 ; set. CO_TEMP_6: ; 2228 ld c,a ; Save the A register whilst the range ; is tested. ld a,d ; The correct range for cp 0x02 ; INVERSE and OVER is only jr nc,REPORT_Kb ; '0-1'. ld a,c ; Fetch the A register. ld hl,P_FLAG ; It is P-FLAG that is to be changed. jr CO_CHANGE ; Exit via CO-CHANGE and alter P-FLAG ; using 'B' as a mask. ; i.e. Bit 0 for OVER & bit 2 for ; INVERSE' ; PAPER & INK are dealt with by the following routine. On entry the carry flag ; is set for INK. CO_TEMP_7: ; 2234 ld a,d ; Fetch the parameter. ld b,0x07 ; Prepare the mask for INK. jr c,CO_TEMP_8 ; Jump forward with INK. rlca ; Multiply the parameter for rlca ; PAPER by eight. rlca ld b,0x38 ; Prepare the mask for PAPER. CO_TEMP_8: ; 223e ld c,a ; Save the parameter in the C register ; whilst the range of the ; parameter is tested. ld a,d ; Fetch the original value. cp 0x0a ; Only allow PAPER/INK a jr c,CO_TEMP_9 ; range of '0' to '9'. ; Report K - Invalid colour REPORT_Kb: ; 2244 rst ERROR_1 ; Call the error handling db 0x13 ; routine. ; Continue to handle PAPER & INK; CO_TEMP_9: ; 2246 ld hl,ATTR_T ; Prepare to alter ATTR-T, MASK-T & ; P-FLAG. cp 0x08 ; Jump forward with PAPER/INK jr c,CO_TEMP_B ; '0' to '7'. ld a,(hl) ; Fetch the current value of jr z,CO_TEMP_A ; ATTR-T and use it unchanged, by ; jumping forward, with ; PAPER/INK '8'. or b ; But for PAPER/INK '9' the cpl ; PAPER and INK colours and 0x24 ; have to be black and white. jr z,CO_TEMP_A ; Jump for black INK/PAPER; ld a,b ; but continue for white INK/ PAPER. CO_TEMP_A: ; 2257 ld c,a ; Move the value to the C register. ; The mask (B) and the value (C) are now used to change ATTR-T. CO_TEMP_B: ; 2258 ld a,c ; Move the value. call CO_CHANGE ; Now change ATTR-T as needed. ; Next MASK-T is considered. ld a,0x07 ; The bits of MASK-T are set cp d ; only when using PAPER/INK sbc a,a ; '8' or '9'. call CO_CHANGE ; Now change MASK-T as needed. ; Next P-FLAG is considered. rlca ; The appropriate mask is rlca ; built up in the B register and 0x50 ; is order to change bits 4 & ld b,a ; 6 as necessary. ld a,0x08 ; The bits of P-FLAG are set cp d ; only when using PAPER/INK sbc a,a ; '9'. Continue into CO-CHANGE to ; manipulate P-FLAG. ; THE 'CO-CHANGE' SUBROUTINE ; This subroutine is used to 'impress' upon a system variable the 'nature' of ; the bits in the A register, The B register holds a mask that shows which bits ; are to be 'copied over' from A to (HL). CO_CHANGE: ; 226c xor (hl) ; The bits, specified by the and b ; mask in the B register, are xor (hl) ; changed in the value and the ld (hl),a ; result goes to form the system ; variable. inc hl ; Move on to address the next system ; variable. ld a,b ; Return with the mask in the ret ; A register. ; FLASH & BRIGHT are handled by the following routine. CO_TEMP_C: ; 2273 sbc a,a ; The zero flag will be set for BRIGHT. ld a,d ; The parameter is fetched and rrca ; rotated. ld b,0x80 ; Prepare the mask for FLASH. jr nz,CO_TEMP_D ; Jump forward with FLASH. rrca ; Rotate an extra time and ld b,0x40 ; prepare the mask for BRIGHT. CO_TEMP_D: ; 227d ld c,a ; Save the value in the C register. ld a,d ; Fetch the parameter and test cp 0x08 ; its range; only '0', '1' jr z,CO_TEMP_E ; & '8' are allowable. cp 0x02 jr nc,REPORT_Kb ; The system variable ATTR-T can now be altered. CO_TEMP_E: ; 2287 ld a,c ; Fetch the value. ld hl,ATTR_T ; This is ATTR-T. call CO_CHANGE ; Now change the system variable. ; The value in MASK-T is now considered. ld a,c ; The value is fetched anew. rrca ; The set bit of FLASH/BRIGHT rrca ; '8' (bit 3) is moved to rrca ; bit 7 (for FLASH) or bit 6 (for ; BRIGHT). jr CO_CHANGE ; Exit via CO-CHANGE. ; THE 'BORDER' COMMAND ROUTINE ; The parameter of the BORDER command is used with an OUT command to actually ; alter the colour of the border. The parameter is then saved in the system ; variable BORDCR. BORDER: ; 2294 call FIND_INT1 ; The parameter is fetched cp 0x08 ; and its range is tested. jr nc,REPORT_Kb out (0xfe),a ; The OUT instruction is then used to ; set the border colour. rlca ; The parameter is then rlca ; multiplied by eight. rlca bit 5,a ; If the border colour is a 'light' jr nz,BORDER_1 ; colour then the INK colour in the ; editing area is to be black - ; make the jump. xor 0x07 ; Change the INK colour. BORDER_1: ; 22a6 ld (BORDCR),a ; Set the system variable as ret ; required and return. ; THE 'PIXEL ADDRESS' SUBROUTINE ; This subroutine is called by the POINT subroutine and by the PLOT command ; routine. Is is entered with the co-ordinates of a pixel in the BC register ; pair and returns with HL holding the address of the display file byte which ; contains that pixel and A pointing to the position of the pixel within the ; byte. PIXEL_ADD: ; 22aa ld a,0xaf ; Test that the y co-ordinate (in sub b ; B) is not greater than 175. jp c,REPORT_Be ld b,a ; B now contains 175 minus y. and a ; A holds b7b6b5b4b3b2b1b0, rra ; the bite of B. And now ; 0b7b6b5b4b3b2b1. scf rra ; Now 10b7b6b5b4b3b2. and a rra ; Now 010b7b6b5b4b3. xor b and 0xf8 ; Finally 010b7b6b2b1b0, so that xor b ; H becomes 64 + 8*INT (B/64) + ld h,a ; B (mod 8), the high byte of the ld a,c ; pixel address. C contains X. rlca ; A starts as c7c6c5c4c3c2c1c0. rlca rlca ; And is now c2c1c0c7c6c5c4c3. xor b and 0xc7 xor b ; Now c2c1b5b4b3c5c4c3. rlca rlca ; Finally b5b4b3c7c6c5c4c3, so ld l,a ; that L becomes 32*INT (B(mod ld a,c ; 64)/8) + INT(x/8), the low byte. and 0x07 ; A holds x(mod 8): so the pixel ret ; is bit (A - 7) within the byte. ; THE 'POINT' SUBROUTINE ; This subroutine is called by the POINT function in SCANNING. It is entered ; with the co-ordinates of a pixel on the calculator stack, and returns a last ; value of 1 if that pixel is ink colour, and 0 if it is paper colour. POINT_SUB: ; 22cb call STK_TO_BC ; Y co-ordinate to B, x to C. call PIXEL_ADD ; Pixel address to HL. ld b,a ; B will count A+1 loops to get inc b ; the wanted bit of (HL) to ld a,(hl) ; location 0. POINT_LP: ; 22d4 rlca ; The shifts. djnz POINT_LP and 0x01 ; The bit is 1 for ink, 0 for paper. jp STACK_A ; It is put on the calculator stack. ; THE 'PLOT' COMMAND ROUTINE ; This routine consists of a main subroutine plus one line to call it and one ; line to exit from it. The main routine is used twice by CIRCLE and the ; subroutine is called by DRAW. The routine is entered with the co-ordinates of ; a pixel on the calculator stack. It finds the address of that pixel and plots ; it, taking account of the status of INVERSE and OVER held in the P-FLAG. PLOT: ; 22dc call STK_TO_BC ; Y co-ordinate to B, x to C. call PLOT_SUB ; The subroutine is called. jp TEMPS ; Exit, setting temporary colours. PLOT_SUB: ; 22e5 ld (COORDS),bc ; The system variable is set. call PIXEL_ADD ; Pixel address to HL. ld b,a ; B will count A+1 loops to get a inc b ; zero to the correct place in A. ld a,0xfe ; The zero is entered. PLOT_LOOP: ; 22f0 rrca ; Then lined up with the pixel djnz PLOT_LOOP ; bit position in the byte. ld b,a ; Then copied to B. ld a,(hl) ; The pixel-byte is obtained in A. ld c,(iy+d_P_FLAG) ; P-FLAG is obtained and first bit 0,c ; tested for OVER. jr nz,PL_TST_IN ; Jump if OVER 1. and b ; OVER 0 first makes the pixel zero. PL_TST_IN: ; 22fd bit 2,c ; Test for INVERSE. jr nz,PLOT_END ; INVERSE 1 just leaves the pixel as it ; was (OVER 1) or zero ; (OVER 0). xor b ; INVERSE 0 leaves the pixel cpl ; complemented (OVER 1) or 1 (OVER 0). PLOT_END: ; 2303 ld (hl),a ; The byte is entered. Its other bits ; are unchanged in every case. jp PO_ATTR ; Exit, setting attribute byte. ; THE 'STK-TO-BC' SUBROUTINE ; This subroutine loads two floating point numbers into the BC register pair. ; It is thus used to pick up parameters in the range +00-+FF. It also obtains ; in DE the 'diagonal move' values (+/-1,+/-1) which are used in the line ; drawing subroutine of DRAW. STK_TO_BC: ; 2307 call STK_TO_A ; First number to A. ld b,a ; Hence to B. push bc ; Save it briefly. call STK_TO_A ; Second number to A. ld e,c ; Its sign indicator to E. pop bc ; Restore first number. ld d,c ; Its signs indicator to D. ld c,a ; Second number to C. ret ; BC, DE are now as required. ; THE 'STK-TO-A' SUBROUTINE ; This subroutine loads the A register with the floating point number held at ; the top of the calculator stack. The number must be in the range 00-FF. STK_TO_A: ; 2314 call FP_TO_A ; Modulus of rounded last value to jp c,REPORT_Be ; A if possible; else, report error. ld c,0x01 ; One to C for positive last value. ret z ; Return if value was positive. ld c,0xff ; Else change C to +FF (i.e. minus ret ; one). Finished. ; THE 'CIRCLE' COMMAND ROUTINE ; This routine draws an approximation to the circle with centre co-ordinates X ; and Y and radius Z. These numbers are rounded to the nearest integer before ; use. Thus Z must be less than 87.5, even when (X,Y) is in the centre of the ; screen. The method used is to draw a series of arcs approximated by straight ; lines. It is illustrated in the BASIC program in the appendix. The notation ; of that program is followed here. ; CIRCLE has four parts: ; i. Tests the radius. If its modulus is less than 1, just plot X,Y; ; ii. Calls CD-PRMS-1 at 2470-24B6, which is used to set the initial parameters ; for both CIRCLE and DRAW; ; iii. Sets up the remaining parameters for CIRCLE, including the initial ; displacement for the first 'arc' (a straight line in fact); ; iv. Jumps into DRAW to use the arc-drawing loop at 2420-24FA. ; Parts i. to iii. will now be explained in turn. ; i. 2320-23AA. The radius, say Z', is obtained from the calculator stack. Its ; modulus Z is formed and used from now on. If Z is less than 1, it is deleted ; from the stack and the point X,Y is plotted by a jump to PLOT. CIRCLE: ; 2320 rst GET_CHAR ; Get the present character. cp 0x2c ; Test for comma. jp nz,REPORT_Cb ; If not so, report the error. rst NEXT_CHAR ; Get next character (the radius). call EXPT_1NUM ; Radius to calculator stack. call CHECK_END ; Move to consider next statement if ; checking syntax. rst FP_CALC ; Use calculator: the stack holds: db C_abs ; X, Y, Z db C_re_stack ; Z is re-stacked; its exponent db C_end_calc ; is therefore available. ld a,(hl) ; Get exponent of radius. cp 0x81 ; Test whether radius less than 1. jr nc,C_R_GRE_1 ; If not, jump. rst FP_CALC ; If less, delete it from the stack. db C_delete ; The stack holds X, Y. db C_end_calc jr PLOT ; Just plot the point X, Y. ; ii. 233B-2346 and the call to CD-PRMS1. 2*PI is stored in mem-5 and CD-PRMS1 ; is called. This subroutine stores in the B register the number of arcs ; required for the circle, viz. A=4*INT (PI*SQR Z/4)+4, hence 4, 8, 12 ..., up ; to a maximum of 32. It also stores in mem-0 to mem-4 the quantities 2*PI/A, ; SIN(PI/A), 0, COS (2*PI/A) and SIN (2*PI/A). C_R_GRE_1: ; 233b rst FP_CALC db C_stk_pi_2 ; X, Y, Z, PI/2. db C_end_calc ; Now increase exponent to 83 ld (hl),0x83 ; hex, changing PI/2 into 2*PI. rst FP_CALC ; X, Y, Z, 2*PI. db C_st_mem_5 ; (2*PI is copied to mem-5). db C_delete ; X, Y, Z db C_end_calc call CD_PRMS1 ; Set the initial parameters. ; iii. 2347-2381: the remaining parameters and the jump to DRAW. A test is made ; to see whether the initial 'arc' length is less than 1. If it is, a jump is ; made simply to plot X, Y. Otherwise, the parameters are set: X+Z and X-Z*SIN ; (PI/A) are stacked twice as start and end point, and copied to COORDS as ; well; zero and 2*Z*SIN (PI/A) are stored in mem-1 and mem-2 as initial ; increments, giving as first 'arc' the vertical straight line joining X+Z, ; y-Z*SIN (PI/A) and X+Z, Y+Z*SIN (PI/A). The arc-drawing loop of DRAW will ; ensure that all subsequent points remain on the same circle as these two ; points, with incremental angle 2*PI/A. But it is clear that these 2 points in ; fact subtend this angle at the point X+Z*(1-COS (PI/A)), Y not at X, Y. Hence ; the end points of each arc of the circle are displaced right by an amount ; 2*(1-COS (PI/A)), which is less than half a pixel, and rounds to one pixel at ; most. push bc ; Save the arc-count in B. rst FP_CALC ; X, Y, Z db C_duplicate ; X, Y, Z, Z db C_get_mem_1 ; X, Y, Z, Z, SIN (PI/A) db C_multiply ; X, Y, Z, Z*SIN (PI/A) db C_end_calc ; Z*SIN (PI/A) is half the initial ld a,(hl) ; 'arc' length; it is tested to see cp 0x80 ; whether it is less than 0.5. jr nc,C_ARC_GE1 ; If not, the jump is made. rst FP_CALC ; Otherwise, Z is deleted from the db C_delete ; stack, with the half-arc too; the db C_delete ; machine stack is cleared; and a db C_end_calc ; jump is made to plot X, Y. pop bc jp PLOT C_ARC_GE1: ; 235a rst FP_CALC ; X, Y, Z, Z*SIN (PI/A) db C_st_mem_2 ; (Z*SIN (PI/A) to mem-2 for now). db C_exchange ; X, Y, Z*SIN (PI/A), Z db C_st_mem_0 ; X, Y, Z*SIN (PI/A), Z db C_delete ; X, Y, Z*SIN (PI/A) db C_subtract ; X, Y-Z*SIN (PI/A) db C_exchange ; Y-Z*SIN (PI/A), X db C_get_mem_0 ; Y-Z*SIN (PI/A), X, Z db C_addition ; Y-Z*SIN (PI/A), X+Z db C_st_mem_0 ; (X+Z is copied to mem-0) db C_exchange ; X+Z, Y-Z*SIN (PI/A) db C_duplicate ; X+Z, Y-Z*SIN (PI/A), Y-Z*SIN (PI/A) db C_get_mem_0 ; sa, sb, sb, sa db C_exchange ; sa, sb, sa, sb db C_duplicate ; sa, sb, sa, sb, sb db C_get_mem_0 ; sa, sb, sa, sb, sb, sa db C_stk_zero ; sa, sb, sa, sb, sb, sa, 0 db C_st_mem_1 ; (mem-1 is set to zero) db C_delete ; sa, sb, sa, sb, sb, sa db C_end_calc ; (Here sa denotes X+Z and sb denotes Y - Z*SIN (PI/A)). inc (iy+d_MEMBOT+10) ; Incrementing the exponent byte of ; mem-2 sets mem-2 to ; 2*Z*SIN(PI/A). call FIND_INT1 ; The last value X+Z is moved ld l,a ; from the stack to A and copied to L. push hl ; It is saved in HL. call FIND_INT1 ; Y - Z*SIN (PI/A) goes from the pop hl ; stack to A and is copied to H. ld h,a ; HL now holds the initial point. ld (COORDS),hl ; It is copied to COORDS. pop bc ; The arc-count is restored. jp DRW_STEPS ; The jump is made to DRAW. ; (The stack now holds X+Z, Y - Z*SIN (PI/A), Y - Z*SIN (PI/A), X+Z). ; THE DRAW COMMAND ROUTINE ; This routine is entered with the co-ordinates of a point X0, Y0, say, in ; COORDS. If only two parameters X, Y are given with the DRAW command, it draws ; an approximation to a straight line from the point X0, Y0 to X0+X, Y0+Y. If a ; third parameter G is given, it draws an approximation to a circular arc from ; X0, Y0 to X0+X, Y0+Y turning anti-clockwise through an angle G radians. ; The routine has four parts: ; i. Just draws a line if only 2 parameters are given or if the diameter of the ; implied circle is less than 1; ; ii. Calls CD-PRMS1 at 247D-24B6 to set the first parameters; ; iii. Sets up the remaining parameters, including the initial displacements ; for the first arc; ; iv. Enters the arc-drawing loop and draws the arc as a series of smaller arcs ; approximated by straight lines, calling the line-drawing subroutine at ; 24B7-24FA as necessary. ; Two subroutines, CD-PRMS1 and DRAW-LINE, follow the main routine. The above 4 ; parts of the main routine will now be treated in turn. ; i. If there are only 2 parameters, a jump is made to LINE-DRAW at 2477. A ; line is also drawn if the quantity Z=(ABS X + ABS Y)/ABS SIN(G/2) is less ; than 1. Z lies between 1 and 1.5 times the diameter of the implied circle. ; In this section mem-0 is set to SIN (G/2), mem-1 to Y, and mem-5 to G. DRAW: ; 2382 rst GET_CHAR ; Get the current character. cp 0x2c ; If it is a comma, jr z,DR_3_PRMS ; then jump. call CHECK_END ; Move on to next statement if checking ; syntax. jp LINE_DRAW ; Jump to just draw the line. DR_3_PRMS: ; 238d rst NEXT_CHAR ; Get next character (the angle). call EXPT_1NUM ; Angle to calculator stack. call CHECK_END ; Move on to next statement if checking ; syntax. rst FP_CALC ; X, Y, G are on the stack. db C_st_mem_5 ; (G is copied to mem-5) db C_stk_half ; X, Y, G, 0.5 db C_multiply ; X, Y, G/2 db C_sin ; X, Y, SIN (G/2) db C_duplicate ; X, Y, SIN (G/2), SIN (G/2) db C_not ; X, Y, SIN (G/2), (0/1) db C_not ; X, Y, SIN (G/2), (1/0) db C_jump_true ; X, Y, SIN (G/2) db DR_SIN_NZ - $ ; (If SIN (G/2)=0 i.e. G = 2*N*PI db C_delete ; just draw a straight line). db C_end_calc ; X, Y jp LINE_DRAW ; Line X0, Y0 to X0+X, Y0+Y. DR_SIN_NZ: ; 23a3 db C_st_mem_0 ; (SIN (G/2) is copied to mem-0) db C_delete ; X, Y are now on the stack. db C_st_mem_1 ; (Y is copied to mem-1). db C_delete ; X db C_duplicate ; X, X db C_abs ; X, X' (X' = ABS X) db C_get_mem_1 ; X, X', Y db C_exchange ; X, Y, X' db C_get_mem_1 ; X, Y, X', Y db C_abs ; X, Y, X', Y' (Y' = ABS Y) db C_addition ; X, Y, X'+Y' db C_get_mem_0 ; X, Y, X'+Y', SIN (G/2) db C_division ; X, Y, (X'+Y')/SIN (G/2)=Z', say db C_abs ; X, Y, Z (Z = ABS Z') db C_get_mem_0 ; X, Y, Z, SIN (G/2) db C_exchange ; X, Y, SIN (G/2), Z db C_re_stack ; (Z is re-stacked to make sure db C_end_calc ; that its exponent is available). ld a,(hl) ; Get exponent of Z. cp 0x81 ; If Z is greater than or equal jr nc,DR_PRMS ; to 1, jump. rst FP_CALC ; X, Y, SIN (G/2), Z db C_delete ; X, Y, SIN (G/2) db C_delete ; X, Y db C_end_calc ; Just draw the line from X0, Y0 jp LINE_DRAW ; to X0+X, Y0+Y. ; ii. Just calls CD-PRMS1. This subroutine saves in the B register the number ; of shorter arcs required for the complete arc, viz. A=4*INT (G'*SQR Z/8)+4, ; where G' = mod G, or 252 if this expression exceeds 252 (as can happen with a ; large chord and a small angle). So A is 4, 8, 12, ... , up to 252. The ; subroutine also stores in mem-0 to mem-4 the quantities G/A, SIN (G/2*A), 0, ; COS (G/A), SIN (G/A). DR_PRMS: ; 23c1 call CD_PRMS1 ; The subroutine is called. ; iii. Sets up the rest of the parameters as follow. The stack will hold these ; 4 items, reading up to the top: X0+X and Y0+Y as end of last arc; then X0 and ; Y0 as beginning of first arc. Mem-0 will hold X0 and mem-5 Y0. Mem-1 and ; mem-2 will hold the initial displacements for the first arc, U and V; and ; mem-3 and mem-4 will hold COS (G/A) and SIN (G/A) for use in the arc-drawing ; loop. ; The formulae for U and V can be explained as follows. Instead of stepping ; along the final chord, of length L, say, with displacements X and Y, we want ; to step along an initial chord (which may be longer) of length L*W, where ; W=SIN (G/2*A)/SIN (G/2), with displacements X*W and Y*W, but turned through ; an angle - (G/2 - G/2*A), hence with true displacements: ; U = Y*W*SIN (G/2 - G/2*A) + X*W*COS (G/2 - G/2*A) ; Y = Y*W*COS (G/2 - G/2*A) - X*W*SIN (G/2 - G/2*A) ; These formulae can be checked from a diagram, using the normal expansion of ; COS (P - Q) and SIN (P - Q), where Q = G/2 - G/2*A push bc ; Save the arc-counter in B. rst FP_CALC ; X, Y, SIN(G/2), Z db C_delete ; X, Y, SIN(G/2) db C_get_mem_1 ; X, Y, SIN(G/2), SIN(G/2*A) db C_exchange ; X, Y, SIN(G/2*A), SIN(G/2) db C_division ; X, Y, SIN(G/2*A)/SIN(G/2)=W db C_st_mem_1 ; (W is copied to mem-1). db C_delete ; X, Y db C_exchange ; Y, X db C_duplicate ; Y, X, X db C_get_mem_1 ; Y, X, X, W db C_multiply ; Y, X, X*W db C_st_mem_2 ; (X*W is copied to mem-2). db C_delete ; Y, X db C_exchange ; X, Y db C_duplicate ; X, Y, Y db C_get_mem_1 ; X, Y, Y, W db C_multiply ; X, Y, Y*W db C_get_mem_2 ; X, Y, Y*W, X*W db C_get_mem_5 ; X, Y, Y*W, X*W, G db C_get_mem_0 ; X, Y, Y*W, X*W, G, G/A db C_subtract ; X, Y, Y*W, X*W, G-G/A db C_stk_half ; X, Y, Y*W, X*W, G-G/A, 1/2 db C_multiply ; X, Y, Y*W, X*W, G/2-G/2*A = F db C_duplicate ; X, Y, Y*W, X*W, F, F db C_sin ; X, Y, Y*W, X*W, F, SIN F db C_st_mem_5 ; (SIN F is copied to mem-5). db C_delete ; X, Y, Y*W, X*W, F db C_cos ; X, Y, Y*W, X*W, COS F db C_st_mem_0 ; (COS F is copied to mem-0). db C_delete ; X, Y, Y*W, X*W db C_st_mem_2 ; (X*W is copied to mem-2). db C_delete ; X, Y, Y*W db C_st_mem_1 ; (Y*W is copied to mem-1). db C_get_mem_5 ; X, Y, Y*W, SIN F db C_multiply ; X, Y, Y*W*SIN F db C_get_mem_0 ; X, Y, Y*W*SIN F, X*W db C_get_mem_2 ; X, Y, Y*W*SIN F, X*W, COS F db C_multiply ; X, Y, Y*W*SIN F, X*W*COS F db C_addition ; X, Y, Y*W*SIN F+X*W*COS F = U db C_get_mem_1 ; X, Y, U, Y*W db C_exchange ; X, Y, Y*W, U db C_st_mem_1 ; (U is copied to mem-1) db C_delete ; X, Y, Y*W db C_get_mem_0 ; X, Y, Y*W, COS F db C_multiply ; X, Y, Y*W*COS F db C_get_mem_2 ; X, Y, Y*W*COS F, X*W db C_get_mem_5 ; X, Y, Y*W*COS F, X*W, SIN F db C_multiply ; X, Y, Y*W*COS F, X*W*SIN F db C_subtract ; X, Y, Y*W*COS F-X*W*SIN F = V db C_st_mem_2 ; (V is copied to mem-2). db C_abs ; X, Y, V' (V' = ABS V) db C_get_mem_1 ; X, Y, V', U db C_abs ; X, Y, V', U' (U' = ABS U) db C_addition ; X, Y, U' + V' db C_delete ; X, Y db C_end_calc ; (DE now points to U' + V'). ld a,(de) ; Get exponent of U' + V' cp 0x81 ; If U' + V' is less than 1, just pop bc ; tidy the stack and draw the line jp c,LINE_DRAW ; from X0, Y0 to X0+X, Y0+Y. push bc ; Otherwise, continue with the rst FP_CALC ; parameters: X, Y, on the stack. db C_exchange ; Y, X db C_end_calc ld a,(COORDS) ; Get X0 into A and so call STACK_A ; on to the stack. rst FP_CALC ; Y, X, X0 db C_st_mem_0 ; (X0 is copied to mem-0). db C_addition ; Y, X0 + X db C_exchange ; X0+X, Y db C_end_calc ld a,(COORDS+1) ; Get Y0 into A and so call STACK_A ; on to the stack. rst FP_CALC ; X0+X, Y, Y0 db C_st_mem_5 ; (Y0 is copied to mem-5). db C_addition ; X0+X, Y0+Y db C_get_mem_0 ; X0+X, Y0+Y, X0 db C_get_mem_5 ; X0+X, Y0+Y, X0, Y0 db C_end_calc pop bc ; Restore the arc-counter in B. ; iv. The arc-drawing loop. This is entered at 2439 with the co-ordinates of ; the starting point on top of the stack, and the initial displacements for the ; first arc in mem-1 and mem-2. It uses simple trigonometry to ensure that all ; subsequent arcs will be drawn to points that lie on the same circle as the ; first two, subtending the same angle at the centre. It can be shown that if 2 ; points X1, Y1 and X2, Y2 lie on a circle and subtend an angle N at the ; centre, which is also the origin of co-ordinates, then X2 = X1*COS N - Y1*SIN ; N, and Y2 = X1*SIN N + Y1*COS N. But because the origin is here at the ; increments, say Un = Xn+1 - Xn and Vn = Yn+1 - Yn, thus achieving the desired ; result. The stack is shown below on the (n+1)th pass through the loop, as Xn ; and Yn are incremented by Un and Vn, after these are obtained from Un-1 and ; Vn-1. The 4 values on the top of the stack at 2425 are, in DRAW, reading ; upwards, X0+X, Y0+Y, Xn and Yn but to save space these are not shown until ; 2439. For the initial values in CIRCLE, see the end of CIRCLE, above. In ; CIRCLE too, the angle G must be taken to be 2*PI. DRW_STEPS: ; 2420 dec b ; B counts the passes through the loop. jr z,ARC_END ; Jump when B has reached zero. jr ARC_START ; Jump into the loop to start. ARC_LOOP: ; 2425 rst FP_CALC ; (See text above for the stack). db C_get_mem_1 ; Un-1 db C_duplicate ; Un-1, Un-1 db C_get_mem_3 ; Un-1, Un-1, COS(G/A) db C_multiply ; Un-1, Un-1*COS(G/A) db C_get_mem_2 ; Un-1, Un-1*COS(G/A), Vn-1 db C_get_mem_4 ; Un-1, Un-1*COS(G/A), Vn-1, SIN(G/A) db C_multiply ; Un-1, Un-1*COS(G/A), Vn-1* SIN(G/A) db C_subtract ; Un-1, Un-1*COS(G/A)-Vn-1* SIN(G/A)=Un db C_st_mem_1 ; (Un is copied to mem-1). db C_delete ; Un-1 db C_get_mem_4 ; Un-1, SIN(G/A) db C_multiply ; Un-1*SIN(G/A) db C_get_mem_2 ; Un-1*SIN(G/A), Vn-1 db C_get_mem_3 ; Un-1*SIN(G/A), Vn-1, COS(G/A) db C_multiply ; Un-1*SIN(G/A), Vn-1*COS(G/A) db C_addition ; Un-1*SIN(G/A)+Vn-1*COS (G/A) = Vn db C_st_mem_2 ; (Vn is copied to mem-2). db C_delete ; (As noted in the text, the stack db C_end_calc ; in fact holds X0+X, Y0+Y, Xn and Yn). ARC_START: ; 2439 push bc ; Save the arc-counter. rst FP_CALC ; X0+X, Y0+y, Xn, Yn db C_st_mem_0 ; (Yn is copied to mem-0). db C_delete ; X0+X, Y0+Y, Xn db C_get_mem_1 ; X0+X, Y0+Y, Xn, Un db C_addition ; X0+X, Y0+Y, Xn+Un = Xn+1 db C_duplicate ; X0+X, Y0+Y, Xn+1, Xn+1 db C_end_calc ; Next Xn', the approximate value of Xn ; reached by ; the line-drawing subroutine ld a,(COORDS) ; is copied to A call STACK_A ; and hence to the stack. rst FP_CALC ; X0+X, Y0+Y, Xn+1, Xn' db C_subtract ; X0+X, Y0+Y, Xn+1, Xn+1, Xn' - Xn' = ; Un' db C_get_mem_0 ; X0+X, Y0+Y, Xn+1, Un', Yn db C_get_mem_2 ; X0+X, Y0+Y, Xn+1, Un', Yn, Vn db C_addition ; X0+X, Y0+Y, Xn+1, Un', Yn + Vn = Yn+1 db C_st_mem_0 ; (Yn+1 is copied to mem-0). db C_exchange ; X0+X, Y0+Y, Xn+1, Yn+1, Un' db C_get_mem_0 ; X0+X, Y0+Y, Xn+1, Yn+1, Un', Yn+1 db C_end_calc ld a,(COORDS+1) ; Yn', approximate like Xn', is call STACK_A ; copied to A and hence to the stack. rst FP_CALC ; X0+X, Y0+Y, Xn+1, Yn+1, Un', Yn+1, ; Yn' db C_subtract ; X0+X, Y0+Y, Xn+1, Yn+1, Un', Vn' db C_end_calc call DRAW_LINE ; The next 'arc' is drawn. pop bc ; The arc-counter is restored. djnz ARC_LOOP ; Jump if more arcs to draw. ARC_END: ; 245f rst FP_CALC ; The co-ordinates of the end db C_delete ; of the last arc that was drawn db C_delete ; are now deleted from the stack. db C_exchange ; Y0+Y, X0+X db C_end_calc ld a,(COORDS) ; The X-co-ordinate of the end of call STACK_A ; the last arc that was drawn, say rst FP_CALC ; Xz', is copied to the stack. db C_subtract ; Y0+Y, X0+X - Xz' db C_exchange ; X0+X - Xz', Y0+Y db C_end_calc ld a,(COORDS+1) ; The Y-co-ordinate is obtained. call STACK_A rst FP_CALC ; X0+X - Xz', Y0+Y, Yz' db C_subtract ; X0+X - Xz', Y0+Y - Yz' db C_end_calc LINE_DRAW: ; 2477 call DRAW_LINE ; The final arc is drawn to reach X0+X, ; Y0+Y (or close the ; circle). jp TEMPS ; Exit, setting temporary colours. ; THE 'INITIAL PARAMETERS' SUBROUTINE ; This subroutine is called by both CIRCLE and DRAW to set their initial ; parameters. It is called by CIRCLE with X, Y and the radius Z on the top of ; the stack, reading upwards. It is called by DRAW with its own X, Y, SIN (G/2) ; and Z, as defined in DRAW i. above, on the top of the stack. In what follows ; the stack is only shown from Z upwards. ; The subroutine returns in B the arc-count A as explained in both CIRCLE and ; DRAW above, and in mem-0 to mem-5 the quantities G/A, SIN (G/2*A), 0, COS ; (G/A), SIN (G/A) and G. For a circle, G must be taken to be equal to 2*PI. CD_PRMS1: ; 247d rst FP_CALC ; Z db C_duplicate ; Z, Z db C_sqr ; Z, SQR Z db C_stk_data ; Z, SQR Z, 2 db 1-1<<6|0x82-0x50 db 0x00 db C_exchange ; Z, 2, SQR Z db C_division ; Z, 2/SQR Z db C_get_mem_5 ; Z, 2/SQR Z, G db C_exchange ; Z, G, 2/SQR Z db C_division ; Z, G*SQR Z/2 db C_abs ; Z, G'*SQR Z/2 (G' = mod G) db C_end_calc ; Z, G'*SQR Z/2 = A1, say call FP_TO_A ; A1 to A from the stack, if possible. jr c,USE_252 ; If A1 rounds to 256 or more, use 252. and 0xfc ; 4*INT (A1/4) to A. add a,0x04 ; Add 4, giving the arc-count A. jr nc,DRAW_SAVE ; Jump if still under 256. USE_252: ; 2495 ld a,0xfc ; Here, just use 252 decimal. DRAW_SAVE: ; 2497 push af ; Now save the arc-count. call STACK_A ; Copy it to calculator stack too. rst FP_CALC ; Z, A db C_get_mem_5 ; Z, A, G db C_exchange ; Z, G, A db C_division ; Z, G/A db C_duplicate ; Z, G/A, G/A db C_sin ; Z, G/A, SIN (G/A) db C_st_mem_4 ; (SIN (G/A) is copied to mem-4). db C_delete ; Z, G/A db C_duplicate ; Z, G/A, G/A db C_stk_half ; Z, G/A, G/A, 0.5 db C_multiply ; Z, G/A, G/2*A db C_sin ; Z, G/A, SIN (G/2*A) db C_st_mem_1 ; (SIN (G/2*A) is copied to mem-1). db C_exchange ; Z, SIN (G/2*A), G/A db C_st_mem_0 ; (G/A is copied to mem-0). db C_delete ; Z, SIN (G/2*A) = S db C_duplicate ; Z, S, S db C_multiply ; Z, S*S db C_duplicate ; Z, S*S, S*S db C_addition ; Z, 2*S*S db C_stk_one ; Z, 2*S*S, 1 db C_subtract ; Z, 2*S*S-1 db C_negate ; Z, 1-2*S*S = COS (G/A) db C_st_mem_3 ; (COS (G/A) is copied to mem-3). db C_delete ; Z db C_end_calc pop bc ; Restore the arc-count to B. ret ; Finished. ; THE LINE-DRAWING SUBROUTINE ; This subroutine is called by DRAW to draw an approximation to a straight line ; from the point X0, Y0 held in COORDS to the point X0+X, Y0+Y, where the ; increments X and Y are on the top of the calculator stack. The subroutine was ; originally intended for the ZX80 and ZX81 8K ROM, and it is described in a ; BASIC program on page 121 of the ZX81 manual. It is also illustrated here in ; the Circle program in the appendix. ; The method is to intersperse as many horizontal or vertical steps as are ; needed among a basic set of diagonal steps, using an algorithm that spaces ; the horizontal or vertical steps as evenly as possible. DRAW_LINE: ; 24b7 call STK_TO_BC ; ABS Y to B; ABS X to C; SGN Y to D; ; SGN X to E. ld a,c ; Jump if ABS X is greater than cp b ; or equal to ABS Y, so that the jr nc,DL_X_GE_Y ; smaller goes to L, and the ld l,c ; larger (later) goes to H. push de ; Save diag. step (+/-1,+/-1) in DE. xor a ; Insert a vertical step (+/-1, 0) ld e,a ; into DE (D holds SGN Y). jr DL_LARGER ; Now jump to set H. DL_X_GE_Y: ; 24c4 or c ; Return if ABS X and ABS Y ret z ; are both zero. ld l,b ; The smaller (ABS Y here) goes to L. ld b,c ; ABS X to B here, for H. push de ; Save the diagonal step here too. ld d,0x00 ; Hor. step (0, +/-1) to DE here. DL_LARGER: ; 24cb ld h,b ; Larger of ABS X, ABS Y to H now. ; The algorithm starts here. The larger of ABS X and ABS Y, say H, is put into ; A and reduced to INT (H/2). The H - L horizontal or vertical steps and L ; diagonal steps are taken (where L is the smaller of ABS X and ABS Y) in this ; way: L is added to A; if A now equals or exceeds H, it is reduced by H and a ; diagonal step is taken; otherwise a horizontal or vertical step is taken. ; This is repeated H times (B also holds H). Note that meanwhile the exchange ; registers H' and L' are used to hold COORDS. ld a,b ; B to A as well as to H. rra ; A starts at INT (H/2). D_L_LOOP: ; 24ce add a,l ; L is added to A. jr c,D_L_DIAG ; If 256 or more, jump - diag. step. cp h ; If A is less than H, jump for jr c,D_L_HR_VT ; horizontal or vertical step. D_L_DIAG: ; 24d4 sub h ; Reduce A by H. ld c,a ; Restore it to C. exx ; Now use the exchange resisters. pop bc ; Diag. step to B'C'. push bc ; Save it too. jr D_L_STEP ; Jump to take the step. D_L_HR_VT: ; 24db ld c,a ; Save A (unreduced) in C. push de ; Step to stack briefly. exx ; Get exchange registers. pop bc ; Step to B'C' now. D_L_STEP: ; 24df ld hl,(COORDS) ; Now take the step: first, COORDS to ; H'L' as the start ; point. ld a,b ; Y-step from B' to A. add a,h ; Add in H'. ld b,a ; Result to B' . ld a,c ; Now the X-step; it will be tested inc a ; for range (Y will be tested in PLOT). add a,l ; Add L' to C' in A, jump on jr c,D_L_RANGE ; carry for further test. jr z,REPORT_Be ; Zero after no carry denotes ; X-position -1, out of range. D_L_PLOT: ; 24ec dec a ; Restore true value to A. ld c,a ; Value to C' for plotting. call PLOT_SUB ; Plot the step. exx ; Restore main registers. ld a,c ; C back to A to continue algorithm. djnz D_L_LOOP ; Loop back for 8 steps (i.e. H steps). pop de ; Clear machine stack. ret ; Finished. D_L_RANGE: ; 24f7 jr z,D_L_PLOT ; Zero after carry denotes X. position ; 255, in range. ; Report B - Integer out of range REPORT_Be: ; 24f9 rst ERROR_1 ; Call the error handling db 0x0a ; routine. ; EXPRESSION EVALUATION ; THE 'SCANNING' SUBROUTINE ; This subroutine is used to produce an evaluation result of the 'next ; expression'. ; The result is returned as the 'last value' on the calculator stack. For a ; numerical result, the last value will be the actual floating point number. ; However, for a string result the last value will consist of a set of ; parameters. The first of the five bytes is unspecified, the second and third ; bytes hold the address of the start of the string and the fourth and fifth ; bytes hold the length of the string. ; Bit 6 of FLAGS is set for a numeric result and reset for a string result. ; When a next expression consists of only a single operand, e.g. ... A ..., ; ... RND ..., ... A$ (4, 3 TO 7) ... , then the last value is simply the value ; that is obtained from evaluating the operand. ; However when the next expression contains a function and an operand, e.g. ; ... CHR$ A..., ... NOT A ... , SIN 1 ..., the operation code of the function ; is stored on the machine stack until the last value of the operand has been ; calculated. This last value is then subjected to the appropriate operation to ; give a new last value. ; In the case of there being an arithmetic or logical operation to be ; performed, e.g. ... A+B ... , A*B ..., ... A=B ... , then both the last ; value of the first argument and the operation code have to be kept until the ; last value of the second argument has been found. Indeed the calculation of ; the last value of the second argument may also involve the storing of last ; values and operation codes whilst the calculation is being performed. ; It can therefore be shown that as a complex expression is evaluated, e.g. ; ... CHR$ (T+A - 26*INT ((T+A)/26)+65)..., a hierarchy of operations yet to be ; performed is built up until the point is reached from which it must be ; dismantled to produce the final last value. ; Each operation code has associated with it an appropriate priority code and ; operations of higher priority are always performed before those of lower ; priority. ; The subroutine begins with the A register being set to hold the first ; character of the expression and a starting priority marker - zero - being put ; on the machine stack. SCANNING: ; 24fb rst GET_CHAR ; The first character is fetched. ld b,0x00 ; The starting priority marker. push bc ; It is stacked. S_LOOP_1: ; 24ff ld c,a ; The main re-entry point. ld hl,SCANNING_LU ; Index into scanning function call INDEXER ; table with the code in C. ld a,c ; Restore the code to A. jp nc,S_ALPHNUM ; Jump if code not found in table. ld b,0x00 ; Use the entry found in the table ld c,(hl) ; to build up the required address add hl,bc ; in HL, and jump to it. jp hl ; Four subroutines follow; they are called by routines from the scanning ; function table. The first one, the 'scanning quotes subroutine', is used by ; S-QUOTE to check that every string quote is matched by another one. S_QUOTE_S: ; 250f call CH_ADD_1 ; Point to the next character. inc bc ; Increase the length count by one. cp 0x0d ; Is it a carriage return? jp z,REPORT_Cb ; Report the error if so. cp 0x22 ; Is it another '"'? jr nz,S_QUOTE_S ; Loop back if it is not. call CH_ADD_1 ; Point to next character; set zero cp 0x22 ; flag if it is another '"'. ret ; Finished. ; The next subroutine, the 'scanning: two co-ordinates' subroutine, is called ; by S-SCREEN$, S-ATTR and S-POINT to make sure the required two co-ordinates ; are given in their proper form. S_2_COORD: ; 2522 rst NEXT_CHAR ; Fetch the next character. cp 0x28 ; Is it a '('? jr nz,S_RPORT_Cb ; Report the error if it is not. call NEXT_2NUM ; Co-ordinates to calculator stack. rst GET_CHAR ; Fetch the current character. cp 0x29 ; Is it a ')'? S_RPORT_Cb: ; 252d jp nz,REPORT_Cb ; Report the error if it is not. ; THE 'SYNTAX-Z' SUBROUTINE ; At this point the 'SYNTAX-Z' subroutine is interpolated. It is called 32 ; times, with a saving of just one byte each call. A simple test of bit 7 of ; FLAGS will give the zero flag reset during execution and set during syntax ; checking. ; i.e. SYNTAX gives Z set. SYNTAX_Z: ; 2530 bit 7,(iy+d_FLAGS) ; Test bit 7 of FLAGS. ret ; Finished. ; The next subroutine is the 'scanning SCREEN$ subroutine', which is used by ; S-SCREENS$ to find the character that appears at line x, column y of the ; screen. It only searches the character set 'pointed to' to CHARS. ; Note: This is normally the characters +20 (space) to +7F (©) although the ; user can alter CHARS to match for other characters, including user-defined ; graphics. S_SCRN__S: ; 2535 call STK_TO_BC ; x to C, y to B; 0<=x<=23 ld hl,(CHARS) ; decimal; O<=y<=31 decimal. ld de,0x0100 ; CHARS plus 256 decimal gives add hl,de ; HL pointing to the character set. ld a,c ; x is copied to A. rrca ; The number 32 (decimal) * (x rrca ; mod 8) + y is formed in A and copied ; to E. rrca ; This is the low byte of the and 0xe0 ; required screen address. xor b ld e,a ld a,c ; x is copied to A again and 0x18 ; Now the number 64 (decimal) + xor 0x40 ; 8*INT (x/8) is inserted into D. ld d,a ; DE now holds the screen address. ld b,0x60 ; B counts the 96 characters. S_SCRN_LP: ; 254f push bc ; Save the count. push de ; And the screen pointer. push hl ; And the character set pointer. ld a,(de) ; Get first row of screen character. xor (hl) ; Match with row from character set. jr z,S_SC_MTCH ; Jump if direct match found. inc a ; Now test for match with inverse ; character (get +00 in A from ; +FF). jr nz,S_SCR_NXT ; Jump if neither match found. dec a ; Restore +FF to A. S_SC_MTCH: ; 255a ld c,a ; Inverse status (+00 or +FF) to C. ld b,0x07 ; B counts through the other 7 rows. S_SC_ROWS: ; 255d inc d ; Move DE to next row (add 256 dec.). inc hl ; Move HL to next row (i.e. next byte). ld a,(de) ; Get the screen row. xor (hl) ; Match with row from the ROM. xor c ; Include the inverse status. jr nz,S_SCR_NXT ; Jump if row fails to match. djnz S_SC_ROWS ; Jump back till all rows done. pop bc ; Discard character set pointer. pop bc ; And screen pointer. pop bc ; Final count to BC. ld a,0x80 ; Last character code in set plus one. sub b ; A now holds required code. ld bc,0x0001 ; One space is now needed in the work ; space. rst BC_SPACES ; Make the space. ld (de),a ; Put the character into it. jr S_SCR_STO ; Jump to stack the character. S_SCR_NXT: ; 2573 pop hl ; Restore character set pointer. ld de,ERROR_1 ; Move it on 8 bytes, to the next add hl,de ; character in the set. pop de ; Restore the screen pointer. pop bc ; And the counter. djnz S_SCRN_LP ; Loop back for the 96 characters. ld c,b ; Stack the empty string (Length zero). S_SCR_STO: ; 257d jp STK_STO__ ; Jump to stack the matching character, ; or the null string if ; no match is found. ; Note: This exit, via STK-STO-$, is a mistake as it leads to 'double storing' ; of the string result (see S-STRING, 25DB). The instruction line should be ; 'RET'. ; The last of these four subroutines is the 'scanning attributes ; subroutine'. It is called by S-ATTR to return the value of ATTR (x,y) which ; codes the attributes of line x, column y on the television screen. S_ATTR_S: ; 2580 call STK_TO_BC ; x to C, y to B. Again, 0<=x<=23 ld a,c ; decimal; 0<=y<=31 decimal. rrca ; x is copied to A and the number rrca ; 32 (decimal)*x (mod 8)+y is rrca ; formed in A and copied to L. ld c,a ; 32*x(mod 8)+INT (x/8) is also and 0xe0 ; copied to C. xor b ld l,a ; L holds low byte of attribute ; address. ld a,c ; 32*x(mod 8)+INT (x/8) is copied to A. and 0x03 ; 88 (decimal)+INT (x/8) is xor 0x58 ; formed in A and copied to H. ld h,a ; H holds high byte of attribute ; address. ld a,(hl) ; The attribute byte is copied to A. jp STACK_A ; Exit, stacking the required byte. ; THE SCANNING FUNCTION TABLE ; This table contains 8 functions and 4 operators. It thus incorporates 5 new ; Spectrum functions and provides a neat way of accessing some functions and ; operators which already existed on the ZX81. SCANNING_LU: ; 2596 db 0x22, S_QUOTE - $ - 1 db 0x28, S_BRACKET - $ - 1 db 0x2e, S_DECIMAL - $ - 1 db 0x2b, S_U_PLUS - $ - 1 db 0xa8, S_FN - $ - 1 db 0xa5, S_RND - $ - 1 db 0xa7, S_PI - $ - 1 db 0xa6, S_INKEY_ - $ - 1 db 0xc4, S_DECIMAL - $ - 1 db 0xaa, S_SCREEN_ - $ - 1 db 0xab, S_ATTR - $ - 1 db 0xa9, S_POINT - $ - 1 db 0x00 ; THE SCANNING FUNCTION ROUTINES S_U_PLUS: ; 25af rst NEXT_CHAR ; For unary plus, simply move on jp S_LOOP_1 ; to the next character and jump back ; to the main re-entry ; of SCANNING. ; The 'scanning QUOTE routine': This routine deals with string quotes, whether ; simple like "name" or more complex like "a ""white"" lie" or the seemingly ; redundant VAL$ """a""". S_QUOTE: ; 25b3 rst GET_CHAR ; Fetch the current character. inc hl ; Point to the start of the string. push hl ; Save the start address. ld bc,0x0000 ; Set the length to zero. call S_QUOTE_S ; Call the "matching" subroutine. jr nz,S_Q_PRMS ; Jump if zero reset - no more quotes. S_Q_AGAIN: ; 25be call S_QUOTE_S ; Call it again for a third quote. jr z,S_Q_AGAIN ; And again for the fifth, seventh etc. call SYNTAX_Z ; If testing syntax, jump to reset jr z,S_Q_PRMS ; bit 6 of FLAGS and to continue ; scanning. rst BC_SPACES ; Make space in the work space for the ; string and the ; terminating quote. pop hl ; Get the pointer to the start. push de ; Save the pointer to the first space. S_Q_COPY: ; 25cb ld a,(hl) ; Get a character from the string. inc hl ; Point to the next one. ld (de),a ; Copy last one to work space. inc de ; Point to the next space. cp 0x22 ; Is last character a '"'? jr nz,S_Q_COPY ; If not, jump to copy next one. ld a,(hl) ; But if it was, do not copy next inc hl ; one; if next one is a '"', jump cp 0x22 ; to copy the one after it; jr z,S_Q_COPY ; otherwise, finished with copying. S_Q_PRMS: ; 25d9 dec bc ; Get true length to BC. ; Note that the first quote was not counted into the length; the final quote ; was, and is discarded now. Inside the string, the first, third, fifth, etc., ; quotes were counted in but the second, fourth, etc., were not. pop de ; Restore start of copied string. S_STRING: ; 25db ld hl,FLAGS ; This is FLAGS; this entry point res 6,(hl) ; is used whenever bit 6 is to be bit 7,(hl) ; reset and a string stacked if exe- call nz,STK_STO__ ; cuting a line. This is done now. jp S_CONT_2 ; Jump to continue scanning the line. ; Note that in copying the string to the work space, every two pairs of string ; quotes inside the string ("") have been reduced to one pair of string ; quotes("). S_BRACKET: ; 25e8 rst NEXT_CHAR ; The 'scanning BRACKET call SCANNING ; routine' simply gets the cp 0x29 ; character and calls SCANNING ; recursively. jp nz,REPORT_Cb ; Report the error if no matching rst NEXT_CHAR ; bracket; then continue scanning. jp S_CONT_2 S_FN: ; 25f5 jp S_FN_SBRN ; The 'scanning FN routine'. ; This routine, for user-defined functions, just jumps to the 'scanning FN ; subroutine'. S_RND: ; 25f8 call SYNTAX_Z ; Unless syntax is being checked, jr z,S_RND_END ; jump to calculate a random number. ld bc,(SEED) ; Fetch the current value of SEED. call STACK_BC ; Put it on the calculator stack. rst FP_CALC ; Now use the calculator, db C_stk_one ; The 'last value' is now db C_addition ; SEED+1. db C_stk_data ; Put the decimal number 75 db 1-1<<6|0x87-0x50 ; on the calculator stack. db 0x16 db C_multiply ; 'last value' (SEED+1)*75. db C_stk_data ; See STACK LITERALS to see db 0x80 ; how bytes are expanded so as to db 2-1<<6|0x51-0x50 ; put the decimal number 65537 db 0x00,0x00,0x80 ; on the calculator stack. db C_n_mod_m ; Divide (SEED+1)*75 by 65537 to give a ; 'remainder' and an ; 'answer'. db C_delete ; Discard the 'answer'. db C_stk_one ; The 'last value' is now db C_subtract ; 'remainder' - 1. db C_duplicate ; Make a copy of the 'last value'. db C_end_calc ; The calculation is finished. call FP_TO_BC ; Use the 'last value' to give the ld (SEED),bc ; new value for SEED. ld a,(hl) ; Fetch the exponent of 'last value'. and a ; Jump forward if the exponent is jr z,S_RND_END ; zero. sub 0x10 ; Reduce the exponent, i.e. divide ld (hl),a ; 'last value' by 65536 to give the ; required 'last value'. S_RND_END: ; 2625 jr S_PI_END ; Jump past the 'PI' routine. ; The 'scanning-PI routine': unless syntax is being checked the value of 'PI' ; is calculated and forms the 'last value' on the calculator stack. S_PI: ; 2627 call SYNTAX_Z ; Test for syntax checking. jr z,S_PI_END ; Jump if required. rst FP_CALC ; Now use the calculator. db C_stk_pi_2 ; The value of PI/2 is put on the db C_end_calc ; calculator stack as the 'last value'. inc (hl) ; The exponent is incremented thereby ; doubling the 'last value' ; giving PI. S_PI_END: ; 2630 rst NEXT_CHAR ; Move on to the next character. jp S_NUMERIC ; Jump forward. S_INKEY_: ; 2634 ld bc,0x105a ; Priority +10 hex, operation rst NEXT_CHAR ; code +5A for the 'read-in' ; subroutine. cp 0x23 ; If next char. is '#', jump. jp z,S_PUSH_PO ; There will be a numerical argument. ld hl,FLAGS ; This is FLAGS. res 6,(hl) ; Reset bit 6 for a string result. bit 7,(hl) ; Test for syntax checking. jr z,S_INK__EN ; Jump if required. call KEY_SCAN ; Fetch a key-value in DE. ld c,0x00 ; Prepare empty string; stack it if jr nz,S_IK__STK ; too many keys pressed. call K_TEST ; Test the key value; stack empty jr nc,S_IK__STK ; string if unsatisfactory. dec d ; +FF to D for L made (bit 3 set). ld e,a ; Key-value to E for decoding. call K_DECODE ; Decode the key-value. push af ; Save the ASCII value briefly. ld bc,0x0001 ; One space is needed in the work ; space. rst BC_SPACES ; Make it now. pop af ; Restore the ASCII value. ld (de),a ; Prepare to stack it as a string. ld c,0x01 ; Its length is one. S_IK__STK: ; 2660 ld b,0x00 ; Complete the length parameter. call STK_STO__ ; Stack the required string. S_INK__EN: ; 2665 jp S_CONT_2 ; Jump forward. S_SCREEN_: ; 2668 call S_2_COORD ; Check that 2 co-ordinates are given. call nz,S_SCRN__S ; Call the subroutine unless rst NEXT_CHAR ; checking syntax; then get next jp S_STRING ; character and jump back. S_ATTR: ; 2672 call S_2_COORD ; Check that 2 co-ordinates are given. call nz,S_ATTR_S ; Call the subroutine unless rst NEXT_CHAR ; checking syntax; then get the jr S_NUMERIC ; next character and jump forward. S_POINT: ; 267b call S_2_COORD ; Check that 2 co-ordinates are given. call nz,POINT_SUB ; Call the subroutine unless rst NEXT_CHAR ; checking syntax; then get the jr S_NUMERIC ; next character and jump forward. S_ALPHNUM: ; 2684 call ALPHANUM ; Is the character alphanumeric? jr nc,S_NEGATE ; Jump if not a letter or a digit. cp 0x41 ; Now jump if it a letter; jr nc,S_LETTER ; otherwise continue on into S-DECIMAL. ; The 'scanning DECIMAL routine' which follows deals with a decimal point or a ; number that starts with a digit. It also takes care of the expression 'BIN', ; which is dealt with in the 'decimal to floating-point' subroutine. S_DECIMAL: ; 268d call SYNTAX_Z ; Jump forward if a line is jr nz,S_STK_DEC ; being executed. ; The action taken is now very different for syntax checking and line ; execution. If syntax is being checked then the floating-point form has to be ; calculated and copied into the actual BASIC line. However when a line is ; being executed the floating-point form will always be available so it is ; copied to the calculator stack to form a 'last value'. ; During syntax checking: call DEC_TO_FP ; The floating-point form is found. rst GET_CHAR ; Set HL to point one past the last ; digit. ld bc,0x0006 ; Six locations are required. call MAKE_ROOM ; Make the room in the BASIC line. inc hl ; Point to the first free space. ld (hl),0x0e ; Enter the number marker code. inc hl ; Point to the second location. ex de,hl ; This pointer is wanted in DE. ld hl,(STKEND) ; Fetch the 'old' STKEND. ld c,0x05 ; There are 5 bytes to move. and a ; Clear the carry flag. sbc hl,bc ; The 'new' STKEND='old' STKEND -5. ld (STKEND),hl ; Move the floating-point number ldir ; from the calculator stack to the ; line. ex de,hl ; Put the line pointer in HL. dec hl ; Point to the last byte added. call TEMP_PTR1 ; This sets CH-ADD. jr S_NUMERIC ; Jump forward. ; During line execution: S_STK_DEC: ; 26b5 rst GET_CHAR ; Get the current character. S_SD_SKIP: ; 26b6 inc hl ; Now move on to the next ld a,(hl) ; character in turn until cp 0x0e ; the number marker code jr nz,S_SD_SKIP ; is found. inc hl ; Point to the first byte of the ; number. call STACK_NUM ; Move the floating-point number. ld (CH_ADD),hl ; Set CH-ADD. ; A numeric result has now been identified, coming from RND, PI, ATTR, POINT or ; a decimal number, therefore bit 6 of FLAGS must be set. S_NUMERIC: ; 26c3 set 6,(iy+d_FLAGS) ; Set the numeric marker flag. jr S_CONT_1 ; Jump forward. ; THE SCANNING VARIABLE ROUTINE ; When a variable name has been identified a call is made to LOOK-VARS which ; looks through those variables that already exist in the variables area (or in ; the program area at DEF FN statements for a user-defined function FN). If an ; appropriate numeric value is found then it is copied to the calculator stack ; using STACK-NUM. However a string or string array entry has to have the ; appropriate parameters passed to the calculator stack by the STK-VAR ; subroutine (or in the case of a user-defined function, by the STK-F-ARG ; subroutine as called from LOOK-VARS). S_LETTER: ; 26c9 call LOOK_VARS ; Look in the existing variables for ; the matching entry. jp c,REPORT_2c ; An error is reported if there is no ; existing entry. call z,STK_VAR ; Stack the parameters of the string ; entry/return numeric ; element base address. ld a,(FLAGS) ; Fetch FLAGS. cp 0xc0 ; Test bits 6 and 7 together. jr c,S_CONT_1 ; One or both bits are reset. inc hl ; A numeric value is to be stacked. call STACK_NUM ; Move the number. S_CONT_1: ; 26dd jr S_CONT_2 ; Jump forward. ; The character is tested against the code for '-', thus identifying the 'unary ; minus' operation. ; Before the actual test the B register is set to hold the priority +09 and the ; C register the operation code +D8 that are required for this operation. S_NEGATE: ; 26df ld bc,0x09db ; Priority +09, operation code +D8. cp 0x2d ; Is it a '-'? jr z,S_PUSH_PO ; Jump forward if it is 'unary minus'. ; Next the character is tested against the code for 'VAL$', with priority 16 ; decimal and operation code 18 hex. ld bc,0x1018 ; Priority 16 dec, operation code +18 ; hex. cp 0xae ; Is it 'VAL$'? jr z,S_PUSH_PO ; Jump forward if it is 'VAL$'. ; The present character must now represent one of the functions CODE to NOT, ; with codes +AF to +C3. sub 0xaf ; The range of the functions is changed ; from +AF to +C3 to ; range +00 to +14 hex. jp c,REPORT_Cb ; Report an error if out of range. ; The function 'NOT' is identified and dealt with separately from the others. ld bc,0x04f0 ; Priority +04, operation code +F0. cp 0x14 ; Is it the function 'NOT'? jr z,S_PUSH_PO ; Jump if it is so. jp nc,REPORT_Cb ; Check the range again. ; The remaining functions have priority 16 decimal. The operation codes for ; these functions are now calculated. Functions that operate on strings need ; bit 6 reset and functions that give string results need bit 7 reset in their ; operation codes. ld b,0x10 ; Priority 16 decimal. add a,0xdc ; The function range is now +DC +EF. ld c,a ; Transfer the operation code. cp 0xdf ; Separate CODE, VAL and LEN jr nc,S_NO_TO__ ; which operate on strings to give res 6,c ; numerical results. S_NO_TO__: ; 2707 cp 0xee ; Separate STR$ and CHR$ jr c,S_PUSH_PO ; which operate on numbers to give ; string results. res 7,c ; Mark the operation codes. The other ; operation codes have ; bits 6 and 7 both set. ; The priority code and the operation code for the function being considered ; are now pushed on to the machine stack. A hierarchy of operations is thereby ; built up. S_PUSH_PO: ; 270d push bc ; Stack the priority and operation rst NEXT_CHAR ; codes before moving on to jp S_LOOP_1 ; consider the next part of the ; expression. ; The scanning of the line now continues. The present argument may be followed ; by a '(', a binary operator or, if the end of the expression has been ; reached, then e.g. a carriage return character or a colon, a separator or a ; 'THEN'. S_CONT_2: ; 2712 rst GET_CHAR ; Fetch the present character. S_CONT_3: ; 2713 cp 0x28 ; Jump forward if it is not a '(', jr nz,S_OPERTR ; which indicates a parenthesised ; expression. ; If the 'last value' is numeric then the parenthesised expression is a true ; sub-expression and must be evaluated by itself. However if the 'last value' ; is a string then the parenthesised expression represents an element of an ; array or a slice of a string. A call to SLICING modifies the parameters of ; the string as required. bit 6,(iy+d_FLAGS) ; Jump forward if dealing with a jr nz,S_LOOP ; numeric parenthesised expression. call SLICING ; Modify the parameters of the 'last ; value'. rst NEXT_CHAR ; Move on to consider the next jr S_CONT_3 ; character. ; If the present character is indeed a binary operator it will be given an ; operation code in the range +C3 - +CF hex, and the appropriate priority code. S_OPERTR: ; 2723 ld b,0x00 ; Original code to BC to index ld c,a ; into table of operators. ld hl,OPERATOR_OT ; The pointer to the table. call INDEXER ; Index into the table. jr nc,S_LOOP ; Jump forward if no operation found. ld c,(hl) ; Get required code from the table. ld hl,PRECEDENCE_TABLE-0xc3 ; The pointer to the priority table: ; i.e. 26ED +C3 gives 27B0 ; as the first address. add hl,bc ; Index into the table. ld b,(hl) ; Fetch the appropriate priority. ; The main loop of this subroutine is now entered. At this stage there are: ; i. A 'last value' on the calculator stack. ; ii. The starting priority market on the machine stack below a hierarchy, of ; unknown size, of function and binary operation codes. This hierarchy may be ; null. ; iii. The BC register pair holding the 'present' operation and priority, which ; if the end of an expression has been reached will be priority zero. ; Initially the 'last' operation and priority are taken off the machine stack ; and compared against the 'present' operation and priority. ; If the 'present' priority is higher than the 'last' priority then an exit ; is made from the loop as the 'present' priority is considered to bind tighter ; than the 'last' priority. ; However, if the present priority is less binding, then the operation ; specified as the 'last' operation is performed. The 'present' operation and ; priority go back on the machine stack to be carried round the loop again. In ; this manner the hierarchy of functions and binary operations that have been ; queued are dealt with in the correct order. S_LOOP: ; 2734 pop de ; Get the 'last' operation and ; priority. ld a,d ; The priority goes to the A register. cp b ; Compare 'last' against 'present'. jr c,S_TIGHTER ; Exit to wait for the argument. and a ; Are both priorities zero? jp z,GET_CHAR ; Exit via GET-CHAR thereby making ; 'last value' the required ; result. ; Before the 'last' operation is performed, the 'USR' function is separated ; into 'USR number' and 'USR string' according as bit 6 of FLAGS was set or ; reset when the argument of the function was stacked as the 'last value'. push bc ; Stack the 'present' values. ld hl,FLAGS ; This is FLAGS. ld a,e ; The 'last' operation is compared cp 0xed ; with the code for USR, which jr nz,S_STK_LST ; will give 'USR number' unless ; modified; jump if not 'USR'. bit 6,(hl) ; Test bit 6 of FLAGS. jr nz,S_STK_LST ; Jump if it is set ('USR number'). ld e,0x99 ; Modify the 'last' operation code: ; 'offset' 19, +80 for string ; input and numerical result ('USR ; string'). S_STK_LST: ; 274c push de ; Stack the 'last' values briefly. call SYNTAX_Z ; Do not perform the actual jr z,S_SYNTEST ; operation if syntax is being checked. ld a,e ; The 'last' operation code. and 0x3f ; Strip off bits 6 and 7 to convert ld b,a ; the operation code to a calculator ; offset. rst FP_CALC ; Now use the calculator. db C_fp_calc_2 ; Perform the actual operation db C_end_calc ; It has been done. jr S_RUNTEST ; Jump forward. ; An important part of syntax checking involves the testing of the operation to ; ensure that the nature of the 'last value' is of the correct type for the ; operation under consideration. S_SYNTEST: ; 275b ld a,e ; Get the 'last' operation code. xor (iy+d_FLAGS) ; This tests the nature of the 'last and 0x40 ; value' against the requirement of the ; operation. They are to be ; the same for correct syntax. S_RPORT_Ca: ; 2761 jp nz,REPORT_Cb ; Jump if syntax fails. ; Before jumping back to go round the loop again the nature of the 'last value' ; must be recorded in FLAGS. S_RUNTEST: ; 2764 pop de ; Get the 'last' operation code. ld hl,FLAGS ; This is FLAGS. set 6,(hl) ; Assume result to be numeric. bit 7,e ; Jump forward if the nature of jr nz,S_LOOPEND ; 'last value' is numeric. res 6,(hl) ; It is string. S_LOOPEND: ; 2770 pop bc ; Get the 'present' values into BC: jr S_LOOP ; Jump back. ; Whenever the 'present' operation binds tighter, the 'last' and the 'present' ; values go back on the machine stack. However if the 'present' operation ; requires a string as its operand then the operation code is modified to ; indicate this requirement. S_TIGHTER: ; 2773 push de ; The 'last' values go on the stack. ld a,c ; Get the 'present' operation code. bit 6,(iy+d_FLAGS) ; Do not modify the operation jr nz,S_NEXT ; code if dealing with a numeric ; operand. and 0x3f ; Clear bits 6 and 7. add a,0x08 ; Increase the code by +08 hex. ld c,a ; Return the code to the C register. cp 0x10 ; Is the operation 'AND'? jr nz,S_NOT_AND ; Jump if it is not so. set 6,c ; 'AND' requires a numeric operand. jr S_NEXT ; Jump forward. S_NOT_AND: ; 2788 jr c,S_RPORT_Ca ; The operations -, *, /, ^ and OR are ; not possible between strings. cp 0x17 ; Is the operation a '+'? jr z,S_NEXT ; Jump if it is so. set 7,c ; The other operations yield a numeric ; result. S_NEXT: ; 2790 push bc ; The 'present' values go on the ; machine stack. rst NEXT_CHAR ; Consider the next character. jp S_LOOP_1 ; Go around the loop again. ; THE TABLE OF OPERATORS OPERATOR_OT: ; 2795 db 0x2b,0xcf ; + db 0x2d,0xc3 ; - db 0x2a,0xc4 ; * db 0x2f,0xc5 ; / db 0x5e,0xc6 ; ^ db 0x3d,0xce ; = db 0x3e,0xcc ; > db 0x3c,0xcd ; < db 0xc7,0xc9 ; <= db 0xc8,0xca ; >= db 0xc9,0xcb ; <> db 0xc5,0xc7 ; OR db 0xc6,0xc8 ; AND db 0x00 ; End marker ; THE TABLE OF PRIORITIES (precedence table) PRECEDENCE_TABLE: ; 27b0 db 0x06 ; - db 0x08 ; * db 0x08 ; / db 0x0a ; ^ db 0x02 ; OR db 0x03 ; AND db 0x05 ; <= db 0x05 ; >= db 0x05 ; <> db 0x05 ; > db 0x05 ; < db 0x05 ; = db 0x06 ; + ; THE 'SCANNING FUNCTION' SUBROUTINE ; This subroutine is called by the 'scanning FN routine' to evaluate a user ; defined function which occurs in a BASIC line. The subroutine can be ; considered in four stages: ; i. The syntax of the FN statement is checked during syntax checking. ; ii. During line execution, a search is made of the program area for a DEF FN ; statement, and the names of the functions are compared, until a match is ; found - or an error is reported. ; iii. The arguments of the FN are evaluated by calls to SCANNING. ; iv. The function itself is evaluated by calling SCANNING, which in turn calls ; LOOK-VARS and so the 'STACK FUNCTION ARGUMENT' subroutine. S_FN_SBRN: ; 27bd call SYNTAX_Z ; Unless syntax is being checked, jr nz,SF_RUN ; a jump is made to SF-RUN. rst NEXT_CHAR ; Get the first character of the name. call ALPHA ; If it is not alphabetic, then jp nc,REPORT_Cb ; report the error. rst NEXT_CHAR ; Get the next character. cp 0x24 ; Is it a '$'? push af ; Save the zero flag on the stack. jr nz,SF_BRKT_1 ; Jump if it was not a '$'. rst NEXT_CHAR ; But get the next character if it was. SF_BRKT_1: ; 27d0 cp 0x28 ; If the character is not a '(', then jr nz,SF_RPRT_C ; report the error. rst NEXT_CHAR ; Get the next character. cp 0x29 ; Is it a ')'? jr z,SF_FLAG_6 ; Jump if it is; there are no ; arguments. SF_ARGMTS: ; 27d9 call SCANNING ; Within the loop, call SCANNING to ; check the syntax of each ; argument and to insert floating-point ; numbers. rst GET_CHAR ; Get the character which follows cp 0x2c ; the argument; if it is not a ',' jr nz,SF_BRKT_2 ; then jump - no more arguments. rst NEXT_CHAR ; Get the first character in the next ; argument. jr SF_ARGMTS ; Loop back to consider this argument. SF_BRKT_2: ; 27e4 cp 0x29 ; Is the current character a ')'? SF_RPRT_C: ; 27e6 jp nz,REPORT_Cb ; Report the error if it is not. SF_FLAG_6: ; 27e9 rst NEXT_CHAR ; Point to the next character in the ; BASIC line. ld hl,FLAGS ; This is FLAGS; assume a string- res 6,(hl) ; valued function and reset bit 6 of ; FLAGS. pop af ; Restore the zero flag, jump if jr z,SF_SYN_EN ; the FN is indeed string valued. set 6,(hl) ; Otherwise, set bit 6 of FLAGS SF_SYN_EN: ; 27f4 jp S_CONT_2 ; Jump back to continue scanning the ; line. ; ii. During line execution, a search must first be made for a DEF FN ; statement. SF_RUN: ; 27f7 rst NEXT_CHAR ; Get the first character of the name. and 0xdf ; Reset bit 5 for upper case. ld b,a ; Copy the name to B. rst NEXT_CHAR ; Get the next character. sub 0x24 ; Subtract 24 hex, the code for '$'. ld c,a ; Copy the result to C (zero for a ; string, non-zero for a numerical ; function). jr nz,SF_ARGMT1 ; Jump if non-zero: numerical function. rst NEXT_CHAR ; Get the next character, the '('. SF_ARGMT1: ; 2802 rst NEXT_CHAR ; Get 1st character of 1st argument. push hl ; Save the pointer to it on the stack. ld hl,(PROG) ; Point to the start of the program. dec hl ; Go back one location. SF_FND_DF: ; 2808 ld de,0x00ce ; The search will be for 'DEF FN'. push bc ; Save the name and 'string status'. call LOOK_PROG ; Search the program now. pop bc ; Restore the name and status. jr nc,SF_CP_DEF ; Jump if a DEF FN statement found. ; REPORT P - FN without DEF. REPORT_Pa: ; 2812 rst ERROR_1 ; Call the error handling db 0x18 ; routine. ; When a DEF FN statement is found, the name and status of the two functions ; are compared: if they do not match, the search is resumed. SF_CP_DEF: ; 2814 push hl ; Save the pointer to the DEF FN ; character in case the search has ; to be resumed. call FN_SKPOVR ; Get the name of the DEF FN function. and 0xdf ; Reset bit 5 for upper case. cp b ; Does it match the FN name? jr nz,SF_NOT_FD ; Jump if it does not match. call FN_SKPOVR ; Get the next character in the DEF FN. sub 0x24 ; Subtract 24 hex, the code for '$'. cp c ; Compare the status with that of FN. jr z,SF_VALUES ; Jump if complete match now found. SF_NOT_FD: ; 2825 pop hl ; Restore the pointer to the 'DEF FN'. dec hl ; Step back one location. ld de,0x0200 ; Use the search routine to find push bc ; the end of the DEF FN state- call EACH_STMT ; ment, preparing for the next pop bc ; search; save the name and status ; meanwhile. jr SF_FND_DF ; Jump back for a further search. ; ii. The correct DEF FN statement has now been found. The arguments of the FN ; statement will be evaluated by repeated calls of SCANNING, and their 5 byte ; values (or parameters, for strings) will be inserted into the DEF FN ; statement in the spaces made there at syntax checking. HL will be used to ; point along the DEF FN statement (calling FN-SKPOVR as needed) while CH-ADD ; points along the FN statement (calling RST 0020, NEXT-CHAR, as needed). SF_VALUES: ; 2831 and a ; If HL is now pointing to a '$', call z,FN_SKPOVR ; move on to the '('. pop de ; Discard the pointer to 'DEF FN'. pop de ; Get the pointer to the first ld (CH_ADD),de ; argument of FN, and copy it to ; CH-ADD. call FN_SKPOVR ; Move past the '(' now. push hl ; Save this pointer on the stack. cp 0x29 ; Is it pointing to a ')'? jr z,SF_R_BR_2 ; If so, jump: FN has no arguments. SF_ARG_LP: ; 2843 inc hl ; Point to the next code. ld a,(hl) ; Put the code into A. cp 0x0e ; Is it the 'number marker' code, 0E ; hex? ld d,0x40 ; Set bit 6 of D for a numerical ; argument. jr z,SF_ARG_VL ; Jump on zero: numerical argument. dec hl ; Now ensure that HL is pointing to the ; '$' character (not e.g. to a call FN_SKPOVR ; control code). inc hl ; HL now points to the 'number marker'. ld d,0x00 ; Bit 6 of D is reset: string argument. SF_ARG_VL: ; 2852 inc hl ; Point to the 1st of the 5 bytes in ; DEF FN. push hl ; Save this pointer on the stack. push de ; Save the 'string status' of the ; argument. call SCANNING ; Now evaluate the argument. pop af ; Get the no./string flag into A. xor (iy+d_FLAGS) ; Test bit 6 of it against the result and 0x40 ; of SCANNING. jr nz,REPORT_Qa ; Give report Q if they did not match. pop hl ; Get the pointer to the first of ex de,hl ; the 5 spaces in DEF FN into DE. ld hl,(STKEND) ; Point HL at STKEND. ld bc,0x0005 ; BC will count 5 bytes to be moved. sbc hl,bc ; First, decrease STKEND by 5, ld (STKEND),hl ; so deleting the 'last value' from the ; stack. ldir ; Copy the 5 bytes into the spaces in ; DEF FN. ex de,hl ; Point HL at the next code. dec hl ; Ensure that HL points to the call FN_SKPOVR ; character after the 5 bytes. cp 0x29 ; Is it a ')'? jr z,SF_R_BR_2 ; Jump if it is: no more arguments in ; the DEF FN ; statement. push hl ; It is a ',': save the pointer to it. rst GET_CHAR ; Get the character after the last ; argument that was evaluated ; from FN. cp 0x2c ; If it is not a ',' jump: mis- jr nz,REPORT_Qa ; matched arguments of FN and DEF FN. rst NEXT_CHAR ; Point CH-ADD to the next argument of ; FN. pop hl ; Point HL to the ',' in DEF FN again. call FN_SKPOVR ; Move HL on to the next argument in ; DEF FN. jr SF_ARG_LP ; Jump back to consider this argument. SF_R_BR_2: ; 2885 push hl ; Save the pointer to the ')' in DEF ; FN. rst GET_CHAR ; Get the character after the last ; argument in FN. cp 0x29 ; Is it a ')'? jr z,SF_VALUE ; If so, jump to evaluate the function; ; but if not, give report ; Q. ; REPORT Q - Parameter error. REPORT_Qa: ; 288b rst ERROR_1 ; Call the error handling db 0x19 ; routine. ; iv. Finally, the function itself is evaluated by calling SCANNING, after ; first setting DEFADD to hold the address of the arguments as they occur in ; the DEF FN statement. This ensures that LOOK-VARS, when called by SCANNING, ; will first search these arguments for the required values, before making a ; search of the variables area. SF_VALUE: ; 288d pop de ; Restore pointer to ')' in DEF FN. ex de,hl ; Get this pointer into HL. ld (CH_ADD),hl ; Insert it into CH-ADD. ld hl,(DEFADD) ; Get the old value of DEFADD. ex (sp),hl ; Stack it, and get the start ld (DEFADD),hl ; address of the arguments area of DEF ; FN into DEFADD. push de ; Save address of ')' in FN. rst NEXT_CHAR ; Move CH-ADD on past ')' and rst NEXT_CHAR ; '=' to the start of the expression ; for the function in DEF FN. call SCANNING ; Now evaluate the function. pop hl ; Restore the address of ')' in FN. ld (CH_ADD),hl ; Store it in CH-ADD. pop hl ; Restore original value of DEFADD. ld (DEFADD),hl ; Put it back into DEFADD. rst NEXT_CHAR ; Get the next character in the BASIC ; line. jp S_CONT_2 ; Jump back to continue scanning. ; THE 'FUNCTION SKIPOVER' SUBROUTINE ; This subroutine is used by FN and by STK-F-ARG to move HL along the DEF FN ; statement while leaving CM-ADD undisturbed, as it points along the FN ; statement. FN_SKPOVR: ; 28ab inc hl ; Point to the next code in the ; statement. ld a,(hl) ; Copy the code to A. cp 0x21 ; Jump back to skip over it if it is jr c,FN_SKPOVR ; a control code or a space. ret ; Finished. ; THE 'LOOK-VARS' SUBROUTINE ; This subroutine is called whenever a search of the variables area or of the ; arguments of a DEF FN statement is required. The subroutine is entered with ; the system variable CH-ADD pointing to the first letter of the name of the ; variable whose location is being sought. The name will be in the program area ; or the work space. The subroutine initially builds up a discriminator byte, ; in the C register, that is based on the first letter of the variable's name. ; Bits 5 & 6 of this byte indicate the type of the variable that is being ; handled. ; The B register is used as a bit register to hold flags. LOOK_VARS: ; 28b2 set 6,(iy+d_FLAGS) ; Presume a numeric variable. rst GET_CHAR ; Get the first character into A. call ALPHA ; Is it alphabetic? jp nc,REPORT_Cb ; Give an error report if it is not so. push hl ; Save the pointer to the first letter. and 0x1f ; Transfer bits 0 to 4 of the letter ld c,a ; to the C register; bits 5 & 7 are ; always reset. rst NEXT_CHAR ; Get the 2nd character into A. push hl ; Save this pointer also. cp 0x28 ; is the 2nd character a '('? jr z,V_RUN_SYN ; Separate arrays of numbers. set 6,c ; Now set bit 6. cp 0x24 ; Is the 2nd character a '$'? jr z,V_STR_VAR ; Separate all the strings. set 5,c ; Now set bit 5. call ALPHANUM ; If the variable's name has only jr nc,V_TEST_FN ; one character then jump forward. ; Now find the end character of a name that has more than one character. V_CHAR: ; 28d4 call ALPHANUM ; Is the character alphanumeric? jr nc,V_RUN_SYN ; Jump out of the loop when the end of ; the name is found. res 6,c ; Mark the discriminator byte. rst NEXT_CHAR ; Get the next character. jr V_CHAR ; Go back to test it. ; Simple strings and arrays of strings require that bit 6 of FLAGS is reset. V_STR_VAR: ; 28de rst NEXT_CHAR ; Step CH-ADD past the '$'. res 6,(iy+d_FLAGS) ; Reset the bit 6 to indicate a string. ; If DEFADD-hi is non-zero, indicating that a 'function' (a 'FN') is being ; evaluated, and if in 'run-time', a search will be made of the arguments in ; the DEF FN statement. V_TEST_FN: ; 28e3 ld a,(DEFADD+1) ; Is DEFADD-hi zero? and a jr z,V_RUN_SYN ; If so, jump forward. call SYNTAX_Z ; In 'run-time'? jp nz,STK_F_ARG ; If so, jump forward to search the DEF ; FN statement. ; Otherwise (or if the variable was not found in the DEF FN statement) a search ; of variables area will be made, unless syntax is being checked. V_RUN_SYN: ; 28ef ld b,c ; Copy the discriminator bytes to the B ; register. call SYNTAX_Z ; Jump forward if in jr nz,V_RUN ; 'run-time'. ld a,c ; Move the discriminator to A. and 0xe0 ; Drop the character code part. set 7,a ; Indicate syntax by setting bit 7. ld c,a ; Restore the discriminator. jr V_SYNTAX ; Jump forward to continue. ; A BASIC line is being executed so make a search of the variables area. V_RUN: ; 28fd ld hl,(VARS) ; Pick up the VARS pointer. ; Now enter a loop to consider the names of the existing variables. V_EACH: ; 2900 ld a,(hl) ; The 1st. letter of each existing ; variable. and 0x7f ; Match on bits 0 to 6. jr z,V_80_BYTE ; Jump when the '80-byte' is reached. cp c ; The actual comparison. jr nz,V_NEXT ; Jump forward if the 1st characters do ; not match. rla ; Rotate A leftwards and then add a,a ; double it to test bits 5 & 6. jp p,V_FOUND_2 ; Strings and array variables. jr c,V_FOUND_2 ; Simple numeric and FOR-NEXT ; variables. ; Long names are required to be matched fully. pop de ; Take a copy of the pointer push de ; to the 2nd. character. push hl ; Save the 1st letter pointer. V_MATCHES: ; 2912 inc hl ; Consider the next character. V_SPACES: ; 2913 ld a,(de) ; Fetch each character in turn. inc de ; Point to the next character. cp 0x20 ; Is the character a 'space'? jr z,V_SPACES ; Ignore the spaces. or 0x20 ; Set bit 5 so as to match lower and ; upper case letters. cp (hl) ; Make the comparison. jr z,V_MATCHES ; Back for another character if it does ; match. or 0x80 ; Will it match with bit 7 set? cp (hl) ; Try it. jr nz,V_GET_PTR ; Jump forward if the 'last characters' ; do not match. ld a,(de) ; Check that the end of the call ALPHANUM ; name has been reached before jr nc,V_FOUND_1 ; jumping forward. ; In all cases where the names fail to match the HL register pair has to be ; made to point to the next variable in the variables area. V_GET_PTR: ; 2929 pop hl ; Fetch the pointer. V_NEXT: ; 292a push bc ; Save B & C briefly. call NEXT_ONE ; DE is made to point to the next ; variable. ex de,hl ; Switch the two pointers. pop bc ; Get B & C back. jr V_EACH ; Go around the loop again. ; Come here if no entry was found with the correct name. V_80_BYTE: ; 2932 set 7,b ; Signal 'variable not found'. ; Come here if checking syntax. V_SYNTAX: ; 2934 pop de ; Drop the pointer to the 2nd. ; character. rst GET_CHAR ; Fetch the present character. cp 0x28 ; Is it a '('? jr z,V_PASS ; Jump forward. set 5,b ; Indicate not dealing with an jr V_END ; array and jump forward. ; Come here when an entry with the correct name was found. V_FOUND_1: ; 293e pop de ; Drop the saved variable pointer. V_FOUND_2: ; 293f pop de ; Drop the 2nd character pointer. pop de ; Drop the first letter pointer. push hl ; Save the 'last' letter pointer. rst GET_CHAR ; Fetch the current character. ; If the matching variable name has more than a single letter then the other ; characters must be passed-over. ; Note: This appears to have been done already at V-CHAR. V_PASS: ; 2943 call ALPHANUM ; Is it alphanumeric? jr nc,V_END ; Jump when the end of the name has ; been found. rst NEXT_CHAR ; Fetch the next character. jr V_PASS ; Go back and test it. ; The exit-parameters are now set. V_END: ; 294b pop hl ; HL holds the pointer to the letter of ; a short name or the ; 'last' character of a long name. rl b ; Rotate the whole register. bit 6,b ; Specify the state of bit 6. ret ; Finished. ; The exit-parameters for the subroutine can be summarised as follows: The ; system variable CH-ADD points to the first location after the name of the ; variable as it occurs in the BASIC line. ; When 'variable not found': ; i. The carry flag is set. ; ii. The zero flag is set only when the search was for an array variable. ; iii. The HL register pair points to the first letter of the name of the ; variable as it occurs in the BASIC line. ; When 'variable found': ; i. The carry flag is reset. ; ii. The zero flag is set for both simple string variables and all array ; variables. ; iii. The HL register pair points to the letter of a 'short' name, or the last ; character of a 'long' name, of the existing entry that was found in the ; variables area. ; In all cases bits 5 & 6 of the C register indicate the type of variable being ; handled. Bit 7 is the complement of the SYNTAX/RUN flag. But only when the ; subroutine is used in 'runtime' will bits 0 to 4 hold the code of the ; variable's letter. ; In syntax time the return is always made with the carry flag reset. The zero ; flag is set for arrays and reset for all other variables, except that a ; simple string name incorrectly followed by a '$' sets the zero flag and, in ; the case of SAVE "name" DATA a$(), passes syntax as well. ; THE 'STACK FUNCTION ARGUMENT' SUBROUTINE ; This subroutine is called by LOOK-VARS when DEFADD-hi is non-zero, to make a ; search of the arguments area of a DEF FN statement, before searching in the ; variables area. If the variable is found in the DEF FN statement, then the ; parameters of a string variable are stacked and a signal is given that there ; is no need to call STK/VAR. But it is left to SCANNING to stack the value of ; a numerical variable at 26DA in the usual way. STK_F_ARG: ; 2951 ld hl,(DEFADD) ; Point to the 1st character in the ld a,(hl) ; arguments area and put it into A. cp 0x29 ; Is it a ')'? jp z,V_RUN_SYN ; Jump to search the variables area. SFA_LOOP: ; 295a ld a,(hl) ; Get the next argument in the loop. or 0x60 ; Set bits 5 & 6, assuming a ld b,a ; simple numeric variable; copy it to ; B. inc hl ; Point to the next code. ld a,(hl) ; Put it into the A register. cp 0x0e ; Is it the 'number marker' code 0E ; hex? jr z,SFA_CP_VR ; Jump if so: numeric variable. dec hl ; Ensure that HL points to the call FN_SKPOVR ; character, not to a space or control ; code. inc hl ; HL now points to the 'number marker'. res 5,b ; Reset bit 5 of B: string variable. SFA_CP_VR: ; 296b ld a,b ; Get the variable name into A. cp c ; Is it the one we are looking for? jr z,SFA_MATCH ; Jump if it matches. inc hl ; Now pass over the 5 bytes of inc hl ; the floating-point number or inc hl ; string parameters to get to the inc hl ; next argument. inc hl call FN_SKPOVR ; Pass on to the next character. cp 0x29 ; Is it a ')'? jp z,V_RUN_SYN ; If so, jump to search the variables ; area. call FN_SKPOVR ; Point to the next argument. jr SFA_LOOP ; Jump back to consider it. ; A match has been found. The parameters of a string variable are stacked, ; avoiding the need to call the STK-VAR subroutine. SFA_MATCH: ; 2981 bit 5,c ; Test for a numeric variable. jr nz,SFA_END ; Jump if the variable is numeric; ; SCANNING will stack it. inc hl ; Point to the first of the 5 bytes to ; be stacked. ld de,(STKEND) ; Point DE to STKEND. call MOVE_FP ; Stack the 5 bytes. ex de,hl ; Point HL to the new position ld (STKEND),hl ; of STKEND, and reset the system ; variable. SFA_END: ; 2991 pop de ; Discard the LOOK-VARS pop de ; pointers (2nd & 1st character ; pointers). xor a ; Return from the search with inc a ; both the carry and zero flags reset - ; signalling that a call ; STK-VAR is not required. ret ; Finished. ; THE 'STK-VAR' SUBROUTINE ; This subroutine is usually used either to find the parameters that define an ; existing string entry in the variables area or to return in the HL register ; pair the base address of a particular element or an array of numbers. When ; called from DIM the subroutine only checks the syntax of the BASIC statement. ; Note that the parameters that define a string may be altered by calling ; SLICING if this should be specified. ; Initially the A and the B registers are cleared and bit 7 of the C register ; is tested to determine whether syntax is being checked. STK_VAR: ; 2996 xor a ; Clear the array flag. ld b,a ; Clear the B register for later. bit 7,c ; Jump forward if syntax is jr nz,SV_COUNT ; being checked. ; Next, simple strings are separated from array variables. bit 7,(hl) ; Jump forward if dealing with jr nz,SV_ARRAYS ; an array variable. ; The parameters for a simple string are readily found. inc a ; Signal 'a simple string'. SV_SIMPLE_: ; 29a1 inc hl ; Move along the entry. ld c,(hl) ; Pick up the low length counter. inc hl ; Advance the pointer. ld b,(hl) ; Pick up the high length pointer. inc hl ; Advance the pointer. ex de,hl ; Transfer the pointer to the actual ; string. call STK_STO__ ; Pass these parameters to the ; calculator stack. rst GET_CHAR ; Fetch the present character and jump ; forward to see if a jp SV_SLICE_ ; 'slice' is required. ; The base address of an element in an array is now found. Initially the ; 'number of dimensions' is collected. SV_ARRAYS: ; 29ae inc hl ; Step past the length bytes. inc hl inc hl ld b,(hl) ; Collect the 'number of dimensions'. bit 6,c ; Jump forward if handling an jr z,SV_PTR ; array of numbers. ; If an array of strings has its 'number of dimensions' equal to '1' then such ; an array can be handled as a simple string. dec b ; Decrease the 'number of jr z,SV_SIMPLE_ ; dimensions' and jump if the number is ; now zero. ; Next a check is made to ensure that in the BASIC line the variable is ; followed by a subscript. ex de,hl ; Save the pointer in DE. rst GET_CHAR ; Get the present character. cp 0x28 ; Is it a '('? jr nz,REPORT_3a ; Report the error if it is not so. ex de,hl ; Restore the pointer. ; For both numeric arrays and arrays of strings the variable pointer is ; transferred to the DE register pair before the subscript is evaluated. SV_PTR: ; 29c0 ex de,hl ; Pass the pointer to DE. jr SV_COUNT ; Jump forward. ; The following loop is used to find the parameters of a specified element ; within an array. The loop is entered at the mid-point - SV-COUNT -, where the ; element count is set to zero. ; The loop is accessed 'B' times, this being, for a numeric array, equal to the ; number of dimensions that are being used, but for an array of strings 'B' is ; one less than the number of dimensions in use as the last subscript is used ; to specify a 'slice' of the string. SV_COMMA: ; 29c3 push hl ; Save the counter. rst GET_CHAR ; Get the present character. pop hl ; Restore the counter. cp 0x2c ; Is the present character a ','? jr z,SV_LOOP ; Jump forward to consider another ; subscript. bit 7,c ; If a line is being executed jr z,REPORT_3a ; then there is an error. bit 6,c ; Jump forward if dealing with jr nz,SV_CLOSE ; an array of strings. cp 0x29 ; Is the present character a ')'? jr nz,SV_RPT_C ; Report an error if not so. rst NEXT_CHAR ; Advance CH-ADD. ret ; Return as the syntax is correct. ; For an array of strings the present subscript may represent a 'slice', or the ; subscript for a 'slice' may yet be present in the BASIC line. SV_CLOSE: ; 29d8 cp 0x29 ; Is the present character a ')'? jr z,SV_DIM ; Jump forward and check whether there ; is another sub- ; script. cp 0xcc ; Is the present character a 'TO'? jr nz,SV_RPT_C ; It must not be otherwise. SV_CH_ADD: ; 29e0 rst GET_CHAR ; Get the present character. dec hl ; Point to the preceding ld (CH_ADD),hl ; character and set CH-ADD. jr SV_SLICE ; Evaluate the 'slice'. ; Enter the loop here. SV_COUNT: ; 29e7 ld hl,0x0000 ; Set the counter to zero. SV_LOOP: ; 29ea push hl ; Save the counter briefly. rst NEXT_CHAR ; Advance CH-ADD. pop hl ; Restore the counter. ld a,c ; Fetch the discriminator byte. cp 0xc0 ; Jump unless checking the jr nz,SV_MULT ; syntax for an array of strings. rst GET_CHAR ; Get the present character. cp 0x29 ; Is it a ')'? jr z,SV_DIM ; Jump forward as finished counting ; elements. cp 0xcc ; Is to 'TO'? jr z,SV_CH_ADD ; Jump back if dealing with a 'slice'. SV_MULT: ; 29fb push bc ; Save the dimension-number counter and ; the discriminator ; byte. push hl ; Save the element-counter. call DE__DE_1_ ; Get a dimension-size Into DE. ex (sp),hl ; The counter moves to HL and the ; variable pointer is stacked. ex de,hl ; The counter moves to DE and the ; dimension-size to HL. call INT_EXP1 ; Evaluate the next subscript. jr c,REPORT_3a ; Give an error if out of range. dec bc ; The result of the evaluation is ; decremented as the counter is to ; count the elements occurring before ; the specified element. call GET_HL_DE ; Multiply the counter by the ; dimension-size. add hl,bc ; Add the result of 'INT-EXP1' to the ; present counter. pop de ; Fetch the variable pointer. pop bc ; Fetch the dimension-number and the ; discriminator byte. djnz SV_COMMA ; Keep going round the loop until 'B' ; equals zero. ; The SYNTAX/RUN flag is checked before arrays of strings are separated from ; arrays of numbers. bit 7,c ; Report an error if checking SV_RPT_C: ; 2a12 jr nz,SL_RPT_C ; syntax at this point. push hl ; Save the counter. bit 6,c ; Jump forward if handling jr nz,SV_ELEM_ ; an array of strings. ; When dealing with an array of numbers the present character must be a ')'. ld b,d ; Transfer the variable pointer ld c,e ; to the BC register pair. rst GET_CHAR ; Fetch the present character. cp 0x29 ; Is it a ')'? jr z,SV_NUMBER ; Jump past the error report unless it ; is needed. ; Report 3 - Subscript out of range REPORT_3a: ; 2a20 rst ERROR_1 ; Call the error handling db 0x02 ; routine. ; The address of the location before the actual floating-point form can now be ; calculated. SV_NUMBER: ; 2a22 rst NEXT_CHAR ; Advance CH-ADD. pop hl ; Fetch the counter. ld de,0x0005 ; There are 5 bytes to each element in ; an array of numbers. call GET_HL_DE ; Compute the total number of bytes ; before the required ; element. add hl,bc ; Make HL point to the location before ; the required element. ret ; Return with this address. ; When dealing with an array of strings the length of an element is given by ; the last 'dimension-size'. The appropriate parameters are calculated and then ; passed to the calculator stack. SV_ELEM_: ; 2a2c call DE__DE_1_ ; Fetch the last dimension-size. ex (sp),hl ; The variable printer goes on the ; stack and the counter to HL. call GET_HL_DE ; Multiply 'counter' by ; 'dimension-size'. pop bc ; Fetch the variable pointer. add hl,bc ; This gives HL pointing to the ; location before the string. inc hl ; So point to the actual 'start'. ld b,d ; Transfer the last dimension- ld c,e ; size to BC to form the 'length'. ex de,hl ; Move the 'start' to DE. call STK_ST_0 ; Pass these parameters to the ; calculator stack. Note: The first ; parameter is zero indicating a string ; from an 'array of strings' ; and hence the existing entry is not ; to be reclaimed. ; There are three possible forms of the last subscript. The first is ; illustrated by - A$(2,4 TO 8) -, the second by - A$(2)(4 TO 8) - and the ; third by - A$(2) - which is the default form and indicates that the whole ; string is required. rst GET_CHAR ; Get the present character. cp 0x29 ; Is it a ')'? jr z,SV_DIM ; Jump if it is so. cp 0x2c ; Is it a ','? jr nz,REPORT_3a ; Report the error if not so. SV_SLICE: ; 2a45 call SLICING ; Use SLICING to modify the set of ; parameters. SV_DIM: ; 2a48 rst NEXT_CHAR ; Fetch the next character. SV_SLICE_: ; 2a49 cp 0x28 ; Is It a '('? jr z,SV_SLICE ; Jump back if there is a 'slice' to be ; considered. ; When finished considering the last subscript a return can be made. res 6,(iy+d_FLAGS) ; Signal - string result. ret ; Return with the parameters of the ; required string forming a ; 'last value' on the calculator stack. ; THE 'SLICING' SUBROUTINE ; The present string can be sliced using this subroutine. The subroutine is ; entered with the parameters of the string being present on the top of the ; calculator stack and in the registers A, B, C, D & E. Initially the ; SYNTAX/RUN flag is tested and the parameters of the string are fetched only ; if a line is being executed. SLICING: ; 2a52 call SYNTAX_Z ; Check the flag. call nz,STK_FETCH ; Take the parameters off the stack in ; 'run-time'. ; The possibility of the 'slice' being '()' has to be considered. rst NEXT_CHAR ; Get the next character. cp 0x29 ; Is it a ')'? jr z,SL_STORE ; Jump forward if it is so. ; Before proceeding the registers are manipulated as follows: push de ; The 'start' goes on the machine ; stack. xor a ; The A register is cleared push af ; and saved. push bc ; The 'length' is saved briefly. ld de,0x0001 ; Presume that the 'slice' is to begin ; with the first character. rst GET_CHAR ; Get the first character. pop hl ; Pass the 'length' to HL. ; The first parameter of the 'slice' is now evaluated. cp 0xcc ; Is the present character a 'TO'? jr z,SL_SECOND ; The first parameter, by default, will ; be '1' if the jump is taken. pop af ; At this stage A is zero. call INT_EXP2 ; BC is made to hold the first ; parameter. A will hold +FF if ; there has been an 'out of range' ; error. push af ; Save the value anyway. ld d,b ; Transfer the first parameter ld e,c ; to DE. push hl ; Save the 'length' briefly. rst GET_CHAR ; Get the present character. pop hl ; Restore the 'length'. cp 0xcc ; Is the present character a 'TO'? jr z,SL_SECOND ; Jump forward to consider the second ; parameter if it is so; cp 0x29 ; otherwise show that there is SL_RPT_C: ; 2a7a jp nz,REPORT_Cb ; a closing bracket. ; At this point a 'slice' of a single character has been identified. e.g. - ; A$(4). ld h,d ; The last character of the 'slice' ld l,e ; is also the first character. jr SL_DEFINE ; Jump forward. ; The second parameter of a 'slice' is now evaluated. SL_SECOND: ; 2a81 push hl ; Save the 'length' briefly. rst NEXT_CHAR ; Get the next character. pop hl ; Restore the 'length'. cp 0x29 ; Is the present character a ')'? jr z,SL_DEFINE ; Jump if there is not a second ; parameter. pop af ; If the first parameter was in range A ; will hold zero; ; otherwise +FF. call INT_EXP2 ; Make BC hold the second parameter. push af ; Save the 'error register'. rst GET_CHAR ; Get the present character. ld h,b ; Pass the result obtained from ld l,c ; INT-EXP2 to the HL register pair. cp 0x29 ; Check that there is a closing jr nz,SL_RPT_C ; bracket now. ; The 'new' parameters are now defined. SL_DEFINE: ; 2a94 pop af ; Fetch the 'error register'. ex (sp),hl ; The second parameter goes on the ; stack and the 'start' goes to ; HL. add hl,de ; The first parameter is added to the ; 'start'. dec hl ; Go back a location to get it correct. ex (sp),hl ; The 'new start' goes on the stack and ; the second parameter ; goes to HL. and a ; Subtract the first parameters sbc hl,de ; from the second to find the length of ; the 'slice'. ld bc,0x0000 ; Initialise the 'new length'. jr c,SL_OVER ; A negative 'slice' is a 'null string' ; rather than an error ; condition. (See manual.) inc hl ; Allow for the inclusive byte. and a ; Only now test the 'error register'. jp m,REPORT_3a ; Jump if either parameter was out of ; range for the string. ld b,h ; Transfer the 'new length' ld c,l ; to BC. SL_OVER: ; 2aa8 pop de ; Get the 'new start'. res 6,(iy+d_FLAGS) ; Ensure that a string is still ; indicated. SL_STORE: ; 2aad call SYNTAX_Z ; Return at this point if ret z ; checking syntax; otherwise continue ; into the STK-STORE ; subroutine. ; THE 'STK-STORE' SUBROUTINE ; This subroutine passes the values held in the A, B, C, D & E registers to the ; calculator stack. The stack thereby grows in size by 5 bytes with every call ; to this subroutine. ; The subroutine is normally used to transfer the parameters of strings but it ; is also used by STACK-BC and LOG (2^A) to transfer 'small integers' to the ; stack. ; Note that when storing the parameters of a string the first value stored ; (coming from the A register) will be a zero if the string comes from an array ; of strings or is a 'slice' of a string. The value will be '1' for a complete ; simple string. This 'flag' is used in the 'LET' command routine when the '1' ; signals that the old copy of the string is to be 'reclaimed'. STK_ST_0: ; 2ab1 xor a ; Signal - a string from an array of ; strings or a 'sliced' ; string. STK_STO__: ; 2ab2 res 6,(iy+d_FLAGS) ; Ensure the flag Indicates a string ; result. STK_STORE: ; 2ab6 push bc ; Save B & C briefly. call TEST_5_SP ; Is there room for 5 bytes? Do not ; return here unless there ; is room available. pop bc ; Restore B & C. ld hl,(STKEND) ; Fetch the address of the first ; location above the present ; stack. ld (hl),a ; Transfer the first byte. inc hl ; Step on. ld (hl),e ; Transfer the second and inc hl ; third bytes; for a string ld (hl),d ; these will be the 'start'. inc hl ; Step on. ld (hl),c ; Transfer the fourth and inc hl ; fifth bytes; for a string ld (hl),b ; these will be the 'length'. inc hl ; Step on so as to point to the ; location above the stack. ld (STKEND),hl ; Save this address In STKEND ret ; and return. ; THE 'INT-EXP' SUBROUTINE ; This subroutine returns the result of evaluating the 'next expression' as an ; integer value held in the BC register pair. The subroutine also tests this ; result against a limit-value supplied in the HL register pair. The carry flag ; becomes set if there is an 'out of range' error. ; The A register is used as an 'error register' and holds +00 of there is no ; 'previous error' and +FF if there has been one. INT_EXP1: ; 2acc xor a ; Clear the 'error register'. INT_EXP2: ; 2acd push de ; Save both the DE & HL push hl ; register pairs throughout. push af ; Save the 'error register' briefly. call EXPT_1NUM ; The 'next expression' is evaluated to ; give a 'last value' ; on the calculator stack. pop af ; Restore the 'error register'. call SYNTAX_Z ; Jump forward if checking jr z,I_RESTORE ; syntax. push af ; Save the error register again. call FIND_INT2 ; The 'last value' is compressed Into ; BC. pop de ; Error register to D. ld a,b ; A 'next expression' that or c ; gives zero is always in scf ; error so jump forward if it jr z,I_CARRY ; is so. pop hl ; Take a copy of the push hl ; limit-value. This will be a ; 'dimension-size' a 'DIM-limit' ; or a 'string length'. and a ; Now compare the result of sbc hl,bc ; evaluating the expression against the ; limit. ; The state of the carry flag and the value held in the D register are now ; manipulated so as to give the appropriate value for the 'error register'. I_CARRY: ; 2ae8 ld a,d ; Fetch the 'old error value' sbc a,0x00 ; Form the 'new error value'; +00 if no ; error at anytime/ ; +FF or less if an 'out of range' ; error on this pass or on ; previous ones. ; Restore the registers before returning. I_RESTORE: ; 2aeb pop hl ; Restore HL & DE. pop de ret ; Return; 'error register' is the A ; register. ; THE 'DE,(DE+1)' SUBROUTINE ; This subroutine performs the construction - LD DE,(DE+1) - and returns HL ; pointing ; to 'DE+2'. DE__DE_1_: ; 2aee ex de,hl ; Use HL for the construction. inc hl ; Point to 'DE+1'. ld e,(hl) ; In effect - LD E,(DE+1). inc hl ; Point to 'DE+2'. ld d,(hl) ; In effect - LD D,(DE+2). ret ; Finished. ; THE 'GET-HL*DE' SUBROUTINE ; Unless syntax is being checked this subroutine calls 'HL=HL*DE' which ; performs the implied construction. ; Overflow of the 16 bits available in the HL register pair gives the report ; 'out of memory'. This is not exactly the true situation but it implies that ; the memory is not large enough for the task envisaged by the programmer. GET_HL_DE: ; 2af4 call SYNTAX_Z ; Return directly if syntax is ret z ; being checked. call HL_HL_DE ; Perform the multiplication. jp c,REPORT_4b ; Report 'Out of memory'. ret ; Finished. ; THE 'LET' COMMAND ROUTINE ; This is the actual assignment routine for the LET, READ and INPUT commands. ; When the destination variable is a 'newly declared variable' then DEST will ; point to the first letter of the variable's name as it occurs in the BASIC ; line. Bit 1 of FLAGX will be set. ; However if the destination variable 'exists already' then bit 1 of FLAGX will ; be reset and DEST will point for a numeric variable to the location before ; the five bytes of the 'old number'; and for a string variable to the first ; location of the 'old string'. The use of DEST in this manner applies to ; simple variables and to elements of arrays. ; Bit 0 of FLAGX is set if the destination variable is a 'complete' simple ; string variable. (Signalling - delete the old copy.) ; Initially the current value of DEST is collected and bit 1 of FLAGS tested. LET: ; 2aff ld hl,(DEST) ; Fetch the present address in DEST. bit 1,(iy+d_FLAGX) ; Jump if handling a variable jr z,L_EXISTS ; that 'exists already'. ; A 'newly declared variable' is being used. So first the length of its name is ; found. ld bc,0x0005 ; Presume dealing with a numeric ; variable - 5 bytes. ; Enter a loop to deal with the characters of a long name. Any spaces or colour ; codes in the name are ignored. L_EACH_CH: ; 2b0b inc bc ; Add '1' to the counter for each ; character of a name. L_NO_SP: ; 2b0c inc hl ; Move along the variable's name. ld a,(hl) ; Fetch the 'present code'. cp 0x20 ; Jump back if it is a 'space'; jr z,L_NO_SP ; thereby Ignoring spaces. jr nc,L_TEST_CH ; Jump forward if the code is +21 to ; +FF. cp 0x10 ; Accept, as a final code, those jr c,L_SPACES ; in the range +00 to +0F. cp 0x16 ; Also accept the range jr nc,L_SPACES ; +16 to +1F. inc hl ; Step past the control code after any ; of INK to OVER. jr L_NO_SP ; Jump back as these control codes are ; treated as spaces. ; Separate 'numeric' and 'string' names. L_TEST_CH: ; 2b1f call ALPHANUM ; Is the code alphanumeric? jr c,L_EACH_CH ; If It is so then accept it as a ; character of a 'long' name. cp 0x24 ; Is the present code a 'S'? jp z,L_NEW_ ; Jump forward as handling a 'newly ; declared' simple string. ; The 'newly declared numeric variable' presently being handled will require ; 'BC' spaces in the variables area for its name and its value. The room is ; made available and the name of the variable is copied over with the ; characters being 'marked' as required. L_SPACES: ; 2b29 ld a,c ; Copy the 'length' to A. ld hl,(E_LINE) ; Make HL point to the dec hl ; '80-byte' at the end of the variables ; area. call MAKE_ROOM ; Now open up the variables area. Note: ; In effect 'BC' spaces are ; made before the displaced '80-byte'. inc hl ; Point to the first 'new' byte. inc hl ; Make DE point to the second ex de,hl ; 'new' byte. push de ; Save this pointer. ld hl,(DEST) ; Fetch the pointer to the start of the ; name. dec de ; Make DE point to the first 'new' ; byte. sub 0x06 ; Make B hold the 'number of ld b,a ; extra letters' that are found in a ; 'long name'. jr z,L_SINGLE ; Jump forward if dealing with a ; variable with a 'short name'. ; The 'extra' codes of a long name are passed to the variables area. L_CHAR: ; 2b3e inc hl ; Point to each 'extra' code. ld a,(hl) ; Fetch the code. cp 0x21 ; Accept codes from +21 to +FF; jr c,L_CHAR ; ignore codes +00 to +20. or 0x20 ; Set bit 5, as for lower case letters. inc de ; Transfer the codes in turn ld (de),a ; to the 2nd 'new' byte onwards. djnz L_CHAR ; Go round the loop for all the 'extra' ; codes. ; The last code of a 'long' name has to be ORed with +80. or 0x80 ; Mark the code as required ld (de),a ; and overwrite the last code. ; The first letter of the name of the variable being handled is now considered. ld a,0xc0 ; Prepare the mark the letter of a ; 'long' name. L_SINGLE: ; 2b4f ld hl,(DEST) ; Fetch the pointer to the letter. xor (hl) ; A holds +00 for a 'short' name and ; +C0 for a 'long' name. or 0x20 ; Set bit 5, as for lower case letters. pop hl ; Drop the pointer now. ; The subroutine L-FIRST is now called to enter the 'letter' into its ; appropriate location. call L_FIRST ; Enter the letter and return with HL ; pointing to 'new ; 80-byte'. ; The 'last value' can now be transferred to the variables area. Note that at ; this point HL always points to the location after the five locations allotted ; to the number. ; A 'RST 0028' instruction is used to call the CALCULATOR and the 'last value' ; is deleted. However this value is not overwritten. L_NUMERIC: ; 2b59 push hl ; Save the 'destination' pointer. rst FP_CALC ; Use the calculator. db C_delete ; This moves STKEND back five db C_end_calc ; bytes. pop hl ; Restore the pointer. ld bc,0x0005 ; Give the number a 'length' of five ; bytes. and a ; Make HL point to the first sbc hl,bc ; of the five locations and jr L_ENTER ; jump forward to make the actual ; transfer. ; Come here if considering a variable that 'exists already'. First bit 6 of ; FLAGS is tested so as to separate numeric variables from string or array of ; string variables. L_EXISTS: ; 2b66 bit 6,(iy+d_FLAGS) ; Jump forward if handling any jr z,L_DELETE_ ; kind of string variable. ; For numeric variables the 'new' number overwrites the 'old' number. So first ; HL has to be made to point to the location after the five bytes of the ; existing entry. At present HL points to the location before the five bytes. ld de,0x0006 ; The five bytes of a number +'1'. add hl,de ; HL now points 'after'. jr L_NUMERIC ; Jump back to make the actual ; transfer. ; The parameters of the string variable are fetched and complete simple strings ; separated from 'sliced' strings and array strings. L_DELETE_: ; 2b72 ld hl,(DEST) ; Fetch the 'start'. Note: This line is ; redundant. ld bc,(STRLEN_) ; Fetch the 'length'. bit 0,(iy+d_FLAGX) ; Jump if dealing with a complete jr nz,L_ADD_ ; simple string; the old string will ; need to be 'deleted' in this case ; only. ; When dealing with a 'slice' of an existing simple string, a 'slice' of a ; string from an array of strings or a complete string from an array of strings ; there are two distinct stages involved. The first is to build up the 'new' ; string in the work space, lengthening or shortening it as required. The ; second stage is then to copy the 'new' string to its allotted room in the ; variables area. ; However do nothing if the string has no 'length'. ld a,b ; Return if the string is or c ; a null string. ret z ; Then make the required number of spaces available in the work space. push hl ; Save the 'start' (DEST). rst BC_SPACES ; Make the necessary amount of room in ; the work space. push de ; Save the pointer to the first ; location. push bc ; Save the 'length' for use later on. ld d,h ; Make DE point to the last ld e,l ; location. inc hl ; Make HL point 'one past' the new ; locations. ld (hl),0x20 ; Enter a 'space' character. lddr ; Copy this character into all the new ; locations. Finish with HL ; pointing to the first new location. ; The parameters of the string being handled are now fetched from the ; calculator stack. push hl ; Save the pointer briefly. call STK_FETCH ; Fetch the 'new' parameters. pop hl ; Restore the pointer. ; Note: At this point the required amount of room has been made available in ; the work space for the 'variable in assignment'. e.g. For statement - LET ; A$(4 to 8)="abcdefg" - five locations have been made. ; The parameters fetched above as a 'last value' represent the string that is ; to be copied into the new locations with Procrustean lengthening or ; shortening as required. ; The length of the 'new' string is compared to the length of the room made ; available for it. ex (sp),hl ; 'Length' of new area to HL. 'Pointer' ; to new area to stack. and a ; Compare the two 'lengths' sbc hl,bc ; and jump forward if the 'new' add hl,bc ; string will fit into the room. jr nc,L_LENGTH ; i.e. No shortening required. ld b,h ; However modify the 'new' ld c,l ; length if it is too long. L_LENGTH: ; 2b9b ex (sp),hl ; 'Length' of new area to stack. ; 'Pointer' to new area to HL. ; As long as the new string is not a 'null string' it is copied into the work ; space. Procrustean lengthening is achieved automatically if the 'new' string ; is shorter than the room available for it. ex de,hl ; 'Start' of new string to HL. ; 'Pointer' to new area to DE. ld a,b ; Jump forward if the or c ; 'new' string is a 'null' jr z,L_IN_W_S ; string. ldir ; Otherwise move the 'new' string to ; the work space. ; The values that have been saved on the machine stack are restored. L_IN_W_S: ; 2ba3 pop bc ; 'Length' of new area. pop de ; 'Pointer' to new area. pop hl ; The start - the pointer to the ; 'variable in assignment' ; which was originally in DEST. L-ENTER ; is now used to pass ; the 'new' string to the variables ; area. ; THE 'L-ENTER' SUBROUTINE ; This short subroutine is used to pass either a numeric value, from the ; calculator stack, or a string, from the work space, to its appropriate ; position in the variables area. ; The subroutine is therefore used for all except 'newly declared' simple ; strings and 'complete & existing' simple strings. L_ENTER: ; 2ba6 ex de,hl ; Change the pointers over. ld a,b ; Check once again that the or c ; length is not zero. ret z push de ; Save the destination pointer. ldir ; Move the numeric value or the string pop hl ; Return with the HL register ret ; pair pointing to the first byte of ; the numeric value or the ; string. ; THE LET SUBROUTINE CONTINUES HERE ; When handling a 'complete & existing' simple string the new string is entered ; as if it were a 'newly declared' simple string before the existing version is ; 'reclaimed'. L_ADD_: ; 2baf dec hl ; Make HL point to the letter dec hl ; of the variable's name. dec hl ; i.e. DEST - 3. ld a,(hl) ; Pick up the letter. push hl ; Save the pointer to the 'existing ; version'. push bc ; Save the 'length' of the 'existing ; string'. call L_STRING ; Use L-STRING to add the new string to ; the variables area. pop bc ; Restore the 'length'. pop hl ; Restore the pointer. inc bc ; Allow one byte for the letter inc bc ; and two bytes for the length. inc bc jp RECLAIM_2 ; Exit by jumping to RECLAIM-2 which ; will reclaim the whole ; of the existing version. ; 'Newly declared' simple strings are handled as follows: L_NEW_: ; 2bc0 ld a,0xdf ; Prepare for the marking of the ; variable's letter. ld hl,(DEST) ; Fetch the pointer to the letter. and (hl) ; Mark the letter as required. L-STRING ; is now used to add ; the new string to the variables area. ; THE 'L-STRING' SUBROUTINE ; The parameters of the 'new' string are fetched, sufficient room is made ; available for it and the string is then transferred. L_STRING: ; 2bc6 push af ; Save the variable's letter call STK_FETCH ; Fetch the 'start' and the 'length' of ; the 'new' string. ex de,hl ; Move the 'start' to HL. add hl,bc ; Make HL point 'one-past' the string. push bc ; Save the 'length'. dec hl ; Make HL point to the end of the ; string. ld (DEST),hl ; Save the pointer briefly. inc bc ; Allow one byte for the letter inc bc ; and two bytes for the length. inc bc ld hl,(E_LINE) ; Make HL point to the dec hl ; '80-bye' at the end of the variables ; area. call MAKE_ROOM ; Now open up the variables area. Note: ; In effect 'BC' spaces are ; made before the displaced '80-byte'. ld hl,(DEST) ; Restore the pointer to the end of the ; 'new' string. pop bc ; Make a copy of the length push bc ; of the 'new' string. inc bc ; Add one to the length in case the ; 'new' string is a 'null' ; string. lddr ; Now copy the 'new' string + one byte. ex de,hl ; Make HL point to the byte inc hl ; that is to hold the high-length. pop bc ; Fetch the 'length'. ld (hl),b ; Enter the high-length. dec hl ; Back one. ld (hl),c ; Enter the low-length. pop af ; Fetch the variable's letter. ; THE 'L-FIRST' SUBROUTINE ; This subroutine is entered with the letter of the variable, suitably marked, ; in the A register. The letter overwrites the 'old 80-byte' in the variables ; area. The subroutine returns with the HL register pair pointing to the 'new ; 80-byte'. L_FIRST: ; 2bea dec hl ; Make HL point to the 'old 80-byte'. ld (hl),a ; It is overwritten with the letter of ; the variable. ld hl,(E_LINE) ; Make HL point to the 'new 80-byte'. dec hl ; Finished with all the ret ; 'newly declared variables'. ; THE 'STK-FETCH' SUBROUTINE ; This important subroutine collects the 'last value' from the calculator ; stack. The five bytes can be either a floating-point number, in 'short' or ; 'long' form, or set of parameters that define a string. STK_FETCH: ; 2bf1 ld hl,(STKEND) ; Get STKEND. dec hl ; Back one; ld b,(hl) ; The fifth value. dec hl ; Back one. ld c,(hl) ; The fourth one. dec hl ; Back one. ld d,(hl) ; The third value. dec hl ; Back one. ld e,(hl) ; The second value. dec hl ; Back one. ld a,(hl) ; The first value. ld (STKEND),hl ; Reset STKEND to its new position ret ; Finished. ; THE 'DIM' COMMAND ROUTINE ; This routine establishes new arrays in the variables area. The routine starts ; by searching the existing variables area to determine whether there is an ; existing array with the same name. If such an array is found then it is ; 'reclaimed' before the new array is established. ; A new array will have all its elements set to zero, if it is a numeric array, ; or to 'spaces', if it is an array of strings. DIM: ; 2c02 call LOOK_VARS ; Search the variables area. D_RPORT_C: ; 2c05 jp nz,REPORT_Cb ; Give report C as there has been an ; error. call SYNTAX_Z ; Jump forward if in jr nz,D_RUN ; 'run time'. res 6,c ; Test the syntax for string arrays as ; if they were numeric. call STK_VAR ; Check the syntax of the parenthesised ; expression. call CHECK_END ; Move on to consider the next ; statement as the syntax ; was satisfactory. ; An 'existing array' is reclaimed. D_RUN: ; 2c15 jr c,D_LETTER ; Jump forward if there is no 'existing ; array'. push bc ; Save the discriminator byte. call NEXT_ONE ; Find the start of the next variable call RECLAIM_2 ; Reclaim the 'existing array'. pop bc ; Restore the discriminator byte. ; The initial parameters of the new array are found. D_LETTER: ; 2c1f set 7,c ; Set bit 7 in the discriminator byte. ld b,0x00 ; Make the dimension counter zero. push bc ; Save the counter and the ; discriminator byte. ld hl,0x0001 ; The HL register pair is to bit 6,c ; hold the size of the elements jr nz,D_SIZE ; in the array, '1' for a string ld l,0x05 ; array/ '5' for a numeric array. D_SIZE: ; 2c2d ex de,hl ; Element size DE. ; The following loop is accessed for each dimension that is specified in the ; parenthesised expression of the DIM statement. The total number of bytes ; required for the elements of the array is built up in the DE register pair. D_NO_LOOP: ; 2c2e rst NEXT_CHAR ; Advance CH-ADD on each pass.. ld h,0xff ; Set a 'limit value'. call INT_EXP1 ; Evaluate a parameter. jp c,REPORT_3a ; Give an error if 'out of range'. pop hl ; Fetch the dimension-counter and the ; discriminator byte. push bc ; Save the parameter on each pass ; through the loop. inc h ; Increase the dimension counter on ; each pass also. push hl ; Restack the dimension-counter and the ; discriminator byte. ld h,b ; The paramenter is moved to ld l,c ; the HL register pair. call GET_HL_DE ; The byte total is built up ex de,hl ; in HL and the transferred to DE. rst GET_CHAR ; Get the present character cp 0x2c ; and go around the loop again jr z,D_NO_LOOP ; if there is another dimension. ; Note: At this point the DE register pair indicates the number of bytes ; required for the elements of the new array and the size of each dimension is ; stacked, on the machine stack. ; Now check that there is indeed a closing bracket to the parenthesised ; expression. cp 0x29 ; Is it a ')'? jr nz,D_RPORT_C ; Jump back if not so. rst NEXT_CHAR ; Advance CH-ADD past it. ; Allowance is now made for the dimension-sizes. pop bc ; Fetch the dimension-counter and the ; discriminator byte. ld a,c ; Pass the discriminator byte to the A ; register for later. ld l,b ; Move the counter to L. ld h,0x00 ; Clear the H register. inc hl ; Increase the dimension- inc hl ; counter by two and double the add hl,hl ; result and form the add hl,de ; correct overall length for the ; variable by adding the ; element byte total. jp c,REPORT_4b ; Give the report 'Out of memory' if ; required. push de ; Save the element byte total. push bc ; Save the dimension counter and the ; discriminator byte. push hl ; Save the overall length also. ld b,h ; Move the overall length to BC. ld c,l ; The required amount of room is made available for the new array at the end of ; the variables area. ld hl,(E_LINE) ; Make the HL register pair dec hl ; point to the '80-byte'. call MAKE_ROOM ; The room is made available. inc hl ; HL is made to point to the first new ; location. ; The parameters are now entered. ld (hl),a ; The letter, suitably marked, is ; entered first. pop bc ; The overall length is fetched dec bc ; and decreased by '3'. dec bc dec bc inc hl ; Advance HL. ld (hl),c ; Enter the low length. inc hl ; Advance HL. ld (hl),b ; Enter the high length. pop bc ; Fetch the dimension counter. ld a,b ; Move it to the A register. inc hl ; Advance HL. ld (hl),a ; Enter the dimension count. ; The elements of the new array are now 'cleared'. ld h,d ; HL is made to point to the ld l,e ; last location of the array dec de ; and DE to the location before that ; one. ld (hl),0x00 ; Enter a zero into the last bit 6,c ; location but overwrite it jr z,DIM_CLEAR ; with 'space' if dealing ld (hl),0x20 ; with an array of strings. DIM_CLEAR: ; 2c7c pop bc ; Fetch the element byte total. lddr ; Clear the array + one extra location. ; The 'dimension-sizes' are now entered. DIM_SIZES: ; 2c7f pop bc ; Get a dimension-size. ld (hl),b ; Enter the high byte. dec hl ; Back one. ld (hl),c ; Enter the low byte. dec hl ; Back one. dec a ; Decrease the dimension counter. jr nz,DIM_SIZES ; Repeat the operation until all the ; dimensions have been ret ; considered; then return. ; THE 'ALPHANUM' SUBROUTINE ; This subroutine returns with the carry flag set if the present value of the A ; register denotes a valid digit or letter. ALPHANUM: ; 2c88 call NUMERIC ; Test for a digit; carry will be reset ; for a digit. ccf ; Complement the carry flag. ret c ; Return if a digit; otherwise continue ; on into 'ALPHA'. ; THE 'ALPHA' SUBROUTINE ; This subroutine returns with the carry flag set if the present value of the A ; register denotes a valid letter of the alphabet. ALPHA: ; 2c8d cp 0x41 ; Test against 41 hex, the code for 'A' ccf ; Complement the carry flag. ret nc ; Return if not a valid character code. cp 0x5b ; Test against 5B hex, 1 more than code ; for 'Z'. ret c ; Return if an upper case letter. cp 0x61 ; Test against 61 hex, the code for ; 'a'. ccf ; Complement the carry flag. ret nc ; Return if not a valid character code. cp 0x7b ; Test against 7B hex, 1 more than the ; code for 'z'. ret ; Finished. ; THE 'DECIMAL TO FLOATING POINT' SUBROUTINE ; As part of syntax checking decimal numbers that occur in a BASIC line are ; converted to their floating-point forms. This subroutine reads the decimal ; number digit by digit and gives its result as a 'last value' on the ; calculator stack. But first it deals with the alternative notation BIN, which ; introduces a sequence of 0's and 1's giving the binary representation of the ; required number. DEC_TO_FP: ; 2c9b cp 0xc4 ; Is the character a 'BIN'? jr nz,NOT_BIN ; Jump if it is not 'BIN'. ld de,0x0000 ; Initialise result to zero in DE. BIN_DIGIT: ; 2ca2 rst NEXT_CHAR ; Get the next character. sub 0x31 ; Subtract the character code for '1'. adc a,0x00 ; 0 now gives 0 with carry set; 1 gives ; 0 with carry reset. jr nz,BIN_END ; Any other character causes a jump to ; BIN-END and will be ; checked for syntax during or after ; scanning. ex de,hl ; Result so far to HL now. ccf ; Complement the carry flag. adc hl,hl ; Shift the result left, with the carry ; going to bit 0. jp c,REPORT_6a ; Report overflow if more than 65535. ex de,hl ; Return the result so far to DE. jr BIN_DIGIT ; Jump back for next 0 or 1. BIN_END: ; 2cb3 ld b,d ; Copy result to BC for stacking. ld c,e jp STACK_BC ; Jump forward to stack the result. ; For other numbers, first any integer part is converted; if the next character ; is a decimal, then the decimal fraction is considered. NOT_BIN: ; 2cb8 cp 0x2e ; Is the first character a '.'? jr z,DECIMAL ; If so, jump forward. call INT_TO_FP ; Otherwise, form a 'last value' of the ; integer. cp 0x2e ; Is the next character a '.'? jr nz,E_FORMAT ; Jump forward to see if it is an 'E'. rst NEXT_CHAR ; Get the next character. call NUMERIC ; Is it a digit? jr c,E_FORMAT ; Jump if not (e.g. 1.E4 is allowed). jr DEC_STO_1 ; Jump forward to deal with the digits ; after the decimal point. DECIMAL: ; 2ccb rst NEXT_CHAR ; If the number started with a call NUMERIC ; decimal, see if the next character is ; a digit. DEC_RPT_C: ; 2ccf jp c,REPORT_Cb ; Report the error if it is not. rst FP_CALC ; Use the calculator to stack zero db C_stk_zero ; as the integer part of such db C_end_calc ; numbers. DEC_STO_1: ; 2cd5 rst FP_CALC ; Use the calculator again. db C_stk_one ; Find the floating-point form of db C_st_mem_0 ; the decimal number '1', and db C_delete ; save it in the memory area. db C_end_calc NXT_DGT_1: ; 2cda rst GET_CHAR ; Get the present character. call STK_DIGIT ; If it is a digit then stack it. jr c,E_FORMAT ; If not jump forward. rst FP_CALC ; Now use the calculator. db C_get_mem_0 ; For each passage of the loop, db C_stk_ten ; the number saved in the memory db C_division ; area is fetched, divided by 10 db C_st_mem_0 ; and restored: i.e. going from .1 to ; .01 to .001 etc. db C_multiply ; The present digit is multiplied db C_addition ; by the 'saved number' and db C_end_calc ; added to the 'last value'. rst NEXT_CHAR ; Get the next character. jr NXT_DGT_1 ; Jump back (one more byte than needed) ; to consider it. ; Next consider any 'E notation', i.e. the form xEm or xem where m is a ; positive or negative integer. E_FORMAT: ; 2ceb cp 0x45 ; Is the present character an 'E'? jr z,SIGN_FLAG ; Jump forward if it is. cp 0x65 ; Is it an 'e'? ret nz ; Finished unless it is so. SIGN_FLAG: ; 2cf2 ld b,0xff ; Use B as a sign flag, FF for '+'. rst NEXT_CHAR ; Get the next character. cp 0x2b ; Is it a '+'? jr z,SIGN_DONE ; Jump forward. cp 0x2d ; Is it a '-'? jr nz,ST_E_PART ; Jump if neither '+' not '-'. inc b ; Change the sign of the flag. SIGN_DONE: ; 2cfe rst NEXT_CHAR ; Point to the first digit. ST_E_PART: ; 2cff call NUMERIC ; Is it indeed a digit? jr c,DEC_RPT_C ; Report the error if not. push bc ; Save the flag in B briefly. call INT_TO_FP ; Stack ABS m, where m is the exponent. call FP_TO_A ; Transfer ABS m to A. pop bc ; Restore the sign flag to B. jp c,REPORT_6a ; Report the overflow now if and a ; ABS m is greater than 255 or jp m,REPORT_6a ; indeed greater than 127 (other values ; greater than about 39 will ; be detected later). inc b ; Test the sign flag in B; '+' (i.e. ; +FF) will now set the zero ; flag. jr z,E_FP_JUMP ; Jump if sign of m is '+'. neg ; Negate m if sign is '-'. E_FP_JUMP: ; 2d18 jp E_TO_FP ; Jump to assign to the 'last value' ; the result of x*10^m. ; THE 'NUMERIC' SUBROUTINE ; This subroutine returns with the carry flag reset if the present value of the ; A register denotes a valid digit. NUMERIC: ; 2d1b cp 0x30 ; Test against 30 hex, the code for ; '0'. ret c ; Return if not a valid character code. cp 0x3a ; Test against the upper limit. ccf ; Complement the carry flag. ret ; Finished. ; THE 'STK DIGIT' SUBROUTINE ; This subroutine simply returns if the current value held in the A register ; does not repre- ; sent a digit but if it does then the floating-point form for the digit ; becomes the 'last ; value' on the calculator stack. STK_DIGIT: ; 2d22 call NUMERIC ; Is the character a digit? ret c ; Return if not in range. sub 0x30 ; Replace the code by the actual digit. ; THE 'STACK-A' SUBROUTINE ; This subroutine gives the floating-point form for the absolute binary value ; currently held in the A register. STACK_A: ; 2d28 ld c,a ; Transfer the value to the C register. ld b,0x00 ; Clear the B register ; THE 'STACK-BC' SUBROUTINE ; This subroutine gives the floating-point form for the absolute binary value ; currently held in the BC register pair. ; The form used in this and hence in the two previous subroutines as well is ; the one reserved in the Spectrum for small integers n, where -65535 <= n <= ; 65535. The first and fifth bytes are zero; the third and fourth bytes are the ; less significant and more significant bytes of the 16 bit integer n in two's ; complement form (if n is negative, these two bytes hold 65536+n); and the ; second byte is a sign byte, 00 for '+' and FF for '-'. STACK_BC: ; 2d2b ld iy,ERR_NR ; Re-initialise IY to ERR-NR. xor a ; Clear the A register. ld e,a ; And the E register, to indicate '+'. ld d,c ; Copy the less significant byte to D. ld c,b ; And the more significant byte to C. ld b,a ; Clear the B register. call STK_STORE ; Now stack the number. rst FP_CALC ; Make HL point to db C_end_calc ; STKEND-5. and a ; Clear the carry flag. ret ; Finished. ; THE 'INTEGER TO FLOATING-POINT' SUBROUTINE ; This subroutine returns a 'last value' on the calculator stack that is the ; result of converting an integer in a BASIC line, i.e. the integer part of the ; decimal number or the line number, to its floating-point form. ; Repeated calls to CH-ADD+1 fetch each digit of the integer in turn. An exit ; is made when a code that does not represent a digit has been fetched. INT_TO_FP: ; 2d3b push af ; Save the first digit - in A. rst FP_CALC ; Use the calculator. db C_stk_zero ; The 'last value' is now zero. db C_end_calc pop af ; Restore the first digit. ; Now a loop is set up. As long as the code represents a digit then the ; floating-point form is found and stacked under the 'last value'. The 'last ; value' is then multiplied by decimal 10 and added to the 'digit' to form a ; new 'last value' which is carried back to the start of the loop. NXT_DGT_2: ; 2d40 call STK_DIGIT ; If the code represents a digit ret c ; then stack the floating-point form. rst FP_CALC ; Use the calculator. db C_exchange ; 'Digit' goes under 'last value'. db C_stk_ten ; Define decimal 10. db C_multiply ; 'Last value' = 'last value' *10. db C_addition ; 'Last value' = 'last value+ 'digit'. db C_end_calc call CH_ADD_1 ; The next code goes into A. jr NXT_DGT_2 ; Loop back with this code. ; THE ARITHMETIC ROUTINES ; THE 'E-FORMAT TO FLOATING-POINT' SUBROUTINE ; (Offset 3C - see CALCULATE below: 'e-to-fp') ; This subroutine gives a 'last value' on the top of the calculator stack that ; is the result of converting a number given in the form xEm, where m is a ; positive or negative integer. The subroutine is entered with x at the top of ; the calculator stack and m in the A register. ; The method used is to find the absolute value of m, say p, and to multiply or ; divide x by 10^p according to whether m is positive or negative. ; To achieve this, p is shifted right until it is zero, and x is multiplied or ; divided by 10^(2^n) for each set bit b(n) of p. Since p is never much more ; than decimal 39, bits 6 and 7 of p will not normally be set. E_TO_FP: ; 2d4f rlca ; Test the sign of m by rotating rrca ; bit 7 of A into the carry without ; changing A. jr nc,E_SAVE ; Jump if m is positive. cpl ; Negate m in A without inc a ; disturbing the carry flag. E_SAVE: ; 2d55 push af ; Save m in A briefly. ld hl,MEMBOT ; This is MEMBOT: a sign flag is call FP_0_1 ; now stored in the first byte of ; mem-0, i.e. 0 for '+' and 1 for rst FP_CALC ; The stack holds x. db C_stk_ten ; x, 10 (decimal) db C_end_calc ; x, 10 pop af ; Restore m in A. E_LOOP: ; 2d60 srl a ; In the loop, shift out the next bit ; of m, modifying the carry ; and zero flags appropriately; jr nc,E_TST_END ; jump if carry reset. push af ; Save the rest of m and the flags. rst FP_CALC ; The stack holds x' and 10^(2^n), ; where x' is an ; interim stage in the multiplication ; of x by 10^m, and n= ; 0,1,2,3,4 or 5. db C_st_mem_1 ; (10^(2^n) is copied to mem-1). db C_get_mem_0 ; x', 10^(2^n), (1/0) db C_jump_true ; x', 10^(2^n) db E_DIVSN - $ ; x', 10^(2^n) db C_multiply ; x'*10^(2^n)= x" db C_jump ; x'' db E_FETCH - $ ; x'' E_DIVSN: ; 2d6d db C_division ; x/10^(2^n)=x'' (x'' is N'*10^ (2^n) ; or x'/10^(2^n) ; according as m is '+' ot '-'). E_FETCH: ; 2d6e db C_get_mem_1 ; x'', 10^(2^n) db C_end_calc ; x'', 10^(2^n) pop af ; Restore the rest of m in A, and the ; flags. E_TST_END: ; 2d71 jr z,E_END ; Jump if m has been reduced to zero. push af ; Save the rest of m in A. rst FP_CALC ; x'', 10^(2^n) db C_duplicate ; x'', 10^(2^n), 10^(2^n) db C_multiply ; x'', 10^(2^(n+1)) db C_end_calc ; x'', 10^(2^(n+1)) pop af ; Restore the rest of m in A. jr E_LOOP ; Jump back for all bits of m. E_END: ; 2d7b rst FP_CALC ; Use the calculator to delete the db C_delete ; final power of 10 reached, db C_end_calc ; leaving the 'last value' x*10^m ret ; on the stack ; THE 'INT-FETCH' SUBROUTINE ; This subroutine collects in DE a small integer n (-65535<=n<=65535) from the ; location addressed by HL: i.e. n is normally the first (or second) number at ; the top of the calcul-ator stack; but HL can alls access (by exchange with ; DE) a number which has been deleted from the stack. The subroutine does not ; itself delete the number from the stack or from memory; it returns HL ; pointing to the fourth byte of the number in its original position. INT_FETCH: ; 2d7f inc hl ; Point to the sign byte of the number. ld c,(hl) ; Copy the sign byte to C. ; The following mechanism will twos complement the number if it is negative (C ; is FF) but leave it unaltered if it is positive (C is 00) inc hl ; Point to the less significant byte. ld a,(hl) ; Collect the byte in A. xor c ; Ones complement it if negative sub c ; This adds 1 for negative numbers; it ; sets the carry unless ; the byte was 0. ld e,a ; Less significant byte to E now. inc hl ; Point to the more significant byte. ld a,(hl) ; Collect it in A. adc a,c ; Finish two complementing in xor c ; the case of a negative number; note ; that the carry is always ; left reset. ld d,a ; More significant byte to D now. ret ; Finished. ; THE 'INT-STORE' SUBROUTINE ; This subroutine stores a small integer n (-65535<=n<=65535) in the location ; addressed by HL and the four following locations: i.e. n replaces the first ; (or second) number at the top of the calculator stack. The subroutine returns ; HL pointing to the first byte of n on the stack. P_INT_STO: ; 2d8c ld c,0x00 ; This entry point would store a number ; known to be positive INT_STORE: ; 2d8e push hl ; The pointer to the first location is ; saved. ld (hl),0x00 ; The first byte is set to zero. inc hl ; Point to the second location. ld (hl),c ; Enter the second byte. ; The same mechanism is now used as in 'INT-FETCH' to twos complement negative ; numbers. This is needed e.g. before and after the multiplication of small ; integers. Addition is however performed without any further twos ; complementing before or afterwards. inc hl ; Point to the third location. ld a,e ; Collect the less significant byte. xor c ; Twos complement it if the sub c ; number is negative ld (hl),a ; Store the byte. inc hl ; Point to the fourth location. ld a,d ; Collect the more significant byte. adc a,c ; Twos complement it if the xor c ; number is negative ld (hl),a ; Store the byte. inc hl ; Point to the fifth location. ld (hl),0x00 ; The fifth byte is set to zero. pop hl ; Return with HL pointing to the ret ; first byte of n on the stack ; THE 'FLOATING-POINT TO BC' SUBROUTINE ; This subroutine is called from four different places for various purposes and ; is used to compress the floating-point 'last value' into the BC register ; pair. If the result is too large, i.e. greater than 65536 decimal, then the ; subroutine returns with the carry flag set. If the 'last value' is negative ; then the zero flag is reset. The low byte of the result is also copied to the ; A register. FP_TO_BC: ; 2da2 rst FP_CALC ; Use the calculator to make HL db C_end_calc ; point to STKEND-5 ld a,(hl) ; Collect the exponent byte of and a ; the 'last value'; jump if it is jr z,FP_DELETE ; zero, indicating a 'small integer'. rst FP_CALC ; Now use the calculator to round db C_stk_half ; the 'last value' to the nearest db C_addition ; integer, which also changes it to db C_int ; 'small integer' form on the db C_end_calc ; calculator stack if that is possible, ; i.e. if -65535.5 <= ; x < 65535.5 FP_DELETE: ; 2dad rst FP_CALC ; Use the calculator to delete the db C_delete ; integer from the stack; DE still db C_end_calc ; points to it in memory (at STKEND). push hl ; Save both stack pointers. push de ex de,hl ; HL now points to the number. ld b,(hl) ; Copy the first byte to B. call INT_FETCH ; Copy bytes 2, 3 and 4 to C, E and D. xor a ; Clear the A register. sub b ; This sets the carry unless B is zero. bit 7,c ; This sets the zero flag if the number ; is positive (NZ denotes ; negative). ld b,d ; Copy the high byte to B. ld c,e ; And the low byte to C. ld a,e ; Copy the low byte to A too. pop de ; Restore the stack pointers. pop hl ret ; Finished. ; THE 'LOG (2^A)' SUBROUTINE ; This subroutine is called by the 'PRINT-FP' subroutine to calculate the ; approximate number of digits before the decimal in x, the number to be ; printed, or, if there are no digits before the decimal, then the approximate ; number of leading zeros after the decimal. It is entered with the A register ; containing e', the true exponent of x, or e'-2, and calculates z=log to the ; base 10 of (2^A). It then sets A equal to ABS INT (z + 0.5), as required, ; using FP-TO-A for this purpose. LOG__2_A_: ; 2dc1 ld d,a ; The integer A is stacked, either rla ; as 00 00 A 00 00 (for positive sbc a,a ; A) or as 00 FF A FF 00 (for negative ; A). ld e,a ; These bytes are first loaded into ld c,a ; A, E, D, C, B and then STK- xor a ; STORE is called to put the ld b,a ; number on the calculator stack. call STK_STORE rst FP_CALC ; The calculator is used db C_stk_data ; Log 2 to the base 10 is now stacked. db 4-1<<6|0x7f-0x50 ; The stack now holds A, log 2. db 0x1a,0x20,0x9a,0x85 db C_multiply ; A*log 2 i.e. log (2^A) db C_int ; INT log (2^A) db C_end_calc ; The subroutine continues on into FP-TO-A to complete the calculation. ; THE 'FLOATING-POINT TO A' SUBROUTINE ; This short but vital subroutine is called at least 8 times for various ; purposes. It uses the last but one subroutine, FP-TO-BC, to get the 'last ; value' into the A register where this is possible. It therefore tests whether ; the modulus of the number rounds to more than 255 and if it does the ; subroutine returns with the carry flag set. Otherwise it returns with the ; modulus of the number, rounded to the nearest integer, in the A register, and ; the zero flag set to imply that the number was positive, or reset to imply ; that it was negative. FP_TO_A: ; 2dd5 call FP_TO_BC ; Compress the 'last value' into BC. ret c ; Return if out of range already. push af ; Save the result and the flags. dec b ; Again it will be out of range inc b ; if the B register does not hold zero. jr z,FP_A_END ; Jump if in range. pop af ; Fetch the result and the flags scf ; Signal the result is out of range. ret ; Finished - unsuccessful. FP_A_END: ; 2de1 pop af ; Fetch the result and the flags. ret ; Finished - successful. ; THE 'PRINT A FLOATING-POINT NUMBER' SUBROUTINE ; This subroutine is called by the PRINT command routine at 2039 and by STR$ at ; 3630, which converts to a string the number as it would be printed. The ; subroutine prints x, the 'last value' on the calculator stack. The print ; format never occupies more than 14 spaces. ; The 8 most significant digits of x, correctly rounded, are stored in an ad ; hoc print buffer in mem-3 and mem-4. Small numbers, numerically less than 1, ; and large numbers, numerically greater than 2 ^ 27, are dealt with ; separately. The former are multiplied by 10 ^ n, where n is the approximate ; number of leading zeros after the decimal, while the latter are divided by 10 ; ^ (n-7), where n is the approximate number of digits before the decimal. This ; brings all numbers into the middle range, and the numbers of digits required ; before the decimal is built up in the second byte of mem-5. Finally the ; printing is done, using E-format if there are more than 8 digits before the ; decimal or, for small numbers, more than 4 leading zeros after the decimal. ; The following program shows the range of print formats: ; 10 FOR a=-11 TO 12: PRINT SGN a*9^a,: NEXT a ; i. First the sign of x is taken care of: ; If X is negative, the subroutine jumps to PF-NEGATIVE, takes ABS x and ; prints the minus sign. ; If x is zero, x is deleted from the calculator stack, a '0' is printed ; and a return is made from the subroutine. ; If x is positive, the subroutine just continues. PRINT_FP: ; 2de3 rst FP_CALC ; Use the calculator db C_duplicate ; x, x db C_less_0 ; x, (1/0) Logical value of x. db C_jump_true ; x db PF_NEGTVE - $ ; x db C_duplicate ; x, x db C_greater_0 ; x, (1/0) Logical value of X. db C_jump_true ; x db PF_POSTVE - $ ; x Hereafter x'=ABS x. db C_delete ; - db C_end_calc ; - ld a,0x30 ; Enter the character code for '0'. rst PRINT_A_1 ; Print the '0'. ret ; Finished as the 'last value' is zero. PF_NEGTVE: ; 2df2 db C_abs ; x' x'=ABS x. db C_end_calc ; x' ld a,0x2d ; Enter the character code for '-'. rst PRINT_A_1 ; Print the '-'. rst FP_CALC ; Use the calculator again. PF_POSTVE: ; 2df8 db C_stk_zero ; The 15 bytes of mem-3, mem-4 db C_st_mem_3 ; and mem-5 are now initialised to db C_st_mem_4 ; zero to be used for a print db C_st_mem_5 ; buffer and two counters. db C_delete ; The stack is cleared, except for x'. db C_end_calc ; x' exx ; H'L', which is used to hold push hl ; calculator offsets, (e.g. for exx ; 'STR$') is saved on the machine ; stack. ; ii. This is the start of a loop which deals with large numbers. However ; every number x is first split into its integer part i and the fractional part ; f. If i is a small integer, i.e. if -65535 <= i <= 65535, it is stored in ; D'E' for insertion into the print buffer. PF_LOOP: ; 2e01 rst FP_CALC ; Use the calculator again. db C_duplicate ; x', x' db C_int ; x', INT (x')=i db C_st_mem_2 ; (i is stored in mem-2). db C_subtract ; x'-i=f db C_get_mem_2 ; f, i db C_exchange ; i, f db C_st_mem_2 ; (f is stored in mem-2). db C_delete ; i db C_end_calc ; i ld a,(hl) ; Is i a small integer (first byte and a ; zero) i.e. is ABS i <= 65535? jr nz,PF_LARGE ; Jump if it is not call INT_FETCH ; i is copied to DE (i, like x', >=0). ld b,0x10 ; B is set to count 16 bits. ld a,d ; D is copied to A for testing: and a ; Is it zero? jr nz,PF_SAVE ; Jump if it is not zero. or e ; Now test E. jr z,PF_SMALL ; Jump if DE zero: x is a pure ; fraction. ld d,e ; Move E to D and set B for 8 ld b,0x08 ; bits: D was zero and E was not. PF_SAVE: ; 2e1e push de ; Transfer DE to D'E', via the exx ; machine stack, to be moved pop de ; into the print buffer at exx ; PF-BITS. jr PF_BITS ; Jump forward. ; iii. Pure fractions are multiplied by 10^n, where n is the approximate number ; of leading zeros after the decimal; and -n is added to the second byte of ; mem-5, which holds the number of digits needed before the decimal; a negative ; number here indicates leading zeros after the decimal; PF_SMALL: ; 2e24 rst FP_CALC ; i (i=zero here), db C_get_mem_2 ; i, f db C_end_calc ; i, f ; Note that the stack is now unbalanced. An extra byte 'DEFB +02, delete' is ; needed at 2E25, immediately after the RST 0028. Now an expression like "2" ; +STR$ 0.5 is evaluated incorrectly as 0.5; the zero left on the stack ; displaces the "2" and is treated as a null string. Similarly all the string ; comparisons can yield incorrect values if the second string takes the form ; STR$ x where x is numerically less than 1; e.g. the expression "50"= ; A >= 111. add a,0x91 ; Range is now 15 dec >= A >= 0. inc hl ; Point HL at second byte. ld d,(hl) ; Second byte to D. inc hl ; Point HL at third byte. ld e,(hl) ; Third byte to E. dec hl ; Point HL at first byte again. dec hl ld c,0x00 ; Assume a positive number. bit 7,d ; Now test for negative (bit 7 set). jr z,T_NUMERIC ; Jump if positive after all. dec c ; Change the sign. T_NUMERIC: ; 3252 set 7,d ; Insert true numeric bit, 1, in D. ld b,0x08 ; Now test whether A >= 8 (one sub b ; byte only) or two bytes needed. add a,b ; Leave A unchanged. jr c,T_TEST ; Jump if two bytes needed. ld e,d ; Put the one byte into E. ld d,0x00 ; And set D to zero. sub b ; Now 1 <= A <= 7 to count the shifts ; needed. T_TEST: ; 325e jr z,T_STORE ; Jump if no shift needed. ld b,a ; B will count the shifts. T_SHIFT: ; 3261 srl d ; Shift D and E right B times to rr e ; produce the correct number. djnz T_SHIFT ; Loop until B is zero. T_STORE: ; 3267 call INT_STORE ; Store the result on the stack. pop de ; Restore STKEND to DE. ret ; Finished. ; Large values of x remains to be considered. T_EXPNENT: ; 326c ld a,(hl) ; Get the exponent byte of x into A. X_LARGE: ; 326d sub 0xa0 ; Subtract 160 decimal, A0 hex, from e. ret p ; Return on plus - x has no significant ; non-integral part. (If ; the true exponent were reduced to ; zero, the 'binary point' ; would come at or after the end of the ; four bytes of the man- ; tissa). neg ; Else, negate the remainder; this ; gives the number of bits to ; become zero (the number of bits after ; the 'binary point'). ; Now the bits of the mantissa can be cleared. NIL_BYTES: ; 3272 push de ; Save the current value of DE ; (STKEND). ex de,hl ; Make HL point one past the fifth ; byte. dec hl ; HL now points to the fifth byte of x. ld b,a ; Get the number of bits to be set srl b ; to zero in B and divide it by B srl b ; to give the number of whole srl b ; bytes implied. jr z,BITS_ZERO ; Jump forward if the result is zero. BYTE_ZERO: ; 327e ld (hl),0x00 ; Else, set the bytes to zero; dec hl ; B counts them. djnz BYTE_ZERO BITS_ZERO: ; 3283 and 0x07 ; Get A (mod 8); this is the number of ; bits still to be set to zero. jr z,IX_END ; Jump to the end if nothing more to ; do. ld b,a ; B will count the bits now. ld a,0xff ; Prepare the mask. LESS_MASK: ; 328a sla a ; With each loop a zero enters the djnz LESS_MASK ; mask from the right and thereby a ; mask of the correct length is ; produced. and (hl) ; The unwanted bits of (HL) are ld (hl),a ; lost as the masking is performed. IX_END: ; 3290 ex de,hl ; Return the pointer to HL. pop de ; Return STKEND to DE. ret ; Finished. ; THE 'RE-STACK TWO' SUBROUTINE ; This subroutine is called to re-stack two 'small integers' in full five byte ; floating-point form for the binary operations of addition, multiplication and ; division. It does so by calling the following subroutine twice. RE_ST_TWO: ; 3293 call RESTK_SUB ; Call the subroutine and then continue ; into it for the second ; call. RESTK_SUB: ; 3296 ex de,hl ; Exchange the pointers at each call. ; THE 'RE-STACK' SUBROUTINE ; (Offset 3D - see CALCULATE below: 're-stack') ; This subroutine is called to re-stack one number (which could be a 'small ; integer') in full five byte floating-point form. It is used for a single ; number by ARCTAN and also, through the calculator offset, by EXP, LN and ; 'get-argt'. RE_STACK: ; 3297 ld a,(hl) ; If the first byte is not zero, and a ; return - the number cannot be ret nz ; a 'small integer'. push de ; Save the 'other' pointer in DE. call INT_FETCH ; Fetch the sign in C and the number in ; DE. xor a ; Clear the A register. inc hl ; Point to the fifth location. ld (hl),a ; Set the fifth byte to zero. dec hl ; Point to the fourth location. ld (hl),a ; Set the fourth byte to zero: bytes 2 ; and 3 will hold the man- ; tissa. ld b,0x91 ; Set B to 145 dec for the exponent ; i.e. for up to 16 bits ; in the integer. ld a,d ; Test whether D is zero so that and a ; at most 8 bits would be needed. jr nz,RS_NRMLSE ; Jump if more than 8 bits needed. or e ; Now test E too. ld b,d ; Save the zero in B (it will give zero ; exponent if E too is zero). jr z,RS_STORE ; Jump if E is indeed zero. ld d,e ; Move E to D (D was zero, E not). ld e,b ; Set E to zero now. ld b,0x89 ; Set B to 137 dec for the exponent - ; no more than 8 bits ; now. RS_NRMLSE: ; 32b1 ex de,hl ; Pointer to DE, number to HL. RSTK_LOOP: ; 32b2 dec b ; Decrement the exponent on each shift. add hl,hl ; Shift the number right one position. jr nc,RSTK_LOOP ; Until the carry is set. rrc c ; Sign bit to carry flag now. rr h ; Insert it in place as the number rr l ; is shifted back one place - normal ; now. ex de,hl ; Pointer to byte 4 back to HL. RS_STORE: ; 32bd dec hl ; Point to the third location. ld (hl),e ; Store the third byte. dec hl ; Point to the second location. ld (hl),d ; Store the second byte. dec hl ; Point to the first location. ld (hl),b ; Store the exponent byte. pop de ; Restore the 'other' pointer to DE. ret ; Finished. ; THE FLOATING-POINT CALCULATOR C_jump_true equ 0x00 C_exchange equ 0x01 C_delete equ 0x02 C_subtract equ 0x03 C_multiply equ 0x04 C_division equ 0x05 C_to_power equ 0x06 C_or equ 0x07 C_no_no equ 0x08 C_no_l_eql equ 0x09 C_no_gr_eq equ 0x0a C_nos_neql equ 0x0b C_no_grtr equ 0x0c C_no_less equ 0x0d C_nos_eql equ 0x0e C_addition equ 0x0f C_str_no equ 0x10 C_str_l_eql equ 0x11 C_str_gr_eq equ 0x12 C_strs_neql equ 0x13 C_str_grtr equ 0x14 C_str_less equ 0x15 C_strs_eql equ 0x16 C_strs_add equ 0x17 C_val_ equ 0x18 C_usr_ equ 0x19 C_read_in equ 0x1a C_negate equ 0x1b C_code equ 0x1c C_val equ 0x1d C_len equ 0x1e C_sin equ 0x1f C_cos equ 0x20 C_tan equ 0x21 C_asn equ 0x22 C_acs equ 0x23 C_atn equ 0x24 C_ln equ 0x25 C_exp equ 0x26 C_int equ 0x27 C_sqr equ 0x28 C_sgn equ 0x29 C_abs equ 0x2a C_peek equ 0x2b C_in equ 0x2c C_usr_no equ 0x2d C_str_ equ 0x2e C_chr_ equ 0x2f C_not equ 0x30 C_duplicate equ 0x31 C_n_mod_m equ 0x32 C_jump equ 0x33 C_stk_data equ 0x34 C_dec_jr_nz equ 0x35 C_less_0 equ 0x36 C_greater_0 equ 0x37 C_end_calc equ 0x38 C_get_argt equ 0x39 C_truncate equ 0x3a C_fp_calc_2 equ 0x3b C_e_to_fp equ 0x3c C_re_stack equ 0x3d C_stk_zero equ 0xa0 C_stk_one equ 0xa1 C_stk_half equ 0xa2 C_stk_pi_2 equ 0xa3 C_stk_ten equ 0xa4 C_st_mem_0 equ 0xc0 C_st_mem_1 equ 0xc1 C_st_mem_2 equ 0xc2 C_st_mem_3 equ 0xc3 C_st_mem_4 equ 0xc4 C_st_mem_5 equ 0xc5 C_get_mem_0 equ 0xe0 C_get_mem_1 equ 0xe1 C_get_mem_2 equ 0xe2 C_get_mem_3 equ 0xe3 C_get_mem_4 equ 0xe4 C_get_mem_5 equ 0xe5 ; THE FLOATING-POINT CALCULATOR ; THE TABLE OF CONSTANTS ; This first table holds the five useful and frequently needed numbers zero, ; one, a half, a half of pi and ten. The numbers are held in a condensed form ; which is expanded by the STACK LITERALS subroutine, see below, to give the ; required floating-point form. FP_stk_zero: ; 32c5 db 0x00 ; zero 00 00 00 00 00 db 0xb0 db 0x00 FP_stk_one: ; 32c8 db 0x40 ; one 00 00 01 00 00 db 0xb0 db 0x00 db 0x01 FP_stk_half: ; 32cc db 0x30 ; a half 80 00 00 00 00 db 0x00 STK_PI_2: ; 32ce db 0xf1 ; a half of pi 81 49 0F DA A2 db 0x49 db 0x0f db 0xda db 0xa2 FP_stk_ten: ; 32d3 db 0x40 ; ten 00 00 0A 00 00 db 0xb0 db 0x00 db 0x0a ; THE TABLE OF ADDRESSES: ; This second table is a look-up table of the addresses of the sixty-six ; operational subroutines of the calculator. The offsets used to index into the ; table are derived either from the operation codes used in SCANNING, see 2734, ; etc., or from the literals that follow a RST 0028 instruction. CALCULATOR_LU: ; 32d7 dw FP_jump_true dw EXCHANGE dw FP_delete dw SUBTRACT dw FP_multiply dw FP_division dw FP_to_power dw FP_or dw FP_no_n_no dw FP_no_l_eql_etc dw FP_no_l_eql_etc dw FP_no_l_eql_etc dw FP_no_l_eql_etc dw FP_no_l_eql_etc dw FP_no_l_eql_etc dw FP_addition dw FP_str_n_no dw FP_no_l_eql_etc dw FP_no_l_eql_etc dw FP_no_l_eql_etc dw FP_no_l_eql_etc dw FP_no_l_eql_etc dw FP_no_l_eql_etc dw FP_strs_add dw FP_val dw USR__ dw FP_read_in dw NEGATE dw FP_code dw FP_val dw FP_len dw FP_sin dw FP_cos dw FP_tan dw FP_asn dw FP_acs dw FP_atn dw FP_ln dw EXP dw FP_int dw FP_sqr dw FP_sgn dw FP_abs dw FP_peek dw FP_in dw FP_usr_no dw STR_ dw FP_chrs dw NOT_ dw MOVE_FP dw FP_n_mod_m dw JUMP dw STK_DATA dw FP_dec_jr_nz dw FP_less_0 dw GREATER_0 dw FP_end_calc dw FP_get_argt dw FP_truncate dw FP_fp_calc_2 dw E_TO_FP dw RE_STACK dw FP_series_06_etc dw FP_stk_zero_etc dw FP_st_mem_0_etc dw FP_get_mem_0_etc ; Note: The last four subroutines are multi-purpose subroutines and are entered ; with a parameter that is a copy of the right hand five bits of the original ; literal. The full set follows: ; Offset 3E: series-06, series-08, & series-0C; literals 86,88 & 8C. ; Offset 3F: stk-zero, stk-one, stk-half, stk-pi/2 & stk-ten; literals A0 to ; A4. ; Offset 40: st-mem-0, st-mem-1, st-mem-2, st-mem-3, st-mem-4 & st-mem-5; ; literals C0 to C5. ; Offset 41: get-mem-0, get-mem-1, get-mem-2, get-mem-3, get-mem-4 & ; get-mem-5; literals E0 to E5. ; THE 'CALCULATE' SUBROUTINE ; This subroutine is used to perform floating-point calculations. These can be ; considered to be of three types: ; i. Binary operations, e.g. addition, where two numbers in floating-point form ; are added together to give one 'last value'. ; ii. Unary operations, e.g. sin, where the 'last value' is changed to give the ; appropriate function result as a new 'last value'. ; iii. Manipulatory operations, e.g. st-mem-0, where the 'last value' is copied ; to the first five bytes of the calculator's memory area. ; The operations to be performed are specified as a series of data-bytes, the ; literals, that follow an RST 0028 instruction that calls this subroutine. The ; last literal in the list is always '38' which leads to an end to the whole ; operation. ; In the case of a single operation needing to be performed, the operation ; offset can be passed to the CALCULATOR in the B register, and operation '3B', ; the SINGLE CALCULATION operation, performed. ; It is also possible to call this subroutine recursively, i.e. from within ; itself, and in such a case it is possible to use the system variable BREG as ; a counter that controls how many operations are performed before returning. ; The first part of this subroutine is complicated but essentially it ; performs the two tasks of setting the registers to hold their required ; values, and to produce an offset, and possibly a parameter, from the literal ; that is currently being considered. ; The offset is used to index into the calculator's table of addresses, see ; above, to find the required subroutine address. ; The parameter is used when the multi-purpose subroutines are called. ; Note: A floating-point number may in reality be a set of string parameters. CALCULATE: ; 335b call STK_PNTRS ; Presume a unary operation and ; therefore set HL to point to the ; start of the 'last value' on the ; calculator stack and DE one- ; past this floating-point number ; (STKEND). GEN_ENT_1: ; 335e ld a,b ; Either, transfer a single ld (BREG),a ; operation offset to BREG temporarily, ; or, when using the ; subroutine recursively pass the ; parameter to BREG to be used ; as a counter. GEN_ENT_2: ; 3362 exx ; The return address of the sub- ex (sp),hl ; routine is store in H'L'. This exx ; saves the pointer to the first ; literal. Entering the CALCUL- ; ATOR at GEN-ENT-2 is used whenever ; BREG is in use as a ; counter and is not to be disturbed. RE_ENTRY: ; 3365 ld (STKEND),de ; A loop is now entered to handle each ; literal in the list that ; follows the calling instruction; so ; first, always set to STKEND. exx ; Go to the alternate register set, ld a,(hl) ; and fetch the literal for this loop. inc hl ; Make H'L' point to the next literal. SCAN_ENT: ; 336c push hl ; This pointer is saved briefly on the ; machine stack. SCAN-ENT ; is used by the SINGLE CALCULATION ; subroutine to find ; the subroutine that is required. and a ; Test the A register. jp p,FIRST_3D ; Separate the simple literals from the ; multi-purpose literals. Jump ; with literals 00 - 3D. ld d,a ; Save the literal in D. and 0x60 ; Continue only with bits 5 & 6. rrca ; Four right shifts make them rrca ; now bits 1 & 2. rrca rrca add a,0x7c ; The offsets required are 3E-41. ld l,a ; and L will now hold double the ; required offset. ld a,d ; Now produce the parameter by and 0x1f ; taking bits 0,1,2,3 & 4 of the ; literal; keep the parameter in A. jr ENT_TABLE ; Jump forward to find the address of ; the required sub- ; routine. FIRST_3D: ; 3380 cp 0x18 ; Jump forward if performing a jr nc,DOUBLE_A ; unary operation. exx ; All of the subroutines that per- ld bc,0xfffb ; form binary operations require ld d,h ; that HL points to the first operand ld e,l ; and DE points to the second add hl,bc ; operand (the 'last value') as they exx ; appear on the calculator stack. DOUBLE_A: ; 338c rlca ; As each entry in the table of ld l,a ; addresses takes up two bytes the ; offset produced is doubled. ENT_TABLE: ; 338e ld de,CALCULATOR_LU ; The base address of the table. ld h,0x00 ; The address of the required add hl,de ; table entry is formed in HL; and ld e,(hl) ; the required subroutine address inc hl ; is loaded into the DE register ld d,(hl) ; pair. ld hl,RE_ENTRY ; The RE-ENTRY address of 3365 ex (sp),hl ; is put on the machine stack push de ; underneath the subroutine address. exx ; Return to the main set of registers. ld bc,(STKEND+1) ; The current value of BREG is ; transferred to the B register ; thereby returning the single ; operation offset. ; (See COMPARISON at 353B). FP_delete: ; 33a1 ret ; An indirect jump to the required ; subroutine. ; THE 'DELETE' SUBROUTINE ; (Offset 02: 'delete) ; This subroutine contains only the single RET instruction at 33A1, above. The ; literal '02' results in this subrouting being considered as a binary ; operation that is to be entered with a first number addressed by the HL ; register pair and a second number addressed by the DE register pair, and the ; result produced again addressed by the HL register pair. ; The single RET instruction thereby leads to the first number being considered ; as the resulting 'last value' and the second number considered as being ; deleted. Of course the number has not been deleted from the memory but ; remains inactive and will probably soon be overwritten. ; THE 'SINGLE OPERATION' SUBROUTINE ; (Offset 3B: 'fp-calc-2') ; This subroutine is only called from SCANNING at 2757 hex and is used to ; perform a single arithmetic operation. The offset that specifies which ; operation is to be performed is supplied to the calculator in the B register ; and subsequently transferred to the system variable BREG. ; The effect of calling this subroutine is essentially to make a jump to the ; appropriate subroutine for the single operation. FP_fp_calc_2: ; 33a2 pop af ; Discard the RE-ENTRY address. ld a,(BREG) ; Transfer the offset to A. exx ; Enter the alternate register set. jr SCAN_ENT ; Jump back to find the required ; address; stack the RE-ENTRY ; address and jump to the subroutine ; for the operation. ; THE 'TEST 5-SPACES' SUBROUTINE ; This subroutine tests whether there is sufficient room in memory for another ; 5-byte floating-point number to be added to the calculator stack. TEST_5_SP: ; 33a9 push de ; Save DE briefly. push hl ; Save HL briefly. ld bc,0x0005 ; Specify the test is for 5 bytes. call TEST_ROOM ; Make the test. pop hl ; Restore HL. pop de ; Restore DE. ret ; Finished. ; THE 'STACK NUMBER' SUBROUTINE ; This subroutine is called by BEEP and SCANNING twice to copy STKEND to DE, ; move a floating-point number to the calculator stack, and reset STKEND from ; DE. It calls 'MOVE-FP' to do the actual move. STACK_NUM: ; 33b4 ld de,(STKEND) ; Copy STKEND to DE as destination ; address. call MOVE_FP ; Move the number. ld (STKEND),de ; Reset STKEND from DE. ret ; Finished. ; THE 'MOVE A FLOATING-POINT NUMBER' SUBROUTINE ; (Offset 31: 'duplicate') ; This subroutine moves a floating-point number to the top of the calculator ; stack (3 cases) or from the top of the stack to the calculator's memory area ; (1 case). It is also called through the calculator when it simply duplicates ; the number at the top of the calculator stack, the 'last value', thereby ; extending the stack by five bytes. MOVE_FP: ; 33c0 call TEST_5_SP ; A test is made for room. ldir ; Move the five bytes involved. ret ; Finished. ; THE 'STACK LITERALS' SUBROUTINE ; (Offset 34: 'stk-data') ; This subroutine places on the calculator stack, as a 'last value', the ; floating-point number supplied to it as 2, 3, 4 or 5 literals. ; When called by using offset '34' the literals follow the '34' in the list of ; literals; when called by the SERIES GENERATOR, see below, the literals are ; supplied by the sub-routine that called for a series to be generated; and ; when called by SKIP CONSTANTS & STACK A CONSTANT the literals are obtained ; from the calculator's table of constants (32C5-32D6). ; In each case, the first literal supplied is divided by Hex.40, and the ; integer quotient plus 1 determines whether 1, 2, 3 or 4 further literals will ; be taken from the source to form the mantissa of the number. Any unfilled ; bytes of the five bytes that go to form a 5-byte floating-point number are ; set to zero. The first literal is also used to determine the exponent, after ; reducing mod Hex.40, unless the remainder is zero, in which case the second ; literal is used, as it stands, without reducing mod Hex.40. In either case, ; Hex.50 is added to the literal, giving the augmented exponent byte, e (the ; true exponent e' plus Hex.80). The rest of the 5 bytes are stacked, including ; any zeros needed, and the subroutine returns. STK_DATA: ; 33c6 ld h,d ; This subroutine performs the ld l,e ; manipulatory operation of adding a ; 'last value' to the cal- ; culator stack; hence HL is set to ; point one-past the present ; 'last value' and hence point to the ; result. STK_CONST: ; 33c8 call TEST_5_SP ; Now test that there is indeed room. exx ; Go to the alternate register set push hl ; and stack the pointer to the exx ; next literal. ex (sp),hl ; Switch over the result pointer and ; the next literal pointer. push bc ; Save BC briefly. ld a,(hl) ; The first literal is put into A and 0xc0 ; and divided by Hex.40 to give rlca ; the integer values 0, 1, 2 or 3. rlca ld c,a ; The integer value is transferred inc c ; to C and incremented, thereby giving ; the range 1, 2, 3 or 4 for ; the number of literals that will be ; needed. ld a,(hl) ; The literal is fetch anew, and 0x3f ; reduced mod Hex.40 and dis- jr nz,FORM_EXP ; carded as inappropriate if the inc hl ; remainder is zero; in which case ld a,(hl) ; the next literal is fetched and used ; unreduced. FORM_EXP: ; 33de add a,0x50 ; The exponent, e, is formed by ld (de),a ; the addition of Hex.50 and passed to ; the calculator stack as ; the first of the five bytes of the ; result. ld a,0x05 ; The number of literals specified sub c ; in C are taken from the source inc hl ; and entered into the bytes of inc de ; the result. ld b,0x00 ldir pop bc ; Restore BC. ex (sp),hl ; Return the result pointer to HL exx ; and the next literal pointer to pop hl ; its usual position in H' & L'. exx ld b,a ; The number of zero bytes xor a ; required at this stage is given by STK_ZEROS: ; 33f1 dec b ; 5-C-1; and this number of zeros ret z ; is added to the result to make ld (de),a ; up the required five bytes. inc de jr STK_ZEROS ; THE 'SKIP CONSTANTS' SUBROUTINE ; This subroutine is entered with the HL register pair holding the base address ; of the calculator's table of constants and the A register holding a parameter ; that shows which of the five constants is being requested. ; The subroutine performs the null operations of loading the five bytes of each ; unwanted constant into the locations 0000, 0001, 0002, 0003 and 0004 at the ; beginning of the ROM until the requested constant is reached. ; The subroutine returns with the HL register pair holding the base address of ; the requested constant within the table of constants. SKIP_CONS: ; 33f7 and a ; The subroutine returns if the SKIP_NEXT: ; 33f8 ret z ; parameter is zero, or when the ; requested constant has been ; reached. push af ; Save the parameter. push de ; Save the result pointer. ld de,0x0000 ; The dummy address. call STK_CONST ; Perform imaginary stacking of an ; expanded constant. pop de ; Restore the result pointer. pop af ; Restore the parameter. dec a ; Count the loops. jr SKIP_NEXT ; Jump back to consider the value of ; the counter. ; THE 'MEMORY LOCATION' SUBROUTINE ; This subroutine finds the base address for each five byte portion of the ; calculator's memory area to or from which a floating-point number is to be ; moved from or to the calculator stack. It does this operation by adding five ; times the parameter supplied to the base address for the area which is held ; in the HL register pair. ; Note that when a FOR-NEXT variable is being handled then the pointers are ; changed so that the variable is treated as if it were the calculator's memory ; area (see address 1D20). LOC_MEM: ; 3406 ld c,a ; Copy the parameter to C. rlca ; Double the parameter. rlca ; Double the result. add a,c ; Add the value of the parameter to ; give five times the original ; value. ld c,a ; This result is wanted in the ld b,0x00 ; BC register pair. add hl,bc ; Produce the new base address. ret ; Finished. ; THE 'GET FROM MEMORY AREA' SUBROUTINE ; (Offsets E0 to E5: 'get-mem-0' to 'get-mem-5') ; This subroutine is called using the literals E0 to E5 and the parameter ; derived from these literals is held in the A register. The subroutine calls ; MEMORY LOCATION to put the required source address into the HL register pair ; and MOVE A FLOATING-POINT NUMBER to copy the five bytes involved from the ; calculator's memory area to the top of the calculator stack to form a new ; 'last value'. FP_get_mem_0_etc: ; 340f push de ; Save the result pointer. ld hl,(MEM) ; Fetch the pointer to the current ; memory area (see above). call LOC_MEM ; The base address is found. call MOVE_FP ; The five bytes are moved. pop hl ; Set the result pointer. ret ; Finished. ; THE 'STACK A CONSTANT' SUBROUTINE ; (offsets A0 to A4: 'stk-zero','stk-one','stk-half','stk-pi/2' & 'stk-ten') ; This subroutine uses SKIP CONSTANTS to find the base address of the requested ; constants from the calculator's table of constants and then calls STACK ; LITERALS, entering at STK-CONST, to make the expanded form of the constant ; the 'last value' on the calculator stack. FP_stk_zero_etc: ; 341b ld h,d ; Set HL to hold the result ; pointer. ld l,e exx ; Go to the alternate register set push hl ; and save the next literal pointer. ld hl,FP_stk_zero ; The base address of the calculator's ; table of constants. exx ; Back to the main set of registers. call SKIP_CONS ; Find the requested base address. call STK_CONST ; Expand the constant. exx pop hl ; Restore the next literal pointer. exx ret ; Finished. ; THE 'STORE IN MEMORY AREA' SUBROUTINE ; (Offsets C0 to C5: 'st-mem-0' to 'st-mem-5') ; This subroutine is called using the literals C0 to C5 and the parameter ; derived from these literals is held in the A register. This subroutine is ; very similar to the GET FROM MEMORY subroutine but the source and destination ; pointers are exchanged. FP_st_mem_0_etc: ; 342d push hl ; Save the result pointer. ex de,hl ; Source to DE briefly. ld hl,(MEM) ; Fetch the pointer to the current ; memory area. call LOC_MEM ; The base address is found. ex de,hl ; Exchange source and destination ; pointers. call MOVE_FP ; The five bytes are moved. ex de,hl ; 'Last value' +5, i.e. STKEND, to DE. pop hl ; Result pointer to HL. ret ; Finished. ; Note that the pointers HL and DE remain as they were, pointing to STKEND-5 ; and STKEND respectively, so that the 'last value' remains on the calculator ; stack. If required it can be removed by using 'delete'. ; THE 'EXCHANGE' SUBROUTINE ; (Offset 01: 'exchange') ; This binary operation 'exchanges' the first number with the second number, ; i.e. the topmost two numbers on the calculator stack are exchanged. EXCHANGE: ; 343c ld b,0x05 ; There are five bytes involved. SWAP_BYTE: ; 343e ld a,(de) ; Each byte of the second number. ld c,(hl) ; Each byte of the first number. ex de,hl ; Switch source and destination. ld (de),a ; Now to the first number. ld (hl),c ; Now to the second number inc hl ; Move to consider the next pair inc de ; of bytes. djnz SWAP_BYTE ; Exchange the five bytes. ex de,hl ; Get the pointers correct as the ; number 5 is an odd number. ret ; Finished. ; THE 'SERIES GENERATOR' SUBROUTINE ; (Offsets 86,88 & 8C: 'series-06','series-08' & 'series-0C') ; This important subroutine generates the series of Chebyshev polynomials which ; are used to approximate to SIN, ATN, LN and EXP and hence to derive the other ; arithmetic functions which depend on these (COS, TAN, ASN, ACS, ** and SQR). ; The polynomials are generated, for n=1,2,..., by the recurrence relation: ; Tn+1(z) = 2zTn(z) - Tn-1(z), where Tn(z) is the nth Chebyshev polynomial in ; z. ; The series in fact generates: ; T0, 2T1, 2T2,.... ,2Tn-1, where n is 6 for SIN, 8 for EXP and 12 decimal, for ; LN and ATN. ; The coefficients of the powers of z in these polynomials may be found in the ; Handbook of Mathematical Functions by M. Abramowitz and I.A. Stegun (Dover ; 1965), page 795. ; BASIC programs showing the generation of each of the four functions are given ; here in the Appendix. ; In simple terms this subroutine is called with the 'last value' on the ; calculator stack, say Z, being a number that bears a simple relationship to ; the argument, say X, when the task is to evaluate, for instance, SIN X. The ; calling subroutine also supplies the list of constants that are to be ; required (six constants for SIN). The SERIES GENERATOR then manipulates its ; data and returns to the calling routine a 'last value' that bears a simple ; relationship to the requested function, for instance, SIN X. ; This subroutine can be considered to have four major parts: ; i. The setting of the loop counter: ; The calling subroutine passes its parameters in the A register for use as a ; counter. The calculator is entered at GEN-ENT-1 so that the counter can be ; set. FP_series_06_etc: ; 3449 ld b,a ; Move the parameter to B. call GEN_ENT_1 ; In effect a RST 0028 instruction but ; sets the counter. ; ii. The handling of the 'last value', Z: ; The loop of the generator requires 2*Z to be placed in mem-0, zero to be ; placed in mem-2 and the 'last value' to be zero. ; calculator stack db C_duplicate ; Z, Z db C_addition ; 2*Z db C_st_mem_0 ; 2*Z mem-0 holds 2*Z db C_delete ; - db C_stk_zero ; 0 db C_st_mem_2 ; 0 mem-2 holds 0 ; iii. The main loop: ; The series is generated by looping, using BREG as a counter; the constants in ; the calling subroutine are stacked in turn by calling STK-DATA; the ; calculator is re-entered at GEN-ENT-2 so as not to disturb the value of BREG; ; and the series is built up in the form: ; B(R) = 2*Z*B(R-1) - B(R-2) + A(R), for R = 1,2,...,N, where A(1), A(2),..., ; A(N) are the constants supplied by the calling subroutine (SIN, ATN, LN and ; EXP) and B(0) = 0 = B(-1). ; The (R+1)th loop starts with B(R) on the stack and with 2*Z, B(R-2) and ; B(R-1) in mem-0, mem-1 and mem-2 respectively. G_LOOP: ; 3453 db C_duplicate ; B(R), B(R) db C_get_mem_0 ; B(R), B(R), 2*Z db C_multiply ; B(R), 2*B(R)*Z db C_get_mem_2 ; B(R), 2*B(R)*Z, B(R-1) db C_st_mem_1 ; mem-1 holds B(R-1) db C_subtract ; B(R), 2*B(R)*Z-B(R-1) db C_end_calc ; The next constant is placed on the calculator stack. call STK_DATA ; B(R),2*B(R)*Z-B(R-1),A(R+1) ; The Calculator is re-entered without disturbing BREG. call GEN_ENT_2 db C_addition ; B(R), 2*B(R)*Z-B(R-1)+A(R+1) db C_exchange ; 2*B(R)*Z-B(R-1)+A(R+1), B(R) db C_st_mem_2 ; mem-2 holds B(R) db C_delete ; 2*B(R)*Z-B(R-1)+A(R+1) = B(R+1) db C_dec_jr_nz ; B(R+1) db G_LOOP - $ ; iv. The subtraction of B(N-2): ; The loop above leaves B(N) on the stack and the required result is given by ; B(N) - B(N-2). db C_get_mem_1 ; B(N), B(N-2) db C_subtract ; B(N)-B(N-2) db C_end_calc ret ; Finished ; THE 'ABSOLUTE MAGNITUDE' FUNCTION ; (Offset 2A: 'abs') ; This subroutine performs its unary operation by ensuring that the sign bit of ; a floating-point number is reset. ; 'Small integers' have to be treated separately. Most of the work is shared ; with the 'unary minus' operation. FP_abs: ; 346a ld b,0xff ; B is set to FF hex. jr NEG_TEST ; The jump is made into 'unary minus'. ; THE 'UNARY MINUS' OPERATION ; (Offset 1B: 'negate') ; This subroutine performs its unary operation by changing the sign of the ; 'last value' on the calculator stack. ; Zero is simply returned unchanged. Full five byte floating-point numbers have ; their sign bit manipulated so that it ends up reset (for 'abs') or changed ; (for 'negate'). 'Small integers' have their sign byte set to zero (for 'abs') ; or changed (for 'negate'). NEGATE: ; 346e call TEST_ZERO ; If the number is zero, the ret c ; subroutine returns leaving 00 00 00 ; 00 00 unchanged. ld b,0x00 ; B is set to +00 hex for 'negate'. ; 'ABS' enters here. NEG_TEST: ; 3474 ld a,(hl) ; If the first byte is zero, the and a ; jump is made to deal with a jr z,INT_CASE ; 'small integer'. inc hl ; Point to the second byte. ld a,b ; Get +FF for 'abs', +00 for 'negate'. and 0x80 ; Now +80 for 'abs', +00 for 'negate'. or (hl) ; This sets bit 7 for 'abs', but ; changes nothing for 'negate'. rla ; Now bit 7 is changed, leading to ccf ; bit 7 of byte 2 reset for 'abs', rra ; and simply changed for 'negate'. ld (hl),a ; The new second byte is stored. dec hl ; HL points to the first byte again. ret ; Finished. ; The 'integer case' does a similar operation with the sign byte. INT_CASE: ; 3483 push de ; Save STKEND in DE. push hl ; Save pointer to the number in HL. call INT_FETCH ; Fetch the sign in C, the number in ; DE. pop hl ; Restore the pointer to the number in ; HL. ld a,b ; Get +FF for 'abs', +00 for 'negate'. or c ; Now +FF for 'abs', no change for ; 'negate' cpl ; Now +00 for 'abs', and a changed byte ld c,a ; for 'negate': store it in C. call INT_STORE ; Store result on the stack. pop de ; Return STKEND to DE. ret ; THE 'SIGNUM' FUNCTION ; (Offset 29: 'sgn') ; This subroutine handles the function SGN X and therefore returns a 'last ; value' of 1 if X is positive, zero if X is zero and -1 if X is negative. FP_sgn: ; 3492 call TEST_ZERO ; If X is zero, just return with ret c ; zero as the 'last value'. push de ; Save the pointer to STKEND. ld de,0x0001 ; Store 1 in DE. inc hl ; Point to the second byte of X. rl (hl) ; Rotate bit 7 into the carry flag. dec hl ; Point to the destination again. sbc a,a ; Set C to zero for positive X and ld c,a ; to FF hex for negative X. call INT_STORE ; Stack 1 or -1 as required. pop de ; Restore the pointer to STKEND' ret ; Finished. ; THE 'IN' FUNCTION ; (Offset 2C: 'in') ; This subroutine handles the function IN X. It inputs at processor level from ; port X, loading BC with X and performing the instruction IN A,(C). FP_in: ; 34a5 call FIND_INT2 ; The 'last value', X, is compressed ; into BC. in a,(c) ; The signal is received. jr IN_PK_STK ; Jump to stack the result. ; THE 'PEEK' FUNCTION ; (Offset 2B: 'peek') ; This subroutine handles the function PEEK X. The 'last value' is unstacked by ; calling FIND-INT2 and replaced by the value of the contents of the required ; location. FP_peek: ; 34ac call FIND_INT2 ; Evaluate the 'last value', rounded to ; the nearest integer; ; test that it is in range and return ; it in BC. ld a,(bc) ; Fetch the required byte. IN_PK_STK: ; 34b0 jp STACK_A ; Exit by jumping to STACK-A. ; THE 'USR' FUNCTION ; (Offset 2D: 'usr-no') ; This subroutine ('USR number' as distinct from 'USR string') handles the ; function USR X, where X is a number. The value of X is obtained in BC, a ; return address is stacked and the machine code is executed from location X. FP_usr_no: ; 34b3 call FIND_INT2 ; Evaluate the 'last value', rounded to ; the nearest integer; ; test that it is in range and return ; it in BC. ld hl,STACK_BC ; Make the return address be that push hl ; of the subroutine STACK-BC. push bc ; Make an indirect jump to the ret ; required location. ; Note: It is interesting that the IY register pair is re-initialised when the ; return to STACK-BC has been made, but the important H'L' that holds the next ; literal pointer is not restored should it have been disturbed. For a ; successful return to BASIC, H'L' must on exit from the machine code contain ; the address in SCANNING of the 'end-calc' instruction, 2758 hex (10072 ; decimal). ; THE 'USR-STRING' FUNCTION ; (Offset 19: 'usr-$') ; This subroutine handles the function USR X$, where X$ is a string. The ; subroutine returns in BC the address of the bit pattern for the user-defined ; graphic corresponding to X$. It reports error A if X$ is not a single letter ; between a and u or a user-defined graphic. USR__: ; 34bc call STK_FETCH ; Fetch the parameters of the string ; X$. dec bc ; Decrease the length by 1 to test it. ld a,b ; If the length was not 1, then or c ; jump to give error report A. jr nz,REPORT_Aa ld a,(de) ; Fetch the single code of the string. call ALPHA ; Does it denote a letter? jr c,USR_RANGE ; If so, jump to gets its address. sub 0x90 ; Reduce range for actual user-defined ; graphics to 0 - 20 ; decimal. jr c,REPORT_Aa ; Give report A if out of range. cp 0x15 ; Test the range again. jr nc,REPORT_Aa ; Give report A if out of range. inc a ; Make range of user-defined graphics 1 ; to 21 decimal, as for ; a to u. USR_RANGE: ; 34d3 dec a ; Now make the range 0 to 20 decimal in ; each case. add a,a ; Multiply by 8 to get an offset add a,a ; for the address. add a,a cp 0xa8 ; Test the range of the offset. jr nc,REPORT_Aa ; Give report A if out of range. ld bc,(UDG) ; Fetch the address of the first ; user-defined graphic in BC. add a,c ; Add C to the offset. ld c,a ; Store the result back in C. jr nc,USR_STACK ; Jump if there is no carry. inc b ; Increment B to complete the address. USR_STACK: ; 34e4 jp STACK_BC ; Jump to stack the address. ; REPORT A - Invalid argument. REPORT_Aa: ; 34e7 rst ERROR_1 ; Call the error handling db 0x09 ; routine. ; THE 'TEST-ZERO' SUBROUTINE ; This subroutine is called at least nine times to test whether a ; floating-point number is zero. This test requires that the first four bytes ; of the number should each be zero. The subroutine returns with the carry flag ; set if the number was in fact zero. TEST_ZERO: ; 34e9 push hl ; Save HL on the stack. push bc ; Save BC on the stack. ld b,a ; Save the value of A in B. ld a,(hl) ; Get the first byte. inc hl ; Point to the second byte. or (hl) ; OR first byte with second. inc hl ; Point to the third byte. or (hl) ; OR the result with the third byte. inc hl ; Point to the fourth byte. or (hl) ; OR the result with the fourth byte. ld a,b ; Restore the original value of A. pop bc ; And of BC. pop hl ; Restore the pointer to the number to ; HL. ret nz ; Return with carry reset if any of the ; four bytes was non-zero. scf ; Set the carry flag to indicate ret ; that the number was zero, and return. ; THE 'GREATER THAN ZERO' OPERATION ; (Offset 37: 'greater-0') ; This subroutine returns a 'last value' of one if the present 'last value' is ; greater than zero and zero otherwise. It is also used by other subroutines to ; 'jump on plus'. GREATER_0: ; 34f9 call TEST_ZERO ; Is the 'last-value' zero? ret c ; If so, return. ld a,0xff ; Jump forward to LESS THAN jr SIGN_TO_C ; ZERO but signal the opposite action ; is needed. ; THE 'NOT' FUNCTION ; (Offset 30: 'not') ; This subroutine returns a 'last value' of one if the present 'last value' is ; zero and zero otherwise. It is also used by other subroutines to 'jump on ; zero'. NOT_: ; 3501 call TEST_ZERO ; The carry flag will be set only if ; the 'last value' is zero; this gives ; the correct result. jr FP_0_1 ; Jump forward. ; THE 'LESS THAN ZERO' OPERATION ; (Offset 36: 'less-0') ; This subroutine returns a 'last value' of one if the present 'last value' is ; less than zero and zero otherwise. It is also used by other subroutines to ; 'jump on minus'. FP_less_0: ; 3506 xor a ; Clear the A register. SIGN_TO_C: ; 3507 inc hl ; Point to the sign byte. xor (hl) ; The carry is reset for a positive dec hl ; number and set for a negative rlca ; number; when entered from GREATER-0 ; the opposite sign ; goes to the carry. ; THE 'ZERO OR ONE' SUBROUTINE ; This subroutine sets the 'last value' to zero if the carry flag is reset and ; to one if it is set. When called from 'E-TO-FP' however it creates the zero ; or one not on the stack but in mem-0. FP_0_1: ; 350b push hl ; Save the result pointer. ld a,0x00 ; Clear A without disturbing the carry. ld (hl),a ; Set the first byte to zero. inc hl ; Point to the second byte. ld (hl),a ; Set the second byte to zero. inc hl ; Point to the third byte. rla ; Rotate the carry into A, making A one ; if the carry was set, but ; zero if the carry was reset. ld (hl),a ; Set the third byte to one or zero. rra ; Ensure that A is zero again. inc hl ; Point to the fourth byte. ld (hl),a ; Set the fourth byte to zero. inc hl ; Point to the fifth byte. ld (hl),a ; Set the fifth byte to zero. pop hl ; Restore the result pointer. ret ; THE 'OR' OPERATION ; (Offset 07: 'or') ; This subroutine performs the binary operation 'X OR Y' and returns X if Y is ; zero and the value 1 otherwise. FP_or: ; 351b ex de,hl ; Point HL at Y, the second number. call TEST_ZERO ; Test whether Y is zero. ex de,hl ; Restore the pointers. ret c ; Return if Y was zero; X is now the ; 'last value'. scf ; Set the carry flag and jump back jr FP_0_1 ; to set the 'last value' to 1. ; THE 'NUMBER AND NUMBER' OPERATION ; (Offset 08: 'no-&-no') ; This subroutine performs the binary operation 'X AND Y' and returns X if Y is ; non-zero and the value zero otherwise. FP_no_n_no: ; 3524 ex de,hl ; Point HL at Y, DE at X. call TEST_ZERO ; Test whether Y is zero. ex de,hl ; Swap the pointers back. ret nc ; Return with X as the 'last value' if ; Y was non-zero. and a ; Reset the carry flag and jump jr FP_0_1 ; back to set the 'last value' to zero. ; THE 'STRING AND NUMBER' OPERATION ; (Offset 10: 'str-&-no') ; This subroutine performs the binary operation 'X$ AND Y' and returns X$ if Y ; is non-zero and a null string otherwise. FP_str_n_no: ; 352d ex de,hl ; Point HL at Y, DE at X$ call TEST_ZERO ; Test whether Y is zero. ex de,hl ; Swap the pointers back. ret nc ; Return with X$ as the 'last value' if ; Y was non-zero. push de ; Save the pointer to the number. dec de ; Point to the fifth byte of the string ; parameters i.e. length- ; high. xor a ; Clear the A register. ld (de),a ; Length-high is now set to zero. dec de ; Point to length-low. ld (de),a ; Length-low is now set to zero. pop de ; Restore the pointer. ret ; Return with the string parameters ; being the 'last value'. ; THE 'COMPARISON' OPERATIONS ; (Offsets 09 to 0E & 11 to 16: 'no-l-eql', 'no-gr-eq', 'nos-neql', 'no-grtr', ; 'no-less', 'nos-eql', 'str-l-eql', 'str-gr-eq', 'strs-neql', 'str-grtr', ; 'str-less' & 'strs-eql') ; This subroutine is used to perform the twelve possible comparison operations. ; The single operation offset is present in the B register at the start of the ; subroutine. FP_no_l_eql_etc: ; 353b ld a,b ; The single offset goes to the ; A register. sub 0x08 ; The range is now 01-06 & 09-0E. bit 2,a ; This range is changed to: jr nz,EX_OR_NOT ; 00-02, 04-06, 08-0A & dec a ; 0C-0E. EX_OR_NOT: ; 3543 rrca ; Then reduced to 00-07 with carry set ; for 'greater than or ; equal to' & 'less than'; the ; operations with carry set are jr nc,NU_OR_STR ; then treated as their push af ; complementary operation once push hl ; their values have been exchanged. call EXCHANGE pop de ex de,hl pop af NU_OR_STR: ; 354e bit 2,a ; The numerical comparisons are jr nz,STRINGS ; now separated from the string ; comparisons by testing bit 2. rrca ; The numerical operations now have the ; range 00-01 with carry ; set for 'equal' and 'not equal'. push af ; Save the offset. call SUBTRACT ; The numbers are subtracted for jr END_TESTS ; the final tests. STRINGS: ; 3559 rrca ; The string comparisons now have the ; range 02-03 with carry ; set for 'equal' and 'not equal'. push af ; Save the offset. call STK_FETCH ; The lengths and starting push de ; addresses of the strings are push bc ; fetched from the calculator call STK_FETCH ; stack. pop hl ; The length of the second string. BYTE_COMP: ; 3564 ld a,h or l ex (sp),hl ld a,b jr nz,SEC_PLUS ; Jump unless the second string or c ; is null. SECND_LOW: ; 356b pop bc ; Here the second string is either null ; or less than the first. jr z,BOTH_NULL pop af ccf ; The carry is complemented to jr STR_TEST ; give the correct test results. BOTH_NULL: ; 3572 pop af ; Here the carry is used as it jr STR_TEST ; stands. SEC_PLUS: ; 3575 or c jr z,FRST_LESS ; The first string is now null, the ; second not. ld a,(de) ; Neither string is null, so their sub (hl) ; next bytes are compared. jr c,FRST_LESS ; The first byte is less. jr nz,SECND_LOW ; The second byte is less. dec bc ; The bytes are equal; so the inc de ; lengths are decremented and a inc hl ; jump is made to BYTE-COMP ex (sp),hl ; to compare the next bytes of dec hl ; the reduced strings. jr BYTE_COMP FRST_LESS: ; 3585 pop bc pop af and a ; The carry is cleared here for the ; correct test results. STR_TEST: ; 3588 push af ; For the string tests, a zero is rst FP_CALC ; put on to the calculator stack. db C_stk_zero db C_end_calc END_TESTS: ; 358c pop af ; These three tests, called as push af ; needed, give the correct results call c,NOT_ ; for all twelve comparisons. The pop af ; initial carry is set for 'not equal' push af ; and 'equal', and the final carry call nc,GREATER_0 ; is set for 'greater than', 'less pop af ; than' and 'equal'. rrca call nc,NOT_ ret ; Finished. ; THE 'STRING CONCATENATION' OPERATION ; (Offset 17: 'strs-add') ; This subroutine performs the binary operation 'A$+B$. The parameters for ; these strings are fetched and the total length found. Sufficient room to hold ; both the strings is made available in the work space and the strings are ; copied over. The result of this subroutine is therefore to produce a ; temporary variable A$+B$ that resides in the work space. FP_strs_add: ; 359c call STK_FETCH ; The parameters of the second push de ; string are fetched and saved. push bc call STK_FETCH ; The parameters of the first string ; are fetched. pop hl push hl ; The lengths are now in HL and BC. push de ; The parameters of the first push bc ; string are saved. add hl,bc ; The total length of the two ld b,h ; strings is calculated and passed ld c,l ; to BC. rst BC_SPACES ; Sufficient room is made available. call STK_STO__ ; The parameters of the new string are ; passed to the ; calculator stack. pop bc ; The parameters of the first pop hl ; string are retrieved and the ld a,b ; string copied to the work space or c ; as long as it is not a null string. jr z,OTHER_STR ldir OTHER_STR: ; 35b7 pop bc ; Exactly the same procedure is pop hl ; followed for the second string ld a,b ; thereby giving 'A$+B$'. or c jr z,STK_PNTRS ldir ; THE 'STK-PNTRS' SUBROUTINE ; This subroutine resets the HL register pair to point to the first byte of the ; 'last value', i.e. STKEND-5, and the DE register pair to point one-past the ; 'last value', i.e. STKEND. STK_PNTRS: ; 35bf ld hl,(STKEND) ; Fetch the current value of STKEND. ld de,0xfffb ; Set DE to -5, twos complement. push hl ; Stack the value for STKEND. add hl,de ; Calculate STKEND-5. pop de ; DE now holds STKEND and HL ret ; THE 'CHR$' FUNCTION ; (Offset 2F: 'chrs') ; This subroutine handles the function CHR$ X and creates a single character ; string in the work space. FP_chrs: ; 35c9 call FP_TO_A ; The 'last value' is compressed into ; the A register. jr c,REPORT_Bb ; Give the error report if X was ; greater than 255 decimal, or jr nz,REPORT_Bb ; X was a negative number. push af ; Save the compressed value of X. ld bc,0x0001 ; Make one space available in the rst BC_SPACES ; work space. pop af ; Fetch the value. ld (de),a ; Copy the value to the work space. call STK_STO__ ; Pass the parameters of the new string ; to the calculator stack. ex de,hl ; Reset the pointers. ret ; Finished. ; REPORT-B - Integer out of range REPORT_Bb: ; 35dc rst ERROR_1 ; Call the error handling db 0x0a ; routine. ; THE 'VAL' AND 'VAL$' FUNCTION ; (Offsets 1D: 'val' and 18: 'val$') ; This subroutine handles the functions VAL X$ and VAL$ X$. When handling VAL ; X$, it return a 'last value' that is the result of evaluating the string ; (without its bounding quotes) as a numerical expression. when handling VAL$ ; X$, it evaluates X$ (without its bounding quotes) as a string expression, and ; returns the parameters of that string expression as a 'last value' on the ; calculator stack. FP_val: ; 35de ld hl,(CH_ADD) ; The current value of CH-ADD is push hl ; preserved on the machine stack. ld a,b ; The 'offset' for 'val' or 'val$' must ; be in the B register; it is ; now copied to A. add a,0xe3 ; Produce +00 and carry set for 'val', ; +FB and carry reset for ; 'val$'. sbc a,a ; Produce +FF (bit 6 therefore set) for ; 'val', but +00 (bit 6 ; reset) for 'val$'. push af ; Save this 'flag' on the machine ; stack. call STK_FETCH ; The parameters of the string are push de ; fetched; the starting address is inc bc ; saved; one byte is added to the rst BC_SPACES ; length and room made available for ; the string (+1) in the work ; space. pop hl ; The starting address of the string ; goes to HL as a source ; address. ld (CH_ADD),de ; The pointer to the first new push de ; space goes to CH-ADD and to the ; machine stack. ldir ; The string is copied to the work ; space, together with an extra ; byte. ex de,hl ; Switch the pointers. dec hl ; The extra byte is replaced by a ld (hl),0x0d ; 'carriage return' character. res 7,(iy+d_FLAGS) ; The syntax flag is reset and the call SCANNING ; string is scanned for correct syntax. rst GET_CHAR ; The character after the string is ; fetched. cp 0x0d ; A check is made that the end of the ; expression has been reached. jr nz,V_RPORT_C ; If not, the error is reported. pop hl ; The starting address of the string is ; fetched. pop af ; The 'flag' for 'val/val$' is xor (iy+d_FLAGS) ; fetched and bit 6 is compared and 0x40 ; with bit 6 of the result of the ; syntax scan. V_RPORT_C: ; 360c jp nz,REPORT_Cb ; Report the error if they do not ; match. ld (CH_ADD),hl ; Start address to CH-ADD again. set 7,(iy+d_FLAGS) ; The flag is set for line execution. call SCANNING ; The string is treated as a 'next ; expression' and a 'last value' ; produced. pop hl ; The original value of CH-ADD is ld (CH_ADD),hl ; restored. jr STK_PNTRS ; The subroutine exits via STK-PNTRS ; which resets the pointers. ; THE 'STR$' FUNCTION ; (Offset 2E: 'str$') ; This subroutine handles the function STR$ X and returns a 'last value' which ; is a set of parameters that define a string containing what would appear on ; the screen if X were displayed by a PRINT command. STR_: ; 361f ld bc,0x0001 ; One space is made in the work rst BC_SPACES ; space and its address is copied ld (K_CUR),hl ; to K-CUR, the address of the cursor. push hl ; This address is saved on the stack ; too. ld hl,(CURCHL) ; The current channel address is push hl ; saved on the machine stack. ld a,0xff ; Channel 'R' is opened, allowing call CHAN_OPEN ; the string to be 'printed' out into ; the work space. call PRINT_FP ; The 'last value', X, is now printed ; out in the work space ; and the work space is expanded with ; each character. pop hl ; Restore CURCHL to HL and call CHAN_FLAG ; restore the flags that are ; appropriate to it. pop de ; Restore the start address of the ; string. ld hl,(K_CUR) ; Now the cursor address is one and a ; past the end of the string and sbc hl,de ; hence the difference is the length. ld b,h ; Transfer the length to BC. ld c,l call STK_STO__ ; Pass the parameters of the new string ; to the calculator stack. ex de,hl ; Reset the pointers. ret ; Finished. ; Note: See PRINT-FP for an explanation of the 'PRINT "A"+STR$ 0.1' error. ; THE 'READ-IN' SUBROUTINE ; (Offset 1A: 'read-in') ; This subroutine is called via the calculator offset through the first line of ; the S-INKEY$ routine in SCANNING. It appears to provide for the reading in of ; data through different streams from those available on the standard Spectrum. ; Like INKEY$ the subroutine returns a string. FP_read_in: ; 3645 call FIND_INT1 ; The numerical parameter is compressed ; into the A register. cp 0x10 ; Is it smaller than 16 decimal? jp nc,REPORT_Bd ; If not, report the error. ld hl,(CURCHL) ; The current channel address is push hl ; saved on the machine stack. call CHAN_OPEN ; The channel specified by the ; parameter is opened. call INPUT_AD ; The signal is now accepted, like a ; 'key-value'. ld bc,0x0000 ; The default length of the resulting ; string is zero. jr nc,R_I_STORE ; Jump if there was no signal. inc c ; Set the length to 1 now. rst BC_SPACES ; Make a space in the work space. ld (de),a ; Put the string into it. R_I_STORE: ; 365f call STK_STO__ ; Pass the parameters of the string to ; the calculator stack. pop hl ; Restore CURCHL and the call CHAN_FLAG ; appropriate flags. jp STK_PNTRS ; Exit, setting the pointers. ; THE 'CODE' FUNCTION ; (Offset 1C: 'code') ; This subroutine handles the function CODE A$ and returns the Spectrum code of ; the first character in A$, or zero if A$ should be null. FP_code: ; 3669 call STK_FETCH ; The parameters of the string are ; fetched. ld a,b ; The length is tested and the A or c ; register holding zero is carried jr z,STK_CODE ; forward is A$ is a null string. ld a,(de) ; The code of the first character is ; put into A otherwise. STK_CODE: ; 3671 jp STACK_A ; The subroutine exits via STACK-A ; which gives the ; correct 'last value'. ; THE 'LEN' FUNCTION ; (Offset 1E: 'len') ; This subroutine handles the function LEN A$ and returns a 'last value' that ; is equal to the length of the string. FP_len: ; 3674 call STK_FETCH ; The parameters of the string are ; fetched. jp STACK_BC ; The subroutine exits via STACK-BC ; which gives the ; correct 'last value'. ; THE 'DECREASE THE COUNTER' SUBROUTINE ; (Offset 35: 'dec-jr-nz') ; This subroutine is only called by the SERIES GENERATOR subroutine and in ; effect is a 'DJNZ' operation but the counter is the system variable, BREG, ; rather than the B register. FP_dec_jr_nz: ; 367a exx ; Go to the alternative register set push hl ; and save the next literal pointer on ; the machine stack. ld hl,BREG ; Make HL point to BREG. dec (hl) ; Decrease BREG. pop hl ; Restore the next literal pointer. jr nz,JUMP_2 ; The jump is made on non-zero. inc hl ; The next literal is passed over. exx ; Return to the main register set. ret ; Finished. ; THE 'JUMP' SUBROUTINE ; (Offset 33: 'jump') ; This subroutine executes an unconditional jump when called by the literal ; '33'. It is also used by the subroutines DECREASE THE COUNTER and JUMP ON ; TRUE. JUMP: ; 3686 exx ; Go to the next alternate register ; set. JUMP_2: ; 3687 ld e,(hl) ; The next literal (jump length) is put ; in the E' register. ld a,e ; The number 00 hex or FF hex rla ; is formed in A according as E' sbc a,a ; is positive or negative, and is ld d,a ; then copied to D'. add hl,de ; The registers H' & L' now hold exx ; the next literal pointer. ret ; Finished. ; THE 'JUMP ON TRUE' SUBROUTINE ; (Offset 00: 'jump-true') ; This subroutine executes a conditional jump if the 'last value' on the ; calculator stack, or more precisely the number addressed currently by the DE ; register pair, is true. FP_jump_true: ; 368f inc de ; Point to the third byte, which is inc de ; zero or one. ld a,(de) ; Collect this byte in the A register. dec de ; Point to the first byte once dec de ; again. and a ; Test the third byte: is it zero? jr nz,JUMP ; Make the jump if the byte is ; non-zero, i.e. if the number is ; not-false. exx ; Go to the alternate register set. inc hl ; Pass over the jump length. exx ; Back to the main set of registers. ret ; Finished. ; THE 'END-CALC' SUBROUTINE ; (Offset 38: 'end-calc') ; This subroutine ends a RST 0028 operation. FP_end_calc: ; 369b pop af ; The return address to the calculator ; ('RE-ENTRY') is ; discarded. exx ; Instead, the address in H'L' is ex (sp),hl ; put on the machine stack and exx ; an indirect jump is made to it. H'L' ; will now hold any earlier ; address in the calculator chain of ; addresses. ret ; Finished. ; THE 'MODULUS' SUBROUTINE ; (Offset 32: 'n-mod-m') ; This subroutine calculates M (mod M), where M is a positive integer held at ; the top of the calculator stack, the 'last value', and N is the integer held ; on the stack beneath M. ; The subroutine returns the integer quotient INT (N/M) at the top of the ; calculator stack, the 'last value', and the remainder N-INT (N/M) in the ; second place on the stack. ; This subroutine is called during the calculation of a random number to reduce ; N mod 65537 decimal. FP_n_mod_m: ; 36a0 rst FP_CALC ; N, M db C_st_mem_0 ; N, M mem-0 holds M db C_delete ; N db C_duplicate ; N, N db C_get_mem_0 ; N, N, M db C_division ; N, N/M db C_int ; N, INT (N/M) db C_get_mem_0 ; N, INT (N/M), M db C_exchange ; N, M, INT (N/M) db C_st_mem_0 ; N, M, INT (N/M) mem-0 holds INT (N/M) db C_multiply ; N, M*INT (N/M) db C_subtract ; n-M*INT (N/M) db C_get_mem_0 ; n-M*INT (N/M), INT (N/M) db C_end_calc ret ; Finished. ; THE 'INT' FUNCTION ; (Offset 27: 'int') ; This subroutine handles the function INT X and returns a 'last value' that is ; the 'integer part' of the value supplied. Thus INT 2.4 gives 2 but as the ; subroutine always rounds the result down INT -2.4 gives -3. ; The subroutine uses the INTEGER TRUNCATION TOWARDS ZERO subroutine at 3214 to ; produce I (X) such that I (2.4) gives 2 and I (-2.4) gives -2. Thus, INT X is ; gives by I (X) for values of X that are greater than or equal to zero, and I ; (X)-1 for negative values of X that are not already integers, when the result ; is, of course, I (X). FP_int: ; 36af rst FP_CALC ; X db C_duplicate ; X, X db C_less_0 ; X, (1/0) db C_jump_true ; X db X_NEG - $ ; X ; For values of X that have been shown to be greater than or equal to zero ; there is no jump and I (X) is readily found. db C_truncate ; I (X) db C_end_calc ret ; Finished. ; when X is a negative integer I (X) is returned, otherwise I (X)-1 is ; returned. X_NEG: ; 36b7 db C_duplicate ; X, X db C_truncate ; X, I (X) db C_st_mem_0 ; X, I (X) mem-0 holds I (X) db C_subtract ; X-I (X) db C_get_mem_0 ; X-I (X), I (X) db C_exchange ; I (X), X-I (X) db C_not ; I (X), (1/0) db C_jump_true ; I (X) db EXIT - $ ; I (X) ; The jump is made for values of X that are negative integers, otherwise there ; is no jump and I (X)-1 is calculated. db C_stk_one ; I (X), 1 db C_subtract ; I (X)-1 ; In either case the subroutine finishes with; EXIT: ; 36c2 db C_end_calc ; I (X) or I (X)-1 ret ; THE 'EXPONENTIAL' FUNCTION ; (Offset 26: 'exp') ; This subroutine handles the function EXP X and is the first of four routines ; that use SERIES GENERATOR to produce Chebyshev polynomials. ; The approximation to EXP X is found as follows: ; i. X is divided by LN 2 to give Y, so that 2 to the power Y is now the ; required result. ; ii. The value N is found, such that N=INT Y. ; iii. The value W is found, such that W=Y-N, where 0<=W<=1, as required for ; the series to converge. ; iv. The argument Z if formed, such that Z=2*w-1. ; v. The SERIES GENERATOR is used to return 2**W. ; vi. Finally N is added to the exponent, giving 2**(N+W), which is 2**Y and ; therefore the required answer for EXP X. ; The method is illustrated using a BASIC program in the Appendix. EXP: ; 36c4 rst FP_CALC ; X ; Perform step i. db C_re_stack ; X (in full floating-point form) db C_stk_data ; X, 1/LN 2 db 4-1<<6|0x81-0x50 db 0x38,0xaa,0x3b,0x29 db C_multiply ; X/LN 2 = Y ; Perform step ii. db C_duplicate ; Y, Y db C_int ; Y, INT Y = N db C_st_mem_3 ; Y, N mem-3 holds N ; Perform step iii. db C_subtract ; Y-N = W ; Perform step iv. db C_duplicate ; W, W db C_addition ; 2*W db C_stk_one ; 2*W, 1 db C_subtract ; 2*W-1 = Z ; Perform step v, passing to the SERIES GENERATOR the parameter '8' and the ; eight constants required. db 0x88 ; Z db 1-1<<6|0x63-0x50 db 0x36 db 2-1<<6|0x68-0x50 db 0x65,0x66 db 3-1<<6|0x6d-0x50 db 0x78,0x65,0x40 db 3-1<<6|0x72-0x50 db 0x60,0x32,0xc9 db 4-1<<6|0x77-0x50 db 0x21,0xf7,0xaf,0x24 db 4-1<<6|0x7b-0x50 db 0x2f,0xb0,0xb0,0x14 db 4-1<<6|0x7e-0x50 db 0x7e,0xbb,0x94,0x58 db 4-1<<6|0x81-0x50 db 0x3a,0x7e,0xf8,0xcf ; At the end of the last loop the 'last value' is 2**W. ; Perform step vi. db C_get_mem_3 ; 2**W, N db C_end_calc call FP_TO_A ; The absolute value of N mod 256 ; decimal, is put into the A ; register. jr nz,N_NEGTV ; Jump forward if N was negative. jr c,REPORT_6b ; Error if ABS N greater than 255 dec. add a,(hl) ; Now add ABS N to the exponent. jr nc,RESULT_OK ; Jump unless e greater than 255 dec. ; Report 6 - Number too big REPORT_6b: ; 3703 rst ERROR_1 ; Call the error handling db 0x05 ; routine. N_NEGTV: ; 3705 jr c,RSLT_ZERO ; The result is to be zero if N is less ; than -255 decimal. sub (hl) ; Subtract ABS N from the exponent as N ; was negative. jr nc,RSLT_ZERO ; Zero result if e less than zero. neg ; Minus e is changed to e. RESULT_OK: ; 370c ld (hl),a ; The exponent, e, is entered. ret ; Finished: 'last value' is EXP X RSLT_ZERO: ; 370e rst FP_CALC ; Use the calculator to make the db C_delete ; 'last value' zero. db C_stk_zero db C_end_calc ret ; Finished, with EXP X = 0. ; THE 'NATURAL LOGARITHM' FUNCTION ; (Offset 25: 'ln') ; This subroutine handles the function LN X and is the second of the four ; routines that use SERIES GENERATOR to produce Chebyshev polynomials. ; The approximation to LN X is found as follows: ; i. X is tested and report A is given if X is not positive. ; ii. X is then split into its true exponent, e', and its mantissa X' = ; X/(2**e'), where X' is greater than, or equal to, 0.5 but still less than 1. ; iii. The required value Y1 or Y2 is formed. If X' is greater than 0.8 then ; Y1=e'*LN 2 and if otherwise Y2 = (e'-1)*LN 2. ; iv. If X' is greater than 0.8 then the quantity X'-1 is stacked; otherwise ; 2*X'-1 is stacked. ; v. Now the argument Z is formed, being if X' is greater than 0.8, Z = ; 2.5*X'-3; otherwise Z = 5*X'-3. In each case, -1<=Z<=1, as required for the ; series to converge. ; vi. The SERIES GENERATOR is used to produce the required function. ; vii. Finally a simply multiplication and addition leads to LN X being ; returned as the 'last value'. FP_ln: ; 3713 rst FP_CALC ; X ; Perform step i. db C_re_stack ; X (in full floating-point form) db C_duplicate ; X, X db C_greater_0 ; X, (1/0) db C_jump_true ; X db VALID - $ ; X db C_end_calc ; X ; Report A - Invalid argument REPORT_Ab: ; 371a rst ERROR_1 ; Call the error handling db 0x09 ; routine. ; Perform step ii. VALID: ; 371c db C_stk_zero ; X,0 The deleted 1 is db C_delete ; X overwritten with zero. db C_end_calc ; X ld a,(hl) ; The exponent, e, goes into A. ld (hl),0x80 ; X is reduced to X'. call STACK_A ; The stack holds: X', e. rst FP_CALC ; X', e db C_stk_data ; X', e, 128 (decimal) db 1-1<<6|0x88-0x50 db 0x00 db C_subtract ; X', e' ; Perform step iii. db C_exchange ; e', X' db C_duplicate ; e', X', X' db C_stk_data ; e', X', X', 0.8 (decimal) db 4-1<<6|0x80-0x50 db 0x4c,0xcc,0xcc,0xcd db C_subtract ; e', X', X'-0.8 db C_greater_0 ; e', X', (1/0) db C_jump_true ; e', X' db GRE_8 - $ ; e', X' db C_exchange ; X', e' db C_stk_one ; X', e', 1 db C_subtract ; X', e'-1 db C_exchange ; e'-1, X' db C_end_calc ; e'-1, X' inc (hl) ; Double X' to give 2*X'. rst FP_CALC ; e'-1, 2*X' GRE_8: ; 373d db C_exchange ; X', e' – X' large. 2*X', e'-1 – X' ; small. db C_stk_data ; X', e', LN 2 db 4-1<<6|0x80-0x50 ; 2*X', e'-1, LN 2 db 0x31,0x72,0x17,0xf8 db C_multiply ; X', e'*LN 2 = Y1 2*X', (e'-1)*LN 2 = ; Y2 ; Perform step iv. db C_exchange ; Y1, X' – X' large. Y2, 2*X' – X' ; small. db C_stk_half ; Y1, X', .5 (decimal) Y2, 2*X', .5 db C_subtract ; Y1, X'-.5 Y2, 2*X'-.5 db C_stk_half ; Y1, X'-.5, .5 Y2, 2*X'-.5, .5 db C_subtract ; Y1, X'-1 Y2, 2*X'-1 ; Perform step v. db C_duplicate ; Y, X'-1, X'-1 Y2, 2*X'-1, 2*X'-1 db C_stk_data ; Y1, X'-1, X'-1, 2.5 (decimal) Y2, ; 2*X'-1, 2*X'-1, 2.5 db 1-1<<6|0x82-0x50 db 0x20 db C_multiply ; Y1, X'-1, 2.5*X'-2.5 Y2, 2*X'-1, ; 5*X'-2.5 db C_stk_half ; Y1, X'-1, 2.5*X'-2.5, .5 Y2, 2*X'-1, ; 5*X'-2.5, .5 db C_subtract ; Y1, X'-1, 2.5*X'-3 = Z Y2, 2*X'-1, ; 5*X'-3 = Z ; Perform step vi, passing to the SERIES GENERATOR the parameter '12' decimal, ; and the twelve constant required. db 0x8c ; Y1, X'-1, Z or Y2, 2*X'-1, Z db 1-1<<6|0x61-0x50 db 0xac db 1-1<<6|0x64-0x50 db 0x09 db 2-1<<6|0x66-0x50 db 0xda,0xa5 db 2-1<<6|0x69-0x50 db 0x30,0xc5 db 2-1<<6|0x6c-0x50 db 0x90,0xaa db 3-1<<6|0x6e-0x50 db 0x70,0x6f,0x61 db 3-1<<6|0x71-0x50 db 0xcb,0xda,0x96 db 3-1<<6|0x74-0x50 db 0x31,0x9f,0xb4 db 4-1<<6|0x77-0x50 db 0xa0,0xfe,0x5c,0xfc db 4-1<<6|0x7a-0x50 db 0x1b,0x43,0xca,0x36 db 4-1<<6|0x7d-0x50 db 0xa7,0x9c,0x7e,0x5e db 4-1<<6|0x80-0x50 db 0x6e,0x23,0x80,0x93 ; At the end of the last loop the 'last value' is: ; either LN X'/(X'-1) for the larger values of X' ; or LN (2*X')/(2*X'-1) for the smaller values of X'. ; Perform step vii. db C_multiply ; Y1=LN (2**e'), LN X' Y2=LN ; (2**(e'-1)), LN (2*X') db C_addition ; LN (2**e')*X') = LN X LN ; (2**(e'-1)*2*X') = LN X db C_end_calc ; LN X ret ; Finished: 'last value' is LN X. ; THE 'REDUCE ARGUMENT' SUBROUTINE ; (Offset 39: 'get-argt') ; This subroutine transforms the argument X of SIN X or COS X into a value V. ; The subroutine first finds a value Y such that: ; Y = X/(2*PI) - INT (X/2*PI) + 0.5), where Y is greater than, or equal to, -.5 ; but less than +.5. ; The subroutine returns with: ; V = 4*Y if -1 <= 4*Y <=1 - case i. ; or, V = 2-4*Y if 1 < 4*Y <2 - case ii. ; or, V = -4*Y-2 if -2 <= 4*Y < -1. - case iii. ; In each case, -1 <= V <= 1 and SIN (PI*V/2) = SIN X FP_get_argt: ; 3783 rst FP_CALC ; X db C_re_stack ; X (in full floating-point form) db C_stk_data ; X, 1/(2*PI) db 4-1<<6|0x7e-0x50 db 0x22,0xf9,0x83,0x6e db C_multiply ; X/(2*PI) db C_duplicate ; X/(2*PI), X/(2*PI) db C_stk_half ; X/(2*PI), X/(2*PI), 0.5 db C_addition ; X/(2*PI), X/(2*PI)+0.5 db C_int ; X/(2*PI), INT (X/(2*PI)+0.5) db C_subtract ; X/(2*PI)-INT (X/(2*PI)+0.5)=Y ; Note: Adding 0.5 and taking INT rounds the result to the nearest integer. db C_duplicate ; Y, Y db C_addition ; 2*Y db C_duplicate ; 2*Y, 2*Y db C_addition ; 4*Y db C_duplicate ; 4*Y, 4*Y db C_abs ; 4*Y, ABS (4*Y) db C_stk_one ; 4*Y, ABS (4*Y), 1 db C_subtract ; 4*Y, ABS (4*Y)-1 = Z db C_duplicate ; 4*Y, Z, Z db C_greater_0 ; 4*Y, Z, (1/0) db C_st_mem_0 ; Mem-0 holds the result of the test. db C_jump_true ; 4*Y, Z db ZPLUS - $ ; 4*Y, Z db C_delete ; 4*Y db C_end_calc ; 4*Y = V – case i. ret ; Finished. ; If the jump was made then continue. ZPLUS: ; 37a1 db C_stk_one ; 4*Y, Z, 1 db C_subtract ; 4*Y, Z-1 db C_exchange ; Z-1, 4*Y db C_less_0 ; Z-1, (1/0) db C_jump_true ; Z-1 db YNEG - $ ; Z-1 db C_negate ; 1-Z YNEG: ; 37a8 db C_end_calc ; 1-Z = V – case ii. Z-1 = V – case ; iii. ret ; Finished. ; THE 'COSINE' FUNCTION ; (Offset 20: 'cos') ; This subroutine handles the function COS X and returns a 'last value' 'that ; is an approximation to COS X. ; The subroutine uses the expression: ; COS X = SIN (PI*W/2), where -1 <= W <= 1. ; In deriving W for X the subroutine uses the test result obtained in the ; previous subroutine and stored for this purpose in mem-0. It then jumps to ; the SINE, subroutine, entering at C-ENT, to produce a 'last value' of COS X. FP_cos: ; 37aa rst FP_CALC ; X db C_get_argt ; V db C_abs ; ABS V db C_stk_one ; ABS V, 1 db C_subtract ; ABS V-1 db C_get_mem_0 ; ABS V-1, (1/0) db C_jump_true ; ABS V-1 db C_ENT - $ ; ABS V-1 = W ; If the jump was not made then continue. db C_negate ; 1-ABS V db C_jump ; 1-ABS V db C_ENT - $ ; 1-ABS V = W ; THE 'SINE' FUNCTION ; (Offset 1F: 'sin') ; This subroutine handles the function SIN X and is the third of the four ; routines that use SERIES GENERATOR to produce Chebyshev polynomials. ; The approximation to SIN X is found as follows: ; i. The argument X is reduced and in this case W = V directly. Note that -1 <= ; W <= 1, as required for the series to converge. ; ii. The argument Z is formed, such that Z = 2*W*W-1. ; iii. The SERIES GENERATOR is used to return (SIN (PI*W/2))/W ; iv. Finally a simple multiplication gives SIN X. FP_sin: ; 37b5 rst FP_CALC ; X ; Perform step i. db C_get_argt ; W ; Perform step ii. The subroutine from now on is common to both the SINE and ; COSINE functions. C_ENT: ; 37b7 db C_duplicate ; W, W db C_duplicate ; W, W, W db C_multiply ; W, W*W db C_duplicate ; W, W*W, W*W db C_addition ; W, 2*W*W db C_stk_one ; W, 2*W*W, 1 db C_subtract ; W, 2*W*W-1 = Z ; Perform step iii, passing to the SERIES GENERATOR the parameter '6' and the ; six constants required. db 0x86 ; W, Z db 1-1<<6|0x64-0x50 db 0xe6 db 2-1<<6|0x6c-0x50 db 0x1f,0x0b db 3-1<<6|0x73-0x50 db 0x8f,0x38,0xee db 4-1<<6|0x79-0x50 db 0x15,0x63,0xbb,0x23 db 4-1<<6|0x7e-0x50 db 0x92,0x0d,0xcd,0xed db 4-1<<6|0x81-0x50 db 0x23,0x5d,0x1b,0xea ; At the end of the last loop the 'last value' is (SIN (PI*W/2))/W. ; Perform step v. db C_multiply ; SIN (PI*W/2) = SIN X (or = COS X) db C_end_calc ret ; Finished: 'last value' = SIN X. or ; ('last value' = COS X) ; THE 'TAN' FUNCTION ; (Offset 21: 'tan') ; This subroutine handles the function TAN X. The subroutine simply returns SIN ; X/COS X, with arithmetic overflow if COS X = 0. FP_tan: ; 37da rst FP_CALC ; X db C_duplicate ; X, X db C_sin ; X, SIN X db C_exchange ; SIN X, X db C_cos ; SIN X, COS X db C_division ; SIN X/COS X = TAN X Report arithmetic ; overflow if ; needed. db C_end_calc ; TAN X ret ; Finished: 'last value' = TAN X. ; THE 'ARCTAN' FUNCTION ; (Offset 24: 'atn') ; This subroutine handles the function ATN X and is the last of the four ; routines that use SERIES GENERATOR to produce Chebyshev polynomials. It ; returns a real number between -PI/2 and PI/2, which is equal to the value in ; radians of the angle whose tan is X. ; The approximation to ATN X is found as follows: ; i. The values W and Y are found for three cases of X, such that: ; if -1 < X < 1 then W = 0 & Y = X - case i. ; if -1 < =X then W = PI/2 & Y = -1/X - case ii. ; if X < =-1 then W = -PI/2 & Y = -1/X - case iii. ; In each case, -1 < =Y < =1, as required for the series to converge. ; ii. The argument Z is formed, such that: ; if -1 < X < 1 then Z = 2*Y*Y-1 = 2*X*X-1 - case i. ; if 1 < X then Z = 2*Y*Y-1 = 2/(X*X)-1 - case ii. ; if X < =-1 then Z = 2*Y*Y-1 = 2/(X*X)-1 - case iii. ; iii. The SERIES GENERATOR is used to produce the required function. ; iv. Finally a simple multiplication and addition give ATN X. ; Perform stage i. FP_atn: ; 37e2 call RE_STACK ; Use the full floating-point form of ; X. ld a,(hl) ; Fetch the exponent of X. cp 0x81 jr c,SMALL ; Jump forward for case i: Y = X. rst FP_CALC ; X db C_stk_one ; X, 1 db C_negate ; X, -1 db C_exchange ; -1, X db C_division ; -1/X db C_duplicate ; -1/X, -1/X db C_less_0 ; -1/X, (1/0) db C_stk_pi_2 ; -1/X, (1/0), PI/2 db C_exchange ; -1/X, PI/2, (1/0) db C_jump_true ; -1/X, PI/2 db CASES - $ ; Jump forward for case ii: Y = -1/X W ; = PI/2 db C_negate ; -1/X, -PI/2 db C_jump ; -1/X, -PI/2 db CASES - $ ; Jump forward for case iii: Y = -1/X ; W = -PI/2 SMALL: ; 37f8 rst FP_CALC ; Y db C_stk_zero ; Y, 0 Continue for case i: W = 0 ; Perform step ii. CASES: ; 37fa db C_exchange ; W, Y db C_duplicate ; W, Y, Y db C_duplicate ; W, Y, Y, Y db C_multiply ; W, Y, Y*Y db C_duplicate ; W, Y, Y*Y, Y*Y db C_addition ; W, Y, 2*Y*Y db C_stk_one ; W, Y, 2*Y*Y, 1 db C_subtract ; W, Y, 2*Y*Y-1 = Z ; Perform step iii, passing to the SERIES GENERATOR the parameter '12' decimal, ; and the twelve constants required. db 0x8c ; W, Y, Z db 1-1<<6|0x60-0x50 db 0xb2 db 1-1<<6|0x63-0x50 db 0x0e db 2-1<<6|0x65-0x50 db 0xe4,0x8d db 2-1<<6|0x68-0x50 db 0x39,0xbc db 2-1<<6|0x6b-0x50 db 0x98,0xfd db 3-1<<6|0x6e-0x50 db 0x00,0x36,0x75 db 3-1<<6|0x70-0x50 db 0xdb,0xe8,0xb4 db 2-1<<6|0x73-0x50 db 0x42,0xc4 db 4-1<<6|0x76-0x50 db 0xb5,0x09,0x36,0xbe db 4-1<<6|0x79-0x50 db 0x36,0x73,0x1b,0x5d db 4-1<<6|0x7c-0x50 db 0xd8,0xde,0x63,0xbe db 4-1<<6|0x80-0x50 db 0x61,0xa1,0xb3,0x0c ; At the end of the last loop the 'last value' is: ; ATN X/X – case i. ; ATN (-1/X)/(-1/X) – case ii. ; ATN (-1/X)/(-1/X) – case iii. ; Perform step iv. db C_multiply ; W, ATN X – case i. W, ATN ; (-1/X) – case ii. ; W, ATN (-1/X) – case iii. db C_addition ; ATN X – all cases now. db C_end_calc ret ; Finished: 'last value' = ATN X. ; THE 'ARCSIN' FUNCTION ; (Offset 22: 'asn') ; This subroutine handles the function ASN X and return a real real number from ; -PI/2 to PI/2 inclusive which is equal to the value in radians of the angle ; whose sine is X. Thereby if Y = ASN X then X = SIN Y. ; This subroutine uses the trigonometric identity: ; TAN (Y/2) = SIN Y/1(1+COS Y) ; to obtain TAN (Y/2) and hence (using ATN) Y/2 and finally Y. FP_asn: ; 3833 rst FP_CALC ; X db C_duplicate ; X, X db C_duplicate ; X, X, X db C_multiply ; X, X*X db C_stk_one ; X, X*X, 1 db C_subtract ; X, X*X-1 db C_negate ; X, 1-X*X db C_sqr ; X, SQR (1-X*X) db C_stk_one ; X, SQR (1-X*X), 1 db C_addition ; X, 1+SQR (1-X*X) db C_division ; X/(1+SQR (1-X*X)) = TAN (Y/2) db C_atn ; Y/2 db C_duplicate ; Y/2, Y/2 db C_addition ; Y = ASN X db C_end_calc ret ; Finished: 'last value' = ASN X. ; THE 'ARCCOS' FUNCTION ; (Offset 23: 'acs') ; This subroutine handles the function ACS X and returns a real number from ; zero to PI inclusive which is equal to the value in radians of the angle ; whose cosine is X. ; This subroutine uses the relation: ; ACS X = PI/2 - ASN X FP_acs: ; 3843 rst FP_CALC ; X db C_asn ; ASN X db C_stk_pi_2 ; ASN X, PI/2 db C_subtract ; ASN X-PI/2 db C_negate ; PI/2-ASN X = ACS X db C_end_calc ret ; Finished: 'last value' = ACS X. ; THE 'SQUARE ROOT' FUNCTION ; (Offset 28: 'sqr') ; This subroutine handles the function SQR X and returns the positive square ; root of the real number X if X is positive, and zero if X is zero. A negative ; value of X gives rise to report A - invalid argument (via In in the ; EXPONENTIATION subroutine). ; This subroutine treats the square root operation as being X**.5 and ; therefore stacks the value .5 and proceeds directly into the EXPONENTIATION ; subroutine. FP_sqr: ; 384a rst FP_CALC ; X db C_duplicate ; X, X db C_not ; X, (1/0) db C_jump_true ; X db LAST - $ ; X ; The jump is made if X = 0, otherwise continue with: db C_stk_half ; X, .5 db C_end_calc ; and then find the result of X**.5. ; THE 'EXPONENTIATION' OPERATION ; (Offset 06: 'to-power') ; This subroutine performs the binary operation of raising the first number, X, ; to the power of the second number, Y. ; The subroutine treats the result X**Y as being equivalent to EXP (Y*LN X). ; It returns this value unless X is zero, in which case it returns 1 if Y is ; also zero (0**0=1), returns zero if Y is positive and reports arithmetic ; overflow if Y is negative. FP_to_power: ; 3851 rst FP_CALC ; X, Y db C_exchange ; Y, X db C_duplicate ; Y, X, X db C_not ; Y, X, (1/0) db C_jump_true ; Y, X db XIS0 - $ ; Y, X ; The jump is made if X = 0, otherwise EXP (Y*LN X) is formed. db C_ln ; Y, LN X ; Giving report A if X is negative. db C_multiply ; Y*LN X db C_end_calc jp EXP ; Exit via EXP to form EXP (Y*LN X). ; The value of X is zero so consider the three possible cases involved. XIS0: ; 385d db C_delete ; Y db C_duplicate ; Y, Y db C_not ; Y, (1/0) db C_jump_true ; Y db ONE - $ ; Y ; The jump is made if X = 0 and Y = 0, otherwise proceed. db C_stk_zero ; Y, 0 db C_exchange ; 0, Y db C_greater_0 ; 0, (1/0) db C_jump_true ; 0 db LAST - $ ; 0 ; The jump is made if X = 0 and Y is positive, otherwise proceed. db C_stk_one ; 0, 1 db C_exchange ; 1, 0 db C_division ; Exit via 'division' as dividing by ; zero gives 'arithmetic overflow'. ; The result is to be 1 for the operation. ONE: ; 386a db C_delete ; – db C_stk_one ; 1 ; Now return with the 'last value' on the stack being 0**Y. LAST: ; 386c db C_end_calc ; (1/0) ret ; Finished: 'last value' is 0 or 1. repeat 15616 - $ db 0xff endrepeat ; THE FONT FONT: ; 3d00 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00010000 db %00010000 db %00010000 db %00010000 db %00000000 db %00010000 db %00000000 db %00000000 db %00100100 db %00100100 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00100100 db %01111110 db %00100100 db %00100100 db %01111110 db %00100100 db %00000000 db %00000000 db %00001000 db %00111110 db %00101000 db %00111110 db %00001010 db %00111110 db %00001000 db %00000000 db %01100010 db %01100100 db %00001000 db %00010000 db %00100110 db %01000110 db %00000000 db %00000000 db %00010000 db %00101000 db %00010000 db %00101010 db %01000100 db %00111010 db %00000000 db %00000000 db %00001000 db %00010000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000100 db %00001000 db %00001000 db %00001000 db %00001000 db %00000100 db %00000000 db %00000000 db %00100000 db %00010000 db %00010000 db %00010000 db %00010000 db %00100000 db %00000000 db %00000000 db %00000000 db %00010100 db %00001000 db %00111110 db %00001000 db %00010100 db %00000000 db %00000000 db %00000000 db %00001000 db %00001000 db %00111110 db %00001000 db %00001000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00001000 db %00001000 db %00010000 db %00000000 db %00000000 db %00000000 db %00000000 db %00111110 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00011000 db %00011000 db %00000000 db %00000000 db %00000000 db %00000010 db %00000100 db %00001000 db %00010000 db %00100000 db %00000000 db %00000000 db %00111100 db %01000110 db %01001010 db %01010010 db %01100010 db %00111100 db %00000000 db %00000000 db %00011000 db %00101000 db %00001000 db %00001000 db %00001000 db %00111110 db %00000000 db %00000000 db %00111100 db %01000010 db %00000010 db %00111100 db %01000000 db %01111110 db %00000000 db %00000000 db %00111100 db %01000010 db %00001100 db %00000010 db %01000010 db %00111100 db %00000000 db %00000000 db %00001000 db %00011000 db %00101000 db %01001000 db %01111110 db %00001000 db %00000000 db %00000000 db %01111110 db %01000000 db %01111100 db %00000010 db %01000010 db %00111100 db %00000000 db %00000000 db %00111100 db %01000000 db %01111100 db %01000010 db %01000010 db %00111100 db %00000000 db %00000000 db %01111110 db %00000010 db %00000100 db %00001000 db %00010000 db %00010000 db %00000000 db %00000000 db %00111100 db %01000010 db %00111100 db %01000010 db %01000010 db %00111100 db %00000000 db %00000000 db %00111100 db %01000010 db %01000010 db %00111110 db %00000010 db %00111100 db %00000000 db %00000000 db %00000000 db %00000000 db %00010000 db %00000000 db %00000000 db %00010000 db %00000000 db %00000000 db %00000000 db %00010000 db %00000000 db %00000000 db %00010000 db %00010000 db %00100000 db %00000000 db %00000000 db %00000100 db %00001000 db %00010000 db %00001000 db %00000100 db %00000000 db %00000000 db %00000000 db %00000000 db %00111110 db %00000000 db %00111110 db %00000000 db %00000000 db %00000000 db %00000000 db %00010000 db %00001000 db %00000100 db %00001000 db %00010000 db %00000000 db %00000000 db %00111100 db %01000010 db %00000100 db %00001000 db %00000000 db %00001000 db %00000000 db %00000000 db %00111100 db %01001010 db %01010110 db %01011110 db %01000000 db %00111100 db %00000000 db %00000000 db %00111100 db %01000010 db %01000010 db %01111110 db %01000010 db %01000010 db %00000000 db %00000000 db %01111100 db %01000010 db %01111100 db %01000010 db %01000010 db %01111100 db %00000000 db %00000000 db %00111100 db %01000010 db %01000000 db %01000000 db %01000010 db %00111100 db %00000000 db %00000000 db %01111000 db %01000100 db %01000010 db %01000010 db %01000100 db %01111000 db %00000000 db %00000000 db %01111110 db %01000000 db %01111100 db %01000000 db %01000000 db %01111110 db %00000000 db %00000000 db %01111110 db %01000000 db %01111100 db %01000000 db %01000000 db %01000000 db %00000000 db %00000000 db %00111100 db %01000010 db %01000000 db %01001110 db %01000010 db %00111100 db %00000000 db %00000000 db %01000010 db %01000010 db %01111110 db %01000010 db %01000010 db %01000010 db %00000000 db %00000000 db %00111110 db %00001000 db %00001000 db %00001000 db %00001000 db %00111110 db %00000000 db %00000000 db %00000010 db %00000010 db %00000010 db %01000010 db %01000010 db %00111100 db %00000000 db %00000000 db %01000100 db %01001000 db %01110000 db %01001000 db %01000100 db %01000010 db %00000000 db %00000000 db %01000000 db %01000000 db %01000000 db %01000000 db %01000000 db %01111110 db %00000000 db %00000000 db %01000010 db %01100110 db %01011010 db %01000010 db %01000010 db %01000010 db %00000000 db %00000000 db %01000010 db %01100010 db %01010010 db %01001010 db %01000110 db %01000010 db %00000000 db %00000000 db %00111100 db %01000010 db %01000010 db %01000010 db %01000010 db %00111100 db %00000000 db %00000000 db %01111100 db %01000010 db %01000010 db %01111100 db %01000000 db %01000000 db %00000000 db %00000000 db %00111100 db %01000010 db %01000010 db %01010010 db %01001010 db %00111100 db %00000000 db %00000000 db %01111100 db %01000010 db %01000010 db %01111100 db %01000100 db %01000010 db %00000000 db %00000000 db %00111100 db %01000000 db %00111100 db %00000010 db %01000010 db %00111100 db %00000000 db %00000000 db %11111110 db %00010000 db %00010000 db %00010000 db %00010000 db %00010000 db %00000000 db %00000000 db %01000010 db %01000010 db %01000010 db %01000010 db %01000010 db %00111100 db %00000000 db %00000000 db %01000010 db %01000010 db %01000010 db %01000010 db %00100100 db %00011000 db %00000000 db %00000000 db %01000010 db %01000010 db %01000010 db %01000010 db %01011010 db %00100100 db %00000000 db %00000000 db %01000010 db %00100100 db %00011000 db %00011000 db %00100100 db %01000010 db %00000000 db %00000000 db %10000010 db %01000100 db %00101000 db %00010000 db %00010000 db %00010000 db %00000000 db %00000000 db %01111110 db %00000100 db %00001000 db %00010000 db %00100000 db %01111110 db %00000000 db %00000000 db %00001110 db %00001000 db %00001000 db %00001000 db %00001000 db %00001110 db %00000000 db %00000000 db %00000000 db %01000000 db %00100000 db %00010000 db %00001000 db %00000100 db %00000000 db %00000000 db %01110000 db %00010000 db %00010000 db %00010000 db %00010000 db %01110000 db %00000000 db %00000000 db %00010000 db %00111000 db %01010100 db %00010000 db %00010000 db %00010000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %11111111 db %00000000 db %00011100 db %00100010 db %01111000 db %00100000 db %00100000 db %01111110 db %00000000 db %00000000 db %00000000 db %00111000 db %00000100 db %00111100 db %01000100 db %00111100 db %00000000 db %00000000 db %00100000 db %00100000 db %00111100 db %00100010 db %00100010 db %00111100 db %00000000 db %00000000 db %00000000 db %00011100 db %00100000 db %00100000 db %00100000 db %00011100 db %00000000 db %00000000 db %00000100 db %00000100 db %00111100 db %01000100 db %01000100 db %00111100 db %00000000 db %00000000 db %00000000 db %00111000 db %01000100 db %01111000 db %01000000 db %00111100 db %00000000 db %00000000 db %00001100 db %00010000 db %00011000 db %00010000 db %00010000 db %00010000 db %00000000 db %00000000 db %00000000 db %00111100 db %01000100 db %01000100 db %00111100 db %00000100 db %00111000 db %00000000 db %01000000 db %01000000 db %01111000 db %01000100 db %01000100 db %01000100 db %00000000 db %00000000 db %00010000 db %00000000 db %00110000 db %00010000 db %00010000 db %00111000 db %00000000 db %00000000 db %00000100 db %00000000 db %00000100 db %00000100 db %00000100 db %00100100 db %00011000 db %00000000 db %00100000 db %00101000 db %00110000 db %00110000 db %00101000 db %00100100 db %00000000 db %00000000 db %00010000 db %00010000 db %00010000 db %00010000 db %00010000 db %00001100 db %00000000 db %00000000 db %00000000 db %01101000 db %01010100 db %01010100 db %01010100 db %01010100 db %00000000 db %00000000 db %00000000 db %01111000 db %01000100 db %01000100 db %01000100 db %01000100 db %00000000 db %00000000 db %00000000 db %00111000 db %01000100 db %01000100 db %01000100 db %00111000 db %00000000 db %00000000 db %00000000 db %01111000 db %01000100 db %01000100 db %01111000 db %01000000 db %01000000 db %00000000 db %00000000 db %00111100 db %01000100 db %01000100 db %00111100 db %00000100 db %00000110 db %00000000 db %00000000 db %00011100 db %00100000 db %00100000 db %00100000 db %00100000 db %00000000 db %00000000 db %00000000 db %00111000 db %01000000 db %00111000 db %00000100 db %01111000 db %00000000 db %00000000 db %00010000 db %00111000 db %00010000 db %00010000 db %00010000 db %00001100 db %00000000 db %00000000 db %00000000 db %01000100 db %01000100 db %01000100 db %01000100 db %00111000 db %00000000 db %00000000 db %00000000 db %01000100 db %01000100 db %00101000 db %00101000 db %00010000 db %00000000 db %00000000 db %00000000 db %01000100 db %01010100 db %01010100 db %01010100 db %00101000 db %00000000 db %00000000 db %00000000 db %01000100 db %00101000 db %00010000 db %00101000 db %01000100 db %00000000 db %00000000 db %00000000 db %01000100 db %01000100 db %01000100 db %00111100 db %00000100 db %00111000 db %00000000 db %00000000 db %01111100 db %00001000 db %00010000 db %00100000 db %01111100 db %00000000 db %00000000 db %00001110 db %00001000 db %00110000 db %00001000 db %00001000 db %00001110 db %00000000 db %00000000 db %00001000 db %00001000 db %00001000 db %00001000 db %00001000 db %00001000 db %00000000 db %00000000 db %01110000 db %00010000 db %00001100 db %00010000 db %00010000 db %01110000 db %00000000 db %00000000 db %00010100 db %00101000 db %00000000 db %00000000 db %00000000 db %00000000 db %00000000 db %00111100 db %01000010 db %10011001 db %10100001 db %10100001 db %10011001 db %01000010 db %00111100 PRINTER_BUFFER equ 0x5b00 KSTATE equ 0x5c00 LAST_K equ 0x5c08 REPDEL equ 0x5c09 REPPER equ 0x5c0a DEFADD equ 0x5c0b K_DATA equ 0x5c0d TVDATA equ 0x5c0e STRMS equ 0x5c10 CHARS equ 0x5c36 RASP equ 0x5c38 ERR_NR equ 0x5c3a FLAGS equ 0x5c3b TV_FLAG equ 0x5c3c ERR_SP equ 0x5c3d LIST_SP equ 0x5c3f MODE equ 0x5c41 NEWPPC equ 0x5c42 NSPPC equ 0x5c44 PPC equ 0x5c45 SUBPPC equ 0x5c47 BORDCR equ 0x5c48 E_PPC equ 0x5c49 VARS equ 0x5c4b DEST equ 0x5c4d CHANS equ 0x5c4f CURCHL equ 0x5c51 PROG equ 0x5c53 NXTLIN equ 0x5c55 DATADD equ 0x5c57 E_LINE equ 0x5c59 K_CUR equ 0x5c5b CH_ADD equ 0x5c5d X_PTR equ 0x5c5f WORKSP equ 0x5c61 STKBOT equ 0x5c63 STKEND equ 0x5c65 BREG equ 0x5c67 MEM equ 0x5c68 FLAGS2 equ 0x5c6a DF_SZ equ 0x5c6b S_TOP equ 0x5c6c OLDPPC equ 0x5c6e OSPCC equ 0x5c70 FLAGX equ 0x5c71 STRLEN_ equ 0x5c72 T_ADDR equ 0x5c74 SEED equ 0x5c76 FRAMES equ 0x5c78 UDG equ 0x5c7b COORDS equ 0x5c7d PR_CC equ 0x5c80 ECHO_E equ 0x5c82 DF_CC equ 0x5c84 DF_CCL equ 0x5c86 S_POSN equ 0x5c88 S_POSNL equ 0x5c8a SCR_CT equ 0x5c8c ATTR_P equ 0x5c8d ATTR_T equ 0x5c8f P_FLAG equ 0x5c91 MEMBOT equ 0x5c92 NMIADD equ 0x5cb0 RAMTOP equ 0x5cb2 P_RAMT equ 0x5cb4 AFTER_SYSVARS equ 0x5cb6 d_KSTATE equ -58 d_K_DATA equ -45 d_RASP equ -2 d_PIP equ -1 d_ERR_NR equ +0 d_FLAGS equ +1 d_TV_FLAG equ +2 d_MODE equ +7 d_NSPPC equ +10 d_SUBPPC equ +13 d_BORDCR equ +14 d_E_PPC equ +15 d_K_CUR equ +33 d_X_PTR equ +37 d_BREG equ +45 d_FLAGS2 equ +48 d_DF_SZ equ +49 d_OSPCC equ +54 d_FLAGX equ +55 d_STRLEN equ +56 d_FRAMES equ +62 d_P_POSN equ +69 d_PR_CC equ +70 d_S_POSN equ +78 d_S_POSNL equ +80 d_SCR_CT equ +82 d_P_FLAG equ +87 d_MEMBOT equ +88 ; Created by Jan Bobrowski © 2025 ; using The Complete Spectrum ROM Disassembly by Ian Logan & Frank O'Hara ; as transcribed by comp.sys.sinclair newsgroup readers.