CompleteSpectrumROMDisassemblyThe PDF
CompleteSpectrumROMDisassemblyThe PDF
BY
Transcribed by the following readers of the comp.sys.sinclair newsgroup:J.R. Biesma Biggo Dr. J. Bland Paul E .Collins Chris Cowley Dr. Rupert Goodwins Jonathan G Harston Marcus Lund Joe Mackay Russell Marks Eduardo Yaez Parareda Adam Stonehewer Mark Street Gerard Sweeney Geoff Wearmouth Matthew Westcott Matthew Wilson Witchy
Preface
The Sinclair ZX Spectrum is a worthy successor to the ZX 81 which in turn replaced the ZX 80. The Spectrum has a 16K monitor program. This program has been developed directly from the 4K program of the ZX 80 although there are now so many new features that the differences outweigh the similarities. We have both enjoyed producing this book. We have learnt a great deal about the techniques of Z80 machine code programming and now feel that between us we have unravelled the secrets of the Spectrum. We would like to thank: -- Our families. -- Alfred Milgrom, our publisher who has been extremely helpful. -- Philip Mitchell whose notes on the cassette format were most informative. -- Clive Sinclair and his team at Sinclair Research Ltd. who have produced such a challenging and useful machine. January 1983 Ian Logan Frank OHara Lincoln, U.K. London, U.K.
Contents
page Preface Introduction The DISASSEMBLY - The restart routines and tables - The keyboard routines - The loudspeaker routines - The cassette handling routines - The screen and printer handling routines - The executive routines - BASIC line and command interpretation - Expression evaluation - The arithmetic routines - The floating-point calculator Appendix - BASIC programs for the main series (SIN X, EXP X, LN X & ATN X) - The DRAW algorithm - The CIRCLE algorithm - Note on small integers and -65536 Index to routines
Introduction
The 16K monitor program of the Spectrum is a complex Z80 machine code program. Its overall structure is very clear in that it is divided into three major parts: a. Input/Output routines. b. BASIC interpreter. c. Expression handling. However these blocks are too large to be managed easily and in this book the monitor program is discussed in ten parts. Each of these parts will now be outlined. The restart routines and tables. At the start of the monitor program are the various restart routines that are called with the single byte RST instructions. All of the restarts are used. For example restart 0008 is used for the reporting of syntax or run-time errors. The tables in this part of the monitor program hold the expanded forms of the tokens and the key-codes. The keyboard routine. The keyboard is scanned every 1/50 th. of a second (U.K. model) and the keyboard routine returns the required character code. All of the keys of the keyboard 'repeat' if they are held down and the keyboard routine takes this into consideration. The loudspeaker routines. The spectrum has a single on-board loudspeaker and a note is produced by repeatedly using the appropriate 'OUT' instruction. In the controller routine great care has been taken to ensure that the note is held at a given 'pitch' throughout its 'duration'. The cassette handling routines. It was a very unfortunate feature of the ZX 81 that so little of the monitor program for that machine was devoted to the cassette handling. However in the Spectrum there is an extensive block of code and now the high standard of cassette handling is one of the most successful features of the machine. BASIC programs or blocks of data are both dealt with in the same manner of having a 'header' block (seventeen bytes) that is SAVEd first. This 'header' describes the 'data block' that is SAVEd after it. One disadvantage of this system is that it is not possible to produce programs with any 'security' whatsoever. The screen and printer handling routines. All of the remaining input/output routines of the Spectrum are 'vectored' through the 'channel & stream information areas'. In the standard Spectrum 'input' is only possible from the keyboard but 'output' can be directed to the printer, the upper part of the T.V. display or the lower part of the T.V. display. The major 'input' routine in this part of the monitor program is the EDITOR that allows the user to enter characters into the lower part of the T.V. display. The PRINT-OUT routine is a rather slow routine as the same routine is used for 'all possibilities'. For example, the adding of a single byte to the 'display area' involves considering the present status of OVER and INVERSE on every occasion. The executive routines In this part of the monitor program are to be found the INITIALISATION procedure and the 'main execution loop' of the BASIC interpreter. In the Spectrum the BASIC line returned by the EDITOR is checked for the correctness of its syntax and then saved in the program area, if it was a line starting with a line number, or 'executed' otherwise. This execution can in turn lead to further statements being considered. (Most clearly seen as in the case of - RUN.)
BASIC line and command interpretation. This part of the monitor program considers a BASIC line as a set of statements and in its turn each statement as starting with a particular command. For each command there is a 'command routine' and it is the execution of the machine code in the appropriate 'command routine' that effects the 'interpretation'. Expression evaluation The Spectrum has a most comprehensive expression evaluator allowing for a wide range of variable types, functions and operations. Once again this part of the monitor is fairly slow as all the possible alternatives have to be considered. The handling of strings is particularly well managed. All simple strings are managed 'dynamically' and old copies are 'reclaimed' once they are redundant. This means that there is no 'garbage collecting' to be done. The arithmetic routines The Spectrum has two forms for numbers. Integer values in the range -65535 to +65535 are in an 'integral' or 'short' form whilst all other numbers are in a five byte floating point form. The present version of the monitor is unfortunately marred by two mistakes in this part. i. There is a mistake in 'division' whereby the 34th bit of a division is lost. ii. The value of -65536 is sometimes put in 'short' form and at other times in 'floating-point' and this leads to troubles. The floating-point calculator The CALCULATOR of the Spectrum handles numbers and strings and its operations are specified by 'literals'. It can therefore be considered that there is an internal 'stack operating' language in the CALCULATOR. This part of the monitor program contains routines for all the mathematical functions. The approximations to SIN X, EXP X, LN X & ATN X are obtained by developing Chebyshev polynomials and full details are given in the appendix. Overall the 16K monitor program offers an extremely wide range of different BASIC commands and functions. The programmers have always however been short of 'room' and hence the program is written for 'compactness' rather than 'speed'.
ii
THE DISASSEMBLY
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'. 0000 START DI Disable the 'keyboard interrupt'. XOR A +00 for start (but +FF for 'NEW'). LD DE,+FFFF Top of possible RAM. JP 11CB,START/NEW Jump forward.
0048
KEY-INT
INC LD LD OR JR INC PUSH PUSH CALL POP POP POP POP EI RET
frame counter are incremented every 20 ms. (U.K.) The highest byte of the frame counter is only incremented when the value of the lower two bytes is zero. Save the current values held in these registers. Now scan the keyboard. Restore the values.
but with carry set. Return with codes +18 to +20 again with carry set. Skip-over once. Jump forward with codes +10 to +15 (INK to OVER). Skip-over once more (AT & TAB). Return with the carry flag set and CH-ADD holding the appropriate address.
H J K L ENTER
Y U I 0 P
6 7 8 9 0
5 4 3 2 1
T R E W Q
G F D S A
V C X Z
(d) Control codes. Digit keys and CAPS SHIFT. 0260 0C 07 06 04 DELETE EDIT 0264 05 08 0A 0B INV VIDEO Cursor left 0268 09 0F Cursor right GRAPHICS (e) Symbol code. Letter keys and symbol shift. 026A E2 2A 3F CD STOP * 026E C8 CC CB 5E >= TO 0272 AC 2D 2B 3D AT 0276 2E 2C 3B 22 . , 027A C7 3C C3 3E <= < 027E C5 2F C9 60 OR / 0282 C6 3A AND : (f) Extended mode. Digit keys and symbol shift. 0284 D0 CE A8 CA FORMAT DEF FN 0288 D3 D4 D1 D2 OPEN CLOSE 028C A9 CF POINT CAT
FN MOVE
LINE ERASE
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.) 0296 KEY-LINE IN CPL AND JR LD LD 029F KEY-3KEYS INC RET SUB SRL JR LD LD JR 02AB KEY-DONE DEC RLC JR Four tests are now made. LD INC RET CP RET A,(C) +1F Z,02AB,KEY-DONE H,A A,L D NZ +08 H NC,02A1,KEY-BITS D,E E,A NZ,029F,KEY-3KEYS L B C,0296,KEY-LINE A,D A Z +28 Z Read from the port specified. A pressed key in the line will set its respective bit (from bit 0 outer key, to bit 4 - inner key). Jump forward if none of the five keys in the line are being pressed. The key-bits go to the H register whilst the initial key value is fetched. If three keys are being pressed on the keyboard then the D register will no longer hold +FF - so return if this happens. Repeatedly subtract '8' from the present key value until a key-bit is found. Copy any earlier key value to the D register. Pass the new key value to the E register. If there is a second, or possibly a third, pressed key in this line then jump back. The line has been scanned so the initial key value is reduced for the next pass. The counter is shifted and the jump taken if there are still lines to be scanned. Accept any key value which still has the D register holding +FF. i.e. A single key pressed or no-key. Accept the key value for a pair of keys if the D key is CAPS SHIFT.
02A1
KEY-BITS
CP RET LD LD LD CP RET
Accept the key value for a pair of keys if the 'D' key is SYMBOL SHIFT. It is however possible for the 'E' key of a pair to be SYMBOL SHIFT - so this has to be considered. Return with the zero flag set if it was SYMBOL SHIFT and 'another key'; otherwise reset.
A double system of 'KSTATE system variables' (KSTATE0 - KSTATE 3 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. 02C6 K-ST-LOOP LD BIT JR INC DEC DEC JR LD HL,KSTATE0 7,(HL) NZ,02D1,K-CH-SET HL (HL) HL NZ,02D1,K-CH-SET (HL),+FF Start with KSTATE0. Jump forward if a 'set is free'; i.e. KSTATE0/4 holds +FF. However if the set is not free decrease its '5 call counter' and when it reaches zero signal the set as free.
After considering the first set change the pointer and consider the second set. 02D1 K-CH-SET LD LD CP JR A,L HL,KSTATE4 L NZ,02C6,K-ST-LOOP Fetch the low byte of the address and jump back if the second set has still to be considered.
Return now if the key value indicates 'no-key' or a shift key only. CALL RET 031E,K-TEST NC Make the necessary tests and 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 CP JR EX LD CP JR HL,KSTATE0 (HL) Z,0310,K-REPEAT DE,HL HL,KSTATE4 (HL) Z,0310,K-REPEAT Look first at KSTATE0. Jump forward if the codes match - indicating a repeat. Save the address of KSTATE0. Now look at KSTATE4. Jump forward if the codes match - indicating a repeat.
But a new key will not be accepted unless one of the sets of KSTATE system variables is 'free'. BIT JR EX 7,(HL) NZ,02F1,K-NEW DE,HL Consider the second set. Jump forward if 'free'. Now consider the first set.
BIT RET
7,(HL) Z
Continue if the set is 'free' but 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. 02F1 K-NEW LD LD INC LD INC LD LD INC E,A (HL),A HL (HL),+05 HL A,(REPDEL) (HL),A HL The code is passed to the E register and to KSTATE0/4. The '5 call counter' for this set is reset to '5'. The third system variable of the set holds the REPDEL value (normally 0.7 secs.). 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 LD PUSH CALL POP LD C,(MODE) D,(FLAGS) HL 0333,K-DECODE HL (HL),A Fetch MODE. Fetch FLAGS. Save the pointer whilst the 'main code' is decoded. 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'. 0308 K-END LD SET RET (LAST-K),A 5,(FLAGS) Enter the final code value into LAST-K and signal 'a new key'. Finally return.
CP JR BIT RET
Jump forward unless the 'E' key was SYMBOL SHIFT. However accept SYMBOL SHIFT and another key; return with SYMBOL SHIFT only.
The 'main code' is found by indexing into the main key table. 032C K-MAIN LD ADD LD SCF RET HL,+0205 HL,DE A,(HL) The base address of the table. Index into the table and fetch the 'main code'. Signal 'valid keystroke' before returning.
Only 'graphics' mode remains and the 'final code' for letter keys in graphics mode is computed from the 'main code'. ADD RET A,+4F Add the offset. Return with the 'final code'.
Letter keys in extended mode are considered next. 0341 K-E-LET LD INC JR LD HL,+01EB B Z,034A,K-LOOK-UP HL,+0205 The base address for table 'b'. Jump forward to use this table if neither shift key is being pressed. 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. 034A K-LOOK-UP LD ADD LD RET D,+00 HL,DE A,(HL) Clear the D register. Index the required table and fetch the 'final code'. 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. 034F K-KLC-LET LD BIT JR BIT JR BIT RET INC RET ADD RET HL,+0229 0,B Z,034A,K-LOOK-UP 3,D Z,0364,K-TOKENS 3,(FLAGS2) NZ B NZ A,+20 The base address for table 'e' Jump back if using the SYMBOL SHIFT key and a letter key. Jump forward if currently in 'K' mode. If CAPS LOCK is set then return with the 'main code' Also return in the same manner if CAPS SHIFT is being pressed. However if lower case codes are 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'.
0364
K-TOKENS
ADD RET
A,+A5
Next the digit keys; and SPACE, ENTER & both shifts; are considered. 0367 K-DIGIT CP RET DEC JP JR LD BIT JR CP JR +30 C C M,039D,K-KLC-DGT NZ,0389,K-GRA-DGT HL,+0254 5,B Z,034A,K-LOOK-UP +38 NC,0382,K-8-&-9 Proceed only with the digit keys. i.e. Return with SPACE (+20), ENTER (+0D) & both shifts (+0E). Now separate the digit keys into three groups - according to the mode. Jump with 'K', 'L' & 'C' modes; and also with 'G' mode. Continue with 'E' mode. The base address for table 'f'. Use this table for SYMBOL SHIFT & a digit key in extended mode. Jump forward with digit keys '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 INC RET ADD RET +20 B Z A,+08 Reduce the range +30 to +37 giving +10 to +17. Return with this 'paper colour code' if the CAPS SHIFT is not being used. But if it is then the range is to be +18 to +1F instead - indicating an 'ink colour code'.
The digit keys '8' and '9' are to give 'BRIGHT' & 'FLASH' codes. 0382 K-8-&-9 SUB INC RET ADD RET +36 B Z A,+FE +38 & +39 go to +02 & +03. Return with these codes if CAPS SHIFT is not being used. (These are 'BRIGHT' codes.) Subtract '2' is CAPS SHIFT is 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). 0389 K-GRA-DGT LD CP JR CP JR AND ADD INC RET XOR RET HL,+0230 +39 Z,034A,K-LOOK-UP +30 Z,034A,K-LOOK-UP +07 A,+80 B Z +0F The base address of table 'd'. Use this table directly for both digit key '9' that is to give GRAPHICS, and digit key '0' that is to give DELETE. For keys '1' to '8' make the range +80 to +87. Return with a value from this range if neither shift key is being pressed. But if 'shifted' make the range +88 to +8F.
Finally consider the digit keys in 'K', 'L' & 'C' modes. 039D K-KLC-DGT INC RET BIT LD JR B Z 5,B HL,+0230 NZ,034A,K-LOOK-UP Return directly if neither shift key is being used. (Final codes +30 to +39.) Use table 'd' if the CAPS SHIFT key is also being pressed.
The codes for the various digit keys and SYMBOL SHIFT can now be found. SUB CP JR CP RET LD RET LD RET +10 +22 Z,03B2,K-@-CHAR +20 NZ A,+5F A,+40 Reduce the range to give +20 to +29. Separate the '@' character from the others. The '-' character has also to be separated. Return now with the 'final codes' +21, +23 to +29. Give the '-' character a code of +5F. Give the '@' character a code of +40.
03B2
K-@-CHAR
10
A,(BORDCR) +38
+08
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. 03D1 03D2 03D3 03D4 03D6 BE-IX+3 BE-IX+2 BE-IX+1 BE-IX+0 BE-H&L-LP NOP NOP NOP INC INC DEC JR LD DEC JP Add '4' T states for each earlier entry port that is used. The values in the B & C registers will come from H & L registers - see below. The 'timing loop'. i.e. 'BC' * '4' T states. (But note that at the half-cycle point - C will be equal to 'L+1'.)
The loudspeaker is now alternately activated and deactivated. XOR +10 Flip bit 4.
11
OUT LD LD BIT JR
Perform the OUT operation; leaving the border unchanged. Reset the B register. Save the A register. Jump if at the half-cycle point.
After a full cycle the DE register pair is tested. LD OR JR LD LD DEC JP A,D E Z,03D6,BE-END A,C C,L DE (IX) Jump forward if the last complete pass has been made already. Fetch the saved value. Reset the C register. Decrease the pass counter. Jump back to the required starting location of the loop.
The parameters for the second half-cycle are set up. 03F2 BE-AGAIN LD INC JP C,L C (IX) Reset the C register. Add '16' T states as this path is shorter. Jump back.
Upon completion of the 'beep' the maskable interrupt has to be enabled. 03F6 BE-END EI RET Enable interrupt. Finally return.
Now perform several tests on i, the integer part of the 'pitch'. LD LD AND JR INC LD INC LD LD RLA SBC CP JR INC CP HL,+5C92 A,(HL) A NZ,046C,REPORT-B HL C,(HL) HL B,(HL) A,B A,A C NZ,046C,REPORT-B HL (HL) This is 'mem-0-1st' (MEMBOT). Fetch the exponent of i. Give an error if i is not in the integral (short) form. Copy the sign byte to the C register. Copy the low-byte to the B register; and to the A register. Again give report B if i does not satisfy the test: -128<=i<=+127
12
JR LD ADD JP JP
Fetch the low-byte and test it further. Accept -60<=i<=67. 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. 0425 0427 BE-i-OK LD BE-OCTAVE INC SUB JR ADD PUSH LD CALL CALL B,+FA B +0C NC,0427,BE-OCTAVE A,+0C BC HL,+046E 3406,LOC-MEM 33B4,STACK-NUM Start '6' octaves below middle C. Repeatedly reduce i in order to find the correct octave. Ass back the last subtraction. Save the octave number. The base address of the 'semitone table'. Consider the table and pass the 'A th.' value to the calculator stack. (Call it C.)
Now the fractional part of the 'pitch' can be taken into consideration. RST DEFB DEFB 0028,FP-CALC +04,multiply +38,end-calc t, pK+1, C t, C(pK+1)
The final frequency f is found by modifying the 'last value' according to the octave number. POP ADD LD RST DEFB DEFB Attention is now turned to the 'duration'. DEFB DEFB CALL CP JR AF A,(HL) (HL),A 0028,FP-CALC +C0,st-mem-0 +02,delete +31,duplicate +38,end-calc 1E94,FIND-INT1 +0B NC,046C,REPORT-B Fetch the octave number. Multiply the 'last value' by '2 to the power of the octave number'. t, f The frequency is put aside for the moment in mem-0. t, t The value 'INT t' must be in the range +00 to +0A.
The number of complete cycles in the 'beep' is given by 'f*t' so this value is now found. RST DEFB DEFB 0028,FP-CALC +E0,get-mem-0 +04,multiply t t, f f*t
The result is left on the calculator stack whilst the length of the 'timing loop' required for the 'beep' is computed; DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB +E0,get-mem-0 +34,stk-data +80,four bytes +43,exponent +93 +55,+9F,+80,(+00) +01,exchange +05,division +34,stk-data +35,exponent +85 +71,(+00,+00,+00) +03,subtract +38,end-calc f*t, f The value '3.5 * 106 /8' is formed on the top of the calculator stack. f*t, f, 437,500 (dec.) f*t, 437,500, f f*t, 437,500/f f*t, 437,500/f, 30.125 (dec.) f*t, 437,500/f - 30.125
13
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 PUSH 1E99,FIND-INT2 BC The 'timing loop' value is compressed into the 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 POP LD LD 1E99,FIND-INT2 HL D,B E,C The 'f*t' value is compressed into the BC register pair. Move the 'timing loop' value to the HL register pair. Move the 'f*t' value to the DE register pair.
However before making the 'beep' test the value 'f*t'. LD OR RET DEC JP Report B - integer out of range 046C REPORT-B RST DEFB 0008,ERROR-1 +0A Call the error handling routine. A,D E Z DE 03B5,BEEPER Return if 'f*t' has given the result of 'no cycles' required. Decrease the cycle number and jump to the BEEPER subroutine (making, at least, one pass).
14
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: 04D8 An 'edge' will be a transition either from 'on' to 'off', or from 'off' to 'on'. SA-LEADER DJNZ 04D8,SA-LEADER OUT (+FE),A XOR +0F LD B,+A4 DEC L JR NZ,04D8,SA-LEADER DEC B DEC JP A sync pulse is now sent. 04EA SA-SYNC-1 LD DJNZ OUT LD LD DJNZ OUT B,+2F 04EA,SA-SYNC-1 (+FE),A A,+0D B,+37 04F2,SA-SYNC-2 (+FE),A MIC off for 667 T states from 'OUT to OUT'. MIC on and RED. Signal 'MIC off & CYAN'. MIC on for 735 T States from 'OUT to OUT'. Now MIC off & border CYAN. H P,04D8,SA-LEADER The main timing period. MIC on/off, border RED/CYAN, on each pass. The main timing constant. Decrease the low counter. Jump back for another pulse. Allow for the longer path (-reduce by 13 T states). Decrease the high counter. Jump back for another pulse until completion of the leader.
04F2
SA-SYNC-2
15
LD EX LD JP
+3B is a timing constant; +0E signals 'MIC off & YELLOW'. Fetch the flag and pass it to the L register for 'sending'. 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. 04FE SA-LOOP LD OR JR LD LD XOR LD LD SCF JP A,D E Z,050E,SA-PARITY L,(IX+00) A,H L H,A A,+01 The 'length' counter is tested and the jump taken when it has reached zero. Fetch the next byte that is to be SAVEd. Fetch the current 'parity'. Include the present byte. Restore the 'parity'. Note that on entry here the 'flag' value initialises 'parity'. Signal 'MIC on & BLUE'. Set the carry flag. This will act as a 'marker' for the 8 bits of a byte. Jump forward.
0505 0507
SA-LOOP-P SA-START
0525,SA-8-BITS
When it is time to send the 'parity' byte then it is transferred to the L register for SAVEing. 050E SA-PARITY LD JR L,H 0505,SA-LOOP-P Get final 'parity' value. 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. 0511 SA-BIT-2 LD BIT 0514 SA-BIT-1 DJNZ JR 051A 051C SA-SET SA-OUT LD DJNZ OUT LD JR DEC XOR INC A,C 7,B 0514,SA-BIT-1 NC,051C,SA-OUT B,+42 051A,SA-SET (+FE),A B,+3E NZ,0511,SA-BIT-2 B A A Come here on the second pass and fetch 'MIC off & YELLOW'. Set the zero flag to show 'second pass'. The main timing loop; always 801 T states on a 2nd. pass. Jump, taking the shorter path, if SAVEing a '0'. However if SAVEing a '1' then add 855 T states. On the 1st. pass 'MIC on & BLUE' and on the 2nd. pass 'MIC off & YELLOW'. Set the timing constant for the second pass. Jump back at the end of the first pass; otherwise reclaim 13 T states. Clear the carry flag and set 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. 0525 SA-8-BITS RL L Move bit 7 to the carry and the 'marker' leftwards.
16
SAVE the bit unless finished with the byte. Decrease the 'counter'. Advance the 'base address'. Set the timing constant for the first bit of the next byte. Return (to SA/LD-RET) if the BREAK key is being pressed. Otherwise test the 'counter and jump back even if it has reached zero (so as to send the 'parity' byte). Exit when the 'counter reaches +FFFF. But first 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.
Continue here. 0554 SA/LD-END POP RET AF Retrieve the carry flag. Return to the calling routine.
DEC
17
DI LD OUT LD PUSH IN RRA AND OR LD CP A,+0F (+FE),A HL,+053F HL A,(+FE) +20 +02 C,A A
The maskable interrupt is now disabled. The border is made WHITE. Preload the machine stack with the address - SA/LD-RET. Make an initial read of port '254' Rotate the byte obtained but keep only the EAR bit, Signal 'RED' border. Store the value in the C register. (+22 for 'off' and +02 for 'on' - the present EAR state.) 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.) 056B 056C LD-BREAK LD-START RET CALL JR NZ 05E7,LD-EDGE-1 NC,056B,LD-BREAK Return if the BREAK key is being pressed. Return with the carry flag reset 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. 0574 LD-WAIT LD DJNZ DEC LD OR JR CALL JR HL,+0415 0574,LD-WAIT HL A,H L NZ,0574,LD-WAIT 05E3,LD-EDGE-2 NC,056B,LD-BREAK The length of this waiting period will be almost one second in duration.
Continue only if two edges are found within the allowed time period.
Now accept only a 'leader signal'. 0580 LD-LEADER LD CALL JR LD CP JR INC JR B,+9C 05E3,LD-EDGE-2 NC,056B,LD-BREAK A,+C6 B NC,056C,LD-START H NZ,0580,LD-LEADER The timing constant, Continue only if two edges are found within the allowed time period. However the edges must have been found within about 3,000 T states of each other Count the pair of edges in the H register until '256' pairs have been found.
After the leader come the 'off' and 'on' part's of the sync pulse. 058F LD-SYNC LD CALL JR LD CP JR CALL RET B,+C9 05E7,LD-EDGE-1 NC,056B,LD-BREAK A,B +D4 NC,058F,LD-SYNC 05E7,LD-EDGE-1 NC The timing constant. Every edge is considered until two edges are found close together - these will be the start and finishing edges of the 'off' sync pulse. The finishing edge of the '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 XOR A,C +03 The border colours from now on will be BLUE & YELLOW.
18
LD LD LD JR
Initialise the 'parity matching' byte to zero. Set the timing constant for the flag byte. 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. 05A9 LD-LOOP EX JR JR LD JR 05B3 LD-FLAG RL XOR RET LD RRA LD INC JR AF,A'F' NZ,05B3,LD-FLAG NC,05BD,LD-VERIFY (IX+00),L 05C2,LD-NEXT C L NZ A,C C,A DE 05CA,LD-DEC Fetch the flags. Jump forward only when handling the first byte. Jump forward if VERIFYing a tape. Make the actual LOAD when required. Jump forward to LOAD the next byte. Keep the carry flag in a safe place temporarily. Return now if the type flag does not match the first byte on the tape. (Carry flag reset.) Restore the carry flag now. Increase the counter to 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. 05BD LD-VERIFY LD XOR RET A,(IX+00) L NZ Fetch the original byte. Match it against the new byte. Return if 'no match'. (Carry flag reset.)
A new byte can now be collected from the tape. 05C2 05C4 05C8 LD-NEXT LD-DEC INC DEC EX LD LD-MARKER LD IX DE AF,A'F' B,+B2 L,+01 Increase the 'destination'. Decrease the 'counter'. Save the flags. Set the timing constant. 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. 05CA LD-8-BITS CALL RET LD CP RL LD JP 05E3,LD-EDGE-2 NC A,+CB B L B,+B0 NC,05CA,LD-8-BITS Find the length of the 'off' and 'on' pulses of the next bit. Return if the time period is exceeded. (Carry flag reset.) Compare the length against approx. 2,400 T states; resetting the carry flag for a '0' and setting it for a '1'. Include the new bit in the L register. Set the timing constant for the next bit. Jump back whilst there are still bits to be fetched.
The 'parity matching' byte has to be updated with each new byte.
19
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,05A9,LD-LOOP zero. LD A,H Fetch the 'parity matching' byte. CP +01 Return with the carry flat set RET if the value is zero. (Carry flag reset if in error.)
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. 05ED LD-SAMPLE INC RET LD IN RRA RET XOR AND JR B Z A,+7F A,(+FE) NC C +20 Z,05ED,LD-SAMPLE Count each pass. Return carry reset & zero set if 'time-up'. Read from port +7FFE. i.e. BREAK & EAR. Shift the byte. Return carry reset & zero reset if BREAK was pressed. Now test the byte against the 'last edge-type'; jump back 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 CPL LD AND OR OUT SCF RET A,C C,A +07 +08 (+FE),A Change the 'last edge-type' and border colour. Keep only the border colour. Signal 'MIC off'. Change the border colour (RED/ CYAN or BLUE/YELLOW). Signal the successful search 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.
20
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'.
0621
SA-SPACE
0629
SA-BLANK
Continue to handle the name of the program. 0644 SA-NULL LD OR JR LD A,B C Z,0652,SA-DATA BC,+000A Jump forward if the name has a 'null' length. But truncate longer names.
The name is now transferred to the work space (second location onwards). 064B SA-NAME PUSH POP INC EX LDIR IX HL HL DE,HL Copy the start address to the HL register pair. Step to the second location. Switch the pointers over and copy the name.
21
The many different parameters, if any, that follow the command are now considered. Start by handling 'xxx "name" DATA'. 0652 SA-DATA RST CP JR LD CP JP RST CALL SET JR LD LD DEC JR Report 2 - Variable not found 0670 REPORT-2 RST DEFB 0008,ERROR-1 +01 Call the error handling routine. 0018,GET-CHAR +E4 NZ,06A0,SA-SCR$ A,(T-ADDR-lo) +03 Z,1C8A,REPORT-C 0020,NEXT-CHAR 28B2,LOOK-VARS 7,C NC,0672,SA-V-OLD HL,+0000 A,(T-ADDR-lo) A Z,0685,SA-V-NEW Is the present code the token 'DATA'? Jump if not. However it is not possible to have 'MERGE name DATA'. Advance CH-ADD. Look in the variables area for the array. Set bit 7 of the array's name. Jump if handling an existing array. Signal 'using a new array'. Consider the value in T-ADDR and give an error if trying to SAVE or VERIFY a new array.
Continue with the handling of an existing array. 0672 SA-V-OLD JP CALL JR INC LD LD INC LD LD INC NZ,1C8A,REPORT-C 2530,SYNTAX-Z Z,0692,SA-DATA-1 HL A,(HL) (IX+0B),A HL A,(HL) (IX+0C),A HL Note: This fails to exclude simple strings. Jump forward if checking syntax. Point to the 'low length' of the variable. The low length byte goes into the work space; followed by the high length byte. Step past the length bytes.
The next part is common to both 'old' and 'new' arrays. Note: Syntax path error. 0685 SA-V-NEW LD LD BIT JR INC LD (IX+0E),C A,+01 6,C Z,068F,SA-V-TYPE A (IX+00),A Copy the array's name. Assume an array of numbers. Jump if it is so. It is an array of characters. Save the 'type' in the first location of the header area.
068F
SA-V-TYPE
The last part of the statement is examined before joining the other pathways. 0692 SA-DATA-1 EX RST CP JR RST CALL EX JP DE,HL 0020,NEXT-CHAR +29 NZ,0672,SA-V-OLD 0020,NEXT-CHAR 1BEE,CHECK-END DE,HL 075A,SA-ALL Save the pointer in DE. Is the next character a ')' ? Give report C if it is not. Advance CH-ADD. Move on to the next statement if checking syntax. Return the pointer to the HL register pair before jumping forward. (The pointer indicates the start of an existing array's contents.)
Now consider 'SCREEN$'. 06A0 SA-SCR$ CP +AA Is the present code the token SCREEN$'.
22
JR LD CP JP RST CALL LD LD LD LD LD JR Now consider 'CODE'. 06C3 SA-CODE CP JR LD CP JP RST CALL JR LD AND JP CALL JR Look for a 'starting address'. 06E1 SA-CODE-1 CALL RST CP JR LD AND JP CALL JR Fetch the 'length' as it was specified. 06F5 SA-CODE-3 RST CALL
NZ,06C3,SA-CODE A,(T-ADDR-lo) +03 Z,1C8A,REPORT-C 0020,NEXT-CHAR 1BEE,CHECK-END (IX+0B),+00 (IX+0C),+1B HL,+4000 (IX+0D),L (IX+0E),H 0710,SA-TYPE-3
Jump if not. However it is not possible to have 'MERGE name SCREEN$'. Advance CH-ADD. Move on to the next statement if checking syntax. The display area and the attribute area occupy +1800 locations and these locations start at +4000; these details are passed to the header area in the work space. Jump forward.
+AF NZ,0716,SA-LINE A,(T-ADDR-lo) +03 Z,1C8A,REPORT-C 0020,NEXT-CHAR 2048,PR-ST-END NZ,06E1,SA-CODE-1 A,(T-ADDR-lo) A Z,1C8A,REPORT-C 1CE6,USE-ZERO 06F0,SA-CODE-2
Is the present code the token 'CODE'? Jump if not. However it is not possible to have 'MERGE name CODE'. Advance CH-ADD. Jump forward if the statement has not finished. However it is not possible to have 'SAVE name CODE' by itself. Put a zero on the calculator stack - for the 'start'. Jump forward.
06F0
SA-CODE-2
Fetch the first number. Is the present character a ',' or not? Jump if it is - the number was a 'starting address'. However refuse 'SAVE name CODE' that does not have a 'start' and a 'length'. Put a zero on the calculator stack - for the 'length'. Jump forward.
0020,NEXT-CHAR 1C82,EXPT-1NUM
The parameters are now stored in the header area of the work space. 06F9 SA-CODE-4 CALL CALL LD LD CALL LD LD LD LD 1BEE,CHECK-END 1E99,FIND-INT2 (IX+0B),C (IX+0C),B 1E99,FIND-INT2 (IX+0D),C (IX+0E),B H,B L,C But move on to the next statement now if checking syntax. Compress the 'length' into the BC register pair and store it. Compress the 'starting address' into the BC register pair and store it. Transfer the 'pointer' to the HL register pair as usual.
'SCREEN$' and 'CODE' are both of type 3. 0710 SA-TYPE-3 LD (IX+00),+03 Enter the 'type' number.
23
JR
075A,SA-ALL
Rejoin the other pathways. Is the present code the token 'LINE'? Jump if it is. Move on to the next statement if checking syntax. When there are no further parameters an +80 is entered. Jump forward.
Now consider 'LINE'; and 'no further parameters'. 0716 SA-LINE CP +CA JR CALL LD JR Z,0723,SA-LINE-1 1BEE,CHECK-END (IX+0E),+80 073A,SA-TYPE-0
Fetch the 'line number' that must follow 'LINE'. 0723 SA-LINE-1 LD AND JP RST CALL CALL CALL LD LD A,(T-ADDR-lo) A NZ,1C8A,REPORT-C 0020,NEXT-Char 1C82,EXPT-1NUM 1BEE,CHECK-END 1E99,FIND-INT2 (IX+0D),C (IX+0E),B However only allow 'SAVE name LINE number'. Advance CH-ADD. Pass the number to the calculator stack. Move on to the next statement if checking syntax. Compress the 'line number' into the BC register pair and store it.
'LINE' and 'no further parameters' are both of type 0. 073A SA-TYPE-0 LD (IX+00),+00 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 LD SCF SBC LD LD LD SBC LD LD EX HL,(E-LINE) DE,(PROG) HL,DE (IX+0B),L (IX+0C),H HL,(VARS) HL,DE (IX+0F),L (IX+10),H DE,HL The pointer to the end of the variables area. The pointer to the start of the BASIC program. Now perform the subtraction to find the length of the 'program + variables'; store the result. Repeat the operation but this time storing the length of the 'program' only. 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. 075A SA-ALL LD AND JP A,(T-ADDR-lo) A Z,0970,SA-CONTRL Jump forward when handling a SAVE command.
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.
24
LD ADD
BC,+0011 IX,BC
Form in the IX register pair the base address of the 'second header area'.
Now enter a loop; leaving it only when a 'header' has been LOADed. 0767 LD-LOOK-H PUSH LD XOR SCF CALL POP JR IX DE,+0011 A 0556,LD-BYTES IX NC,0767,LD-LOOK-H Make a copy of the base address. LOAD seventeen bytes. Signal 'header'. Signal 'LOAD'. Now look for a header. Retrieve the base address. 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 CALL LD LD LD CP JR LD 078A LD-TYPE CP JR A,+FE 1601,CHAN-OPEN (SCR-CT),+03 C,+80 A,(IX+00) (IX-11) NZ,078A,LD-TYPE C,+F6 +04 NC,0767,LD-LOOK-H Ensure that channel 'S' is open. Set the scroll counter. Signal 'names do not match'. Compare the 'new' type against the 'old' type. Jump if the 'types' do not match. But if they do; signal 'ten characters are to match'. Clearly the 'header' is nonsense if 'type 4 or more'.
The appropriate message - 'Program:', 'Number array:', 'Character array:' or 'Bytes:' is printed. LD PUSH CALL POP DE,+09C0 BC 0C0A,PO-MSG BC The base address of the message block. Save the C register whilst the appropriate message is printed.
The 'new name' is printed and as this is done the 'old' and the 'new' names are compared. PUSH POP LD ADD LD LD INC JR LD ADD LD IX DE HL,+FFF0 HL,DE B,+0A A,(HL) A NZ,07A6,LD-NAME A,C A,B C,A Make the DE register pair point to the 'new type' and the HL register pair to the 'old name'. Ten characters are to be considered. Jump forward if the match is to be against an actual name. But if the 'old name' is 'null' then signal 'ten characters 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. 07A6 LD-NAME INC LD CP INC JR INC RST DJNZ BIT JR DE A,(DE) (HL) HL NZ,07AD,LD-CH-PR C 0010,PRINT-A-1 07A6,LD-NAME 7,C NZ,0767,LD-LOOK-H Consider each character of the 'new name' in turn. Match it against the appropriate character of the 'old name'. Do not count it if it does not does not match. Print the 'new' character. Loop for ten characters. Accept the name only if the counter has reached zero.
07AD
LD-CH-PR
25
LD RST
A,+0D 0010,PRINT-A-1
The correct header has been found and the time has come to consider the three commands LOAD, VERIFY, & MERGE separately. POP LD CP JR LD DEC JP CP JP HL A,(IX+00) +03 Z,07CB,VR-CONTRL A,(T-ADDR-lo) A Z,0808,LD-CONTRL +02 Z,08B6,ME-CONTRL Fetch the pointer. 'SCREEN$ and CODE' are handled with VERIFY. Jump forward if using a LOAD command. Jump forward if using a MERGE command; continue with a VERIFY command.
The routine continues by considering the 'destination pointer'. 07E9 VR-CONT-1 POP LD OR JR LD LD HL A,H L NZ,07F4,VR-CONT-2 L,(IX+0D) H,(IX+0E) Fetch the 'pointer', i.e. the 'start'. This 'pointer' will be used unless it is zero, in which case the 'start' found in the 'new' header will be used instead.
The VERIFY/LOAD flag is now considered and the actual LOAD made. 07F4 VR-CONT-2 PUSH POP LD CP SCF JR AND LD HL IX A,(T-ADDR-lo) +02 NZ,0800,VR-CONT-3 A A,+FF Move the 'pointer' to the IX register pair. Jump forward unless using the VERIFY command; with the carry flag signalling 'LOAD' Signal 'VERIFY'. Signal 'accept data block only' before LOADing the block.
0800
VR-CONT-3
26
Report R - Tape loading error 0806 REPORT-R RST DEFB 0008,ERROR-1 +1A Call the error handling routine.
Consider now if there is enough room in memory for the new data block. 0819 LD-CONT-1 LD LD EX SCF SBC JR L,(IX-06) H,(IX-05) DE,HL HL,DE C,082E,LD-DATA Fetch the size of the existing 'program+variables or array'. Jump forward if no extra room will be required; taking into account the reclaiming of the presently used memory.
Make the actual test for room. 0825 LD-CONT-2 LD ADD LD LD CALL DE,+0005 HL,DE B,H C,L 1F05,TEST-ROOM Allow an overhead of five bytes. Move the result to the BC register pair and make the test.
Now deal with the LOADing of arrays. 082E LD-DATA POP LD AND JR LD OR JR DEC LD DEC LD DEC INC INC INC LD CALL LD HL A,(IX+00) A Z,0873,LD-PROG A,H L Z,084C,LD-DATA-1 HL B,(HL) HL C,(HL) HL BC BC BC (X-PTR),IX 19E8,RECLAIM-2 IX,(X-PTR) Fetch the 'pointer' anew. Jump forward if LOADing a BASIC program. Jump forward if LOADing a new array. Fetch the 'length' of the existing array by collecting the length bytes from the variables area. Point to its old name. Add three bytes to the length - one for the name and two for the 'length'. Save the IX register pair temporarily whilst the old array is reclaimed.
Space is now made available for the new array - at the end of the present variables area. 084C LD-DATA-1 LD DEC LD LD PUSH HL,(E-LINE) HL C,(IX+0B) B,(IX+0C) BC Find the pointer to the end-marker of the variables area - the '80-byte'. Fetch the 'length' of the new array. Save this 'length'.
27
INC INC INC LD PUSH CALL INC POP LD POP INC LD INC LD INC PUSH POP SCF LD JP
Add three bytes - one for the name and two for the 'length'. 'IX+0E' of the old header gives the name of the array. The name is saved whilst the appropriate amount of room is made available. In effect 'BC' spaces before the 'new 80-byte'. The name is entered. The 'length' is fetched and its two bytes are also entered. HL now points to the first location that is to be filled with data from the tape. This address is moved to the IX register pair; the carry flag set; 'data block' is signalled; and the block LOADed.
Now deal with the LOADing of a BASIC program and its variables 0873 LD-PROG EX LD DEC LD LD LD PUSH CALL POP PUSH PUSH CALL LD INC LD LD ADD LD LD LD AND JR LD LD LD The data block can now be LOADed. 08AD LD-PROG-1 POP POP SCF LD JP DE IX A,+FF 0802,LD-BLOCK Fetch the 'length'. Fetch the 'start'. Signal 'LOAD'. Signal 'data block' only. Now LOAD it. DE,HL HL,(E-LINE) HL (X-PTR),IX C,(IX+0B) B,(IX+0C) BC 19E5,RECLAIM-1 BC HL BC 1655,MAKE-ROOM IX,(X-PTR) HL C,(IX+0F) B,(IX+10) HL,BC (VARS),HL H,(IX+0E) A,H +C0 NZ,08AD,LD-PROG-1 L,(IX+0D) (NEWPPC),HL (NSPPC),+00 Save the 'destination pointer'. Find the address of the end-marker of the current variables area - the '80-byte'. Save IX temporarily. Fetch the 'length' of the new data block. Keep a copy of the 'length' whilst the present program and variables areas are reclaimed. Save the pointer to the program area and the length of the new data block. Make sufficient room available for the new program and its variables. Restore the IX register pair. The system variable VARS has also to be set for the new program. If a line number was specified then it too has to be considered. Jump if 'no number'; otherwise set NEWPPC & NSPPC.
28
Start therefore with the LOADing of the data block. 08B6 ME-CONTRL LD LD PUSH INC RST LD EX POP PUSH PUSH POP SCF LD CALL C,(IX+0B) B,(IX+0C) BC BC 0030,BC-SPACES (HL),+80 DE,HL DE HL HL IX A,+FF 0802,LD-BLOCK Fetch the 'length' of the data block. Save a copy of the 'length'. Now made 'length+1' locations available in the work space. Place an end-marker in the extra location. Move the 'start' pointer to the HL register pair. Fetch the original 'length'. Save a copy of the 'start'. Now set the IX register pair for the actual LOAD. Signal 'LOAD'. Signal 'data block only'. LOAD the data block.
The lines of the new program are MERGEd with the lines of the old program. POP LD HL DE,(PROG) Fetch the 'start' of the new program. Initialise DE to the 'start' of the old program.
Enter a loop to deal with the lines of the new program. 08D2 ME-NEW-LP LD AND JR A,(HL) +C0 NZ,08F0,ME-VAR-LP Fetch a line number and test it. Jump when finished with all the lines.
Now enter an inner loop to deal with the lines of the old program. 08D7 ME-OLD-LP LD INC CP INC JR LD CP DEC DEC JR PUSH EX CALL POP JR 08EB ME-NEW-L2 CALL JR A,(DE) DE (HL) HL NZ,08DF,ME-OLD-L1 A,(DE) (HL) DE HL NC,08EB,ME-NEW-L2 HL DE,HL 19B8,NEXT-ONE HL 08D7,ME-OLD-LP 092C,ME-ENTER 08D2,ME-NEW-LP Fetch the high line number byte and compare it. Jump forward if it does not match but in any case advance both pointers. Repeat the comparison for the low line number bytes. Now retreat the pointers. Jump forward if the correct place has been found for a line of the new program. Otherwise find the address of the start of the next old line. Go round the loop for each of the 'old lines'. Enter the 'new line' and go round the outer loop again.
08DF
ME-OLD-L1
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. 08F0 ME-VAR-LP LD A,(HL) Fetch each variable name in
29
LD CP RET PUSH LD
turn and test it. Return when all the variables have been considered. Save the current new pointer. Fetch VARS (for the old program).
Now enter an inner loop to search the existing variables area. 08F9 ME-OLD-VP LD CP JR CP JR 0901 ME-OLD-V1 PUSH CALL POP EX JR A,(HL) +80 Z,0923,ME-VAR-L2 C Z,0909,ME-OLD-V2 BC 19B8,NEXT-ONE BC DE,HL 08F9,ME-OLD-VP Fetch each variable name and test it. Jump forward once the end marker is found. (Make an 'addition'.) Compare the names (1st. bytes). Jump forward to consider it further; returning here if it proves not to match fully. Save the new variable's name whilst the next 'old variable' is located. Restore the pointer to the 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. 0909 ME-OLD-V2 AND CP JR POP PUSH PUSH +E0 +A0 NZ,0921,ME-VAR-L1 DE DE HL Consider bits 7, 6 & 5 only. Accept all the variable types except 'long named variables'. Make DE point to the first character of the 'new name'. Save the pointer to the 'old name'.
Enter a loop to compare the letters of the long names. 0912 ME-OLD-V3 INC INC LD CP JR RLA JR POP 091E ME-OLD-V4 JR POP JR HL DE A,(DE) (HL) NZ,091E,ME-OLD-V4 NC,0912,ME-OLD-V3 HL 0921,ME-VAR-L1 HL 0901,ME-OLD-V1 Update both the 'old' and the new' pointers. Compare the two letters Jump forward if the match fails. Go round the loop until the 'last character' is found. Fetch the pointer to the start of the 'old' name and jump forward - successful. Fetch the pointer and jump back - unsuccessful.
Come here if the match was found. 0921 ME-VAR-L1 LD A,+FF Signal 'replace' variable.
And here if not. (A holds +80 - variable to be 'added'.) 0923 ME-VAR-L2 POP EX INC SCF CALL JR DE DE,HL A Fetch pointer to 'new' name. Switch over the registers. The zero flag is to be set if there is to be a 'replacement'; reset for an 'addition'. Signal 'handling variables'. Now make the entry. Go round the loop to consider the next new variable.
092C,ME-ENTER 08F0,ME-VAR-LP
30
The new entry can now be made. 093E ME-ENT-1 EX PUSH CALL LD LD EX PUSH EX JR DEC CALL INC JR CALL INC POP POP LD LD PUSH PUSH EX LDIR AF,A'F' DE 19B8,NEXT-ONE (X-PTR),HL HL,(PROG) (SP),HL BC AF,A'F' C,0955,ME-ENT-2 HL 1655,MAKE-ROOM HL 0958,ME-ENT-3 1655,MAKE-ROOM HL BC DE (PROG),DE DE,(X-PTR) BC DE DE,HL Save the flags. Make a copy of the 'destination' pointer. Find the length of the 'new' variable/line. Save the pointer to the 'new' variable/line. Fetch PROG - to avoid corruption. Save PROG on the stack and fetch the 'new' pointer. Save the length. Retrieve the flags. Jump forward if adding a new variable. A new line is added before the 'destination' location. Make the room for the new line. Jump forward. Make the room for the new variable. Point to the 1st new location. Retrieve the length. Retrieve PROG and store it in its correct place. Also fetch the 'new' pointer. Again save the length and the new' pointer. Switch the pointers and copy the 'new' variable/line into the room made for it.
0955 0958
ME-ENT-2 ME-ENT-3
The 'new' variable/line has now to be removed from the work space. POP POP PUSH CALL POP RET HL BC DE 19E8,RECLAIM-2 DE Fetch the 'new' pointer. Fetch the length. Save the 'old' pointer. (Points to the location after the 'added' variable/line.) Remove the variable/line from the work space. Return with the 'old' pointer in the DE register pair.
31
Upon receipt of a keystroke the 'header' is saved. PUSH LD XOR CALL IX DE,+0011 A 04C2,SA-BYTES Save the base address of the 'header' on the machine stack. Seventeen bytes are to be SAVEd. Signal 'it is a header'. 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 0991 SA-1-SEC LD HALT DJNZ LD LD LD POP JP IX B,+32 0991,SA-1-SEC E,(IX+0B) D,(IX+0C) A,+FF IX 04C2,SA-BYTES Retrieve the pointer to the 'header'. The delay is for fifty interrupts, i.e. one second. Fetch the length of the data block that is to be SAVEd. Signal 'data block'. Fetch the 'start of block pointer' and SAVE the block.
32
0A38 0A3A
PO-BACK-2 PO-BACK-3
33
Set P-FLAG to OVER 1. A 'space'. Print the character. Fetch the old value of P-FLAG. Finished. Note: The programmer has forgotten to exit via PO-STORE.
Enter here when handling the characters AT & TAB. 0A75 PO-2-OPER LD JR DE,+0A6D 0A7D,PO-TV-1 The character code will be 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. 0A7A 0A7D PO-1-OPER PO-TV-1 LD LD DE,+0A87 (TVDATA-lo),A The 'output' routine is to be changed to PO-CONT (+0A87). Save the control character code.
34
0A80
HL will point to the 'output' routine address. Enter the new 'output' routine address and thereby force the next character code to be considered as an operand.
Once the operands have been collected the routine continues. 0A87 PO-CONT LD CALL LD LD LD CP JP JR Now deal with the AT control character. LD LD LD SUB JR ADD LD BIT JR LD SUB 0AAC PO-AT-ERR JP INC LD INC BIT JP CP JP JP B,H C,D A,+1F C C,0AAC,PO-AT-ERR A,+02 C,A 1,(FLAGS) NZ,0ABF,PO-AT-SET A,+16 B C,1E9F,REPORT-B A B,A B 0,(TV-FLAG) NZ,0C55,PO-SCR (DF-SZ) C,0C86,REPORT-5 0D09,CL-SET The line number. The column number. Reverse the column number; i.e. +00 - +1F becomes +1F +00. Must be in range. Add in the offset to give C holding +21 - +22. Jump forward if handling the printer. Reverse the line number; i.e. +00 - +15 becomes +16 +01. If appropriate jump forward. The range +16 - +01 becomes +17 - +02. And now +18 - +03. If printing in the lower part of the screen then consider whether scrolling is needed. Give report 5 - Out of screen, if required. Return via CL-SET & PO-STORE. DE,+09F4 0A80,PO-CHANGE HL,(TVDATA) D,A A,L +16 C,2211,CO-TEMPS NZ,0AC2,PO-TAB Restore the original address for PRINT-OUT (+09F4). Fetch the control code and the first operand if there are indeed two operands. The 'last' operand and the control code are moved. Jump forward if handling INK to OVER. Jump forward if handling TAB.
0ABF
PO-AT-SET
And the TAB control character. 0AC2 0AC3 PO-TAB PO-FILL LD CALL ADD DEC AND RET LD SET LD CALL DEC JR RET A,H 0B03,PO-FETCH A,C A +1F Z D,A 0,(FLAGS) A,+20 0C3B,PO-SAVE D NZ,0AD0,PO-SPACE Fetch the first operand. The current print position. Add the current column value. Find how many 'spaces', modulo 32, are required and return if the result is zero. Use 0 as the counter. Suppress 'leading space'. Print 'D number' of spaces. Now finished.
0AD0
PO-SPACE
35
0AD9
PO-ABLE
CALL
0B24,PO-ANY
0AF0
PO-ST-E
0AFC
PO-ST-PR
0B1D
PO-F-PR
Graphic characters are constructed in an Ad Hoc manner in the calculator's memory area; i.e. MEM-0 & MEM-1. 0B38 0B3E PO-GR-1 PO-GR-2 LD CALL RR SBC AND HL,+5C92 0B3E,PO-GR-2 B A,A +0F This is MEMBOT. In effect call the following subroutine twice. Determine bit 0 (and later bit 2) of the graphic code. The A register will hold +00 or +0F depending on the value of the bit in the code.
36
0B4C
PO-GR-3
Save the result in C. Determine bit 1 (and later bit 3) of the graphic code. The A register will hold +00 or +F0. The two results are combined. The A register holds half the character form and has to be used four times. This is done for the upper half of the character form and then the lower.
Token codes and user-defined graphic codes are now separated. 0B52 PO-T&UDG SUB JR ADD PUSH LD JR CALL JP +A5 NC,0B5F,PO-T A,+15 BC BC,(UDG) 0B6A,PO-CHAR-2 0C10,PO-TOKENS 0B03,PO-FETCH Jump forward with token codes UDG codes are now +00 - +0F. Save the current position values on the machine stack. Fetch the base address of the UDG area and jump forward. Now print the token and return via PO-FETCH.
0B5F
PO-T
The required character form is identified. 0B65 0B6A PO-CHAR PO-CHAR-2 PUSH LD EX LD RES CP JR SET LD LD ADD ADD ADD ADD POP EX BC BC,(CHARS) DE,HL HL,+5C3B 0,(HL) +20 NZ,0B76,PO-CHAR-3 0,(HL) H,+00 L,A HL,HL HL,HL HL,HL HL,BC BC DE,HL The current position is saved. The base address of the character area is fetched. The print address is saved. This is FLAGS. Allow for a leading space Jump forward if the character is not a 'space'. But 'suppress' if it is. Now pass the character code to the HL register pair. The character code is in effect multiplied by 8. The base address of the character form is found. The current position is fetched and the base address passed to the DE register pair.
0B76
PO-CHAR-3
0B93
PR-ALL-1
37
DE Z,0C55,PO-SCR DE
Now consider the present state of INVERSE & OVER PUSH PUSH LD LD RRA JR INC RRA RRA SBC LD LD AND BIT JR SET SCF 0BB6 PR-ALL-3 EX DE,HL BC HL A,(P-FLAG) B,+FF C,0BA4,PR-ALL-2 B A,A C,A A,+08 A 1,(FLAGS) Z,0BB6,PR-ALL-3 1,(FLAGS2) Save the position values and the destination address on the machine stack. Fetch P-FLAG and read bit 0. Prepare the 'OVER-mask' in the B register; i.e. OVER 0 = +00 & OVER 1 - +FF. Read bit 2 of P-FLAG and prepare the 'INVERSE-mask' in the C register; i.e. INVERSE 0 = +00 & INVERSE 1 = +FF. Set the A register to hold the 'pixel-line' counter and clear the carry flag. Jump forward if handling the screen. Signal 'printer buffer no longer empty'. Set the carry flag to show that the printer is being used. Exchange the destination address with the base address before entering the loop.
0BA4
PR-ALL-2
The character can now be printed. Eight passes of the loop are made - one for each 'pixel-line'. 0BB7 PR-ALL-4 EX LD AND XOR XOR LD EX JR INC INC DEC JR AF,A'F' A,(DE) B (HL) C (DE),A AF,A'F' C,0BD3,PR-ALL-6 D HL A NZ,0BB7,PR-ALL-4 The carry flag is set when using the printer. Save this flag in F'. Fetch the existing 'pixel-line'. Use the 'OVER-mask' and then XOR the result with the 'pixelline' of the character form. Finally consider the 'INVERSEmask'. Enter the result. Fetch the printer flag and jump forward if required. Update the destination address Update the 'pixel-line' of the character form. Decrease the counter and loop back unless it is zero.
0BC1
PR-ALL-5
Once the character has been printed the attribute byte is to set as required. EX DEC BIT CALL POP POP DEC INC RET DE,HL H 1,(FLAGS) Z,0BDB,PO-ATTR HL BC C HL Make the H register hold a correct high-address for the character area. Set the attribute byte only if handling the screen. Restore the original destination address and the position values. Decrease the column number and increase the destination address before returning.
When the printer is being used the destination address has to be updated in increments of +20.
38
0BD3
PR-ALL-6
EX LD ADD LD EX JR
Save the printer flag again. The required increment value. Add the value and pass the result back to the E register. Fetch the flag. Jump back into the loop.
+03 +58 H,A DE,(ATTR-T) A,(HL) E D E 6,(P-FLAG) Z,0BFA,PO-ATTR-1 +C7 2,A NZ,0BFA,PO-ATTR-1 +38 4,(P-FLAG) Z,0C08,PO-ATTR-2 +F8 5,A NZ,0C08,PO-ATTR-2 +07 (HL),A
0BFA
PO-ATTR-1
0C08
PO-ATTR-2
Enter here when expanding token codes. 0C10 PO-TOKENS LD PUSH AF The table is searched and the correct entry printed. 0C14 PO-TABLE CALL JR LD 0C41,PO-SEARCH C,0C22,PO-EACH A,+20 Locate the required entry. Print the message/token. A 'space' will be printed DE,+0095 The base address of the token table. Save the code on the stack. (Range +00 - +5A; RND - COPY).
39
BIT CALL
0,(FLAGS) Z,0C3B,PO-SAVE
The characters of the message/token are printed in turn. 0C22 PO-EACH LD AND CALL LD INC ADD JR A,(DE) +7F 0C3B,PO-SAVE A,(DE) DE A,A NC,0C22,PO-EACH Collect a code. Cancel any 'inverted bit'. Print the character. Collect the code again. Advance the pointer. The 'inverted bit' goes to the carry flag and signals the end of the message/token; otherwise jump back.
Now consider whether a 'trailing space' is required. POP CP JR CP RET LD CP RET LD DE +48 Z,0C35,PO-TRSP +82 C A,D +03 C A,+20 For messages - D holds +00; for tokens - D holds +00 - +5A. Jump forward if the last character was a '$'. Return if the last character was any other before 'A'. Examine the value in D and return if it indicates a message, RND, INKEY$ or PI. All other cases will require a 'trailing space'.
0C35
PO-TR-SP
40
0C55
PO-SCR
BIT RET LD PUSH LD BIT JP CP JR RET BIT JR LD DEC JR LD CALL LD RES RET
1,(FLAGS) NZ DE,+0DD9 DE A,B 0,(TV-FLAG) NZ,0D02,PO-SCR-4 (DF-SZ) C,0C86,REPORT-6 NZ 4,(TV-FLAG) Z,0C88,PO-SCR-2 E,(BREG) E Z,0CD2,PO-SCR-3 A,+00 1601,CHAN-OPEN SP,(LIST-SP) 4,(TV-FLAG)
Return immediately if the printer is being used. Pre-load the machine stack with the address of 'CL-SET'. Transfer the line number. Jump forward if considering 'INPUT ... AT ..'. Return, via CL-SET, if the line number is greater than the value of DF-SZ; give report 5 if it is less; otherwise continue. Jump forward unless dealing with an 'automatic listing'. Fetch the line counter. Decrease this counter. Jump forward if the listing is to be scrolled. Otherwise open channel 'K', restore the stack pointer, flag that the automatic listing has finished and return via CL-SET.
Report 5 - Out of screen 0C86 REPORT-5 RST DEFB 0008,ERROR-1 +04 Call the error handling routine.
Now consider if the prompt 'scroll?' is required. 0C88 PO-SCR-2 DEC JR (SCR-CT) NZ,0CD2,PO-SCR-3 Decrease the scroll counter and proceed to give the prompt only if is becomes zero.
Proceed to give the prompt message. LD SUB LD LD PUSH LD PUSH LD CALL XOR LD CALL SET LD SET RES EXX CALL EXX CP JR CP JR OR CP JR LD CALL POP A,+18 B (SCR-CT),A HL,(ATTR-T) HL A,(P-FLAG) AF A,+FD 1601,CHAN-OPEN A DE,+0CF8 0C0A,PO-MSG 5,(TV-FLAG) HL,+5C3B 3,(HL) 5,(HL) 15D4,WAIT-KEY +20 Z,0D00,REPORT-D +E2 Z,0D00,REPORT-D +20 +6E Z,0D00,REPORT-D A,+FE 1601,CHAN-OPEN AF The counter is reset. The current values of ATTR-T and MASK-T are saved. The current value of P-FLAG is saved. Channel 'K' is opened. The message 'scroll?' is message '0'. This message is now printed. Signal 'clear the lower screen after a keystroke'. This is FLAGS. Signal 'L mode'. Signal 'no key yet'. Note: DE should be pushed also. Fetch a single key code. Restore the registers. There is a jump forward to REPORT-D - 'BREAK - CONT repeats' - if the keystroke was 'BREAK', 'STOP', 'N' or 'n'; otherwise accept the keystroke as indicating the need to scroll the display. Open channel 'S'. Restore the value of
41
LD POP LD The display is now scrolled. 0CD2 PO-SCR-3 CALL LD INC LD PUSH CALL LD RRCA RRCA RRCA AND OR LD
(P-FLAG),A HL (ATTR-T),HL
The whole display is scrolled. The line and column numbers for the start of the line above the lower part of the display are found and saved. The corresponding attribute byte for this character area is then found. The HL register pair holds the address of the byte.
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 LD LD LD EX LD LD INC INC DJNZ POP RET The 'scroll?' message 0CF8 DEFB DEFB DEFB +80 +73,+63,+72,+6F +6C,+6C,+BF Initial marker - stepped over. s-c-r-o l - l - ? (inverted). DE,+5AE0 A,(DE) C,(HL) B,+20 DE,HL (DE),A (HL),C DE HL 0CF0,PO-SCR-3A BC DE points to the first attribute byte of the bottom line. The value is fetched. The 'lower part' value. There are thirty two bytes. Exchange the pointers. Make the first exchange and then proceed to use the same values for the thirty two attribute bytes of the two lines being handled. The line and column numbers of the bottom line of the 'upper part' are fetched before returning.
0CF0
PO-SCR-3A
Report 0 - BREAK - CONT repeats 0D00 REPORT-D RST DEFB 0008,ERROR-1 +0C Call the error handling routine.
The lower part of the display is handled as follows: 0D02 PO-SCR-4 CP JR ADD SUB RET NEG PUSH LD LD PUSH LD PUSH CALL +02 C,0C86,REPORT-5 A,(DF-SZ) +19 NC The 'out of screen' error is given if the lower part is going to be 'too large' and a return made if scrolling is unnecessary. The A register will now hold 'the number of scrolls to be made'. The line and column numbers are now saved. The 'scroll number', ATTR-T MASK-T & P-FLAG are all saved. The 'permanent' colour items are to be used.
42
LD
A,B
The lower part of the screen is now scrolled 'A' number of times. 0D1C PO-SCR-4A PUSH LD LD LD INC LD LD CP JR INC LD 0D2D PO-SCR-4B CALL POP DEC JR POP LD POP LD LD RES CALL SET POP RET AF HL,+5C6B B,(HL) A,B A (HL),A HL,+5C89 (HL) C,0D2D,PO-SCR-4B (HL) B,+18 0E00,CL-SCROLL AF A NZ,0D1C,PO-SCR-4A HL (P-FLAG),L HL (ATTR-T),HL BC,(S-POSN) 0,(TV-FLAG) 0DD9,CL-SET 0,(TV-FLAG) BC Save the 'number'. This is DF-SZ. The value in DF-SZ is incremented; the B register set to hold the former value and the A register the new value. This is S-POSN-hi. The jump is taken if only the lower part of the display is to be scrolled. (B = old DF-SZ). Otherwise S-POSN-hi is incremented and the whole display scrolled. (B = +18) Scroll 'B' lines. Fetch and decrement the scroll number'. Jump back until finished. Restore the value of P-FLAG. Restore the values of ATTR-T and MASK-T. In case S-POSN has been changed CL-SET is called to give a matching value to DF-CC. Reset the flag to indicate that the lower screen is being handled, fetch the line and column numbers, and then return.
0D5B
TEMPS-1
Next P-FLAG is considered. LD JR LD RRCA 0D65 TEMPS-2 XOR AND XOR LD RET
43
0D6B 0D6E
CLS
CALL
The whole of the display is 'cleared'. This is TV-FLAG. Signal 'do not clear the lower screen after keystroke'. Signal 'lower part'. Use the permanent values. i.e. ATTR-T is copied from BORDCR. The lower part of the screen is 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 LD DEC JR LD DEC LD DEC JR DJNZ HL,+5AC0 A,(ATTR-P) B 0D8E,CLS-3 C,+20 HL (HL),A C NZ,0D89,CLS-2 0D87,CLS-1 Attribute byte at start of line '22'. Fetch ATTR-P. The line counter. Jump forward into the loop. +20 characters per line. Go back along the line setting the attribute bytes. Loop back until finished.
0D87 0D89
CLS-1 CLS-2
0D8E
CLS-3
The size of the lower part of the display can now be fixed. LD (DF-SZ),+02 It will be two lines in size.
It now remains for the following 'house keeping' tasks to be performed. 0D94 CL-CHAN LD CALL LD LD AND LD INC LD INC LD CCF JR LD A,+FD 1601,CHAN-OPEN HL,(CURCHL) DE,+09F4 A (HL),E HL (HL),D HL DE,+10A8 C,0DA0,CL-CHAN-A BC,+1721 Open channel 'K'. Fetch the address of the current channel and make the output address +09F4 (= PRINT-OUT) and the input address +10A8 (= KEY-INPUT). First the output address then the input address. As the lower part of the display is being handled the 'lower print line' will be line '23'. Return via CL-SET.
0DA0
CL-CHAN-A
JR
0DD9,CL-SET
44
LD INC LD LD LD
(PRINT-OUT). Reset the scroll counter. As the upper part of the display is being handled the 'upper print line' will be Line '0'. Continue into CL-SET.
0DEE
CL-SET-1
The main entry point - from above and when scrolling for INPUT..AT. 0E00 CL-SCROLL CALL LD 0E9B,CL-ADDR C,+08 Find the starting address of the line. 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. 0E05 CL-SCR-1 PUSH PUSH LD AND LD JR BC HL A,B +07 A,B NZ,0E19,CL-SCR-3 Save both counters. Save the starting address. Jump forward unless dealing at the present moment with a 'third' of 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.)
45
0E0D
CL-SCR-2
The result of this manipulation is to leave HL unchanged and DE pointing to the required destination. There are +20 characters. Decrease the counter as one line is being dealt with. 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. 0E19 CL-SCR-3 EX LD ADD EX LD AND RRCA RRCA RRCA LD LD LD LDIR LD ADD AND JR DE,HL HL,+FFE0 HL,DE DE,HL B,A +07 Again DE is made to point to the required destination. This time only thirty two locations away. Save the line number in B. Now find how many characters there are remaining in the 'third'. Pass the 'character total' to the C register. Fetch the line number. BC holds the 'character total' and a pixel line from each of the characters is 'scrolled'. Now prepare to increment the address to jump across a 'third' boundary. Increase HL by +0700. Jump back if there are any 'thirds' left to consider.
Now find if the loop has been used eight times - once for each pixel line. POP INC POP DEC JR HL H BC C NZ,0E05,CL-SCR-1 Fetch the original address. Address the next pixel line. Fetch the counters. Decrease the pixel line counter 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 0E88,CL-ATTR The required address in the attribute area and the number of characters in 'B' lines are found. The displacement for all the attribute bytes is thirty two locations away. The attribute bytes are 'scrolled'.
LD ADD EX LDIR
It remains now to clear the bottom line of the display. LD B,+01 The B register is loaded with +01 and CL-LINE is entered.
46
LD
C,+08
Now enter a loop to clear all the pixel lines. 0E4A CL-LINE-1 PUSH PUSH LD AND RRCA RRCA RRCA LD LD LD DEC LD LD LD INC LDIR LD ADD DEC AND LD JR BC HL A,B +07 Save the line number and the pixel line counter. Save the address. Save the line number in A. Find how many characters are involved in 'B mod 8' lines. Pass the result to the C register. (C will hold +00 i.e. 256 dec. for a 'third'.) Fetch the line number. Make the BC register pair hold 'one less' than the number of characters. Make DE point to the first character. Clear the pixel-byte of the first character. Make DE point to the second character and then clear the pixel-bytes of all the other characters. For each 'third' of the display HL has to be increased by +0701. Now decrease the line number. Discard any extra lines and pass the 'third' count to B. Jump back if there are still 'thirds' to be dealt with.
0E4D
CL-LINE-2
Now find if the loop has been used eight times. POP INC POP DEC JR HL H BC C NZ,0E4A,CL-LINE-1 Update the address for each pixel line. Fetch the counters. Decrease the pixel line 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 LD LD INC LD BIT JR LD LD DEC LDIR POP LD RET 0E88,CL-ATTR H,D L,E DE A,(ATTR-P) 0,(TV-FLAG) Z,0E80,CL-LINE-3 A,(BORDCR) (HL),A BC BC C,+21 The address of the first attribute byte and the number of bytes are found. HL will point to the first attribute byte and DE the second. Fetch the value in ATTR-P. Jump forward if handling the main part of the screen. Otherwise use BORDCR instead. Set the attribute byte. One byte has been done. Now copy the value to all the attribute bytes. Restore the line number. Set the column number to the lefthand column and return.
0E80
CL-LINE-3
47
A +50 H,A DE,HL H,C L,B HL,HL HL,HL HL,HL HL,HL HL,HL B,H C,L
LD RET
H,A
48
LD AND JR
Jump forward and hence round the loop again directly for the eight pixel lines of a character line.
For each new line of characters the base address has to be updated. LD ADD LD CCF SBC AND ADD LD DJNZ JR A,L A,+20 L,A A,A +F8 A,H H,A 0EB2,COPY-1 0EDA,COPY-END Fetch the low byte. Update it by +20 bytes. The carry flag will be reset when 'within thirds' of the display. Change the carry flag. The A register will hold +F8 when within a 'third' but +00 when a new third' is reached. The high byte of the address is now updated. Jump back until '176' lines have been printed. Jump forward to the end routine.
0EC9
COPY-2
0ED3
Continue into the COPY-END routine. 0EDA COPY-END LD OUT EI A,+04 (+FB),A Stop the printer motor. Enable the maskable interrupt and continue into CLEAR-PRB.
49
OUT LD
(+FB),A D,A
Slow the motor for the last two pixel lines only. The D register will hold either +00 or +02.
There are three tests to be made before doing any 'printing'. 0EFD COPY-L-1 CALL JR LD OUT EI CALL RST DEFB IN ADD RET JR LD Now enter a loop to handle these bytes. 0F14 0F18 COPY-L-3 COPY-L-4 LD INC LD RL RL RR IN RRA JR LD OUT E,(HL) HL B,+08 D E D A,(+FB) NC,0F1E,COPY-L-5 A,D (+FB),A Fetch a byte. Update the pointer. Eight bits per byte. Move D left. Move each bit into the carry. Move D back again, picking up the carry from E. Again fetch the status of the printer and wait for the signal from the encoder. Now go ahead and pass the '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'. 'Print' each bit. Decrease the byte counter. Jump back whilst there are still bytes; otherwise return. 1F54,BREAK-KEY C,0F0C,COPY-L-2 A,+04 (+FB),A 0EDF,CLEAR-PRB 0008,ERROR-1 +0C A,(+FB) A,A M NC,0EFD,COPY-L-1 C,+20 Jump forward unless the BREAK key is being pressed. But if it is then; stop the motor, enable the maskable interrupt, clear the printer buffer and exit via the error handling routine - 'BREAK-CONT repeats'. Fetch the status of the printer. Make an immediate return if the printer is not present. Wait for the stylus. There are thirty two bytes.
0F0C
COPY-L-2
0F1E
COPY-L-5
0F18,COPY-L-4 C NZ,0F14,COPY-L-3
A loop is now entered to handle each keystroke. 0F38 ED-LOOP CALL PUSH 15D4,WAIT-KEY AF Return once a key has been pressed. Save the code temporarily.
50
Fetch the duration of the keyboard click. And the pitch. Now make the 'pip'. Restore the code. Pre-load the machine stack with the address of ED-LOOP.
Accept all character codes, graphic codes and tokens. Also accept ','. Jump forward if the code represents an editing key.
The control keys - INK to TAB -are now considered. LD LD CP JR AT & TAB would be handled as follows: INC BIT JP CALL LD BC 7,(FLAGX) Z,101E,ED-IGNORE 15D4,WAIT-KEY E,A Three locations required. Jump forward unless dealing with INPUT LINE... . Get the second code. and put it in E. BC,+0002 D,A +16 C,0F6C,ED-CONTR INK & PAPER will require two locations. Copy the code to D. Jump forward with INK & PAPER
The other bytes for the control characters are now fetched. 0F6C ED-CONTR CALL PUSH LD RES CALL POP INC LD INC LD JR 15D4,WAIT-KEY DE HL,(K-CUR) 0,(MODE) 1655,MAKE-ROOM BC HL (HL),B HL (HL),C 0F8B,ADD-CH-1 Get another code. Save the previous codes. Fetch K-CUR. Signal 'K mode'. Make two or three spaces. Restore the previous codes. Point to the first location. Enter first code. Then enter the second code which will be overwritten if there are only two codes - i.e. with INK & PAPER. Jump forward.
The editing keys are dealt with as follows: 0F92 ED-KEYS LD LD LD ADD LD ADD E,A C,+00 HL,+0F99 HL,DE E,(HL) HL,DE The code is transferred to the DE register pair. The base address of the editing key table. The entry is addressed and then fetched into E. The address of the handling
51
PUSH LD RET
HL HL,(K-CUR)
routine is saved on the machine stack. The HL register pair is set and an indirect jump made to the required routine.
HL,(E-LINE) HL HL HL HL
52
(K-CUR),HL HL 1615,CHAN-FLAG
Fetch the former channel address and set the appropriate flags before returning to ED-LOOP.
1001
ED-STOP
1011
ED-CUR
53
BC C BC B,H C,L
Drop the return address. Return via ED-LOOP if the carry flag is set. Restore the return address. Move the current address of the cursor to BC.
Now enter a loop to check that control characters are not split from their parameters. 103E ED-EDGE-1 LD LD INC LD AND CP JR INC LD SUB ADC H,D L,E HL A,(DE) +F0 +10 NZ,1051,ED-EDGE-2 HL A,(DE) +17 A,+00 HL will point to the character in the line after that addressed by DE. Fetch a character code. Jump forward if the code does not represent INK to TAB. Allow for one parameter. Fetch the code anew. Carry is reset for TAB. Note: This splits off AT & TAB but AT & TAB in this form are not implemented anyway so it makes no difference. Jump forward unless dealing with AT & TAB which would have two parameters, if used. Prepare for true subtraction. The carry flag will be reset when the 'updated pointer' reaches K-CUR. For the next loop use the 'updated pointer', but if exiting use the 'present pointer' for K-CUR. Note: It is the control character that is deleted when using DELETE.
106E
ED-LIST
54
LD LD LD LD CALL JP
Cancel the error number and give a 'rasp' before going around the editor again.
Now deal with the FLASH, BRIGHT& INVERSE codes. LD AND LD LD RRA ADD JR B,A +01 C,A A,B A,+12 1105,KEY-DATA Save the code. Keep only bit 0. C holds +00 (= OFF) or C holds +01 (= ON). Fetch the code. Rotate it once (losing bit 0). Increase it by +12 giving for FLASH - +12, BRIGHT - +13 and INVERSE - +14.
The CAPS LOCK code and the mode codes are dealt with 'locally'. 10DB KEY-M&CL JR LD LD XOR LD JR CP NZ,10E6,KEY-MODE HL,+5C6A A,+08 (HL) (HL),A 10F4,KEY-FLAG +0E Jump forward with 'mode' codes. This is FLAGS2. Flip bit 3 of FLAGS2. This is the CAPS LOCK flag. Jump forward. Check the lower limit.
10E6
KEY-MODE
55
10F4
KEY-FLAG
RET C SUB +0D LD HL,+5C41 CP (HL) LD (HL),A JR NZ,10F4,KEY-FLAG LD (HL),+00 SET 3,(TV-FLAG) CP RET A
Reduce the range. This is MODE. Has it been changed? Enter the new 'mode' code. Jump if it has changed; otherwise make it 'L mode'. Signal 'the mode might have changed. Reset the carry flag and return.
The control key codes (apart from FLASH, BRIGHT & INVERSE) are manipulated. 10FA KEY-CONTR LD AND LD LD BIT JR INC B,A +07 C,A A,+10 3,B NZ,1105,KEY-DATA A Save the code. Make the C register hold the parameter. (+00 to +07) A now holds the INK code. But if the code was an 'unshifted' code then make A hold the PAPER code.
The parameter is saved in K-DATA and the channel address changed from KEY-INPUT to KEY-NEXT. 1105 KEY-DATA LD LD JR (K-DATA),C DE,+110D 1113,KEY-CHAN Save the parameter. This is KEY-NEXT. 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. 110D KEY-NEXT LD LD A,(K-DATA) DE,+10A8 Fetch the parameter. This is KEY-INPUT.
Now set the input address in the first channel area. 1113 KEY-CHAN LD INC INC LD INC LD HL,(CHANS) HL HL (HL),E HL (HL),D Fetch the channel address. Now set the input address.
Finally exit with the required code in the A register. 111B KEY-DONE SCF RET Show a code has been found and return.
56
HL,(ECHO-E) HL 1195,SET-HL DE,HL 187D,OUT-LINE2 DE,HL 18E1,OUT-CURS HL,(S-POSNL) (SP),HL DE,HL 0D4D,TEMPS
Push the value of ECHO-E on to the stack. Make HL point to the start of the space and DE the end. Now print the line. Exchange the pointers and print the cursor. Next fetch the Current value of S-POSNL and exchange it with ECHO-E. Pass ECHO-E to DE. 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. 1150 ED-BLANK LD SUB JR JR LD SUB 115E JR ED-SPACES LD PUSH CALL POP JR A,(S-POSNL-hi) D C,117C,ED-C-DONE NZ,115E,ED-SPACES A,E (S-POSNL-lo) NC,117C,ED-C-DONE A,+20 DE 09F4,PRINT-OUT DE 1150,ED-BLANK Fetch the current line number and subtract the old line number. Jump forward if no 'blanking' of lines required. Jump forward if not on the same line. Fetch the old column number and subtract the new column number. Jump if no spaces required. A 'space'. Save the old values, Print it. Fetch the old values. Back again.
New deal with any errors. 1167 ED-FULL LD LD LD CALL LD LD JR D,+00 E,(RASP) HL,+1A90 03B5,BEEPER (ERR-NR),+FF DE,(S-POSNL) 117E,ED-C-END Give out a 'rasp'.
Cancel the error number. Fetch the current value of S-POSNL and jump forward.
The normal exit upon completion of the copying over of the editor the INPUT line. 117C ED-C-DONE POP POP DE HL The new position value. The 'error address'.
But come here after an error. 117E ED-C-END POP LD POP PUSH CALL POP LD LD RET HL (ERR-SP),HL BC DE 0DD9,CL-SET HL (ECHO-E),HL (X-PTR-hi),+00 The old value of ERR-SP is restored. Fetch the old value of S-POSNL. Save the new position values. Set the system variables. The old value of S-POSNL goes into ECHO-E. X-PTR is cleared in a suitable manner and the return made.
57
1195
SET-DE
of the editing area. Clear the carry flag. Point to the start of the editing area and return if in 'editing mode'. Otherwise change DE. Return if now intended. Fetch STKBOT and then return.
58
Now the memory is checked. 11DA 11DC RAM-CHECK LD LD RAM-FILL LD DEC CP JR AND SBC ADD INC JR DEC JR DEC JR 11EF RAM-DONE DEC H,D L,E (HL),+02 HL H NZ,11DC,RAM-FILL A HL,DE HL,DE HL NC,11EF,RAM-DONE (HL) Z,11EF,RAM-DONE (HL) Z,11E2,RAM-READ HL Transfer the value in DE (START = +FFFF, NEW = RAMTOP). Enter the value of +02 into every location above +3FFF. Prepare for true subtraction. The carry flag will become reset when the top is reached. Update the pointer. Jump when at top. +02 goes to +01. But if zero then RAM is faulty. Use current HL as top. +01 goes to +00. Step to the next test unless it fails. HL points to the last actual location in working order.
11E2
RAM-READ
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/PIP),DE &UDG LD (UDG),HL EXX INC B Test the START/NEW flag. JR Z,1219,RAM-SET Jump forward if coming from the NEW command routine.
59
Overwrite the system variables when coming from START and initialise the user-defined graphics area. LD LD LD EX LDDR EX INC LD DEC LD LD (P-RAMT),HL DE,+3EAF BC,+00A8 DE,HL DE,HL HL (UDG),HL HL BC,+0040 (RASP/PIP),BC Top of physical RAM. Last byte of 'U' in character set. There are this number of bytes in twenty one letters. Switch the pointers. Now copy the character forms of the letter 'A' to 'U'. Switch the pointers back. Point to the first byte. Now set UDG. Down one location. Set the system variables RASP & PIP.
The remainder of the routine is common to both the START and the NEW operations. 1219 RAM-SET LD (RAMTOP),HL Set RAMTOP. LD HL,+3C00 Initialise the system variable LD (CHARS),HL CHARS. Next the machine stack is set up. LD LD DEC LD DEC DEC LD The initialisation routine continues with: IM LD EI 1 IY,+5C3A Interrupt mode 1 is used. IY holds +ERR-NR always. The maskable interrupt can now be enabled. The real-time clock will be updated and the keyboard scanned every 1/50th of a second. The base address of the channel information area. The initial channel data is moved from the table (15AF) to the channel information area. The system variable DATADD is made to point to the last location of the channel data. And PROG & VARS to the the location after that. The end-marker of the variables area. Move on one location to find the value for E-LINE. Make the edit-line be a single 'carriage return' character. Now enter an end-marker. Move on one location to find the value for WORKSP, STKBOT & STKEND. HL,(RAMTOP) (HL),+3E HL SP,HL HL HL (ERR-SP),HL The top location is made to hold +3E. The next location is left holding zero. These two locations represent the 'last entry'. Step down two locations to find the correct value for ERR-SP.
HL,+5CB6 (CHANS),HL DE,15AF BC,+0015 DE,HL DE,HL HL (DATADD),HL HL (PROG),HL (VARS),HL (HL),+80 HL (E-LINE),HL (HL),+0D HL (HL),+80 HL (WORKSP),HL (STKBOT),HL
60
(STKEND),HL A,+38 (ATTR-P),A (ATTR-T),A (BORDCR),A HL,+0523 (REPDEL),HL (KSTATE-0) (KSTATE-4) HL,+15C6 DE,+5C10 BC,+000E 1,(FLAGS) 0EDF,CLEAR-PRB (DF-SZ),+02 0D6B,CLS A DE,+1538 0C0A,PO-MSG 5,(TV-FLAG) 12A9,MAIN-1
Initialise the colour system variables to : FLASH 0, BRIGHT 0, PAPER 7, & INK 0. Initialise the system variables REPDEL & REPPER. Make KSTATE-0 hold +FF Make KSTATE-4 hold +FF Next move the initial stream data from its table to the streams area. Signal 'printer in use' and clear the printer buffer. Set the size of the lower part of the display and clear the whole display. Now print the message ' 1982 Sinclair Research Ltd' on the bottom line. Signal 'the lower part will required to be cleared. Jump forward into the main execution loop.
12AC
MAIN-2
A,+00 1601,CHAN-OPEN 0F2C,EDITOR 1B17,LINE-SCAN 7,(ERR-NR) NZ,12CF,MAIN-3 4,(FLAGS2) Z,1303,MAIN-4 HL,(E-LINE) 11A7,REMOVE-FP (ERR-NR),+FF 12AC,MAIN-2
The 'edit-line' has passed syntax and the three types of line that are possible have to be distinguished from each other. 12CF MAIN-3 LD LD CALL LD OR JR HL,(E-LINE) (CH-ADD),HL 19FB,E-LINE-NO A,B C NZ,155D,MAIN-ADD Point to the start of the line. Set CH-ADD to the start also. Fetch any line number into BC. Is the line number a valid one? Jump if it is so, and add the new line to the existing program.
61
RST CP JR
Fetch the first character of the line and see if the line is 'carriage return only'. 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 CALL CALL LD SUB LD SET LD LD CALL 0,(FLAGS2) NZ,0DAF,CL-ALL 0D6E,CLS-LOWER A,+19 (S-POSN-hi) (SCR-CT),A 7,(FLAGS) (ERR-NR),+FF (NSPPC),+01 1B8A,PROG-RUN Clear the whole display unless the flag says it is unnecessary. Clear the lower part anyway. Set the appropriate value for the scroll counter. Signal 'line execution'. Ensure ERR-NR is correct. Deal with the first statement in the line. 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. 1303 MAIN-4 HALT RES BIT CALL LD INC PUSH LD LD LD LD LD LD CALL RES CALL SET POP LD CP JR ADD 133C MAIN-5 CALL LD RST LD LD CALL XOR LD CALL LD CALL LD RST 5,(FLAGS) 1,(FLAGS2) NZ,0ECD,COPY-BUFF A,(ERR-NR) A AF HL,+0000 (FLAGX),H (X-PTR-hi),H (DEFADD),HL HL,+0001 (STRMS-6),HL 16B0,SET-MIN 5,(FLAGX) 0D6E,CLS-LOWER 5,(TV-FLAG) AF B,A +0A C,133C,MAIN-5 A,+07 15EF,OUT-CODE A,+20 0010,PRINT-A-1 A,B DE,+1391 0C0A,PO-MSG A DE,+1536 0C0A,PO-MSG BC,(PPC) 1A1B,OUT-NUM1 A,+3A 0010,PRINT-A-1 The maskable interrupt must be enabled. Signal 'ready for a new key'. Empty the printer buffer if it has been used. Fetch the error number and increment it. Save the new value. The system variables FLAGX, X-PTR-hi & DEFADD are all set to zero. Ensure that stream +00 points to channel 'K' Clear all the work areas and the calculator stack. Signal 'editing mode'. Clear the lower screen. Signal 'the lower screen will require clearing'. Fetch the report value. Make a copy in B. Jump forward with report numbers '0 to 9'. Add the ASCII letter offset value. Print the report code and follow it with a 'space'. Fetch the report value and use it to identify the required report message. Print the message and follow it by a 'comma' and a 'space'. Now fetch the current line number and print it as well. Follow it by a ':'
1313
MAIN-G
62
C,(SUBPPC) B,+00 1A1B,OUT-NUM1 1097,CLEAR-SP A,(ERR-NR) A Z,1386,MAIN-9 +09 Z,1373,MAIN-6 +15 NZ,1376,MAIN-7 (SUBPPC) BC,+0003 DE,+5C70 HL,+5C44 7,(NSPPC) Z,1384,MAIN-8 HL,BC (NSPPC),+FF 3,(FLAGS) 12AC,MAIN-2
1373 1376
MAIN-6 MAIN-7
1384 1386
MAIN-8 MAIN-9
Fetch the current statement number into the BC register pair and print it. Clear the editing area. Fetch the error number again. Increment it as usual. If the program was completed successfully there cannot be any 'CONTinuing' so jump. If the program halted with 'STOP statement' or 'BREAK into program' CONTinuing will be from the next statement; otherwise SUBPPC is unchanged. The system variables OLDPPC & OSPCC have now to be made to hold the CONTinuing line and statement numbers. The values used will be those in PPC & SUBPPC unless NSPPC indicates that the 'break' occurred before a 'jump'. (i.e. after a GO TO statement etc.) NSPPC is reset to indicate 'no jump'. 'K mode' is selected. And finally the jump back is made but no program listing will appear until requested.
63
There are also the following two messages. 1537 1539 ', ' ' 1982 Sinclair Research Ltd' - a 'comma' and a 'space'
Report G - No room for line 1555 REPORT-G LD LD JP A,+10 BC,+0000 1313,MAIN-G 'G' has the code '10+07+30' Clear BC. Jump back to give the report.
157D
MAIN-ADD1
64
Destination into HL & number into DE. Fetch the new line's length. The high length byte. The low length byte. The low line number byte. The high line number byte. Drop the address of REPORT-G. Jump back and this time do produce and automatic listing.
15AB
MAIN-ADD2
65
RST DEFB
0008,ERROR-1 +07
Now call the actual subroutine. HL points to the output or the input address as directed. 15F7 CALL-SUB LD INC LD EX CALL POP EXX RET E,(HL) HL D,(HL) DE,HL 162C,CALL-JUMP HL Fetch the low byte. Fetch the high byte. Move the address to the HL register pair. Call the actual subroutine. Restore the registers. Return will be from here unless an error occurred.
Using the stream data now find the base address of the channel information associated with that stream. 1610 CHAN-OP-1 DEC LD DE HL,(CHANS) Reduce the stream data. The base address of the whole channel information area.
66
ADD
HL,DE
162C
CALL-JUMP
67
Alter all the pointers before making the 'room'. Make HL hold the new STKEND. Switch 'old' and 'new'. Now make the 'room' 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.
A loop is now entered to consider each pointer in turn. Only those pointers that point beyond the 'position' are changed. 166B PTR-NEXT LD INC LD EX AND SBC ADD EX JR PUSH EX ADD EX LD DEC LD INC POP INC DEC JR E,(HL) HL D,(HL) (SP),HL A HL,DE HL,DE (SP),HL NC,167F,PTR-DONE DE DE,HL HL,BC DE,HL (HL),D HL (HL),E HL DE HL A NZ,166B,PTR-NEXT Fetch the two bytes of the current pointer. Exchange the system variable with the address of the 'position'. The carry flag will become set if the system variable's address is to be updated. Restore the 'position'. Jump forward if the pointer is to be left; otherwise change it. Save the old value. Now add the value in BC to the old value. Enter the new value into the system variable - high byte before low byte. Point again to the high byte. Fetch the old value. Point to the next system variable and jump back until all fourteen have been considered.
167F
PTR-DONE
Now find the size of the block to be moved. EX POP POP AND SBC LD LD INC DE,HL DE AF A HL,DE B,H C,L BC Put the old value of STKEND in HL and restore the other registers. Now find the difference between the old value of STKEND and the 'position'. Transfer the result to BC and add '1' for the inclusive byte.
68
ADD EX RET
HL,DE DE,HL
The usual entry point is at LINE-NO. 1695 LINE-NO LD AND JR LD INC LD RET A,(HL) +C0 NZ,1691,LINE-NO-A D,(HL) HL E,(HL) Fetch the high byte and test it. Jump back if not suitable. Fetch the high byte. Fetch the low byte and 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.
Entering here will 'clear' the work space and the calculator stack. 16BF SET-WORK LD LD HL,(WORKSP) (STKBOT),HL Fetch the WORKSP. This clears the work space.
69
Entering here will 'clear' only the calculator stack. 16C5 SET-STK LD LD HL,(STKBOT) (STKEND),HL Fetch STKBOT. This clears the stack.
In all cases make MEM address the calculator's memory area. PUSH LD LD POP RET HL HL,+5C92 (MEM),HL HL Save STKEND. The base of the memory area. Set MEM to this address. Restore STKEND to the HL register pair before returning.
16FC
CLOSE-1
70
HL,(CHANS) HL,BC HL HL HL C,(HL) DE,HL HL,+1716 16DC,INDEXER C,(HL) B,+00 HL,BC (HL)
Fetch the base address of the channel information area and find the channel data for the stream being CLOSEd. Step past the subroutine addresses and pick up the code for that channel. Save the pointer. The base address of the 'CLOSE stream look-up' table. Index into this table and locate the required offset. Pass the offset to the BC register pair. Jump forward to the appropriate routine.
Continue with valid stream numbers. 1727 STR-DATA1 ADD RLCA LD LD LD ADD LD INC LD DEC RET A,+03 HL,+5C10 C,A B,+00 HL,BC C,(HL) HL B,(HL) HL Range now +03 to +12; and now +06 to +24. The base address of the stream data area. Move the stream code to the BC register pair. Index into the data area and fetch the two data bytes into the BC register pair. Make the pointer address the first of the data bytes before returning.
71
1756
OPEN-1
+38,end-calc 171E,STR-DATA A,B C Z,1756,OPEN-1 DE,HL HL,(CHANS) HL,BC HL HL HL A,(HL) DE,HL +4B Z,1756,OPEN-1 +53 Z,1756,OPEN-1 +50 NZ,1725,REPORT-O 175D,OPEN-2 (HL),E HL (HL),D
and the channel code. Fetch the data for the stream. Jump forward if both bytes of the data are zero, i.e. the stream was in a closed state. Save DE. Fetch CHANS - the base address of the channel information and find the code of the channel associated with the stream being OPENed. Return DE. The code fetched from the channel information area must be 'K', 'S' or 'P'; give an error if it is not. Collect the appropriate data in DE. Enter the data into the two bytes in the stream information area. Finally return.
Continue if no error occurred. 1767 OPEN-3 PUSH LD AND LD LD CALL JR LD LD ADD POP JP BC A,(DE) +DF C,A HL,+177A 16DC,INDEXER NC,1765,REPORT-F C,(HL) B,+00 HL,BC BC (HL) The length of the expression is saved. Fetch the first character. Convert lower case codes to upper case ones. Move code to the C register. The base address of the 'OPEN stream look-up' table. Index into this table and locate the required offset. Jump back if not found. Pass the offset to the BC register pair. Make HL point to the start of the appropriate subroutine. Fetch the length of the expression before jumping to the subroutine.
72
53 08 50 0A 00
- channel 'S', offset +08, address 1785 - channel 'P', offset +0A, address 1789 - end marker;
(TV-FLAG),+10 0DAF,CL-ALL 0,(TV-FLAG) B,(DF-SZ) 0E44,CL-LINE 0,(TV-FLAG) 0,(FLAGS2) HL,(E-PPC) DE,(S-TOP) A HL,DE HL,DE C,17E1,AUTO-L-2
The 'automatic' number has now to be altered to give a listing with the 'current' line appearing near the bottom of the screen. PUSH CALL LD EX DE 196E,LINE-ADDR DE,+02C0 DE,HL Save the 'automatic' number. Find the address of the start of the 'current' line and produce an address roughly
73
a 'screen before it' (negated). Save the 'result' on the machine stack whilst the 'automatic' line address is also found (in HL). 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. 17CE AUTO-L-1 PUSH CALL POP ADD JR EX LD INC LD DEC LD JR Now the 'automatic' listing can be made. 17E1 17E4 AUTO-L-2 AUTO-L-3 LD LD CALL JR EX CALL RES RET (S-TOP),HL HL,(S-TOP) 196E,LINE-ADDR Z,17ED,AUTO-L-4 DE,HL 1833,LIST-ALL 4,(TV-FLAG) When E-PPC is less than S-TOP. Fetch the top line's number and hence its address. If the line cannot be found use DE instead. The listing is produced. The return will be to here unless scrolling was needed to show the current line. BC 19B8,NEXT-ONE BC HL,BC C,17E4,AUTO-L-3 DE,HL D,(HL) HL E,(HL) HL (S-TOP),DE 17CE,AUTO-L-1 Save the 'result'. Find the address of the start of the line after the present 'automatic' line (in DE). Restore the 'result'. Perform the computation and jump forward if finished. Move the next line's address to the HL register pair and collect its line number. Now S-TOP can be updated and the test repeated with the new line.
17ED
AUTO-L-4
1814 181A
LIST-2 LIST-3
74
Come here if the stream was unaltered. 181F 1822 LIST-4 LIST-5 CALL CALL CALL LD AND LD LD LD CALL 1833 LIST-ALL LD 1CDE,FETCH-NUM 1BEE,CHECK-END 1E99,FIND-INT A,B +3F H,A L,C (E-PPC),HL 196E,LINE-ADDR E,+01 Fetch any line or use zero if none supplied. If checking the syntax of the edit-line move on to the next statement. Line number to BC. High byte to A. Limit the high byte to the correct range and pass the whole line number to HL. Set E-PPC and find the address of the start of this line or the first line after it if the actual line does not exist. Flag 'before the current line'.
Now the controlling loop for printing a series of lines is entered. 1835 LIST-ALL-1 CALL RST BIT JR LD SUB JR XOR RET PUSH PUSH LD CALL POP POP JR 1855,OUT-LINE 0010,PRINT-A-1 4,(TV-FLAG) Z,1835,LIST-ALL-1 A,(DF-SZ) (S-POSN-hi) NZ,1835,LIST-ALL-1 E Z HL DE HL,+5C6C 190F,LN-FETCH DE HL 1835,LIST-ALL-1 Print the whole of a BASIC line. This will be a 'carriage return'. Jump back unless dealing with an automatic listing. Also jump back if there is still part of the main screen that can be used. A return can be made at this point if the screen is full and the current line has been printed (E = +00) However if the current line is missing from the listing then S-TOP has to be updated and a further line printed (using scrolling).
1865
OUT-LINE1
75
187D 1881
OUT-LINE2 OUT-LINE3
INC INC INC RES LD AND JR RST SET PUSH EX RES LD RES BIT JR SET
HL HL HL 0,(FLAGS) A,D A Z,1881,OUT-LINE3 0010,PRINT-A-1 0,(FLAGS) DE DE,HL 2,(FLAGS2) HL,+5C3B 2,(HL) 5,(FLAGX) 1894,OUT-LINE4 2,(HL)
Move the pointer on to address the first command code in the line. Signal 'leading space allowed' Fetch the cursor code and jump forward unless the cursor is to be printed. So print the cursor now. Signal 'no leading space now'. Save the registers. Move the pointer to DE. Signal 'not in quotes'. This is FLAGS. Signal 'print in K-mode'. Jump forward unless in INPUT mode. 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. 1894 OUT-LINE4 LD AND SBC JR LD CALL CALL EX LD CALL INC CP JR EX CALL JR The line has now been printed. 18B4 OUT-LINE6 POP RET DE Restore the DE register pair and return. HL,(X-PTR) A HL,DE NZ,18A1,OUT-LINE5 A,+3F 18C1,OUT-FLASH 18E1,OUT-CURS DE,HL A,(HL) 18B6,NUMBER HL +0D Z,18B4,OUT-LINE6 DE,HL 1937,OUT-CHAR 1894,OUT-LINE4 Fetch the syntax error pointer and jump forward unless it is time to print the error marker. Print the error marker now. It is a flashing '?'. Consider whether to print the cursor. Move the pointer to HL now. Fetch each character in turn. If the character is a 'number marker' then the hidden floatingpoint form is not to be printed. Update the pointer for the next pass. Is the character a 'carriage return'. Jump if it is. Switch the pointer to DE. Print the character. Go around the loop for at least one further pass.
18A1
OUT-LINE5
76
1909
OUT-C-2
Note: It is the action of considering which cursor-letter is to be printed that determines the mode - 'K' vs. 'L/C'.
77
CALL POP
1695,LINE-NO HL
The pointer is saved. The line number is moved to the HL register pair and incremented. The address of the start of this line is found, or the next line if the actual line number is not being used. The number of that line is fetched. The pointer to the system variable is restored.
The entry point LN-STORE is used by the EDITOR. 191C LN-STORE BIT RET LD DEC LD RET 5,(FLAGX) NZ (HL),D HL (HL),E Return if in 'INPUT mode'; otherwise proceed to enter the line number into the two locations of the system variable. Return when it has been done.
192A
OUT-SP-NO
The HL register pair holds the line number and the BC register the value for 'repeated subtraction'. (BC holds '-1000, -100 or -10'.) 192B OUT-SP-1 ADD INC JR SBC DEC JR JP HL,BC A C,192B,OUT-SP-1 HL,BC A Z,1925,OUT-SP-2 15EF,OUT-CODE The 'trial subtraction'. Count each 'trial'. Jump back until exhausted. Restore last 'subtraction' and discount it. If no 'subtractions' were possible jump back to see if a space is to be printed. Otherwise print the digit.
The entry point OUT-CHAR is used for all characters, tokens and control characters. 1937 OUT-CHAR CALL JR CP JR RES CP JR CP JR BIT JR BIT JR JR 2D1B,NUMERIC NC,196C,OUT-CH-3 +21 C,196C,OUT-CH-3 2,(FLAGS) +CB Z,196C,OUT-CH-3 +3A NZ,195A,OUT-CH-1 5,(FLAGX) NZ,1968,OUT-CH-2 2,(FLAGS2) Z,196C,OUT-CH-3 1968,OUT-CH-2 Return carry reset if handling a digit code. Jump forward to print the digit. Also print the control characters and 'space'. Signal 'print in K-mode'. Jump forward if dealing with the token 'THEN'. Jump forward unless dealing with ':'. Jump forward to print the ':' if in 'INPUT mode'. Jump forward if the ':' is 'not in quotes', i.e. an inter-statement marker. The ':' is inside quotes and can now be printed.
78
195A
OUT-CH-1
1968 196C
OUT-CH-2 OUT-CH-3
Accept for printing all characters except '"'. Save the character code whilst changing the 'quote mode'. Fetch FLAGS2 and flip bit 2. Enter the amended value and restore the character code. Signal 'the next character is to be printed in L-mode'. The present character is 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.
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. 1974 LINE-AD-1 POP CALL RET PUSH CALL EX JR BC 1980,CP-LINES NC BC 19B8,NEXT-ONE DE,HL 1974,LINE-AD-1 The given line number. Compare the given line number against the addressed line number. Return if carry reset; otherwise address the next line's number. Switch the pointers and jump back to consider the next line of the program.
79
1988 198B
HL HL HL (CH-ADD),HL C,+00
Not used. Set CH-ADD to the current byte. Set a 'quotes off' flag.
Enter a loop to handle each statement in the BASIC line. 1990 EACH-S-1 DEC RET RST CP JR AND RET D Z 0020,NEXT-CHAR E NZ,199A,EACH-S-3 A Decrease 'D' and return if the required statement has been found. Fetch the next character code and jump if it does not match the given token code. But should it match then 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. 1998 199A EACH-S-2 EACH-S-3 INC LD CALL LD CP JR DEC CP JR CP JR BIT JR CP JR DEC SCF RET HL A,(HL) 18B6,NUMBER (CH-ADD),HL +22 NZ,19A5,EACH-S-4 C +3A Z,19AD,EACH-S-5 +CB NZ,19B1,EACH-S-6 0,C Z,1990,EACH-S-1 +0D NZ,1998,EACH-S-2 D Update the pointer and fetch the new code. Step over any number. Update CH-ADD. Jump forward if the character is not a '"'. Otherwise set the 'quotes flag'. Jump forward if the character is a ':'. Jump forward unless the code is the token 'THEN'. Read the 'quotes flag' and jump back at the end of each statement (including after 'THEN'). Jump back unless at the end of a BASIC line. Decrease the statement counter and set the carry flag before returning.
19A5
EACH-S-4
19AD
EACH-S-5
19B1
EACH-S-6
19CE
NEXT-O-2
80
long name is reached. Increment the pointer and fetch the new code. Jump back unless the previous code was the last code of the variable's name. Now jump forward (BC = +0005 or +0012). Step past the low byte of the line number. Now point to the low byte of the length. Fetch the length into the BC register pair. Allow for the inclusive byte.
In all cases the address of the 'next' line or variable is found. 19DB NEXT-O-5 ADD POP HL,BC DE Point to the first byte of the 'next' line or variable. Fetch the address of the previous one and continue into the 'difference' subroutine.
81
1A15
E-L-1
Now the integer form of the number in the HL register pair is printed. 1A30 OUT-NUM-3 LD CALL LD CALL LD CALL BC,+FC18 192A,OUT-SP-NO BC,+FF9C 192A,OUT-SP-NO C,+F6 192A,OUT-SP-NO This is '-1,000'. Print a first digit. This is '-100'. Print the second digit. This is '-10'. Print the third digit.
82
A,L 15EF,OUT-CODE HL DE
Move any remaining part of the number to the A register. Print the digit. Restore the registers before returning.
83
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. 1A7A 1A7D 1A81 P-LET P-GO-TO P-IF DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB +01 +3D +02 +06 +00 +67,+1E +06 +CB +05 +F0,+1C +06 +00 +ED,+1E +00 +EE,+1C +00 +23,+1F +04 +3D +06 +CC +06 +05 +03,+1D CLASS-01 '=' CLASS-02 CLASS-06 CLASS-00 GO-TO,1E67 CLASS-06 'THEN' CLASS-05 IF,1CF0 CLASS-06 CLASS-00 GO-SUB,1EED CLASS-00 STOP,1CEE CLASS-00 RETURN,1F23 CLASS-04 '=' CLASS-06 'TO' CLASS-06 CLASS-05 FOR,1D03
84
1A98 1A9C 1A9F 1AA2 1AA5 1AA8 1AAB 1AAE 1AB1 1AB5 1AB8 1ABB 1ABE 1AC1 1AC5 1AC9 1ACC 1ACF 1AD2 1AD6 1AD9 1ADC 1ADF 1AE0 1AE1 1AE2 1AE3 1AE7 1AEB 1AEC 1AED 1AEE
DEFB DEFB DEFB P-PRINT DEFB DEFB P-INPUT DEFB DEFB P-DIM DEFB DEFB P-REM DEFB DEFB P-NEW DEFB DEFB P-RUN DEFB DEFB P-LIST DEFB DEFB P-POKE DEFB DEFB DEFB P-RANDOM DEFB DEFB P-CONT DEFB DEFB P-CLEAR DEFB DEFB P-CLS DEFB DEFB P-PLOT DEFB DEFB DEFB P-PAUSE DEFB DEFB DEFB P-READ DEFB DEFB P-DATA DEFB DEFB P-RESTORE DEFB DEFB P-DRAW DEFB DEFB DEFB P-COPY DEFB DEFB P-LPRINT DEFB DEFB P-LLIST DEFB DEFB P-SAVE DEFB P-LOAD DEFB P-VERIFY DEFB P-MERGE DEFB P-BEEP DEFB DEFB DEFB P-CIRCLE DEFB DEFB DEFB P-INK DEFB P-PAPER DEFB P-FLASH DEFB P-BRIGHT DEFB
P-NEXT
+04 +00 +AB,+1D +05 +CD,+1F +05 +89,+20 +05 +02,+2C +05 +B2,+1B +00 +B7,+11 +03 +A1,+1E +05 +F9,+17 +08 +00 +80,+1E +03 +4F,+1E +00 +5F,+1E +03 +AC,+1E +00 +6B,+0D +09 +00 +DC,+22 +06 +00 +3A,+1F +05 +ED,+1D +05 +27,+1E +03 +42,+1E +09 +05 +82,+23 +00 +AC+0E +05 +C9,+1F +05 +F5,+17 +0B +0B +0B +0B +08 +00 +F8,+03 +09 +05 +20,+23 +07 +07 +07 +07
CLASS-04 CLASS-00 NEXT,1DAB CLASS-05 PRINT,1FCD CLASS-05 INPUT,2089 CLASS-05 DIM,2C02 CLASS-05 REM,1BB2 CLASS-00 NEW,11B7 CLASS-03 RUN,1EA1 CLASS-05 LIST,17F9 CLASS-08 CLASS-00 POKE,1E80 CLASS-03 RANDOMIZE,1E4F CLASS-00 CONTINUE,1E5F CLASS-03 CLEAR,1EAC CLASS-00 CLS,0D6B CLASS-09 CLASS-00 PLOT,22DC CLASS-06 CLASS-00 PAUSE,1F3A CLASS-05 READ,1DED CLASS-05 DATA,1E27 CLASS-03 RESTORE,1E42 CLASS-09 CLASS-05 DRAW,2382 CLASS-00 COPY,0EAC CLASS-05 LPRINT,1FC9 CLASS-05 LLIST,17F5 CLASS-0B CLASS-0B CLASS-0B CLASS-0B CLASS-08 CLASS-00 BEEP,03F8 CLASS-09 CLASS-05 CIRCLE,2320 CLASS-07 CLASS-07 CLASS-07 CLASS-07
85
1B10 1B14
P-ERASE P-CAT
DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB
+07 +07 +08 +00 +7A,+1E +06 +00 +94,+22 +05 +60,+1F +06 +2C +0A +00 +36,+17 +06 +00 +E5,+16 +0A +00 +93,+17 +0A +2C +0A +00 +93,+17 +0A +00 +93,+17 +00 +93,+17
CLASS-07 CLASS-07 CLASS-08 CLASS-00 OUT,1E7A CLASS-06 CLASS-00 BORDER,2294 CLASS-05 DEF-FN,1F60 CLASS-06 ',' CLASS-0A CLASS-00 OPEN,1736 CLASS-06 CLASS-00 CLOSE,16E5 CLASS-0A CLASS-00 CAT-ETC,1793 CLASS-0A ',' CLASS-0A CLASS-00 CAT-ETC,1793 CLASS-0A CLASS-00 CAT-ETC,1793 CLASS-00 CAT-ETC,1793
Note: The requirements for the different command classes are as follows: CLASS-00 CLASS-01 CLASS-02 CLASS-03 CLASS-04 CLASS-05 CLASS-06 CLASS-07 CLASS-08 CLASS-09 CLASS-0A CLASS-0B No further operands. Used in LET. A variable is required. Used in LET. An expression, numeric or string, must follow. A numeric expression may follow. Zero to be used in case of default. A single character variable must follow. A set of items may be given. A numeric expression must follow. Handles colour items. Two numeric expressions, separated by a comma, must follow. As for CLASS-08 but colour items may precede the expressions. A string expression must follow. Handles cassette routines.
86
A statement has been identified so, first, its initial command is considered. LD PUSH LD RST LD SUB JP LD LD ADD LD ADD JR HL,+1B76 HL C,A 0020,NEXT-CHAR A,C +CE C,1C8A,REPORT-C C,A HL,+1A48 HL,BC C,(HL) HL,BC 1B55,GET-PARAM Pre-load the machine stack with the return address - STMT-RET. Save the command temporarily in the C register whilst CH-ADD is advanced again. Reduce the command's code by +CE; giving the range +00 to +31 for the fifty commands. Give the appropriate error if not a command code. Move the command code to the BC register pair (B holds +00). The base address of the syntax offset table. The required offset is passed to the C register and used to compute the base address for the command's entries in the parameter table. 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. 1B52 1B55 SCAN-LOOP LD GET-PARAM LD INC LD LD PUSH LD CP JR LD LD ADD LD ADD PUSH HL,(T-ADDR) A,(HL) HL (T-ADDR),HL BC,+1B52 BC C,A +20 NC,1B6F,SEPARATOR HL,+1C01 B,+00 HL,BC C,(HL) HL,BC HL The temporary pointer to the entries in the parameter table. Fetch each entry in turn. Update the pointer to the entries for the next pass. Pre-load the machine stack with the return address SCAN-LOOP. Copy the entry to the C register for later. Jump forward if the entry is a 'separator'. The base address of the 'command class' table. Clear the B register and index into the table. Fetch the offset and compute the starting address of the required command class routine Push the address on to the machine stack.
87
0018,GET-CHAR B
Before making an indirect jump to the command class routine pass the command code to the A register and set the B register to +FF.
Continue here as the BREAK key was not pressed. 1B7D STMT-R-1 BIT JR LD BIT JR 7,(NSPPC) NZ,1BF4,STMT-NEXT HL,(NEWPPC) 7,H Z,1B9E,LINE-NEW Jump forward if there is not a 'jump' to be made. Fetch the 'new line' number and jump forward unless dealing with a further statement in the editing area.
88
check the validity of the statement number - must be zero. Also check that the 'first line after' is not after the actual 'end of program'. Jump forward with valid addresses; otherwise signal the error 'OK'. Use the error handling routine.
0008,ERROR-1 +FF
Note: Obviously not an error in the normal sense but rather a jump past the program.
89
LD LD LD LD DEC LD JP INC CALL JR Report N - 'Statement lost' 1BEC REPORT-N RST DEFB
location before the first character to be considered. The statement number is fetched. The E register is cleared in case EACH-STMT is used. Signal 'no jump'. The statement number minus one goes into SUBPPC. A first statement can now be considered. However for later statements the 'starting address' has to be found. Jump forward unless the statement does not exist.
0008,ERROR-1 +16
90
The commands of class-00 must not have any operands. e.g. COPY & CONTINUE. 1C10 CLASS-00 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". 1C11 CLASS-05 POP CALL BC Z,1BEE,CHECK-END In all cases drop the address - SCAN-LOOP. If handling commands of classes 00 & 03 AND syntax is being checked move on now to consider the next statement. Save the line pointer in the DE register pair.
EX
DE,HL
Continue with the handling of existing variables. 1C30 VAR-A-2 CALL Z,2996,STK-VARS The parameters of simple string variables and all array variables are passed to the calculator stack. (STK-VARS will 'slice' a string if required.) Jump forward if handling a numeric variable. Clear the A register. The parameters of the string of string array variable are fetched unless syntax is being checked. This is FLAGX.
91
OR LD EX
Bit 0 is set only when handling complete simple strings' thereby signalling 'old copy to be deleted'. 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'. 1C46 VAR-A-3 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 RET (DEST),HL Set DEST as required and return.
Command class 02 is concerned with the actual calculation of the value to be assigned in a LET statement. 1C4E CLASS-02 POP CALL CALL RET BC 1C56,VAL-FET-1 1BEE,CHECK-END The address - SCAN-LOOP is dropped. The assignment is made. Move on to the next statement either via CHECK-END if checking syntax, or STMT-RET if in 'run-time'.
92
The entry point EXPT-2NUM (EQU. CLASS-08) allows for two numeric expressions, separated by a comma, to be evaluated. 1C7A EXPT-2NUM CALL (CLASS-08) CP JR RST 1C82,EXPT-1NUM +2C NZ,1C8A 0020,NEXT-CHAR Evaluate each expression in turn - so evaluate the first. Give an error report if the separator is not a comma. Advance CH-ADD.
The entry point EXPT-1NUM (EQU. CLASS-06) allows for a single numeric expression to be evaluated. 1C82 EXPT-1NUM CALL (CLASS-06) BIT RET 24FB,SCANNING 6,(FLAGS) NZ Evaluate the next expression. Return as long as the result was numeric; otherwise it is an error.
Report C - Nonsense in BASIC 1C8A REPORT-C RST DEFB 0008,ERROR-1 +0B Call the error handling routine.
The entry point EXPT-EXP (EQU. CLASS-0A) allows for a single string expression to be evaluated. 1C8C EXPT-EXP (CLASS-0A) CALL BIT RET JR 24FB,SCANNING 6,(FLAGS) Z 1C8A,REPORT-C Evaluate the next expression. This time return if the result indicates a string; otherwise give an error report.
93
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 XOR AND XOR LD RET (HL) +AA (HL) (HL),A Move the mask leftwards. Impress onto the mask only the even bits of the other byte. Restore the result.
The calculator is now used to add the value zero to the calculator stack. 1CE6 USE-ZERO CALL RET RST DEFB DEFB RET 2530,SYNTAX-Z Z 0028,FP-CALC +A0,stk-zero +38,end-calc Do not perform the operation if syntax is being checked. Use the calculator. The 'last value' is now zero. Return with zero added to the stack.
94
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 DEFB DEFB EX CALL JP 1D00 IF-1 JP 0028,FP-CALC +02,delete +38,end-calc DE,HL 34E9,TEST-ZERO C,1BB3,LINE-END 1B29,STMT-L-1 Use the calculator. The present 'last value' is deleted. Make HL point to the first byte and call TEST-ZERO. If the value was 'FALSE' jump to the next line. But if 'TRUE' jump to the next statement (after the THEN).
There has not been a STEP supplied so the value '1' is to be used. 1D10 F-USE-1 CALL RST DEFB DEFB 1BEE,CHECK-END 0028,FP-CALC +A1,stk-one +38,end-calc Move on to the next statement if checking syntax; otherwise use the calculator to place a '1' on the calculator stack.
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. 1D16 F-REORDER RST DEFB DEFB DEFB DEFB DEFB DEFB 0028,FP-CALC +C0,st-mem-0 +02,delete +01,exchange +E0,get-mem-0 +01,exchange +38,end-calc v, l, s v, l, s (mem-0 = s) v, l l, v l, v, s l, s, v
A FOR control variable is now established and treated as a temporary calculator memory area. CALL LD 2AFF,LET (MEM),HL The variable is found, or created if needed (v is used). 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.
95
Fetch the variable's single character name. Ensure bit 7 of the name is set. It will have six locations at least. Make HL point after them. Rotate the name and jump if it was already a FOR variable. Otherwise create thirteen more locations. Again make HL point to the LIMIT position.
The initial values for the LIMIT and the STEP are now added. 1D34 F-L&S PUSH RST DEFB DEFB DEFB POP EX LD LDIR HL 0028,FP-CALC +02,delete +02,delete +38,end-calc HL DE,HL C,+0A The pointer is saved. l, s l DE still points to 'l'. The pointer is restored and both pointers exchanged. The ten bytes of the LIMIT and the STEP are moved.
The looping line number and statement number are now entered. LD EX LD INC LD LD INC INC LD HL,(PPC) DE,HL (HL),E HL (HL),D D,(SUBPPC) D HL (HL),D The current line number. Exchange the registers before adding the line number to the FOR control variable. The looping statement is always the next statement whether it exists or not.
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 RET LD LD LD LD NEG LD LD LD 1DDA,NEXT-LOOP NC B,(STRLEN-lo) HL,(PPC) (NEWPPC),HL A,(SUBPPC) D,A HL,(CH-ADD) E,+F3 Is a 'pass' possible? Return now if it is. Fetch the variable's name. Copy the present line number to NEWPPC. Fetch the current statement number and two's complement it. Transfer the result to the D register. Fetch the current value of CH-ADD. 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. 1D64 F-LOOP PUSH LD CALL LD POP JR BC BC,(NXTLIN) 1D86,LOOK-PROG (NXTLIN),BC BC C,1D84,REPORT-I Save the variable's name. Fetch the current value of NXTLIN. The program area is now searched and BC will change with each new line examined. Upon return save the pointer. Restore the variable's name. If there are no further NEXTs then give an error.
96
RST OR CP JR RST JR
Advance past the NEXT that was found. Allow for upper and lower case letters before the new variable name is tested. Jump forward if it matches. Advance CH-ADD again and 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. 1D7C F-FOUND RST LD SUB LD RET REPORT I - FOR without NEXT 1D84 REPORT-I RST DEFB 0008,ERROR-1 +11 Call the error handling routine. 0020,NEXT-CHAR A,+01 D (NSPPC),A Advance CH-ADD. The statement counter in the D register counted statements back from zero so it has to be subtracted from '1'. The result is stored. Now return - to STMT-RET.
Now a loop is entered to examine each further line in the program. 1D8B LOOK-P-1 INC LD AND SCF RET LD INC LD LD INC LD INC LD PUSH ADD LD LD POP LD PUSH CALL POP RET JR HL A,(HL) +CO NZ B,(HL) HL C,(HL) (NEWPPC),BC HL C,(HL) HL B,(HL) HL HL,BC B,H C,L HL D,+00 BC 198B,EACH-STMT BC NC 1D8B,LOOK-P-1 Fetch the high byte of the line number and return with carry set if there are no further lines in the program. The line number is fetched and passed to NEWPPC. Then the length is collected.
1DA3
LOOK-P-2
The pointer is saved whilst the address of the end of the line is formed in the BC register pair. The pointer is restored. Set the statement counter to zero. The end-of-line pointer is saved whilst the statements of the line are examined. Make a return if there was an 'occurrence'; otherwise consider the next line.
97
Next the variable's VALUE and STEP are manipulated by the calculator. INC LD RST DEFB DEFB DEFB DEFB DEFB DEFB HL (MEM),HL 0028,FP-CALC +E0,get-mem-0 +E2,get-mem-2 +0F,addition +C0,st-mem-0 +02,delete +38,end-calc Step past the name. Make the variable a temporary 'memory area'. v v, s v+s v+s -
The result of adding the VALUE and the STEP is now tested against the LIMIT by calling NEXT-LOOP. CALL RET 1DDA,NEXT-LOOP C Test the new VALUE against the LIMIT Return now if the FOR-NEXT loop has been completed.
Otherwise collect the 'looping' line number and statement. LD LD ADD LD INC LD INC LD EX JP HL,(MEM) DE,+000F HL,DE E,(HL) HL D,(HL) HL H,(HL) DE,HL 1E73,GO-TO-2 Find the address of the low byte of the looping line number. Now fetch this line number.
Followed by the statement number. Exchange the numbers before jumping forward to treat them as the destination line of a GO TO command.
Report 1 - NEXT without FOR 1DD8 REPORT-1 RST DEFB 0008,ERROR-1 +00 Call the error handling routine.
1DE2
98
However if the loop is impossible the carry flag has to be set. 1DE9 NEXT-2 DEFB SCF RET +38,end-calc Set the carry flag and return.
Continue - picking up a value from the DATA list. 1E0A READ-1 CALL CALL RST LD LD LD CALL 1E1E READ-2 RST CP JR CALL RET 0077,TEMP-PTR1 1C56,VAL-FET-1 0018,GET-CHAR (DATADD),HL HL,(X-PTR) (X-PTR-hi),+00 0078,TEMP-PTR2 0018,GET-CHAR +2C Z,1DEC,READ-3 1BEE,CHECK-END Advance the pointer along the DATA list and set CH-ADD. Fetch the value and assign it to the variable. Fetch the current value of CH-ADD and store it in DATADD. Fetch the pointer to the READ statement and clear X-PTR. Make CH-ADD once again point to the READ statement. GET the present character and see if it is a ','. If it is then jump back as there are further items; otherwise return either via CHECK-END (if checking syntax) or the RET instruction (to STMT-RET).
99
100
101
Z 0008,ERROR-1 +0A
indicated by a set carry flag. Return with all positive numbers that are in range. Call the error handling routine.
Continue with the CLEAR operation. 1EDC CLEAR-2 EX LD POP POP LD DEC LD
102
PUSH LD EX JP
to an empty GO SUB stack. Next pass the 'error address' to the stack and save its address in ERR-SP. An indirect return is now 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.
103
LD LD RET
B,H C,L
104
C A,+FE A,(+FE)
Return if the BREAK key is not being pressed. Form the port address +FEFE and read in a byte. Again examine bit 0. Return with carry reset if both keys are being pressed.
105
RST CP JR RST LD PUSH CALL POP XOR AND 1FBD DEF-FN-7 JP CALL
0020,NEXT-CHAR +3D NZ,1FBD,DEF-FN-7 0020,NEXT-CHAR A,(FLAGS) AF 2F4B,SCANNING AF (FLAGS) +40 NZ,1C8A,REPORT-C 1BEE,CHECK-END
The next character is fetched. It must be an '='. Fetch the next character. Save the nature - numeric or string - of the variable. Now consider the definition as an expression. Fetch the nature of the variable and check that it is of the same type as found for the definition. Give an error report if it is required. Exit via the CHECK-END subroutine. (Thereby moving on to consider the next statement in the line.)
106
107
A loop is now set up to deal with each character in turn of the string. 203C PR-STRING LD A,B OR C DEC BC RET Z LD A,(DE) INC DE RST 0010,PRINT-A-1 JR 203C,PR-STRING
Return now if there are no characters remaining in the string; otherwise decease the counter. Fetch the code and increment the pointer. The code is printed and a jump taken to consider any further characters.
108
NC,160E,REPORT-O 1601,CHAN-OPEN A
over +FF. Use the channel for the stream in question. Clear the carry flag and return.
109
JR
20FA,IN-PROMPT
Jump forward to issue the prompt message. Jump to consider going round the loop again if the present character is not a letter. Determine the destination address for the variable. Signal 'not INPUT LINE'. Jump forward if only checking syntax. The work space is set to null. This is FLAGX. Signal 'string result'. Signal 'INPUT mode'. Allow the prompt message only a single location. Jump forward if using 'LINE'. Jump forward if awaiting a numeric entry. A string entry will need three locations. Bit 6 of FLAGX will become set for a numeric entry. The required number of locations is made available. A 'carriage return' goes into the last location. Test bit 6 of the C register and jump forward if only one location was required. A 'double quotes' character goes into the first and second locations.
Proceed to handle simple INPUT variables. 20ED IN-ITEM-3 CALL 2C8D,ALPHA JP NC,21AF-IN-NEXT-1 CALL 1C1F,CLASS-01
RES 7,(FLAGX) The prompt message is now built up in the work space. 20FA IN-PROMPT CALL 2530,SYNTAX-Z JP Z,21B2,IN-NEXT-2 CALL 16BF,SET-WORK LD HL,+5C71 RES 6,(HL) SET 5,(HL) LD BC,+0001 BIT JR LD AND JR LD 211A 211C IN-PR-1 IN-PR-2 OR LD RST LD LD RRCA RRCA JR LD LD DEC LD LD 7,(HL) NZ,211C,IN-PR-2 A,(FLAGS) +40 NZ,211A,IN-PR-1 C,+03 (HL) (HL),A 0030,BC-SPACES (HL),+0D A,C NC,2129,IN-PR-3 A,+22 (DE),A HL (HL),A (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,(FLAGX) Jump forward with INPUT JR NZ,215E,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 213A IN-VAR-1 LD HL,+213A This will be the 'return PUSH HL point' in case of errors. BIT 4,(FLAGS2) Only change the error JR Z,2148,IN-VAR-2 stack pointer if using channel LD (ERR-SP),SP 'K'. 2148 IN-VAR-2 LD HL,(WORKSP) Set HL to the start of the CALL 11A7,REMOVE-FP INPUT line and remove any floating-point forms. (There will not be any except perhaps after an error.) LD (ERR-NR),+FF Signal 'no error yet'.
2129
IN-PR-3
110
Now get the INPUT and with the syntax/run flag indicating syntax, check the INPUT for errors; jump if in order; return to IN-VAR-1 if not. 215E IN-VAR-3 CALL 0F2C,EDITOR Get a 'LINE'. All the system variables have to be reset before the actual assignment of a value can be made. 2161 IN-VAR-4 LD (K-CUR-hi),+00 The cursor address is reset. CALL 21D6,IN-CHAN-K The jump is taken if using JR NZ,2174,IN-VAR-5 other than channel 'K'. CALL 111D,ED-COPY The input-line is copied to LD BC,(ECHO-E) the display and the position CALL 0DD9,CL-SET in ECHO-E made the current position in the lower screen. 2174 IN-VAR-5 LD HL,+5C71 This is FLAGX. RES 5,(HL) Signal 'edit mode'. BIT 7,(HL) Jump forward if handling an RES 7,(HL) INPUT LINE. JR NZ,219B,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,(FLAGS) Now with the syntax/run flag CALL 21B9,IN-ASSIGN indicating 'run' make the assignment. LD HL,(X-PTR) Restore the original address LD (X-PTR-hi),+00 to CH-ADD and clear X-PTR. LD (CH-ADD),HL JR 21B2,IN-NEXT-2 Jump forward to see if there are further INPUT items. 219B IN-VAR-6 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 2AB2,STK-ST-$ These parameters are stacked CALL 2AFF,LET and the actual assignment made. JR 21B2,IN-NEXT-2 Also jump forward to consider further items. Further items in the INPUT statement are considered. 21AF IN-NEXT-1 CALL 1FFC,PR-ITEM-1 Handle any print items. 21B2 IN-NEXT-2 CALL 204E,PR-POSN-1 Handle any position controllers. JP Z,20C1,IN-ITEM-1 Go around the loop again if there are further items; RET otherwise return.
111
RST 0018,GET-CHAR CP +0D RET Z Report C - Nonsense in BASIC 21CE REPORT-C RST 0008,ERROR-1 DEFB +0B Come here if the INPUT line starts with 'STOP'. 21D0 IN-STOP CALL 2530,SYNTAX-Z RET Z Report H - STOP in INPUT 21D4 REPORT-H RST 0008,ERROR-1 DEFB +10
Get the present character and check it is a 'carriage return'. Return if it is. Call the error handling routine. But do not give the error report on the syntax-pass. Call the error handling routine.
112
The control character is sent out. LD A,D Then the parameter is fetched RST 0010,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. 2211 CO-TEMP-5 SUB +11 Reduce the range and jump ADC A,+00 forward with INK & PAPER. JR Z,2234,CO-TEMP-7 SUB +02 Reduce the range once again ADC A,+00 and jump forward with FLASH JR Z,2273,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 +01 Prepare to jump with OVER. LD A,D Fetch the parameter. LD B,+01 Prepare the mask for OVER. JR NZ,2228,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,+04 INVERSE 1; the mask is to have bit 2 set. 2228 CO-TEMP-6 LD C,A Save the A register whilst the range is tested. LD A,D The correct range for CP +02 INVERSE and OVER is only JR NC,2244,REPORT-K '0-1'. LD A,C Fetch the A register. LD HL,+5C91 It is P-FLAG that is to be changed. JR 226C,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. 2234 CO-TEMP-7 LD A,D Fetch the parameter. LD B,+07 Prepare the mask for INK. JR C,223E,CO-TEMP-8 Jump forward with INK. RLCA Multiply the parameter for RLCA PAPER by eight. RLCA LD B,+38 Prepare the mask for PAPER. 223E CO-TEMP-8 LD C,A Save the parameter in the C register whilst the range of the parameter is tested.
is reduced to the control character range (+10 to +15). The control character code is preserved whilst the parameter is moved to the calculator stack. A return is made at this point if syntax is being checked. The control character code is preserved whilst the parameter is moved to the D register.
113
LD CP JR Report K - Invalid colour 2244 REPORT-K RST DEFB Continue to handle PAPER & INK; 2246 CO-TEMP-9 LD CP JR LD JR OR CPL AND JR LD
A,D +0A C,2246,CO-TEMP-9 0008,ERROR-1 +13 HL,+5C8F +08 C,2258,CO-TEMP-B A,(HL) Z,2257,CO-TEMP-A B +24 Z,2257,CO-TEMP-A A,B
Fetch the original value. Only allow PAPER/INK a range of '0' to '9'. Call the error handling routine. Prepare to alter ATTR-T, MASK-T & P-FLAG. Jump forward with PAPER/INK '0' to '7'. Fetch the current value of ATTR-T and use it unchanged, by jumping forward, with PAPER/INK '8'. But for PAPER/INK '9' the PAPER and INK colours have to be black and white. Jump for black INK/PAPER; but continue for white INK/ PAPER. Move the value to the C register. Move the value. Now change ATTR-T as needed. The bits of MASK-T are set only when using PAPER/INK '8' or '9'. Now change MASK-T as needed. The appropriate mask is built up in the B register is order to change bits 4 & 6 as necessary. The bits of P-FLAG are set only when using PAPER/INK '9'. Continue into CO-CHANGE to manipulate P-FLAG.
2257 CO-TEMP-A LD C,A The mask (B) and the value (C) are now used to change ATTR-T. 2258 CO-TEMP-B LD A,C CALL 226C,CO-CHANGE Next MASK-T is considered. LD A,+07 CP D SBC A,A CALL 226C,CO-CHANGE Next P-FLAG is considered. RLCA RLCA AND +50 LD B,A LD A,+08 CP D SBC A,A
114
2273
CO-TEMP-C
SBC
A,A
LD A,D RRCA LD B,+80 JR NZ,227D,CO-TEMP-D RRCA LD B,+40 227D CO-TEMP-D LD C,A LD A,D CP +08 JR Z,2287,CO-TEMP-E CP +02 JR NC,2244,REPORT-K The system variable ATTR-T can now be altered. 2287 CO-TEMP-E LD A,C LD HL,+5C8F CALL 226C,CO-CHANGE The value in MASK-T is now considered. LD A,C RRCA RRCA RRCA JR 226C,CO-CHANGE
The zero flag will be set for BRIGHT. The parameter is fetched and rotated. Prepare the mask for FLASH. Jump forward with FLASH. Rotate an extra time and prepare the mask for BRIGHT. Save the value in the C register. Fetch the parameter and test its range; only '0', '1' & '8' are allowable.
Fetch the value. This is ATTR-T. Now change the system variable. The value is fetched anew. The set bit of FLASH/BRIGHT '8' (bit 3) is moved to bit 7 (for FLASH) or bit 6 (for BRIGHT). Exit via CO-CHANGE.
115
AND RRA XOR AND XOR LD LD RLCA RLCA RLCA XOR AND XOR RLCA RLCA LD LD AND RET
A Now 010b7b6b5b4b3. B +F8 B H,A A,C Finally 010b7b6b2b1b0, so that H becomes 64 + 8*INT (B/64) + B (mod 8), the high byte of the pixel address. C contains X. A starts as c7c6c5c4c3c2c1c0. And is now c2c1c0c7c6c5c4c3. B +C7 B L,A A,C +07 Now c2c1b5b4b3c5c4c3. Finally b5b4b3c7c6c5c4c3, so that L becomes 32*INT (B(mod 64)/8) + INT(x/8), the low byte. A holds x(mod 8): so the pixel is bit (A - 7) within the byte.
116
complemented (OVER 1) or 1 (OVER 0). The byte is entered. Its other bits are unchanged in every case. Exit, setting attribute byte.
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. 2320 CIRCLE RST CP JP RST CALL CALL RST DEFB DEFB DEFB LD 0017,GET-CHAR +2C NZ,1C8A,REPORT-C 0020,NEXT-CHAR 1C82,EXPT-1NUM 1BEE,CHECK-END 0028,FP-CALC +2A,abs +3D,re-stack +38,end-calc A,(HL) Get the present character. Test for comma. If not so, report the error. Get next character (the radius). Radius to calculator stack. Move to consider next statement if checking syntax. Use calculator: the stack holds: X, Y, Z Z is re-stacked; its exponent is therefore available. Get exponent of radius.
117
Test whether radius less than 1. If not, jump. If less, delete it from the stack. The stack holds X, Y. 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). 233B C-R-GRE-1 RST DEFB DEFB LD RST DEFB DEFB DEFB CALL 0028,FP-CALC +A3,stk-pi/2 +38,end-calc (HL),+83 0028,FP-CALC +C5,st-mem-5 +02,delete +38,end-calc 247D,CD-PRMS1 X, Y, Z, PI/2. Now increase exponent to 83 hex, changing PI/2 into 2*PI. X, Y, Z, 2*PI. (2*PI is copied to mem-5). X, Y, Z 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. 2347 PUSH RST DEFB DEFB DEFB DEFB LD CP JR RST DEFB DEFB DEFB POP JP RST DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB BC 0028,FP-CALC +31,duplicate +E1,get-mem-1 +04,multiply +38,end-calc A,(HL) +80 NC,235A,C-ARC-GE1 0028,FP-CALC +02,delete +02,delete +38,end-calc BC 22DC,PLOT 0028,FP-CALC +C2,st-mem-2 +01,exchange +C0,st-mem-0 +02,delete X,Y,Z*SIN (PI/A) +03,subtract +01,exchange +E0,get-mem-0 +0F,addition +CO,st-mem-0 +01,exchange +31,duplicate +E0,get-mem-0 Save the arc-count in B. X,Y,Z X,Y,Z,Z X,Y,Z,Z,SIN (PI/A) X,Y,Z,Z*SIN (PI/A) Z*SIN (PI/A) is half the initial 'arc' length; it is tested to see whether it is less than 0.5. If not, the jump is made. Otherwise, Z is deleted from the stack, with the half-arc too; the machine stack is cleared; and a jump is made to plot X, Y. X,Y,Z,Z*SIN (PI/A) (Z*SIN (PI/A) to mem-2 for now). X,Y,Z*SIN (PI/A),Z X,Y,Z*SIN (PI/A),Z X, Y - Z*SIN (PI/A) Y - Z*SIN (PI/A), X Y - Z*SIN (PI/A), X, Z Y - Z*SIN (PI/A), X+Z (X+Z is copied to mem-0) X+Z, Y - Z*SIN (PI/A) X+Z, Y-Z*SIN (PI/A), Y-Z*SIN (PI/A) sa,sb,sb,sa
235A
C-ARC-GE1
118
(Here sa denotes X+Z and sb denotes Y - Z*SIN (PI/A)). INC CALL LD PUSH CALL POP LD LD POP JP (mem-2-1st) 1E94,FIND-INT1 L,A HL 1E94,FIND-INT1 HL H,A (COORDS),HL BC 2420,DRW-STEPS Incrementing the exponent byte of mem-2 sets mem-2 to 2*Z*SIN(PI/A). The last value X+Z is moved from the stack to A and copied to L. It is saved in HL. Y - Z*SIN (PI/A) goes from the stack to A and is copied to H. HL now holds the initial point. It is copied to COORDS. The arc-count is restored. The jump is made to DRAW.
(The stack now holds X+Z, Y - Z*SIN (PI/A), Y - Z*SIN (PI/A), X+Z).
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. 2382 DRAW RST CP JR CALL JP RST CALL CALL RST DEFB DEFB DEFB DEFB DEFB 0018,GET-CHAR +2C Z,238D,DR-3-PRMS 1BEE,CHECK-END 2477,LINE-DRAW 0020,NEXT-CHAR 1C82,EXPT-1NUM 1BEE,CHECK-END 0028,FP-CALC +C5,st-mem-5 +A2,stk-half +04,multiply +1F,sin +31,duplicate Get the current character. If it is a comma, then jump. Move on to next statement if checking syntax. Jump to just draw the line. Get next character (the angle). Angle to calculator stack. Move on to next statement if checking syntax. X, Y, G are on the stack. (G is copied to mem-5) X, Y, G, 0.5 X, Y, G/2 X, Y, SIN (G/2) X, Y, SIN (G/2), SIN (G/2)
238D
DR-3-PRMS
119
23A3
DR-SIN-NZ
DEFB DEFB DEFB DEFB DEFB DEFB JP DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB LD CP JR RST DEFB DEFB DEFB JP
+30,not +30,not +00,jump-true +06,to DR-SIN-NZ +02,delete +38,end-calc 2477,LINE-DRAW +C0,st-mem-0 +02,delete +C1,st-mem-1 +02,delete +31,duplicate +2A,abs +E1,get-mem-1 +01,exchange +E1,get-mem-1 +2A,abs +0F,addition +E0,get-mem-0 +05,division +2A,abs +E0,get-mem-0 +01,exchange +3D,re-stack +38,end-calc A,(HL) +81 NC,23C1,DR-PRMS 0028,FP-CALC +02,delete +02,delete +38,end-calc 2477,LINE-DRAW
X, Y, SIN (G/2), (0/1) X, Y, SIN (G/2), (1/0) X, Y, SIN (G/2) (If SIN (G/2)=0 i.e. G = 2*N*PI just draw a straight line). X, Y Line X0, Y0 to X0+X, Y0+Y. (SIN (G/2) is copied to mem-0) X, Y are now on the stack. (Y is copied to mem-1). X X, X X, X' (X' = ABS X) X, X', Y X, Y, X' X, Y, X', Y X, Y, X', Y' (Y' = ABS Y) X, Y, X'+Y' X, Y, X'+Y', SIN (G/2) X, Y, (X'+Y')/SIN (G/2)=Z', say X, Y, Z (Z = ABS Z') X, Y, Z, SIN (G/2) X, Y, SIN (G/2), Z (Z is re-stacked to make sure that its exponent is available). Get exponent of Z. If Z is greater than or equal to 1, jump. X, Y, SIN (G/2), Z X, Y, SIN (G/2) X, Y Just draw the line from X0, Y0 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). 23C1 DR-PRMS CALL 247D,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 23C4 PUSH RST DEFB DEFB DEFB DEFB DEFB BC 0028,FP-CALC +02,delete +E1,get-mem-1 +01,exchange +05,division +C1,st-mem-1 Save the arc-counter in B. X,Y,SIN(G/2),Z X,Y,SIN(G/2) X,Y,SIN(G/2),SIN(G/2*A) X,Y,SIN(G/2*A),SIN(G/2) X,Y,SIN(G/2*A)/SIN(G/2)=W (W is copied to mem-1).
120
DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB LD CP POP JP PUSH RST DEFB DEFB LD CALL RST
+02,delete +01,exchange +31,duplicate +E1,get-mem-1 +04,multiply +C2,st-mem-2 +02,delete +01,exchange +31,duplicate +E1,get-mem-1 +04,multiply +E2,get-mem-2 +E5,get-mem-5 +E0,get-mem-0 +03,subtract +A2,stk-half +04,multiply +31,duplicate +1F,sin +C5,st-mem-5 +02,delete +20,cos +C0,st-mem-0 +02,delete +C2,st-mem-2 +02,delete +C1,st-mem-1 +E5,get-mem-5 +04,multiply +E0,get-mem-0 +E2,get-mem-2 +04,multiply +0F,addition +E1,get-mem-1 +01,exchange +C1,st-mem-1 +02,delete +E0,get-mem-0 +04,multiply +E2,get-mem-2 +E5,get-mem-5 +04,multiply +03,subtract +C2,st-mem-2 +2A,abs +E1,get-mem-1 +2A,abs +0F,addition +02,delete +38,end-calc A,(DE) +81 BC C,2477,LINE-DRAW BC 0028,FP-CALC +01,exchange +38,end-calc A,(COORDS-lo) 2D28,STACK-A 0028,FP-CALC
X,Y Y,X Y,X,X Y,X,X,W Y,X,X*W (X*W is copied to mem-2). Y,X X,Y X,Y,Y X,Y,Y,W X,Y,Y*W X,Y,Y*W,X*W X,Y,Y*W,X*W,G X,Y,Y*W,X*W,G,G/A X,Y,Y*W,X*W,G - G/A X,Y,Y*W,X*W,G - G/A, X,Y,Y*W,X*W, G/2 - G/2*A=F X,Y,Y*W,X*W, F, F X,Y,Y*W,X*W, F, SIN F (SIN F is copied to mem-5). X,Y,Y*W,X*W,F X,Y,Y*W,X*W, COS F (COS F is copied to mem-0). X,Y,Y*W,X*W (X*W is copied to mem-2). X,Y,Y*W (Y*W is copied to mem-1). X,Y,Y*W,SIN F X,Y,Y*W*SIN F X,Y,Y*W*SIN F,X*W X,Y,Y*W*SIN F,X*W, COS F X,Y,Y*W*SIN F,X*W*COS F X,Y,Y*W*SIN F+X*W*COS F=U X,Y,U,Y*W X,Y,Y*W,U (U is copied to mem-1) X,Y,Y*W X,Y,Y*W, COS F X,Y,Y*W*COS F X,Y,Y*W*COS F,X*W X,Y,Y*W*COS F,X*W, SIN F X,Y,Y*W*COS F,X*W*SIN F X,Y,Y*W*COS F - X*W*SIN F=V (V is copied to mem-2). X, Y, V' (V' = ABS V) X, Y, V', U X, Y, V', U' (U' = ABS U) X, Y, U' + V' X, Y (DE now points to U' + V'). Get exponent of U' + V' If U' + V' is less than 1, just tidy the stack and draw the line from X0, Y0 to X0+X, Y0+Y. Otherwise, continue with the parameters: X, Y, on the stack. Y, X Get X0 into A and so on to the stack. Y, X, X0
121
DEFB DEFB DEFB DEFB LD CALL RST DEFB DEFB DEFB DEFB DEFB POP
+C0,st-mem-0 +0F,addition +01,exchange +38,end-calc A,(COORDS-hi) 2D28,STACK-A 0028,FP-CALC +C5,st-mem-5 +0F,addition +E0,get-mem-0 +E5,get-mem-5 +38,end calc BC
(X0 is copied to mem-0). Y, X0 + X X0+X, Y Get Y0 into A and so on to the stack. X0+X, Y, Y0 (Y0 is copied to mem-5). X0+X, Y0+Y X0+X, Y0+Y, X0 X0+X, Y0+Y, X0, Y0 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. 2420 DRW-STEPS DEC JR JR RST DEFB DEFB DEBF DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB 2439 ARC-START PUSH RST DEFB DEFB DEFB DEFB B Z,245F,ARC-END 2439,ARC-START 0028,FP-CALC +E1,get-mem-1 +31,duplicate +E3,get-mem-3 +04,multiply +E2,get-mem-2 +E4,get-mem-4 +04,multiply +03,subtract +C1,st-mem-1 +02,delete +E4,get-mem-4 +04,multiply +E2,get-mem-2 +E3,get-mem-3 +04,multiply +0F,addition +C2,st-mem-2 +02,delete +38,end-calc BC 0028,FP-CALC +C0,st-mem-0 +02,delete +E1,get-mem-1 +0F,addition B counts the passes through the loop. Jump when B has reached zero. Jump into the loop to start. (See text above for the stack). Un-1 Un-1,Un-1 Un-1,Un-1,COS(G/A) Un-1,Un-1*COS(G/A) Un-1,Un-1*COS(G/A),Vn-1 Un-1,Un-1*COS(G/A),Vn-1, SIN(G/A) Un-1,Un-1*COS(G/A),Vn-1* SIN(G/A) Un-1,Un-1*COS(G/A)-Vn-1* SIN(G/A)=Un (Un is copied to mem-1). Un-1 Un-1,SIN(G/A) Un-1*SIN(G/A) Un-1*SIN(G/A),Vn-1 Un-1*SIN(G/A),Vn-1,COS(G/A) Un-1*SIN(G/A),Vn-1*COS(G/A) Un-1*SIN(G/A)+Vn-1*COS (G/A)=Vn (Vn is copied to mem-2). (As noted in the text, the stack in fact holds X0+X,Y0+Y, Xn and Yn). Save the arc-counter. X0+X, Y0+y, Xn, Yn (Yn is copied to mem-0). X0+X, Y0+Y, Xn X0+X, Y0+Y, Xn, Un X0+X, Y0+Y, Xn+Un = Xn+1
2425
ARC-LOOP
122
DEFB DEFB LD CALL RST DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB LD CALL RST DEFB DEFB CALL POP DJNZ RST DEFB DEFB DEFB DEFB LD CALL RST DEFB DEFB DEFB LD CALL RST DEFB DEFB CALL JP
+31,duplicate +38,end-calc A,(COORDS-lo) 2D28,STACK-A 0028,FP-CALC +03,subtract +E0,get-mem-0 +E2,get-mem-2 +0F,addition +C0,st-mem-0 +01,exchange +E0,get-mem-0 +38,end-calc A,(COORDS-hi) 2D28,STACK-A 0028,FP-CALC +03,subtract +38,end-calc 24B7,DRAW-LINE BC 2425,ARC-LOOP 0028,FP-CALC +02,delete +02,delete +01,exchange +38,end-calc A,(COORDS-lo) 2D28,STACK-A 0028,FP-CALC +03,subtract +01,exchange +38,end-calc A,(COORDS-hi) 2D28,STACK-A 0028,FP-CALC +03,subtract +38,end-calc 24B7,DRAW-LINE 0D4D,TEMPS
X0+X, Y0+Y, Xn+1, Xn+1 Next Xn', the approximate value of Xn reached by the line-drawing subroutine is copied to A and hence to the stack. X0+X,Y0+Y,Xn+1,Xn' X0+X,Y0+Y,Xn+1,Xn+1,Xn' - Xn' = Un' X0+X,Y0+Y,Xn+1,Un',Yn X0+X,Y0+Y,Xn+1,Un',Yn,Vn X0+X,Y0+Y,Xn+1,Un',Yn + Vn = Yn+1 (Yn+1 is copied to mem-0). X0+X,Y0+Y,Xn+1,Yn+1,Un' X0+X,Y0+Y,Xn+1,Yn+1, Un',Yn+1 Yn', approximate like Xn', is copied to A and hence to the stack. X0+X,Y0+Y,Xn+1,Yn+1, Un',Yn+1,Yn' X0+X,Y0+Y,Xn+1,Yn+1, Un',Vn' The next 'arc' is drawn. The arc-counter is restored. Jump if more arcs to draw. The co-ordinates of the end of the last arc that was drawn are now deleted from the stack. Y0+Y, X0+X The X-co-ordinate of the end of the last arc that was drawn, say Xz', is copied to the stack. Y0+Y, X0+X - Xz' X0+X - Xz', Y0+Y The Y-co-ordinate is obtained. X0+X - Xz', Y0+Y, Yz' X0+X - Xz', Y0+Y - Yz' The final arc is drawn to reach X0+X, Y0+Y (or close the circle). Exit, setting temporary colours.
245F
ARC-END
2477
LINE-DRAW
123
DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB CALL JR AND ADD JR USE-252 LD DRAW-SAVE PUSH CALL RST DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB POP RET
+32,exponent +82 +00,(+00,+00,+00) +01,exchange +05,division +E5,get-mem-5 +01,exchange +05,division +2A,abs +38,end-calc 2DD5,FP-TO-A C,2495,USE-252 +FC A,+04 NC,2497,DRAW-SAVE A,+FC AF 2D28,STACK-A 0028,FP-CALC +E5,get-mem-5 +01,exchange +05,division +31,duplicate +1F,sin +C4,st-mem-4 +02,delete +31,duplicate +A2,stk-half +04,multiply +1F,sin +C1,st-mem-1 +01,exchange +C0,st-mem-0 +02,delete +31,duplicate +04,multiply +31,duplicate +0F,addition +A1,stk-one +03,subtract +1B,negate +C3,st-mem-3 +02,delete +38,end-calc BC
2495 2497
Z, 2, SQR Z Z, 2/SQR Z Z, 2/SQR Z, G Z, G, 2/SQR Z Z, G*SQR Z/2 Z, G'*SQR Z/2 (G' = mod G) Z, G'*SQR Z/2 = A1, say A1 to A from the stack, if possible. If A1 rounds to 256 or more, use 252. 4*INT (A1/4) to A. Add 4, giving the arc-count A. Jump if still under 256. Here, just use 252 decimal. Now save the arc-count. Copy it to calculator stack too. Z, A Z, A, G Z, G, A Z, G/A Z,G/A, G/A Z, G/A, SIN (G/A) (SIN (G/A) is copied to mem-4). Z, G/A Z, G/A, G/A Z, G/A, G/A, 0.5 Z, G/A, G/2*A Z, G/A, SIN (G/2*A) (SIN (G/2*A) is copied to mem-1). Z, SIN (G/2*A), G/A (G/A is copied to mem-0). Z, SIN (G/2*A) = S Z, S, S Z, S*S Z, S*S, S*S Z, 2*S*S Z, 2*S*S, 1 Z, 2*S*S - 1 Z, 1 - 2*S*S = COS (G/A) (COS (G/A) is copied to mem-3). Z Restore the arc-count to B. Finished.
124
24C4
DL-X-GE-Y
24CB
DL-LARGER
smaller goes to L, and the larger (later) goes to H. Save diag. step (1,1) in DE. Insert a vertical step (1, 0) into DE (D holds SGN Y). Now jump to set H. Return if ABS X and ABS Y are both zero. The smaller (ABS Y here) goes to L. ABS X to B here, for H. Save the diagonal step here too. Hor. step (0, 1) to DE here. 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 RRA ADD JR CP JR SUB LD EXX POP PUSH JR LD PUSH EXX POP LD LD ADD LD LD INC ADD JR JR 24EC D-L-PLOT DEC LD CALL EXX LD DJNZ POP RET A,B A,L C,24D4,D-L-DIAG H C,24DB,D-L-HR-VT H C,A BC BC 24DF,D-L-STEP C,A DE BC HL,(COORDS) A,B A,H B,A A,C A A,L C,24F7,D-L-RANGE Z,24F9,REPORT-B A C,A 22E5,PLOT-SUB A,C 24CE,D-L-LOOP DE B to A as well as to H. A starts at INT (H/2). L is added to A. If 256 or more, jump - diag. step. If A is less than H, jump for horizontal or vertical step. Reduce A by H. Restore it to C. Now use the exchange resisters. Diag. step to B'C'. Save it too. Jump to take the step. Save A (unreduced) in C. Step to stack briefly. Get exchange registers. Step to B'C' now. Now take the step: first, COORDS to H'L' as the start point. Y-step from B' to A. Add in H'. Result to B' . Now the X-step; it will be tested for range (Y will be tested in PLOT). Add L' to C' in A, jump on carry for further test. Zero after no carry denotes X-position -1, out of range. Restore true value to A. Value to C' for plotting. Plot the step. Restore main registers. C back to A to continue algorithm. Loop back for 8 steps (i.e. H steps). Clear machine stack. Finished.
24CE
D-L-LOOP
24D4
D-L-DIAG
24DB
D-L-HR-VT
24DF
D-L-STEP
125
24F7
D-L-RANGE
JR
Z,24EC,D-L-PLOT
Report B - Integer out of range 24F9 REPORT-B RST DEFB 0008,ERROR-1 +0A Call the error handling routine.
126
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. 24FB 24FF SCANNING S-LOOP-1 RST LD PUSH LD LD CALL LD JP LD LD ADD JP 0018,GET-CHAR B,+00 BC C,A HL,+2596 16DC,INDEXER A,C NC,2684,S-ALPHNUM B,+00 C,(HL) HL,BC (HL) The first character is fetched. The starting priority marker. It is stacked. The main re-entry point. Index into scanning function table with the code in C. Restore the code to A. Jump if code not found in table. Use the entry found in the table to build up the required address in HL, and jump to it.
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. 250F S-QUOTE-S CALL INC CP JP CP JR CALL CP RET 0074,CH-ADD+1 BC +0D Z, 1C8A,REPORT-C +22 NZ,250F,S-QUOTE-S 0074,CH-ADD+1 +22 Point to the next character. Increase the length count by one. Is it a carriage return? Report the error if so. Is it another '"'? Loop back if it is not. Point to next character; set zero flag if it is another '"'. 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. 2522 S-2-COORD RST CP 0020, NEXT-CHAR +28 Fetch the next character. Is it a '('?
127
252D
S-RPORT-C
JR CALL RST CP JP
Report the error if it is not. Co-ordinates to calculator stack. Fetch the current character. Is it a ')'? Report the error if it is not.
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. 2535 S-SCRN$-S CALL LD LD ADD LD RRCA RRCA RRCA AND XOR LD LD AND XOR LD LD PUSH PUSH PUSH LD XOR JR INC JR DEC LD LD 2307,STK-TO-BC HL,(CHARS) DE,+0100 HL,DE A,C x to C, y to B; 0<=x<=23 decimal; O<=y<=31 decimal. CHARS plus 256 decimal gives HL pointing to the character set. x is copied to A. The number 32 (decimal) * (x mod 8) + y is formed in A and copied to E. This is the low byte of the required screen address. x is copied to A again Now the number 64 (decimal) + 8*INT (x/8) is inserted into D. DE now holds the screen address. B counts the 96 characters. Save the count. And the screen pointer. And the character set pointer. Get first row of screen character. Match with row from character set. Jump if direct match found. Now test for match with inverse character (get +00 in A from +FF). Jump if neither match found. Restore +FF to A. Inverse status (+00 or +FF) to C. B counts through the other 7 rows. Move DE to next row (add 256 dec.). Move HL to next row (i.e. next byte). Get the screen row. Match with row from the ROM. Include the inverse status. Jump if row fails to match. Jump back till all rows done. Discard character set pointer. And screen pointer.
254F
S-SCRN-LP
+E0 B E,A A,C +18 +40 D,A B,+60 BC DE HL A,(DE) (HL) Z,255A,S-SC-MTCH A NZ,2573,S-SCR-NXT A C,A B,+07 D HL A,(DE) (HL) C NZ,2573,S-SCR-NXT 255D,S-SC-ROWS BC BC
255A 255D
S-SC-MTCH
128
2573
S-SCR-NXT
257D
S-SCR-STO
JP 2AB2,STK-STO-$
Final count to BC. Last character code in set plus one. A now holds required code. One space is now needed in the work space. Make the space. Put the character into it. Jump to stack the character. Restore character set pointer. Move it on 8 bytes, to the next character in the set. Restore the screen pointer. And the counter. Loop back for the 96 characters. Stack the empty string (Length zero). 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. 2580 S-ATTR-S CALL LD RRCA RRCA RRCA LD AND XOR LD LD AND XOR LD LD JP 2307,STK-TO-BC A,C x to C, y to B. Again, 0<=x<=23 decimal; 0<=y<=31 decimal. x is copied to A and the number 32 (decimal)*x (mod 8)+y is formed in A and copied to L. 32*x(mod 8)+INT (x/8) is also copied to C. L holds low byte of attribute address. 32*x(mod 8)+INT (x/8) is copied to A. 88 (decimal)+INT (x/8) is formed in A and copied to H. H holds high byte of attribute address. The attribute byte is copied to A. Exit, stacking the required byte.
129
25AC 25AE
A9 00
CE
S-POINT
267B End-marker
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""". 25B3 S-QUOTE RST INC PUSH LD CALL JR CALL JR CALL JR RST POP PUSH 25CB S-Q-COPY LD INC LD INC CP JR LD INC CP JR DEC 0018,GET-CHAR HL HL BC,+0000 250F,S-QUOTE-S NZ,25D9,S-Q-PRMS 250F,S-QUOTE-S Z,25BE,S-Q-AGAIN 2530,SYNTAX-Z Z,25D9,S-Q-PRMS 0030,BC-SPACES HL DE A,(HL) HL (DE),A DE +22 NZ,25CB,S-Q-COPY A,(HL) HL +22 Z,25CB,S-Q-COPY BC Fetch the current character. Point to the start of the string. Save the start address. Set the length to zero. Call the "matching" subroutine. Jump if zero reset - no more quotes. Call it again for a third quote. And again for the fifth, seventh etc. If testing syntax, jump to reset bit 6 of FLAGS and to continue scanning. Make space in the work space for the string and the terminating quote. Get the pointer to the start. Save the pointer to the first space. Get a character from the string. Point to the next one. Copy last one to work space. Point to the next space. Is last character a '"'? If not, jump to copy next one. But if it was, do not copy next one; if next one is a '"', jump to copy the one after it; otherwise, finished with copying. Get true length to BC.
25BE
S-Q-AGAIN
25D9
S-Q-PRMS
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. 25DB S-STRING POP LD RES BIT CALL JP DE HL,+5C3B 6,(HL) 7,(HL) NZ,2AB2,STK-STO-S 2712,S-CONT-2 Restore start of copied string. This is FLAGS; this entry point is used whenever bit 6 is to be reset and a string stacked if executing a line. This is done now. 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("). 25E8 S-BRACKET RST 0020,NEXT-CHAR CALL 24FB,SCANNING CP +29 JP RST NZ,1C8A,REPORT-C 0020,NEXT-CHAR The 'scanning BRACKET routine' simply gets the character and calls SCANNING recursively. Report the error if no matching bracket; then continue scanning.
130
25F5
S-FN
JP JP
This routine, for user-defined functions, just jumps to the 'scanning FN subroutine'. 25F8 S-RND CALL JR LD CALL RST DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB CALL LD LD AND JR SUB LD 2625 S-RND-END JR 2530,SYNTAX-Z Z,2626,S-RND-END BC,(SEED) 2D2B,STACK-BC 0028,FP-CALC +A1,stk-one +0F,addition +34,stk-data +37,exponent+87 +16,(+00,+00,+00) +04,multiply +34,stk-data +80,(four bytes) +41,exponent +91 +00,+00,+80,(+00) +32,n-mod-m +02,delete +A1,stk-one +03,subtract +31,duplicate +38,end-calc 2DA2,FP-TO-BC (SEED),BC A,(HL) A Z,2625,S-RND-END +10 (HL),A 2630,S-PI-END Unless syntax is being checked, jump to calculate a random number. Fetch the current value of SEED. Put it on the calculator stack. Now use the calculator, The 'last value' is now SEED+1. Put the decimal number 75 on the calculator stack. 'last value' (SEED+1)*75. See STACK LITERALS to see how bytes are expanded so as to put the decimal number 65537 on the calculator stack. Divide (SEED+1)*75 by 65537 to give a 'remainder' and an 'answer'. Discard the 'answer'. The 'last value' is now 'remainder' - 1. Make a copy of the 'last value'. The calculation is finished. Use the 'last value' to give the new value for SEED. Fetch the exponent of 'last value'. Jump forward if the exponent is zero. Reduce the exponent, i.e. divide 'last value' by 65536 to give the required 'last value'. 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. 2627 S-PI CALL JR RST DEFB DEFB INC 2630 2634 S-PI-END S-INKEY$ RST JP LD RST CP JP LD RES BIT 2530,SYNTAX-Z Z,2630,S-PI-END 0028,FP-CALC +A3,stk-pi/2 +3B,end-calc (HL) 0020,NEXT-CHAR 26C3,S-NUMERIC BC,+105A 0020,NEXT-CHAR +23 Z,270D,S-PUSH-PO HL,+5C3B 6,(HL) 7,(HL) Test for syntax checking. Jump if required. Now use the calculator. The value of PI/2 is put on the calculator stack as the 'last value'. The exponent is incremented thereby doubling the 'last value' giving PI. Move on to the next character. Jump forward. Priority +10 hex, operation code +5A for the 'read-in' subroutine. If next char. is '#', jump. There will be a numerical argument. This is FLAGS. Reset bit 6 for a string result. Test for syntax checking.
131
JR CALL LD JR CALL JR DEC LD CALL PUSH LD RST POP LD LD LD CALL JP CALL CALL RST JP CALL CALL RST JR 267B S-POINT CALL CALL RST JR 2684 S-ALPHNUM CALL JR CP JR
Z,2665,S-INK$-EN 028E,KEY-SCAN C,+00 NZ,2660,S-IK$-STK 031E,K-TEST NC,2660,S-IK$-STK D E,A 0333,K-DECODE AF BC,+0001 0030,BC-SPACES AF (DE),A C,+01 B,+00 2AB2,STK-STO-$ 2712,S-CONT-2 2522,S-2-COORD NZ,2535,S-SCRN$-S 0020,NEXT-CHAR 25DB,S-STRING 2522,5-2-COORD NZ,2580,S-ATTR-S 0020,NEXT-CHAR 26C3,S-NUMERIC 2522,S-2-COORD NZ,22CB,POINT-SUB 0020,NEXT-CHAR 26C3,S-NUMERIC 2C88,ALPHANUM NC,26DF,S-NEGATE +41 NC,26C9,S-LETTER
2672
S-ATTR
Jump if required. Fetch a key-value in DE. Prepare empty string; stack it if too many keys pressed. Test the key value; stack empty string if unsatisfactory. +FF to D for L made (bit 3 set). Key-value to E for decoding. Decode the key-value. Save the ASCII value briefly. One space is needed in the work space. Make it now. Restore the ASCII value. Prepare to stack it as a string. Its length is one. Complete the length parameter. Stack the required string. Jump forward. Check that 2 co-ordinates are given. Call the subroutine unless checking syntax; then get next character and jump back. Check that 2 co-ordinates are given. Call the subroutine unless checking syntax; then get the next character and jump forward. Check that 2 co-ordinates are given. Call the subroutine unless checking syntax; then get the next character and jump forward. Is the character alphanumeric? Jump if not a letter or a digit. Now jump if it a 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. 268D S-DECIMAL CALL (EQU. S-BIN) JR 2530,SYNTAX-Z NZ,2685,S-STK-DEC Jump forward if a line is 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 RST LD CALL INC LD INC EX 2C9B,DEC-TO-FP 0018,GET-CHAR BC,+0006 1655,MAKE-ROOM HL (HL),+0E HL DE,HL The floating-point form is found. Set HL to point one past the last digit. Six locations are required. Make the room in the BASIC line. Point to the first free space. Enter the number marker code. Point to the second location. This pointer is wanted in DE.
132
LD LD AND SBC LD LDIR EX DEC CALL JR During line execution: 26B5 S-STK-DEC 26B6 S-SD-SKIP RST INC LD CP JR INC CALL LD
HL,(STKEND) C,+05 A HL,BC (STKEND),HL DE,HL HL 0077,TEMP-PTR1 26C3,S-NUMERIC 0018,GET-CHAR HL A,(HL) +0E NZ,26B6,S-SD-SKIP HL 33B4,STACK-NUM (CH-ADD),HL
Fetch the 'old' STKEND. There are 5 bytes to move. Clear the carry flag. The 'new' STKEND='old' STKEND -5. Move the floating-point number from the calculator stack to the line. Put the line pointer in HL. Point to the last byte added. This sets CH-ADD. Jump forward. Get the current character. Now move on to the next character in turn until the number marker code is found. Point to the first byte of the number. Move the floating-point number. 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. 26C3 S-NUMERIC SET JR 6,(FLAGS) 26DD,S-CONT-1 Set the numeric marker flag. Jump forward.
26DD
S-CONT-1
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. 26DF S-NEGATE LD CP JR BC,+09DB +2D Z,270D,S-PUSH-PO Priority +09, operation code +D8. Is it a '-'? 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.
133
LD CP JR
Priority 16 dec, operation code +18 hex. Is it 'VAL$'? 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 JP +AF C,1C8A,REPORT-C The range of the functions is changed from +AF to +C3 to range +00 to +14 hex. Report an error if out of range.
The function 'NOT' is identified and dealt with separately from the others. LD CP JR JP BC,+04F0 +14 Z,270D,S-PUSH-PO NC,1C8A,REPORT-C Priority +04, operation code +F0. Is it the function 'NOT'? Jump if it is so. 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 ADD LD CP JR RES CP JR RES B,+10 A,+DC C,A +DF NC,2707.S-NO-TO-S 6,C +EE C,2700,S-PUSH-PO 7,C Priority 16 decimal. The function range is now +DC +EF. Transfer the operation code. Separate CODE, VAL and LEN which operate on strings to give numerical results. Separate STR$ and CHR$ which operate on numbers to give string results. Mark the operation codes. The other operation codes have bits 6 and 7 both set.
2707
S-NO-TO-S
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. 270D S-PUSH-PO PUSH RST JP BC 0020,NEXT-CHAR 24FF,S-LOOP-1 Stack the priority and operation codes before moving on to 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'. 2712 2713 S-CONT-2 S-CONT-3 RST CP JR 0018,GET-CHAR +28 NZ,2723,S-OPERTR Fetch the present character. Jump forward if it is not a '(', 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 JR CALL RST JR 6,(FLAGS) NZ,2734,S-LOOP 2A52,SLICING 0020,NEXT-CHAR 2713,S-CONT-3 Jump forward if dealing with a numeric parenthesised expression. Modify the parameters of the 'last value'. Move on to consider the next character.
134
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. 2723 S-OPERTR LD LD LD CALL JR LD LD ADD LD B,+00 C,A HL,+2795 16DC,INDEXER NC,2734,SLOOP C,(HL) HL,+26ED HL,BC B,(HL) Original code to BC to index into table of operators. The pointer to the table. Index into the table. Jump forward if no operation found. Get required code from the table. The pointer to the priority table: i.e. 26ED +C3 gives 27B0 as the first address. Index into the table. Fetch the appropriate priority.
The main loop of this subroutine is now entered. At this stage there are: i. ii. iii. A 'last value' on the calculator stack. 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. 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. 2734 S-LOOP POP LD CP JR AND JP DE A,D B C,2773,S-TIGHTER A Z,0018,GET-CHAR Get the 'last' operation and priority. The priority goes to the A register. Compare 'last' against 'present'. Exit to wait for the argument. Are both priorities zero? 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 LO LD CP JR BIT JR LD BC HL,+5C3B A,E +ED NZ,274C,S-STK-LST 6,(HL) NZ,274C,S-STK-LST E,+99 Stack the 'present' values. This is FLAGS. The 'last' operation is compared with the code for USR, which will give 'USR number' unless modified; jump if not 'USR'. Test bit 6 of FLAGS. Jump if it is set ('USR number'). Modify the 'last' operation code: 'offset' 19, +80 for string input and numerical result ('USR string'). Stack the 'last' values briefly. Do not perform the actual operation if syntax is being checked.
274C
S-STK-LST
PUSH CALL JR
DE 2530,SYNTAX-Z Z,275B,S-SYNTEST
135
The 'last' operation code. Strip off bits 6 and 7 to convert the operation code to a calculator offset. Now use the calculator. Perform the actual operation It has been done. 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. 275B S-SYNTEST LD XOR AND JP A,E (FLAGS) +40 NZ,1C8A,REPORT-C Get the 'last' operation code. This tests the nature of the 'last value' against the requirement of the operation. They are to be the same for correct syntax. Jump if syntax fails.
2761
S-RPORT-C
Before jumping back to go round the loop again the nature of the 'last value' must be recorded in FLAGS. 2764 POP LD SET BIT JR RES S-LOOPEND POP JR S-RUNTEST DE HL,+5C3B 6,(HL) 7,E NZ,2770,S-LOOPEND 6,(HL) BC 2734,S-LOOP Get the 'last' operation code. This is FLAGS. Assume result to be numeric. Jump forward if the nature of 'last value' is numeric. It is string. Get the 'present' values into BC: Jump back.
2770
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. 2773 S-TIGHTER PUSH LD BIT JR AND ADD LD CP JR SET 2788 S-NOT-AND JR JR CP JR SET 2790 S-NEXT PUSH RST JP DE A,C 6,(FLAGS) NZ,2790,S-NEXT +3F A,+08 C,A +10 NZ,2788,S-NOT-AND 6,C 2790,S-NEXT C,2761,S-RPORT-C +17 Z,2790,S-NEXT 7,C BC 0020,NEXT-CHAR 24FF,S-LOOP-1 The 'last' values go on the stack. Get the 'present' operation code. Do not modify the operation code if dealing with a numeric operand. Clear bits 6 and 7. Increase the code by +08 hex. Return the code to the C register. Is the operation 'AND'? Jump if it is not so. 'AND' requires a numeric operand. Jump forward. The operations -, *, /, ^ and OR are not possible between strings. Is the operation a '+'? Jump if it is so. The other operations yield a numeric result. The 'present' values go on the machine stack. Consider the next character. Go around the loop again.
136
27D9
SF-ARGMTS CALL
RST CP JR RST
137
JR 27E4 27E6 27E9 SF-BRKT-2 SF-RPRT-C SF-FLAG-6 CP JP RST LD RES POP JR SET JP
27F4
SF-SYN-EN
Loop back to consider this argument. Is the current character a ')'? Report the error if it is not. Point to the next character in the BASIC line. This is FLAGS; assume a stringvalued function and reset bit 6 of FLAGS. Restore the zero flag, jump if the FN is indeed string valued. Otherwise, set bit 6 of FLAGS Jump back to continue scanning the line.
ii. During line execution, a search must first be made for a DEF FN statement. 27F7 SF-RUN RST AND LD RST SUB LD JR 2802 RST SF-ARGMT1 RST PUSH LD 2808 SF-FND-DF DEC LD PUSH CALL POP JR REPORT P - FN without DEF. 2812 REPORT-P RST DEFB 0008,ERROR-1 +18 Call the error handling routine. 0020,NEXT-CHAR +DF B,A 0020,NEXT-CHAR +24 C,A NZ,2802,SF-ARGMT1 0020,NEXT-CHAR 0020,NEXT-CHAR HL HL,(PROG) HL DE,+00CE BC 1D86,LOOK-PROG BC NC,2814,SF-CP-DEF Get the first character of the name. Reset bit 5 for upper case. Copy the name to B. Get the next character. Subtract 24 hex, the code for '$'. Copy the result to C (zero for a string, non-zero for a numerical function). Jump if non-zero: numerical function. Get the next character, the '('. Get 1st character of 1st argument. Save the pointer to it on the stack. Point to the start of the program. Go back one location. The search will be for 'DEF FN'. Save the name and 'string status'. Search the program now. Restore the name and status. Jump if a DEF FN statement found.
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. 2814 SF-CP-DEF PUSH CALL AND CP JR CALL SUB CP HL 28AB,FN-SKPOVR +DF B NZ,2825,SF-NOT-FD 28AB,FN-SKPOVR +24 C Save the pointer to the DEF FN character in case the search has to be resumed. Get the name of the DEF FN function. Reset bit 5 for upper case. Does it match the FN name? Jump if it does not match. Get the next character in the DEF FN. Subtract 24 hex, the code for '$'. Compare the status with that of FN.
138
Jump if complete match now found. Restore the pointer to the 'DEF FN'. Step back one location. Use the search routine to find the end of the DEF FN statement, preparing for the next search; save the name and status meanwhile. 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). 2831 SF-VALUES AND CALL POP POP LD CALL PUSH CP JR 2843 SF-ARG-LP INC LD CP LD JR DEC CALL INC LD 2852 SF-ARG-VL INC PUSH PUSH CALL POP XOR AND JR POP EX LD LD SBC A Z,28AB,FN-SKPOVR DE DE (CH-ADD),DE 28AB,FN-SKPOVR HL +29 Z,2885,SF-R-BR-2 HL A,(HL) +0E D,+40 Z,2852,SF-ARG-VL HL 28AB,FN-SKPOVR HL D,+00 HL HL DE 24FB,SCANNING AF (FLAGS) +40 NZ,288B,REPORT-Q HL DE,HL HL,(STKEND) BC,+0005 HL,BC If HL is now pointing to a '$', move on to the '('. Discard the pointer to 'DEF FN'. Get the pointer to the first argument of FN, and copy it to CH-ADD. Move past the '(' now. Save this pointer on the stack. Is it pointing to a ')'? If so, jump: FN has no arguments. Point to the next code. Put the code into A. Is it the 'number marker' code, 0E hex? Set bit 6 of D for a numerical argument. Jump on zero: numerical argument. Now ensure that HL is pointing to the '$' character (not e.g. to a control code). HL now points to the 'number marker'. Bit 6 of D is reset: string argument. Point to the 1st of the 5 bytes in DEF FN. Save this pointer on the stack. Save the 'string status' of the argument. Now evaluate the argument. Get the no./string flag into A. Test bit 6 of it against the result of SCANNING. Give report Q if they did not match. Get the pointer to the first of the 5 spaces in DEF FN into DE. Point HL at STKEND. BC will count 5 bytes to be moved. First, decrease STKEND by 5,
139
LD LDIR EX DEC CALL CP JR PUSH RST CP JR RST POP CALL JR 2885 SF-R-BR-2 PUSH RST CP JR
(STKEND),HL
DE,HL HL 28AB,FN-SKPOVR +29 Z,2885,SF-R-BR-2 HL 0018,GET-CHAR +2C NZ,288B,REPORT-Q 0020,NEXT-CHAR HL 28AB,FN-SKPOVR 2843,SF-ARG-LP HL 0018,GET-CHAR +29 Z,288D,SF-VALUE
so deleting the 'last value' from the stack. Copy the 5 bytes into the spaces in DEF FN. Point HL at the next code. Ensure that HL points to the character after the 5 bytes. Is it a ')'? Jump if it is: no more arguments in the DEF FN statement. It is a ',': save the pointer to it. Get the character after the last argument that was evaluated from FN. If it is not a ',' jump: mismatched arguments of FN and DEF FN. Point CH-ADD to the next argument of FN. Point HL to the ',' in DEF FN again. Move HL on to the next argument in DEF FN. Jump back to consider this argument. Save the pointer to the ')' in DEF FN. Get the character after the last argument in FN. Is it a ')'? If so, jump to evaluate the function; but if not, give report Q. Call the error handling routine.
0008,ERROR-1 +19
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. 288D SF-VALUE POP EX LD LD EX LD PUSH RST RST CALL POP LD POP LD DE DE,HL (CH-ADD),HL HL,(DEFADD) (SP),HL (DEFADD),HL DE 0020,NEXT-CHAR 0020,NEXT-CHAR 24FB,SCANNING HL (CH-ADD),HL HL (DEFADD),HL Restore pointer to ')' in DEF FN. Get this pointer into HL. Insert it into CH-ADD. Get the old value of DEFADD. Stack it, and get the start address of the arguments area of DEF FN into DEFADD. Save address of ')' in FN. Move CH-ADD on past ')' and '=' to the start of the expression for the function in DEF FN. Now evaluate the function. Restore the address of ')' in FN. Store it in CH-ADD. Restore original value of DEFADD. Put it back into DEFADD.
140
RST JP
0020,NEXT-CHAR 2712,S-CONT-2
Get the next character in the BASIC line. Jump back to continue scanning.
Now find the end character of a name that has more than one character. 28D4 V-CHAR CALL JR RES RST JR 2C88,ALPHANUM NC,28EF,V-RUN/SYN 6,C 0020,NEXT-CHAR 28D4,V-CHAR Is the character alphanumeric? Jump out of the loop when the end of the name is found. Mark the discriminator byte. Get the next character. Go back to test it.
Simple strings and arrays of strings require that bit 6 of FLAGS is reset. 28DE V-STR-VAR RST RES 0020,NEXT-CHAR 6,(FLAGS) Step CH-ADD past the '$'. 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. 28E3 V-TEST-FN LD A,(DEFADD-hi) Is DEFADD-hi zero?
141
AND JR CALL JP
If so, jump forward. In 'run-time'? 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. 28EF V-RUN/SYN LD CALL JR LD AND SET LD JR B,C 2530,SYNTAX-Z NZ,28FD,V-RUN A,C +E0 7,A C,A 2934,V-SYNTAX Copy the discriminator bytes to the B register. Jump forward if in 'run-time'. Move the discriminator to A. Drop the character code part. Indicate syntax by setting bit 7. Restore the discriminator. Jump forward to continue.
A BASIC line is being executed so make a search of the variables area. 28FD V-RUN LD HL,(VARS) Pick up the VARS pointer.
Now enter a loop to consider the names of the existing variables. 2900 V-EACH LD AND JR CP JR RLA ADD JP JR A,(HL) +7F Z,2932,V-80-BYTE C NZ,292A,V-NEXT A,A P,293F,V-FOUND-2 C,293F,V-FOUND-2 The 1st. letter of each existing variable. Match on bits 0 to 6. Jump when the '80-byte' is reached. The actual comparison. Jump forward if the 1st characters do not match. Rotate A leftwards and then double it to test bits 5 & 6. Strings and array variables. Simple numeric and FOR-NEXT variables. Take a copy of the pointer to the 2nd. character. Save the 1st letter pointer. Consider the next character. Fetch each character in turn. Point to the next character. Is the character a 'space'? Ignore the spaces. Set bit 5 so as to match lower and upper case letters. Make the comparison. Back for another character if it does match. Will it match with bit 7 set? Try it. Jump forward if the 'last characters' do not match. Check that the end of the name has been reached before jumping forward.
Long names are required to be matched fully. POP DE PUSH DE PUSH HL 2912 V-MATCHES INC HL 2913 V-SPACES LD A,(DE) INC DE CP +20 JR Z,2913,V-SPACES OR +20 CP JR OR CP JR LD CALL JR (HL) Z,2912,V-MATCHES +80 (HL) NZ,2929,V-GET-PTR A,(DE) 2C88,ALPHANUM NC,293E,V-FOUND-1
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. 2929 292A V-GET-PTR V-NEXT POP PUSH CALL HL BC 19B8,NEXT-ONE Fetch the pointer. Save B & C briefly. DE is made to point to the next variable.
142
EX POP JR
DE,HL BC 2900,V-EACH
Switch the two pointers. Get B & C back. Go around the loop again.
Come here if no entry was found with the correct name. 2932 V-80-BYTE SET 7,B Signal 'variable not found'.
Come here if checking syntax. 2934 V-SYNTAX POP RST CP JR SET JR DE 0018,GET-CHAR +28 Z,2943,V-PASS 5,B 294B,V-END Drop the pointer to the 2nd. character. Fetch the present character. Is it a '('? Jump forward. Indicate not dealing with an array and jump forward.
Come here when an entry with the correct name was found. 293E 293F V-FOUND-1 V-FOUND-2 POP POP POP PUSH RST DE DE DE HL 0018,GET-CHAR Drop the saved variable pointer. Drop the 2nd character pointer. Drop the first letter pointer. Save the 'last' letter pointer. 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. 2943 V-PASS CALL JR RST JR The exit-parameters are now set. 294B V-END POP RL BIT RET HL B 6,B HL holds the pointer to the letter of a short name or the 'last' character of a long name. Rotate the whole register. Specify the state of bit 6. Finished. 2C88,ALPHANUM NC,294B,V-END 0020,NEXT-CHAR 2943,V-PASS Is it alphanumeric? Jump when the end of the name has been found. Fetch the next character. Go back and test it.
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. ii. iii. The carry flag is set. The zero flag is set only when the search was for an array variable. 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. ii. iii. The carry flag is reset. The zero flag is set for both simple string variables and all array variables. 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.
143
295A
SFA-LOOP
A match has been found. The parameters of a string variable are stacked, avoiding the need to call the STK-VAR subroutine. 2981 SFA-MATCH BIT JR INC LD CALL EX LD 2991 SFA-END POP POP XOR INC RET 5,C NZ,2991,SFA-END HL DE,(STKEND) 33C0,MOVE-FP DE,HL (STKEND),HL DE DE A A Test for a numeric variable. Jump if the variable is numeric; SCANNING will stack it. Point to the first of the 5 bytes to be stacked. Point DE to STKEND. Stack the 5 bytes. Point HL to the new position of STKEND, and reset the system variable. Discard the LOOK-VARS pointers (2nd & 1st character pointers). Return from the search with both the carry and zero flags reset - signalling that a call STK-VAR is not required. Finished.
144
Next, simple strings are separated from array variables. BIT JR 7,(HL) NZ,29AE,SV-ARRAYS Jump forward if dealing with an array variable.
The parameters for a simple string are readily found. 29A1 INC SV-SIMPLE$ INC LD INC LD INC EX CALL RST JP A HL C,(HL) HL B,(HL) HL DE,HL 2AB2,STK-STORE 0018,GET-CHAR 2A49,SV-SLICE? Signal 'a simple string'. Move along the entry. Pick up the low length counter. Advance the pointer. Pick up the high length pointer. Advance the pointer. Transfer the pointer to the actual string. Pass these parameters to the calculator stack. Fetch the present character and jump forward to see if a 'slice' is required.
The base address of an element in an array is now found. Initially the 'number of dimensions' is collected. 29AE SV-ARRAYS INC INC INC LD BIT JR HL HL HL B,(HL) 6,C Z,29C0,SV-PTR Step past the length bytes. Collect the 'number of dimensions'. Jump forward if handling an 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 JR B Z,29A1,SV-SIMPLE$ Decrease the 'number of 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 RST CP JR EX DE,HL 0018,GET-CHAR +28 NZ,2A20,REPORT-3 DE,HL Save the pointer in DE. Get the present character. Is it a '('? Report the error if it is not so. 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. 29C0 SV-PTR EX JR DE,HL 29E7,SV-COUNT Pass the pointer to DE. Jump forward.
145
The following loop is used to find the parameters of a specified element within an array. The loop is entered at the mid-point - SVCOUNT -, 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. 29C3 SV-COMMA PUSH RST POP CP JR BIT JR BIT JR CP JR RST RET HL 0018,GET-CHAR HL +2C Z,29EA,SV-LOOP 7,C Z,2A20,REPORT-3 6,C NZ,29D8,SV-CLOSE +29 NZ,2A12,SV-RPT-C 0020,NEXT-CHAR Save the counter. Get the present character. Restore the counter. Is the present character a ','? Jump forward to consider another subscript. If a line is being executed then there is an error. Jump forward if dealing with an array of strings. Is the present character a ')'? Report an error if not so. Advance CH-ADD. 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. 29D8 SV-CLOSE CP JR CP JR RST DEC LD JR +29 Z,2A48,SV-DIM +CC NZ,2A12,SV-RPT-C 0018,GET-CHAR HL (CH-ADD),HL 2A45,SV-SLICE Is the present character a ')'? Jump forward and check whether there is another subscript. Is the present character a 'TO'? It must not be otherwise. Get the present character. Point to the preceding character and set CH-ADD. Evaluate the 'slice'.
29E0
SV-CH-ADD
Enter the loop here. 29E7 29EA SV-COUNT SV-LOOP LD PUSH RST POP LD CP JR RST CP JR CP JR 29FB SV-MULT PUSH PUSH CALL EX EX CALL JR DEC HL,+0000 HL 0020,NEXT-CHAR HL A,C +C0 NZ,29FB,SV-MULT 0018,GET-CHAR +29 Z,2A48,SV-DIM +CC Z,29E0,SV-CH-ADD BC HL 2AEE,DE,(DE+1) (SP),HL DE,HL 2ACC,INT-EXP1 C,2A20,REPORT-3 BC Set the counter to zero. Save the counter briefly. Advance CH-ADD. Restore the counter. Fetch the discriminator byte. Jump unless checking the syntax for an array of strings. Get the present character. Is it a ')'? Jump forward as finished counting elements. Is to 'TO'? Jump back if dealing with a 'slice'. Save the dimension-number counter and the discriminator byte. Save the element-counter. Get a dimension-size Into DE. The counter moves to HL and the variable pointer is stacked. The counter moves to DE and the dimension-size to HL. Evaluate the next subscript. Give an error if out of range. The result of the evaluation is decremented as the counter is to
146
count the elements occurring before the specified element. Multiply the counter by the dimension-size. Add the result of 'INT-EXP1' to the present counter. Fetch the variable pointer. Fetch the dimension-number and the discriminator byte. 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. 2A12 SV-RPT-C BIT JR PUSH BIT JR 7,C NZ,2A7A,SL-RPT-C HL 6,C NZ,2A2C,SV-ELEM$ Report an error if checking syntax at this point. Save the counter. Jump forward if handling an array of strings.
When dealing with an array of numbers the present character must be a ')'. LD LD RST CP JR Report 3 - Subscript out of range 2A20 REPORT-3 RST DEFB 0008,ERROR-1 +02 Call the error handling routine. B,D C,E 0018,GET-CHAR +29 Z,2A22,SV-NUMBER Transfer the variable pointer to the BC register pair. Fetch the present character. Is it a ')'? Jump past the error report unless it is needed.
The address of the location before the actual floating-point form can now be calculated. 2A22 SV-NUMBER RST POP LD CALL ADD RET 0020,NEXT-CHAR HL DE,+0005 2AF4,GET-HL*DE HL,BC Advance CH-ADD. Fetch the counter. There are 5 bytes to each element in an array of numbers. Compute the total number of bytes before the required element. Make HL point to the location before the required element. 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. 2A2C SV-ELEM$ CALL EX CALL POP ADD INC LD LD EX CALL 2AEE,DE,(DE+1) (SP),HL 2AF4,GET-HL*DE BC HL,BC HL B,D C,E DE,HL 2AB1,STK-ST-0 Fetch the last dimension-size. The variable printer goes on the stack and the counter to HL. Multiply 'counter' by 'dimension-size'. Fetch the variable pointer. This gives HL pointing to the location before the string. So point to the actual 'start'. Transfer the last dimensionsize to BC to form the 'length'. Move the 'start' to DE. Pass these parameters to the calculator stack. Note: The first parameter is zero indicating a string from an 'array of strings'
147
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 CP JR CP JR CALL RST CP JR 0018,GET-CHAR +29 Z,2A48,SV-DIM +2C NZ,2A20,REPORT-3 2A52,SLlCING 0020,NEXT-CHAR +28 Z,2A45,SV-SLICE Get the present character. Is it a ')'? Jump if it is so. Is it a ','? Report the error if not so. Use SLICING to modify the set of parameters. Fetch the next character. Is It a '('? Jump back if there is a 'slice' to be considered. Signal - string result. Return with the parameters of the required string forming a 'last value' on the calculator stack.
When finished considering the last subscript a return can be made. RES 6,(FLAGS) RET
The possibility of the 'slice' being '()' has to be considered. RST 0020,NEXT-CHAR CP +29 JR Z,2AAD,SL-STORE Before proceeding the registers are manipulated as follows: PUSH XOR PUSH PUSH LD RST POP DE A AF BC DE,+0001 0018,GET-CHAR HL
The 'start' goes on the machine stack. The A register is cleared and saved. The 'length' is saved briefly. Presume that the 'slice' is to begin with the first character. Get the first character. Pass the 'length' to HL.
The first parameter of the 'slice' is now evaluated. CP JR POP CALL +CC Z,2A81,SL-SECOND AF 2ACD,INT-EXP2 Is the present character a 'TO'? The first parameter, by default, will be '1' if the jump is taken. At this stage A is zero. BC is made to hold the first parameter. A will hold +FF if there has been an 'out of range' error. Save the value anyway. Transfer the first parameter to DE. Save the 'length' briefly.
PUSH LD LD PUSH
AF D,B E,C HL
148
Get the present character. Restore the 'length'. Is the present character a 'TO'? Jump forward to consider the second parameter if it is so; CP +29 otherwise show that there is a closing bracket.
At this point a 'slice' of a single character has been identified. e.g. - A$(4). LD LD JR H,D L,E 2A94,SL-DEFINE The last character of the 'slice' is also the first character. Jump forward.
The second parameter of a 'slice' is now evaluated. 2A81 SL-SECOND PUSH RST POP CP JR POP CALL PUSH RST LD LD CP JR The 'new' parameters are now defined. 2A94 SL-DEFINE POP EX ADD DEC EX AND SBC LD JR INC AND JP LD LD POP RES AF (SP),HL HL,DE HL (SP),HL A HL,DE BC,+0000 C,2AA8,SL-OVER HL A M,2A20,REPORT-3 B,H C,L DE 6,(FLAGS) Fetch the 'error register'. The second parameter goes on the stack and the 'start' goes to HL. The first parameter is added to the 'start'. Go back a location to get it correct. The 'new start' goes on the stack and the second parameter goes to HL. Subtract the first parameters from the second to find the length of the 'slice'. Initialise the 'new length'. A negative 'slice' is a 'null string' rather than an error condition. (See manual.) Allow for the inclusive byte. Only now test the 'error register'. Jump if either parameter was out of range for the string. Transfer the 'new length' to BC. Get the 'new start'. Ensure that a string is still indicated. HL 0020,NEXT-CHAR HL +29 Z,2A94,SL-DEFINE AF 2ACD,INT-EXP2 AF 0018,GET-CHAR H,B L,C +29 NZ,2A7A,SL-RPT-C Save the 'length' briefly. Get the next character. Restore the 'length'. Is the present character a ')'? Jump if there is not a second parameter. If the first parameter was in range A will hold zero; otherwise +FF. Make BC hold the second parameter. Save the 'error register'. Get the present character. Pass the result obtained from INT-EXP2 to the HL register pair. Check that there is a closing bracket now.
2AA8
SL-OVER
149
2AAD
SL-STORE
CALL RET
2530,SYNTAX-Z Z
Return at this point if checking syntax; otherwise continue into the STK-STORE subroutine.
150
syntax. Save the error register again. The 'last value' is compressed Into BC. Error register to D. A 'next expression' that gives zero is always in error so jump forward if it is so. Take a copy of the limit-value. This will be a 'dimension-size' a 'DIM-limit' or a 'string length'. Now compare the result of 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'. 2AE8 I-CARRY LD SBC A,D A,+00 Fetch the 'old error value' 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. 2AEB I-RESTORE POP POP RET HL DE Restore HL & DE. Return; 'error register' is the A register.
151
'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. 2AFF LET LD BIT JR HL,(DEST) 1,(FLAGX) Z,2B66,L-EXISTS Fetch the present address in DEST. Jump if handling a variable that 'exists already'.
A 'newly declared variable' is being used. So first the length of its name is found. LD BC,+0005 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. 2B0B 2B0C L-EACH-CH L-NO-SP INC INC LD CP JR JR CP JR CP JR INC JR Separate 'numeric' and 'string' names. 2B1F L-TEST -CH CALL JR CP JP 2C88,ALPHANUM C,2B0B,L-EACH-CH +24 Z,2BC0,L-NEWS Is the code alphanumeric? If It is so then accept it as a character of a 'long' name. Is the present code a 'S'? Jump forward as handling a 'newly declared' simple string. BC HL A,(HL) +20 Z,2B0C,L-NO-SP NC,2B1F,L-TEST-CH +10 C,2B29,L-SPACES +16 NC,2B29,L-SPACES HL 2B0C,L-NO-SP Add '1' to the counter for each character of a name. Move along the variable's name. Fetch the 'present code'. Jump back if it is a 'space'; thereby Ignoring spaces. Jump forward if the code is +21 to +FF. Accept, as a final code, those in the range +00 to +0F. Also accept the range +16 to +1F. Step past the control code after any of INK to OVER. Jump back as these control codes are treated as spaces.
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. 2B29 L-SPACES LD LD DEC CALL A,C HL,(E-LlNE) HL 1655,MAKE-ROOM Copy the 'length' to A. Make HL point to the '80-byte' at the end of the variables area. Now open up the variables area. Note: In effect 'BC' spaces are made before the displaced '80-byte'. Point to the first 'new' byte. Make DE point to the second 'new' byte. Save this pointer. Fetch the pointer to the start of the name. Make DE point to the first 'new' byte. Make B hold the 'number of extra letters' that are found in a 'long name'.
152
JR
Z,2B4F,L-SINGLE
The 'extra' codes of a long name are passed to the variables area. 2B3E L-CHAR INC LD CP JR OR INC LD DJNZ HL A,(HL) +21 C,2B3E,L-CHAR +20 DE (DE),A 2B3E,L-CHAR Point to each 'extra' code. Fetch the code. Accept codes from +21 to +FF; ignore codes +00 to +20. Set bit 5, as for lower case letters. Transfer the codes in turn to the 2nd 'new' byte onwards. Go round the loop for all the 'extra' codes.
The last code of a 'long' name has to be ORed with +80. OR LD +80 (DE),A Mark the code as required and overwrite the last code.
The first letter of the name of the variable being handled is now considered. LD 2B4F L-SINGLE LD XOR OR POP A,+C0 HL,(DEST) (HL) +20 HL Prepare the mark the letter of a 'long' name. Fetch the pointer to the letter. A holds +00 for a 'short' name and +C0 for a 'long' name. Set bit 5, as for lower case letters. Drop the pointer now.
The subroutine L-FIRST is now called to enter the 'letter' into its appropriate location. CALL 2BEA,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. 2B59 L-NUMERIC PUSH RST DEFB DEFB POP LD AND SBC JR HL 0028,FP-CALC +02,delete +38,end-calc HL BC,+0005 A HL,BC 2BA6,L-ENTER Save the 'destination' pointer. Use the calculator. This moves STKEND back five bytes. Restore the pointer. Give the number a 'length' of five bytes. Make HL point to the first of the five locations and 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. 2B66 L-EXISTS BIT JR 6,(FLAGS) Z,2B72,L-DELETES Jump forward if handling any 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 ADD JR DE,+0006 HL,DE 2B59,L-NUMERIC The five bytes of a number +'1'. HL now points 'after'. Jump back to make the actual transfer.
153
The parameters of the string variable are fetched and complete simple strings separated from 'sliced' strings and array strings. 2B72 L-DELETE$ LD LD BIT JR HL,(DEST) BC,(STRLEN) 0,(FLAGX) NZ,2BAF,L-ADD$ Fetch the 'start'. Note: This line is redundant. Fetch the 'length'. Jump if dealing with a complete 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 OR RET A,B C Z Return if the string is a null string.
Then make the required number of spaces available in the work space. PUSH RST PUSH PUSH LD LD INC LD LDDR HL 0030,BC-SPACES DE BC D,H E,L HL (HL),+20 Save the 'start' (DEST). Make the necessary amount of room in the work space. Save the pointer to the first location. Save the 'length' for use later on. Make DE point to the last location. Make HL point 'one past' the new locations. Enter a 'space' character. 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 2BF1,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 AND SBC ADD JR LD LD EX (SP),HL A HL,BC HL,BC NC,2B9B,L-LENGTH B,H C,L (SP),HL 'Length' of new area to HL. 'Pointer' to new area to stack. Compare the two 'lengths' and jump forward if the 'new' string will fit into the room. i.e. No shortening required. However modify the 'new' length if it is too long. 'Length' of new area to stack. 'Pointer' to new area to HL.
2B9B
L-LENGTH
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.
154
EX LD OR JR LDIR
'Start' of new string to HL. 'Pointer' to new area to DE. Jump forward if the 'new' string is a 'null' string. Otherwise move the 'new' string to the work space.
The values that have been saved on the machine stack are restored. 2BA3 L-IN-W/S POP POP POP BC DE HL 'Length' of new area. 'Pointer' to new area. 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.
'Newly declared' simple strings are handled as follows: 2BC0 L-NEW$ LD A,+DF Prepare for the marking of the variable's letter.
155
LD AND
HL,(DEST) (HL)
Fetch the pointer to the letter. Mark the letter as required. L-STRING is now used to add the new string to the variables area.
HL,(DEST) BC BC BC
156
An 'existing array' is reclaimed. 2C15 D-RUN JR PUSH CALL CALL POP C,2C1F,D-LETTER BC 19B8,NEXT-ONE 19E8,RECLAIM-2 BC Jump forward if there is no 'existing array'. Save the discriminator byte. Find the start of the next variable Reclaim the 'existing array'. Restore the discriminator byte.
The initial parameters of the new array are found. 2C1F D-LETTER SET LD PUSH LD BIT JR LD EX 7,C B,+00 BC HL,+0001 6,C NZ,2C2D,D-SIZE L,+05 DE,HL Set bit 7 in the discriminator byte. Make the dimension counter zero. Save the counter and the discriminator byte. The HL register pair is to hold the size of the elements in the array, '1' for a string array/ '5' for a numeric array. Element size DE.
2C2D
D-SIZE
157
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. 2C2E D-NO-LOOP RST LD CALL JP POP PUSH INC PUSH LD LD CALL EX RST CP JR 0020,NEXT-CHAR H,+FF 2ACC,INT-EXP1 C,2A20,REPORT-3 HL BC H HL H,B L,C 2AF4,GET-HL*DE DE,HL 0018,GET-CHAR +2C Z,2C2E,D-NO-LOOP Advance CH-ADD on each pass.. Set a 'limit value'. Evaluate a parameter. Give an error if 'out of range'. Fetch the dimension-counter and the discriminator byte. Save the parameter on each pass through the loop. Increase the dimension counter on each pass also. Restack the dimension-counter and the discriminator byte. The parameter is moved to the HL register pair. The byte total is built up in HL and the transferred to DE. Get the present character and go around the loop again 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 JR RST +29 NZ,2C05,D-REPORT-C 0020,NEXT-CHAR Is it a ')'? Jump back if not so. Advance CH-ADD past it.
Allowance is now made for the dimension-sizes. POP LD LD LD INC INC ADD ADD JP PUSH PUSH PUSH LD LD BC A,C L,B H,+00 HL HL HL,HL HL,DE C,1F15,REPORT-4 DE BC HL B,H C,L Fetch the dimension-counter and the discriminator byte. Pass the discriminator byte to the A register for later. Move the counter to L. Clear the H register. Increase the dimensioncounter by two and double the result and form the correct overall length for the variable by adding the element byte total. Give the report 'Out of memory' if required. Save the element byte total. Save the dimension counter and the discriminator byte. Save the overall length also. Move the overall length to BC.
The required amount of room is made available for the new array at the end of the variables area. LD DEC CALL INC The parameters are now entered. HL,(E-LINE) HL 1655,MAKE-ROOM HL Make the HL register pair point to the '80-byte'. The room is made available. HL is made to point to the first new location.
158
The letter, suitably marked, is entered first. The overall length is fetched and decreased by '3'. Advance HL. Enter the low length. Advance HL. Enter the high length. Fetch the dimension counter. Move it to the A register. Advance HL. Enter the dimension count.
The elements of the new array are now 'cleared'. LD LD DEC LD BIT JR LD POP LDDR H,D L,E DE (HL),+00 6,C Z,2C7C,DIM-CLEAR (HL),+20 BC HL is made to point to the last location of the array and DE to the location before that one. Enter a zero into the last location but overwrite it with 'space' if dealing with an array of strings. Fetch the element byte total. Clear the array + one extra location.
2C7C
DIM-CLEAR
The 'dimension-sizes' are now entered. 2C7F DIM-SIZES POP LD DEC LD DEC DEC JR RET BC (HL),B HL (HL),C HL A NZ,2C7F,DIM-SIZES Get a dimension-size. Enter the high byte. Back one. Enter the low byte. Back one. Decrease the dimension counter. Repeat the operation until all the dimensions have been considered; then return.
159
NC +7B
Complement the carry flag. Return if not a valid character code. Test against 7B hex, 1 more than the code for 'z'. Finished.
EX CCF ADC JP EX JR LD LD JP
2CB3
BIN-END
For other numbers, first any integer part is converted; if the next character is a decimal, then the decimal fraction is considered. 2CB8 NOT-BIN CP JR CALL CP JR RST CALL JR JR 2CCB 2CCF DECIMAL DEC-RPT-C RST CALL JP RST DEFB DEFB RST DEFB DEFB +2E Z,2CCB,DECIMAL 2D3B,INT-TO-FP +2E NZ,2CEB,E-FORMAT 0020,NEXT-CHAR 2D1B,NUMERIC C,2CEB,E-FORMAT 2CD5,DEC-STO-1 0020,NEXT-CHAR 2D1B,NUMERIC C,1C8A,REPORT-C 0028,FP-CALC +A0,stk-zero +38,end-calc 0028,FP-CALC +A1,stk-one +C0,st-mem-0 Is the first character a '.'? If so, jump forward. Otherwise, form a 'last value' of the integer. Is the next character a '.'? Jump forward to see if it is an 'E'. Get the next character. Is it a digit? Jump if not (e.g. 1.E4 is allowed). Jump forward to deal with the digits after the decimal point. If the number started with a decimal, see if the next character is a digit. Report the error if it is not. Use the calculator to stack zero as the integer part of such numbers. Use the calculator again. Find the floating-point form of the decimal number '1', and
2CD5
DEC-STO-1
160
2CDA
NXT-DGT-1
DEFB DEFB RST CALL JR RST DEFB DEFB DEFB DEFB DEFN DEFB DEFB RST JR
+02,delete +38,end-calc 0018,GET-CHAR 2D22,STK-DIGIT C,2CEB,E-FORMAT 0028,FP-CALC +E0,get-mem-0 +A4,stk-ten +05,division +C0,st-mem-0 +04,multiply +0F,addition +38,end-calc 0020,NEXT-CHAR 2CDA,NXT-DGT-1
save it in the memory area. Get the present character. If it is a digit then stack it. If not jump forward. Now use the calculator. For each passage of the loop, the number saved in the memory area is fetched, divided by 10 and restored: i.e. going from .1 to .01 to .001 etc. The present digit is multiplied by the 'saved number' and added to the 'last value'. Get the next character. 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. 2CEB E-FORMAT CP JR CP RET LD RST CP JR CP JR INC RST CALL JR PUSH CALL CALL POP JP AND JP INC JR NEG JP +45 Z,2CF2,SIGN-FLAG +65 NZ B,+FF 0020,NEXT-CHAR +2B Z,2CFE,SIGN-DONE +2D NZ,2CFF,ST-E-PART B 0020,NEXT-CHAR 2D1B,NUMERIC C,2CCF,DEC-RPT-C BC 2D3B,INT-TO-FP 2DD5,FP-TO-A BC C,31AD,REPORT-6 A M,31AD,REPORT-6 B Z,2D18,E-FP-JUMP 2D4F,E-TOO-FP Is the present character an 'E'? Jump forward if it is. Is it an 'e'? Finished unless it is so. Use B as a sign flag, FF for '+'. Get the next character. Is it a '+'? Jump forward. Is it a '-'? Jump if neither '+' not '-'. Change the sign of the flag. Point to the first digit. Is it indeed a digit? Report the error if not. Save the flag in B briefly. Stack ABS m, where m is the exponent. Transfer ABS m to A. Restore the sign flag to B. Report the overflow now if ABS m is greater than 255 or indeed greater than 127 (other values greater than about 39 will be detected later). Test the sign flag in B; '+' (i.e. +FF) will now set the zero flag. Jump if sign of m is '+'. Negate m if sign is '-'. Jump to assign to the 'last value' the result of x*10^m.
2CF2
SIGN-FLAG
2CFE 2CFF
SIGN-DONE ST-E-PART
2D18
E-FP-JUMP
161
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.
162
2D40
NXT-DGT-2
If the code represents a digit then stack the floating-point form. Use the calculator. 'Digit' goes under 'last value'. Define decimal 10. 'Last value' = 'last value' *10. 'Last value' = 'last value+ 'digit'. The next code goes into A. Loop back with this code.
163
2D55
E-SAVE
2D60
E-LOOP
2D6D 2D6E
E-DIVSN E-FETCH
DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB POP JR PUSH RST DEFB DEFB DEFB POP JR RST
+C1,st-mem-1 +E0,get-mem-0 +00,jump-true +04,to E-DIVSN +04,multiply +33,jump +02,to E-FETCH +05,division +E1,get-mem-1 +38,end-calc AF Z,2D7B,E-END AF 0028,FP-CALC +31,duplicate +04,multiply +38,end-calc AF 2D60,E-LOOP 0028,FP-CALC
2D71
E-TST-END
2D7B
E-END
164
+02,delete +28,end-calc
final power of 10 reached, leaving the 'last value' x*10^m on the stack
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 LD XOR SUB LD INC LD ADC HL A,(HL) C C E,A HL A,(HL) A,C Point to the less significant byte. Collect the byte in A. Ones complement it if negative This adds 1 for negative numbers; it sets the carry unless the byte was 0. Less significant byte to E now. Point to the more significant byte. Collect it in A. Finish two complementing in the case of a negative number; note that the carry is always left reset. More significant byte to D now. Finished.
LD RET
D,A
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 LD XOR SUB LD INC LD ADC XOR HL A,E C C (HL),A HL A,D A,C C Point to the third location. Collect the less significant byte. Twos complement it if the number is negative Store the byte. Point to the fourth location. Collect the more significant byte. Twos complement it if the number is negative
165
(HL),A HL (HL),+00 HL
Store the byte. Point to the fifth location. The fifth byte is set to zero. Return with HL pointing to the first byte of n on the stack
166
The calculator is used Log 2 to the base 10 is now stacked. The stack now holds A, log 2. A*log 2 i.e. log (2^A) INT log (2^A)
2DE1
FP-A-END
167
DEFB DEFB DEFB DEFB LD RST RET 2DF2 PF-NEGTVE DEFB DEFB LD RST RST DEFB DEFB DEFB DEFB DEFB DEFB EXX PUSH EXX
+00,jump-true +0D,to PF-POSTVE +02,delete +38,end-calc A,+30 0010,PRINT-A-1 +2A,abs +38,end-calc A,+2D 0010,PRINT-A-1 0028,FP-CALC +A0,stk-zero +C3,st-mem-3 +C4,st-mem-4 +C5,st-mem-5 +02,delete +38,end-calc HL
2DF8
PF-POSTVE
x x Hereafter x'=ABS x. Enter the character code for '0'. Print the '0'. Finished as the 'last value' is zero. x' x'=ABS x. x' Enter the character code for '-'. Print the '-'. Use the calculator again. The 15 bytes of mem-3, mem-4 and mem-5 are now initialised to zero to be used for a print buffer and two counters. The stack is cleared, except for x'. x' H'L', which is used to hold calculator offsets, (e.g. for '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. 2E01 PF-LOOP RST DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB LD AND JR CALL LD LD AND JR OR JR LD LD PUSH EXX POP EXX JR 0028,FP-CALC +31,duplicate +27,int +C2,st-mem-2 +03,subtract +E2,get-mem-2 +01,exchange +C2,st-mem-2 +03,delete +38,end-calc A,(HL) A NZ,2E56,PF-LARGE 2D7F,INT-FETCH B,+10 A,D A NZ,2E1E,PF-SAVE E Z,2E24,PF-SMALL D,E B,+08 DE DE 2E78,PF-BITS Use the calculator again. x', x' x', INT (x')=i (i is stored in mem-2). x'-i=f f,i i,f (f is stored in mem-2). i i Is i a small integer (first byte zero) i.e. is ABS i <= 65535? Jump if it is not i is copied to DE (i, like x', >=0). B is set to count 16 bits. D is copied to A for testing: Is it zero? Jump if it is not zero. Now test E. Jump if DE zero: x is a pure fraction. Move E to D and set B for 8 bits: D was zero and E was not. Transfer DE to D'E', via the machine stack, to be moved into the print buffer at PF-BITS. Jump forward.
2E1E
PF-SAVE
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; 2E24 PF-SMALL RST DEFB 0028,FP-CALC +E2,get-mem-2 i (i=zero here), i,f
168
DEFB
+38,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"<STR$ 0 .1 yields the logical value "true"; once again "" is used instead of "50". LD SUB CALL LD LD SUB LD LD CALL RST DEFB DEFB DEFB DEFB DEFB DEFB CALL PUSH LD DEC RLA SBC INC LD LD INC ADD LD POP JP A,(HL) +7E 2DC1,LOG (2^A) D,A A,(mem-5-2nd) D (mem-5-2nd),A A,D 2D4F,E-TO-FP 0028,FP-CALC +31,duplicate +27,int +C1,st-mem-1 +03,subtract +E1,get-mem-1 +38,end-calc 2DD5,FP-TO-A HL (mem-3-1st),A A A,A A HL,+5CAB (HL),A HL A,(HL) (HL),A HL 2ECF,PF-FRACTN The exponent byte e of f is copied to A. A becomes e - 126 dec i.e. e'+2, where e' is the true exponent of f. The construction A = ABS INT (LOG (2^A)) is performed (LOG is to base 10); i.e. A=n, say: n is copied from A to D. The current count is collected from the second byte of mem-5 and n is subtracted from it. n is copied from D to A. y=f*10^n is formed and stacked. i, y i, y, y i, y, (INT (y) = i2 (i2 is copied to mem-1). i, y - i2 i, y - i2, i2 i, f2, i2 (f2 = y - i2) i2 is transferred from the stack to A. The pointer to f2 is saved. i2 is stored in the first byte of mem-3: a digit for printing. i2 will not count as a digit for printing if it is zero; A is manipulated so that zero will produce zero but a non-zero digit will produce 1. The zero or one is inserted into the first byte of mem-5 (the no. of digits for printing) and added to the second byte of mem-5 (the number of digits before the decimal). The pointer to f2 is restored. Jump to store f2 in buffer (HL now points to f2, DE to i2).
iv. Numbers greater than 2 ^ 27 are similarly multiplied by 2 ^ (-n+7), reducing the number of digits before the decimal to 8, and the loop is re-entered at PF-LOOP. 2E56 PF-LARGE SUB CP JR CALL SUB LD LD ADD LD +80 +1C C,2E6F,PF-MEDIUM 2DC1,LOG (2^A) +07 B,A HL,+5CAC A,(HL) (HL),A e - 80 hex = e', the true exponent of i. Is e' less than 28 decimal? Jump if it is less. n is formed in A. And reduced to n - 7. Then copied to B. n - 7 is added in to the second byte of mem-5, the number of digits required before the
169
LD NEG CALL JR
decimal in x. Then i is multiplied by 10^(-n+7) This will bring it into medium range for printing. Round the loop again to deal with the now medium-sized number.
v. The integer part of x is now stored in the print buffer in mem-3 and mem-4. 2E6F PF-MEDIUM EX CALL EXX SET LD EXX SUB LD DE,HL 2FBA,FETCH-TWO 7,D A,L +80 B,A DE now points to i, HL to f. The mantissa of i is now in D',E',D,E. Get the exchange registers. True numerical bit 7 to D'. Exponent byte e of i to A. Back to the main registers. True exponent e'=e - 80 hex to A. This gives the required bit count.
Note that the case where i us a small integer (less than 65536) re-enters here. 2E7B PF-BITS SLA RL EXX RL RL EXX LD LD LD ADC DAA LD DEC DEC JR DJNZ E D E D HL,+5CAA C,+05 A,(HL) A,A (HL),A HL C NZ,2E8A,PF-BYTES 2E7B,PF-BITS The mantissa of i is now rotated left and all the bits of i are thus shifted into mem-4 and each byte of mem-4 is decimal adjusted at each shift. All four bytes of i. Back to the main registers. Address of fifth byte of mem-4 to HL; count of 5 bytes to C. Get the byte of mem-4. Shift it left, taking in the new bit. Decimal adjust the byte. Restore it to mem-4. Point to next byte of mem-4. Decrease the byte count by one. Jump for each byte of mem-4. Jump for each bit of INT (x).
2E8A
PF-BYTES
Decimal adjusting each byte of mem-4 gave 2 decimal digits per byte, there being at most 9 digits. The digits will now be re-packed, one to a byte, in mem-3 and mem-4, using the instruction RLD. XOR LD LD LD RLD LD 2EA1 PF-DIGITS RLD JR DEC INC JR LD NZ,2EA9,PF-INSERT C C NZ,2EB3,PF-TEST-2 (DE),A A HL,+5CA6 DE,+5CA1 B,+09 C,+FF A is cleared to receive the digits. Source address: first byte of mem-4. Destination: first byte of mem-3. There are at most 9 digits. The left nibble of mem-4 is discarded. FF in C will signal a leading zero, 00 will signal a non-leading zero. Left nibble of (HL) to A, right nibble of (HL) to left. Jump if digit in A is not zero. Test for a leading zero: it will now give zero reset. Jump it it was a leading zero. Insert the digit now.
2EA9
PF-INSERT
170
INC INC INC LD 2EB3 PF-TEST-2 BIT JR INC DJNZ LD SUB JR DEC LD CP 2ECB PF-MORE JR RST DEFB DEFB DEFB
DE (mem-5-1st) (mem-5-2nd) C,+00 0,B Z,2EB8,PF,ALL-9 HL 2EA1,PF-DIGITS A,(mem-5-1st) +09 C,2ECB,PF-MORE (mem-5-1st) A,+04 (mem-4-4th) 2F0C,PF-ROUND 0028,FP-CALC +02,delete +E2,get-mem-2 +38,end-calc
2EB8
PF-ALL-9
Point to next destination. One more digit for printing, and one more before the decimal. Change the flag from leading zero to other zero. The source pointer needs to be incremented on every second passage through the loop, when B is odd. Jump back for all 9 digits. Get counter: were there 9 digits excluding leading zeros? If not, jump to get more digits. Prepare to round: reduce count to 8. Compare 9th digit, byte 4 of mem-4, with 4 to set carry for rounding up. Jump forward to round up. Use the calculator again. - (i is now deleted). f f
vi. The fractional part of x is now stored in the print buffer. 2ECF PF-FRACTN EX CALL EXX LD SUB LD SET EXX CALL LP CP JR EXX RL EXX JR LD LD CALL LD LD CALL LD PUSH EXX POP DJNZ LD LD LD ADD LD DE,HL 2FBA,FETCH-TWO A,+80 L L,+00 7,D 2FDD,SHIFT-FP A,(mem-5-1st) +08 C,2EEC,PR-FR-DGT D 2F0C,PF-ROUND BC,+0200 A,E 2F8B,CA=10*A+C E,A A,D 2F8B,CA=10*A+C D,A BC BC 2EEF,PF-FR-EXX HL,+5CA1 A,C C,(mem-5-1st) HL,BC (HL),A DE now points to f. The mantissa of f is now in D',E',D,E. Get the exchange registers. The exponent of f is reduced to zero, by shifting the bits of f 80 hex - e places right, where L' contained e. True numerical bit to bit 7 of D'. Restore the main registers. Now make the shift. Get the digit count. Are there already 8 digits? If not, jump forward. If 8 digits, just use f to round i up, rotating D' left to set the carry. Restore main registers and jump forward to round up. Initial zero to C, count of 2 to B. D'E'DE is multiplied by 10 in 2 stages, first DE then D'E', each byte by byte in 2 steps, and the integer part of the result is obtained in C to be passed into the print buffer. The count and the result alternate between BC and B'C'. Look back once through the exchange registers. The start - 1st byte of mem-3. Result to A for storing. Count of digits so far in number to C. Address the first empty byte. Store the next digit.
2EDF
PF-FRN-LP
2EEC 2EEF
PF-FR-DGT PF-FR-EXX
171
INC JR
(mem-5-1st) 2EDF,PF-FRN-LP
Step up the count of digits. Loop back until there are 8 digits.
vii. The digits stored in the print buffer are rounded to a maximum of 8 digits for printing. 2F0C PF-ROUND PUSH LD LD LD ADD LD POP DEC LD ADC LD AND JR CP CCF JR DJNZ LD INC INC 2F2D PF-COUNT LD RST DEFB DEFB EXX POP EXX AF HL,+5CA1 C,(mem-5-1st) B,+00 HL,BC B,C AF HL A,(HL) A,+00 (HL),A A Z,2F25,PF-R-BACK +0A NC,2F2D,PF-COUNT 2F18,PF-RND-LP (HL),+01 B (mem-5-2nd) (mem-5-1st),B 0028,FP-CALC +02,delete +38,end-calc HL Save the carry flag for the rounding. Base address of number: mem-3, byte 1. Offset (number of digits in number) to BC. Address the last byte of the number. Copy C to B as the counter. Restore the carry flag. This is the last byte of the number. Get the byte into A. Add in the carry i.e. round up. Store the rounded byte in the buffer. If the byte is 0 or 10, B will be decremented and the final zero (or the 10) will not be counted for printing. Reset the carry for a valid digit. Jump if carry reset. Jump back for more rounding or more final zeros. There is overflow to the left; an extra 1 is needed here. It is also an extra digit before the decimal. B now sets the count of the digits to be printed (final zeros will not be printed). f is to be deleted. The calculator offset saved on the stack is restored to H'L'.
2F18
PF-RND-LP
2F25
PF-R-BACK
viii. The number can now be printed. First C will be set to hold the number of digits to be printed, not counting final zeros, while B will hold the number of digits required before the decimal. LD LD LD CP JR CP JR AND CALL BC,(mem-5-1st) HL,+5CA1 A,B +09 C,2F46,PF-NOT-E +FC C,2F6C,PF-E-FRMT A Z,15EF,OUT-CODE The counters are set. The start of the digits. If more than 9, or fewer than minus 4, digits are required before the decimal, then E-format will be needed. Fewer than 4 means more than 4 leading zeros after the decimal. Are there no digits before the decimal? If so, print an initial zero.
2F46
PF-NOT-E
The next entry point is also used to print the digits needed for E-format printing. 2F4A PF-E-SBRN XOR A Start by setting A to zero.
172
SUB JR LD JR 2F52 PF-OUT-LP LD AND JR LD INC DEC CALL DJNZ LD AND RET INC LD RST LD DJNZ LD 2F6C PF-E-FRMT JR LD DEC LD CALL LD RST LD LD AND JP NEG LD LD JR LD RST LD JP
B M,2F52,PF-OUT-LP B,A 2F5E,PF-DC-OUT A,C A Z,2F59,PF-OUT-DT A,(HL) HL C 15EF,OUT-CODE 2F52,PF-OUT-LP A,C A Z B A,+2E 0010,PRINT-A-1 A,+30 2F64,PF-DEC-0S B,C 2F52,PF-OUT-LP D,B D B,+01 2F4A,PF-E-SBRN A,+45 0010,PRINT-A-1 C,D A,C A P,2F83,PF-E-POS C,A A,+2D 2F85,PF-E-SIGN A,+2B 0010,PRINT-A-1 B,+00 1A1B,OUT-NUM
2F59 2F5E
PF-OUT-DT PF-DC-OUT
2F64
PF-DEC-0S
2F83 2F85
PF-E-POS PF-E-SIGN
Subtract B: minus will mean there are digits before the decimal; jump forward to print them. A is now required as a counter. Jump forward to print the decimal part. Copy the number of digits to be printed to A. If A is 0, there are still final zeros to print (B is non-zero), so jump. Get a digit from the print buffer. Point to the next digit. Decrease the count by one. Print the appropriate digit. Loop back until B is zero. It is time to print the decimal, unless C is now zero; in that case, return - finished. Add 1 to B - include the decimal. Put the code for '.' into A. Print the '.'. Enter the character code for '0'. Loop back to print all needed zeros. Set the count for all remaining digits. Jump back to print them. The count of digits is copied to D. It is decremented to give the exponent. One digit is required before the decimal in E-format. All the part of the number before the 'E' is now printed. Enter the character code for 'E'. Print the 'E'. Exponent to C now for printing. And to A for testing. Its sign is tested. Jump if it is positive. Otherwise, negate it in A. Then copy it back to C for printing. Enter the character code for '-'. Jump to print the sign. Enter the character code for '+'. Now print the sign: '+' or '-'. BC holds the exponent for printing. Jump back to print it and finish.
173
2F8B
CA=10*A+C
DE L,A H,+00 E,L D,H HL,HL HL,HL HL,DE HL,HL E,C HL,DE C,H A,L DE
Save whichever DE pair is in use. Copy the multiplicand from A to HL. Copy it to DE too. Double HL. Double it again. Add in DE to give HL=5*A. Double again: now HL=10*A. Copy C to DE (D is zero) for addition. Now HL=10*A+C. H is copied to C. L is copied to A, completing the task. The DE register pair is restored. Finished.
2FAF
NEG-BYTE
174
2FBA
HL AF - M1, M2, M3, M4 & M5. - N1, N2, N3, N4 & N5. C,(HL) HL B,(HL) (HL),A HL A,C C,(HL) BC HL C,(HL) HL B,(HL) DE,HL D,A E,(HL) DE HL D,(HL) HL E,(HL) DE DE HL BC HL D,(HL) HL E,(HL) AF HL
HL is preserved. AF is preserved.
Call the five bytes of the first number and the second number LD INC LD LD INC LD LD PUSH INC LD INC LD EX LD LD PUSH INC LD INC LD PUSH EXX POP POP POP EXX INC LD INC LD POP POP RET Summary:
M1 to C. Next. M2 to B. Copy the sign of the result to (HL). Next. M1 to A. M3 to C. Save M2 & M3 on the machine stack. Next. M4 to C. Next. M5 to B. HL now points to N1. M1 to D. N1 to E. Save M1 & N1 on the machine stack. Next. N2 to D. Next. N3 to E. Save N2 &N3 on the machine stack. Get the exchange registers. N2 to D' & N3 to E'. M1 to H' & N1 to L'. M2 to B' & M3 to C'. Get the original set of registers. Next. N4 to D. Next. N5 to E. Restore the original AF. Restore the original HL. Finished.
M1 - M5 are in H', B', C', C, B. N1 - N5 are in: L', D', E', D, E. HL points to the first byte of the first number.
2FE5
ONE-SHIFT
175
RR RR EXX RR RR DJNZ POP RET CALL RET 2FF9 2FFB ADDEND-0 ZEROS-4/5 EXX XOR LD LD LD EXX LD
D E D E 2FE5,ONE-SHIFT BC NC 3004,ADD-BACK NZ
RET
Rotate right with carry D', E', D & E. Thereby shifting the whole five bytes of the number to the right as many times as B counts. Loop back until B reaches zero. Restore the original BC. Done if no carry to retrieve. Retrieve carry. Return unless the carry rippled right back. (In this case there is nothing to add). Fetch L', D' & E'. Clear the A register. Set the addend to zero in D',E', D & E, together with its marker byte (sign indicator) L', which was Hex.00 for a positive number and Hex.FF for a negative number. ZEROS-4/5 produces only 4 zero bytes when called for near underflow at 3160. Finished.
300D
ALL-ADDED
176
HL points to the second number from the top, the augend/multiplier/dividend. DE points to the number at the top of the calculator stack, the addend/multiplicand/divisor. Afterwards HL points to the resultant 'last value' whose address can also be considered to be STKEND - 5. But the addition subroutine first tests whether the 2 numbers to be added are 'small integers'. If they are, it adds them quite simply in HL and BC, and puts the result directly on the stack. No twos complementing is needed before or after the addition, since such numbers are held on the stack in twos complement form, ready for addition. 3014 addition LD OR JR PUSH INC PUSH INC LD INC LD INC INC INC LD INC LD INC LD POP EX ADD EX ADC RRCA ADC JR SBC 3032 LD INC LD INC LD DEC DEC DEC POP RET A,(DE) (HL) NZ,303E,FULL-ADDN DE HL HL HL E,(HL) HL D,(HL) HL HL HL A,(HL) HL C,(HL) HL B,(HL) HL DE,HL HL,BC DE,HL A,(HL) A,+00 NZ,303C,ADDN-OFLW A,A (HL),A HL (HL),E HL (HL),D HL HL HL DE Test whether the first bytes of both numbers are zero. If not, jump for full addition. Save the pointer to the second number. Point to the second byte of the first number and save that pointer too. Point to the less significant byte. Fetch it in E. Point to the more significant byte. Fetch it in D. Move on to the second byte of the second number. Fetch it in A (this is the sign byte). Point to the less significant byte. Fetch it in C. Point to the more significant byte. Fetch is in B. Fetch the pointer to the sign byte of the first number; put it in DE, and the number in HL. Perform the addition: result in HL. Result to DE, sign byte to HL. Add the sign bytes and the carry into A; this will detect any overflow. A non-zero A now indicates overflow. Jump to reset the pointers and to do full addition. Define the correct sign byte for the result. Store it on the stack. Point to the next location. Store the low byte of the result. Point to the next location. Store the high byte of the result. Move the pointer back to address the first byte of the result. Restore STKEND to DE. Finished.
Note that the number -65536 decimal can arise here in the form 00 FF 00 00 00 as the result of the addition of two smaller negative integers, e.g. -65000 and -536. It is simply stacked in this form. This is a mistake. The Spectrum system cannot handle this number.
177
Most functions treat it as zero, and it is printed as -1E-38, obtained by treating is as 'minus zero' in an illegitimate format. One possible remedy would be to test for this number at about byte 3032 and, if it is present, to make the second byte 80 hex and the first byte 91 hex, so producing the full five byte floating-point form of the number, i.e. 91 80 00 00 00, which causes no problems. See also the remarks in 'truncate' below, before byte 3225, and the Appendix. 303C ADDN-OFLW DEC POP 303E FULL-ADDN CALL HL DE 3293,RE-ST-TWO Restore the pointer to the first number. Restore the pointer to the second number. Re-stack both numbers in full five byte floating-point form.
The full ADDITION subroutine first calls PREP-ADD for each number, then gets the two numbers from the calculator stack and puts the one with the smaller exponent into the addend position. It then calls SHIFT-FP to shift the addend up to 32 decimal places right to line it up for addition. The actual addition is done in a few bytes, a single shift is made for carry (overflow to the left) if needed, the result is twos complemented if negative, and any arithmetic overflow is reported; otherwise the subroutine jumps to TEST-NORM to normalise the result and return it to the stack with the correct sign bit inserted into the second byte. EXX PUSH EXX PUSH PUSH CALL LD EX CALL LD CP JR LD LD EX PUSH SUB CALL CALL POP POP LD PUSH LD LD ADD EXX EX ADC EX LD ADC LD RRA XOR EXX HL DE HL 2F9B,PREP-ADD B,A DE,HL 2F9B,PREP-ADD C,A B NC,3055,SHIFT-LEN A,B B,C DE,HL AF B 2FBA,FETCH-TWO 2FDD,SHIFT-FP AF HL (HL),A HL L,B H,C HL,DE DE,HL HL,BC DE,HL A,H A,L L,A L Exchange the registers. Save the next literal address. Exchange the registers. Save pointer to the addend. Save pointer to the augend. Prepare the augend. Save its exponent in B. Exchange its pointers. Prepare the addend. Save its exponent in C. If the first exponent is smaller, keep the first number in the addend position; otherwise change the exponents and the pointers back again. Save the larger exponent in A. The difference between the exponents is the length of the shift right. Get the two numbers from the stack. Shift the addend right. Restore the larger exponent. HL is to point to the result. Store the exponent of the result. Save the pointer again. M4 to H & M5 to L, (see FETCH-TWO). Add the two right bytes. N2 to H' & N3 to L', (see FETCH-TWO). Add left bytes with carry. Result back in D'E'. Add H', L' and the carry; the resulting mechanisms will ensure that a single shift right is called if the sum of 2 positive numbers has overflowed left, or the sum of 2 negative numbers has not overflowed left.
3055
SHIFT-LEN
178
EX POP RRA JR LD CALL INC JR 307C TEST-NEG EXX LD AND EXX INC LD DEC JR LD NEG CCF LD LD CPL ADC LD EXX LD CPL ADC LD LD CPL ADC JR RRA EXX INC
DE,HL HL NC,307C,TEST-NEG A,+01 2FDD,SHIFT-FP (HL) Z,309F,ADD-REP-6 A,L +80 HL (HL),A HL Z,30A5,GO-NC-MLT A,E E,A A,D A,+00 D,A A,E A,+00 E,A A,D A,+00 NC,30A3,END-COMPL (HL)
The result is now in DED'E. Get the pointer to the exponent. The test for shift (H', L' were Hex. 00 for positive numbers and Hex.FF for negative numbers). A counts a single shift right. The shift is called. Add 1 to the exponent; this may lead to arithmetic overflow. Test for negative result: get sign bit of L' into A (this now correctly indicates the sign of the result). Store it in the second byte position of the result on the calculator stack. If it is zero, then do not twos complement the result. Get the first byte. Negate it. Complement the carry for continued negation, and store byte. Get the next byte. Ones complement it. Add in the carry for negation. Store the byte. Proceed to get next byte into the A register. Ones complement it. Add in the carry for negation. Store the byte. Get the last byte. Ones complement it. Add in the carry for negation. Done if no carry. Else, get .5 into mantissa and add 1 to the exponent; this will be needed when two negative numbers add to give an exact power of 2, and it may lead to arithmetic overflow. Give the error if required. Store the last byte. Clear the carry flag. Exit via TEST-NORM.
ADD-REP-6
30B1
HL-LOOP
179
30BC 30BE
HL-AGAIN HL-END
Jump if overflow. Rotate bit 7 of C into the carry. Rotate the carry bit into bit 0 and bit 7 into the carry flag. Jump if the carry flag is reset. Otherwise add DE in once. Jump if overflow. Until 16 passes have been made. Restore BC. Finished.
180
00 FF 00 00 00 is replaced by zero; that they should not be needed if this number were excluded from the system (see after 303B) above). 30EA MULT-RSLT CALL 2D8E,INT-STORE Now store the result on the stack. POP DE Restore STKEND to DE. RET Finished. 30EF MULT-OFLW POP DE Restore the pointer to the second number. 30F0 MULT-LONG CALL 3293,RE-ST-TWO Re-stack both numbers in full five byte floating-point form. The full MULTIPLICATION subroutine prepares the first number for multiplication by calling PREP-M/D, returning if it is zero; otherwise the second number is prepared by again calling PREP-M/D, and if it is zero the subroutine goes to set the result to zero. Next it fetches the two numbers from the calculator stack and multiplies their mantissas in the usual way, rotating the first number (treated as the multiplier) right and adding in the second number (the multiplicand) to the result whenever the multiplier bit is set. The exponents are then added together and checks are made for overflow and for underflow (giving the result zero). Finally, the result is normalised and returned to the calculator stack with the correct sign bit in the second byte. XOR A A is set to Hex.00 so that the sign of the first number will go into A. CALL 30C0,PREP-M/D Prepare the first number, and RET C return if zero. (Result already zero.) EXX Exchange the registers. PUSH HL Save the next literal address. EXX Exchange the registers. PUSH DE Save the pointer to the multiplicand. EX DE,HL Exchange the pointers. CALL 30C0,PREP-M/D Prepare the 2nd number. EX DE,HL Exchange the pointers again. JR C,315D,ZERO-RSLT Jump forward if 2nd number is zero. PUSH HL Save the pointer to the result. CALL 2FBA,FETCH-TWO Get the two numbers from the stack. LD A,B M5 to A (see FETCH-TWO). AND A Prepare for a subtraction. SBC HL,HL Initialise HL to zero for the result. EXX Exchange the registers. PUSH HL Save M1 & N1 (see FETCH-TWO). SBC HL,HL Also initialise H'L' for the result. EXX Exchange the registers. LD B,+21 B counts 33 decimal, Hex.21, shifts. JR 3125,STRT-MLT Jump forward into the loop. Now enter the multiplier loop. 3114 MLT-LOOP JR ADD EXX ADC NC,311B,NO-ADD HL,DE HL,DE Jump forward to NO-ADD if no carry, i.e. the multiplier bit was reset. Else, add the multiplicand in D'E'DE (see FETCH-TWO) into the result being built up on
OR JR LD
E NZ,30EA,MULT-RSLT C,A
181
311B
NO-ADD
H L H L
3125
STRT-MLT
H'L'HL. Whether multiplicand was added or not, shift result right in H'L'HL, i.e. the shift is done by rotating each byte with carry, so that any bit that drops into the carry is picked up by the next byte, and the shift continued into B'C'CA. Shift right the multiplier in B'C'CA (see FETCH-TWO & above). A final bit dropping into the carry will trigger another add of the multiplicand to the result. Loop 33 times to get all the bits. Move the result from: H'L'HL to D'E'DE.
Now add the exponents together. POP POP LD ADD JR AND 313B MAKE-EXPT DEC CCF BC HL A,B A,C NZ,313B,MAKE-EXPT A A Restore the exponents - M1 & N1. Restore the pointer to the exponent byte. Get the sum of the two exponent bytes in A, and the correct carry. If the sum equals zero then clear the carry; else leave it unchanged. Prepare to increase the exponent by Hex.80.
The rest of the subroutine is common to both MULTIPLICATION and DIVISION. 313D DIVN-EXPT RLA CCF RRA JP JR 3146 AND OFLW1-CLR INC JR JR EXX BIT EXX JR OFLW2-CLR LD EXX LD EXX P,3146,OFLW1-CLR NC,31AD,REPORT-6 A A NZ,3151,OFLW2-CLR C,3151,OFLW2-CLR 7,D NZ,31AD,REPORT-6 (HL),A A,B These few bytes very cleverly make the correct exponent byte. Rotating left then right gets the exponent byte (true exponent plus Hex.80) into A. If the sign flag is reset, no report of arithmetic overflow needed. Report the overflow if carry reset. Clear the carry now. The exponent byte is now complete; but if A is zero a further check for overflow is needed. If there is no carry set and the result is already in normal form (bit 7 of D' set) then there is overflow to report; but if bit 7 of D' is reset, the result in just in range, i.e. just under 2**127. Store the exponent byte, at last. Pass the fifth result byte to A for the normalisation sequence, i.e. the overflow from L into B'.
3151
182
The remainder of the subroutine deals with normalisation and is common to all the arithmetic routines. 3155 3159 315D 315E TEST-NORM JR LD AND NEAR-ZERO LD JR ZERO-RSLT XOR SKIP-ZERO EXX AND CALL RLCA LD JR INC LD DEC JR The actual normalisation operation. 316C 316E NORMALISE LD SHIFT-ONE EXX BIT EXX JR RLCA RL RL EXX RL RL EXX DEC JR DJNZ JR B,+20 7,D NZ,3186,NORML-NOW E D E D (HL) Z,3159,NEAR-ZERO 316E,SHIFT-ONE 315D,ZERO-RSLT Normalise the result by up to 32 decimal, Hex.20, shifts left of D'E'DE (with A adjoined) until bit 7 of D' is set. A holds zero after addition so no precision is gained or lost; A holds the fifth byte from B' after multiplication or division; but as only about 32 bits can be correct, no precision is lost. Note that A is rotated circularly, with branch at carry .... eventually a random process. The exponent is decremented on each shift. If the exponent becomes zero, then number from 2**-129 are rounded up to 2**-128. Loop back, up to 32 times. If bit 7 never became 1 then the whole result is to be zero. NC,316C,NORMALISE A,(HL) A A,+80 Z,315E,SKIP-ZERO A D 2FFB,ZEROS-4/5 (HL),A C,3195,OFLOW-CLR HL (HL),A HL 3195,OFLOW-CLR If no carry then normalise now. Else, deal with underflow (zero result) or near underflow (result 2**-128): return exponent to A, test if A is zero (case 2**-128) and if so produce 2**-128 if number is normal; otherwise produce zero. The exponent must then be set to zero (for zero) or 1 (for 2**-128). Restore the exponent byte. Jump if case 2**-128. Otherwise, put zero into second byte of result on the calculator stack. Jump forward to transfer the result.
Finish the normalisation by considering the 'carry'. 3186 NORML-NOW RLA JR CALL JR EXX LD EXX INC JR NC,3195,OFLW-CLR 3004,ADD-BACK NZ,3195,OFLW-CLR D,+80 (HL) Z,31AD,REPORT-6 After normalisation add back any final carry that went into A. Jump forward if the carry does not ripple right back. If it should ripple right back then set mantissa to 0.5 and increment the exponent. This action may lead to arithmetic overflow (final case).
The final part of the subroutine involves passing the result to the bytes reserved for it on the calculator stack and resetting the pointers. 3195 OFLOW-CLR PUSH INC EXX PUSH EXX HL HL DE Save the result pointer. Point to the sign byte in the result. The result is moved from its present registers, D'E'DE, to BCDE; and then to ACDE.
183
POP LD RLA RL RRA LD INC LD INC LD INC LD POP POP EXX POP EXX RET Report 6 - Arithmetic overflow 31AD REPORT-6 RST DEFB
The sign bit is retrieved from its temporary store and transferred to its correct position of bit 7 of the first byte of the mantissa. The first byte is stored. Next. The second byte is stored. Next. The third byte is stored. Next. The fourth byte is stored. Restore the pointer to the result. Restore the pointer to second number. Exchange the register. Restore the next literal address. Exchange the registers. Finished.
0008,ERROR-1 +05
184
XOR LD
A B,+DF
JR
31E2,DIV-START
Clear A and reset the carry flag. B will count upwards from -33 to -1, twos complement, Hex. DF to FF, looping on minus and will jump again on zero for extra precision. Jump forward into the division loop for the first trial subtraction.
Now enter the division loop. 31D2 DIV-LOOP RLA RL EXX RL RL EXX ADD EXX ADC EXX JR 31E2 DIV-START SBC EXX SBC EXX JR Shift the result left into B'C'CA, shifting out the bits already there, picking up 1 from the C carry whenever it is set, and B rotating left each byte with carry to achieve the 32 bit shift. HL,HL Move what remains of the dividend left in H'L'HL before HL,HL the next trial subtraction; if a bit drops into the carry, force no restore and a bit for the quotient, thus retrieving the lost C,31F2,SUBN-ONLY bit and allowing a full 32-bit divisor. HL,DE Trial subtract divisor in D'E'DE from rest of dividend in H'L'HL; HL,DE there is no initial carry (see previous step). NC,31F9,NO-RSTORE Jump forward if there is no carry. HL,DE Otherwise restore, i.e. add back the divisor. Then clear the carry HL,DE so that there will be no bit for the quotient (the divisor 'did A not go'). 31FA,COUNT-ONE Jump forward to the counter. A Just subtract with no restore HL,DE and go on to set the carry flag because the lost bit of the diviHL,DE dend is to be retrieved and used for the quotient. One for the quotient in B'C'CA. B Step the loop count up by one. M,31D2,DIV-LOOP Loop 32 times for all bits. AF Save any 33rd bit for extra precision (the present carry). Z,31E2,DIV-START Trial subtract yet again for any 34th bit; the PUSH AF above saves this bit too. C
31DB
DIV-34TH
31F2
31F9 31FA
ADD EXX ADC EXX AND JR SUBN-ONLY AND SBC EXX SBC EXX NO-RSTORE SCF COUNT-ONE INC JP PUSH JR
Note: This jump is made to the wrong place. No 34th bit will ever be obtained without first shifting the dividend. Hence important results like 1/10 and 1/1000 are not rounded up as they should be. Rounding up never occurs when it depends on the 34th bit. The jump should have been to 31DB DIV-34TH above: i.e. byte 3200 hex in the ROM should read DA hex (128 decimal) instead of E1 hex (225 decimal). LD LD EXX LD LD POP E,A D,C E,C D,B AF Now move the four bytes that form the mantissa bytes of the result from B'C'CA to D'E'DE. Then put the 34th and 33rd bits
185
B AF B BC HL A,B C 313D,DIVN-EXPT
into B' to be picked up on normalisation. Restore the exponent bytes, M1 & N1. Restore the pointer to the result. Get the difference between the two exponent bytes into A and set the carry flag if required. Exit via DIVN-EXPT.
The next 26 bytes seem designed to test whether x is in fact -65536 decimal, i.e. 91 80 00 00 00, and if it is, to set it to 00 FF 00 00 00. This is a mistake. As already stated at byte 303B above, the Spectrum system cannot handle this number. The result here is simply to make INT (-65536) return the value -1. This is a pity, since the number would have been perfectly all right if left alone. The remedy would seem to be simply to omit the 28 bytes from 3223 above to 323E inclusive from the program. 3225 INC INC INC LD AND DEC OR DEC JR LD XOR 3233 T-FIRST DEC JR LD INC HL HL HL A,+80 (HL) HL (HL) HL NZ,3233,T-FIRST A,+80 (HL) HL NZ,326C,T-EXPNENT (HL),A HL HL is pointed at the fourth byte of x, where the 17 bits of the integer part of x end after the first bit. The first bit is obtained in A. using 80 hex as a mask. That bit and the previous 8 bits are tested together for zero. HL is pointed at the second byte of x. If already non-zero, the test can end. Otherwise, the test for -65536 is now completed: 91 80 00 00 00 will leave the zero flag set now. HL is pointed at the first byte of x. If zero reset, the jump is made. The first byte is set to zero. HL points to the second byte.
186
LD DEC LD JR
The second byte is set to FF. HL again points to the first byte. The last 24 bits are to be zero. The jump to NIL-BYTES completes the number 00 FF 00 00 00.
If the exponent byte of x is between 81 and 90 hex (129 and 144 decimal) inclusive, I(x) is a 'small integer', and will be compressed into one or two bytes. But first a test is made to see whether x is, after all, large. 323F T-SMALL JR PUSH CPL ADD INC LD INC LD DEC DEC LD BIT JR DEC SET LD SUB ADD JR LD LD SUB JR LD SRL RR DJNZ CALL POP RET NC,326D,X-LARGE DE A,+91 HL D,(HL) HL E,(HL) HL HL C,+00 7,D Z,3252,T-NUMERIC C 7,D B,+08 B A,B C,325E,T-TEST E,D D,+00 B Z,3267,T-STORE B,A D E 3261,T-SHIFT 2D8E,INT-STORE DE Jump with exponent byte 92 or more (it would be better to jump with 91 too). Save STKEND in DE. Range 129 <= A <= 144 becomes 126 >= A >= 111. Range is now 15 dec >= A >= 0. Point HL at second byte. Second byte to D. Point HL at third byte. Third byte to E. Point HL at first byte again. Assume a positive number. Now test for negative (bit 7 set). Jump if positive after all. Change the sign. Insert true numeric bit, 1, in D. Now test whether A >= 8 (one byte only) or two bytes needed. Leave A unchanged. Jump if two bytes needed. Put the one byte into E. And set D to zero. Now 1 <= A <= 7 to count the shifts needed. Jump if no shift needed. B will count the shifts. Shift D and E right B times to produce the correct number. Loop until B is zero. Store the result on the stack. Restore STKEND to DE. Finished.
3252
T-NUMERIC
Large values of x remains to be considered. 326C 326D T-EXPNENT X-LARGE LD SUB RET A,(HL) +A0 P Get the exponent byte of x into A. Subtract 160 decimal, A0 hex, from e. 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 mantissa). Else, negate the remainder; this gives the number of bits to become zero (the number of bits after the 'binary point').
NEG
187
Now the bits of the mantissa can be cleared. 3272 NIL-BYTES PUSH EX DEC LD SRL SRL SRL JR 327E 3283 BYTE-ZERO BITS-ZERO LD DEC DJNZ AND JR LD LD SLA DJNZ AND LD EX POP RET DE DE,HL HL B,A B B B Z,3283,BITS-ZERO (HL),+00 HL 327E,BYTE-ZERO +07 Z,3290,IX-END B,A A,+FF A 328A,LESS-MASK (HL) (HL),A DE,HL DE Save the current value of DE (STKEND). Make HL point one past the fifth byte. HL now points to the fifth byte of x. Get the number of bits to be set to zero in B and divide it by B to give the number of whole bytes implied. Jump forward if the result is zero. Else, set the bytes to zero; B counts them. Get A (mod 8); this is the number of bits still to be set to zero. Jump to the end if nothing more to do. B will count the bits now. Prepare the mask. With each loop a zero enters the mask from the right and thereby a mask of the correct length is produced. The unwanted bits of (HL) are lost as the masking is performed. Return the pointer to HL. Return STKEND to DE. Finished.
328A
LESS-MASK
3290
IX-END
188
LD LD AND JR OR LD JR LD LD LD 32B1 32B2 RS-NRMLSE EX RSTK-LOOP DEC ADD JR RRC RR RR 32BD RS-STORE EX DEC LD DEC LD DEC LD POP RET
B,+91 A,D A NZ,32B1,RS-NRMLSE E B,D Z,32BD,RS-STORE D,E E,B B,+89 DE,HL B HL,HL NC,32B2,RSTK-LOOP C H L DE,HL HL (HL),E HL (HL),D HL (HL),B DE
Set B to 145 dec for the exponent i.e. for up to 16 bits in the integer. Test whether D is zero so that at most 8 bits would be needed. Jump if more than 8 bits needed. Now test E too. Save the zero in B (it will give zero exponent if E too is zero). Jump if E is indeed zero. Move E to D (D was zero, E not). Set E to zero now. Set B to 137 dec for the exponent - no more than 8 bits now. Pointer to DE, number to HL. Decrement the exponent on each shift. Shift the number right one position. Until the carry is set. Sign bit to carry flag now. Insert it in place as the number is shifted back one place normal now. Pointer to byte 4 back to HL. Point to the third location. Store the third byte. Point to the second location. Store the second byte. Point to the first location. Store the exponent byte. Restore the 'other' pointer to DE. Finished.
189
32C8
stk-one
one
00 00 01 00 00
32CC 32CE
stk-half stk-pi/2
a half a half of pi
80 00 00 00 00 81 49 0F DA A2
32D3
stk-ten
ten
00 00 0A 00 00
190
no-gr-eq nos-neql no-grtr no-less nos-eql addition str-&-no str-l-eql str-gr-eq strs-neql str-grtr str-less strs-eql strs-add val$ usr-$ read-in negate code val len sin cos
3B 35 3B 35 3B 35 3B 35 3B 35 14 30 2D 35 3B 35 3B 35 3B 35 3B 35 3B 35 3B 35 9C 35 DE 35 BC 34 45 36 6E 34 69 36 DE 45 74 36 B5 37 AA 37
peek in usr-no str$ chr$ not duplicate n-mod-m jump stk-data dec-jr-nz less-0 greater-0 end-calc get-argt truncate fp-calc-2 e-to-fp re-stack series-06 etc. stk-zero etc. st-mem-0 etc. get-mem-0 etc.
AC 34 A5 34 B3 34 1F 36 C9 35 01 35 C0 33 A0 36 86 36 C6 33 7A 36 06 35 F9 34 9B 36 83 37 14 32 A2 33 4F 2D 97 32 49 34 1B 34 2D 34 0F 34
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.
191
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. 335B CALCULATE CALL 35BF,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 onepast this floating-point number (STKEND). Either, transfer a single operation offset to BREG temporarily, or, when using the subroutine recursively pass the parameter to BREG to be used as a counter. The return address of the subroutine is store in H'L'. This saves the pointer to the first literal. Entering the CALCULATOR at GEN-ENT-2 is used whenever BREG is in use as a counter and is not to be disturbed. A loop is now entered to handle each literal in the list that follows the calling instruction; so first, always set to STKEND. Go to the alternate register set, and fetch the literal for this loop.
335E
GEN-ENT-1
LD LD
A,B (BREG),A
3362
GEN-ENT-2
EXX EX EXX
(SP),HL
3365
RE-ENTRY
LD
(STKEND),DE
EXX LD
A,(HL)
192
HL HL
AND JP LD AND RRCA RRCA RRCA RRCA ADD LD LD AND JR 3380 FIRST-3D CP JR EXX LD LD LD ADD EXX RLCA LD LD LD ADD LD INC LD LD EX PUSH EXX LD
Make H'L' point to the next literal. 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. Test the A register. Separate the simple literals from the multi-purpose literals. Jump with literals 00 - 3D. Save the literal in D. Continue only with bits 5 & 6. Four right shifts make them now bits 1 & 2. The offsets required are 3E-41. and L will now hold double the required offset. Now produce the parameter by taking bits 0,1,2,3 & 4 of the literal; keep the parameter in A. Jump forward to find the address of the required subroutine. Jump forward if performing a unary operation. All of the subroutines that perform binary operations require that HL points to the first operand and DE points to the second operand (the 'last value') as they appear on the calculator stack. As each entry in the table of addresses takes up two bytes the offset produced is doubled. The base address of the table. The address of the required table entry is formed in HL; and the required subroutine address is loaded into the DE register pair. The RE-ENTRY address of 3365 is put on the machine stack underneath the subroutine address. Return to the main set of registers. The current value of BREG is transferred to the B register thereby returning the single operation offset. (See COMPARISON at 353B) An indirect jump to the required subroutine.
A,+7C L,A A,D +1F 338E,ENT-TABLE +18 NC,338C,DOUBLE-A BC,+FFFB D,H E,L HL,BC L,A DE,+32D7 H,+00 HL,DE E,(HL) HL D,(HL) HL,+3365 (SP),HL DE
338C 338E
DOUBLE-A ENT-TABLE
BC,(STKEND-hi)
33A1
delete
RET
193
194
33C8
STK-CONST CALL EXX PUSH EXX EX PUSH LD AND RLCA RLCA LD INC
LD AND JR INC
195
33F1
STK-ZEROS
LD SUB INC INC LD LDIR POP EX EXX POP EXX LD XOR DEC RET LD INC JR
the next literal is fetched and used unreduced. The exponent, e, is formed by the addition of Hex.50 and passed to the calculator stack as the first of the five bytes of the result. The number of literals specified in C are taken from the source and entered into the bytes of the result. Restore BC. Return the result pointer to HL and the next literal pointer to its usual position in H' & L'. The number of zero bytes required at this stage is given by 5-C-1; and this number of zeros is added to the result to make up the required five bytes.
196
LD LD ADD RET
value. This result is wanted in the BC register pair. Produce the new base address. Finished.
197
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'.
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 DEFB +31,duplicate Z,Z DEFB +0F,addition 2*Z DEFB +C0,st-mem-0 2*Z mem-0 holds 2*Z DEFB +02,delete -
198
DEFB DEFB
+A0,stk-zero +C2,st-mem-2
0 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. 3453 G-LOOP DEFB DEFB DEFB DEFB DEFB +38,end-calc DEFB +31,duplicate +E0,get-mem-0 +04,multiply +E2,get-mem-2 +C1,st-mem-1 +03,subtract B(R),B(R) B(R),B(R),2*Z B(R),2*B(R)*Z B(R),2*B(R)*Z,B(R-1) mem-1 holds B(R-1) B(R),2*B(R)*Z-B(R-1)
DEFB
The next constant is placed on the calculator stack. CALL 33C6,STK-DATA B(R),2*B(R)*Z-B(R-1),A(R+1)
The Calculator is re-entered without disturbing BREG. CALL DEFB DEFB DEFB DEFB DEFB DEFB 3362,GEN-ENT-2 +0F,addition +01,exchange +C2,st-mem-2 +02,delete +35,dec-jr-nz +EE,to 3453,G-LOOP B(R),2*B(R)*Z-B(R-1)+A(R+1) 2*B(R)*Z-B(R-1)+A(R+1),B(R) mem-2 holds B(R) 2*B(R)*Z-B(R-1)+A(R!1) = B(R!1) B(R+1)
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). DEFB +E1,get-mem-1 B(N),B(N-2) DEFB +03,subtract B(N)-B(N-2) DEFB +38,end-calc RET Finished
199
LD 'ABS' enters here. 3474 NEG-TEST LD AND JR INC LD AND OR RLA CCF RRA LD DEC RET
B,+00
(HL),A HL
If the first byte is zero, the jump is made to deal with a 'small integer'. Point to the second byte. Get +FF for 'abs', +00 for 'negate'. Now +80 for 'abs', +00 for 'negate'. This sets bit 7 for 'abs', but changes nothing for 'negate'. Now bit 7 is changed, leading to bit 7 of byte 2 reset for 'abs', and simply changed for 'negate'. The new second byte is stored. HL points to the first byte again. Finished.
The 'integer case' does a similar operation with the sign byte. 3483 INT-CASE PUSH PUSH CALL POP LD OR CPL LD CALL POP RET DE HL 2D7F,INT-FETCH HL A,B C C,A 2D8E,INT-STORE DE Save STKEND in DE. Save pointer to the number in HL. Fetch the sign in C, the number in DE. Restore the pointer to the number in HL. Get +FF for 'abs', +00 for 'negate'. Now +FF for 'abs', no change for 'negate' Now +00 for 'abs', and a changed byte for 'negate': store it in C. Store result on the stack. Return STKEND to DE.
200
34A5
in
CALL IN JR
The 'last value', X, is compressed into BC. The signal is received. Jump to stack the result.
34B0
IN-PK-STK
LD JP
A,(BC) 2D28,STACK-A
HL,+2D2B HL BC
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).
201
34D3
A A,A A,A A,A +A8 NC,34E7,REPORT-A BC,(UDG) A,C C,A NC,34E4,USR-STACK B 2D2B,STACK-BC
Now make the range 0 to 20 decimal in each case. Multiply by 8 to get an offset for the address. Test the range of the offset. Give report A if out of range. Fetch the address of the first user-defined graphic in BC. Add C to the offset. Store the result back in C. Jump if there is no carry. Increment B to complete the address. Jump to stack the address.
34E4
USR-STACK JP
REPORT A - Invalid argument. 34E7 REPORT-A RST DEFB 0008,ERROR-1 +09 Call the error handling routine.
202
3501
NOT
CALL JR
34E9,TEST-ZERO 350B,FP-0/1
The carry flag will be set only if the 'last value' is zero; this gives the correct result. Jump forward.
203
3524
no-&-no
Point HL at Y, DE at X. Test whether Y is zero. Swap the pointers back. Return with X as the 'last value' if Y was non-zero. Reset the carry flag and jump back to set the 'last value' to zero.
3543
EX-OR-NOT
354E
NU-OR-STR
AF 300F,SUBTRACT
The numerical comparisons are now separated from the string comparisons by testing bit 2. The numerical operations now have the range 00-01 with carry set for 'equal' and 'not equal'. Save the offset. The numbers are subtracted for
204
3559
STRINGS
JR RRCA
358C,END-TESTS
3564
356B
PUSH CALL PUSH PUSH CALL POP BYTE-COMP LD OR EX LD JR OR SECND-LOW POP JR POP CCF JR POP JR OR JR LD SUB JR JR DEC INC INC EX DEC JR POP POP AND PUSH RST DEFB DEFB POP PUSH CALL POP PUSH CALL POP RRCA CALL RET
AF 2BF1,STK-FETCH DE BC 2BF1,STK-FETCH HL A,H L (SP),HL A,B NZ,3575,SEC-PLUS C BC Z,3572,BOTH-NULL AF 3588,STR-TEST AF 3588,STR-TEST C Z,3585,FRST-LESS A,(DE) (HL) C,3585,FRST-LESS NZ,356B,SECND-LOW BC DE HL (SP),HL HL 3564,BYTE-COMP BC AF A AF 0028,FP-CALC +A0,stk-zero +38,end-calc AF AF C,3501,NOT AF AF NC,34F9,GREATER-0 AF NC,3501,NOT
the final tests. The string comparisons now have the range 02-03 with carry set for 'equal' and 'not equal'. Save the offset. The lengths and starting addresses of the strings are fetched from the calculator stack. The length of the second string.
Jump unless the second string is null. Here the second string is either null or less than the first. The carry is complemented to give the correct test results. Here the carry is used as it stands. The first string is now null, the second not. Neither string is null, so their next bytes are compared. The first byte is less. The second byte is less. The bytes are equal; so the lengths are decremented and a jump is made to BYTE-COMP to compare the next bytes of the reduced strings.
3572 3575
BOTH-NULL SEC-PLUS
3585
FRST-LESS
3588
STR-TEST
The carry is cleared here for the correct test results. For the string tests, a zero is put on to the calculator stack. These three tests, called as needed, give the correct results for all twelve comparisons. The initial carry is set for 'not equal' and 'equal', and the final carry is set for 'greater than', 'less than' and 'equal'. Finished.
358C
END-TESTS
205
359C
strs-add
CALL PUSH PUSH CALL POP PUSH PUSH PUSH ADD LD LD RST CALL
2BF1,STK-FETCH DE BC 2BF1,STK-FETCH HL HL DE BC HL,BC B,H C,L 0030,BC-SPACES 2AB2,STK-STORE BC HL A,B C Z,35B7,OTHER-STR BC HL A,B C Z,35BF,STK-PNTRS
The parameters of the second string are fetched and saved. The parameters of the first string are fetched. The lengths are now in HL and BC. The parameters of the first string are saved. The total length of the two strings is calculated and passed to BC. Sufficient room is made available. The parameters of the new string are passed to the calculator stack. The parameters of the first string are retrieved and the string copied to the work space as long as it is not a null string. Exactly the same procedure is followed for the second string thereby giving 'A$+B$'.
35B7
206
0008,ERROR-1 +0A
207
CALL POP LD JR
The string is treated as a 'next expression' and a 'last value' produced. The original value of CH-ADD is restored. The subroutine exits via STKPNTRS which resets the pointers.
Note: See PRINT-FP for an explanation of the 'PRINT "A"+STR$ 0.1' error.
208
365F
R-I-STORE
The signal is now accepted, like a 'key-value'. The default length of the resulting string is zero. Jump if there was no signal. Set the length to 1 now. Make a space in the work space. Put the string into it. Pass the parameters of the string to the calculator stack. Restore CURCHL and the appropriate flags. Exit, setting the pointers.
209
3686 3687
JUMP JUMP-2
Go to the next alternate register set. The next literal (jump length) is put in the E' register. The number 00 hex or FF hex is formed in A according as E' is positive or negative, and is then copied to D'. The registers H' & L' now hold the next literal pointer. Finished.
HL
(SP).HL
RET
210
N, INT (N/M),M N, M, INT (N/M) N, M, INT (N/M) mem-0 holds INT (N/M) N, M*INT (N/M) n-M*INT (N/M) n-M*INT (N/M), INT (N/M) Finished.
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. DEFB DEFB RET +3A,truncate +38,end-calc I (X) Finished.
when X is a negative integer I (X) is returned, otherwise I (X)-1 is returned. 36B7 X-NEG DEFB +31,duplicate X, X DEFB +3A,truncate X, I (X) DEFB +C0,st-mem-0 X, I (X) mem-0 holds I (X) DEFB +03,subtract X-I (X) DEFB +E0,get-mem-0 X-I (X), I (X) DEFB +01,exchange I (X), X-I (X) DEFB +30,not I (X), (1/0) DEFB +00,jump-true I (X) DEFB +03,to 36C2,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. DEFB DEFB +A1,stk-one +03,subtract I (X), 1 I (X)-1
In either case the subroutine finishes with; 36C2 EXIT RET DEFB +38,end-calc I (X) or I (X)-1
211
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. 36C4 EXP Perform step i. RST DEFB DEFB DEFB DEFB DEFB Perform step ii. DEFB DEFB DEFB Perform step iii. DEFB Perform step iv. DEFB DEFB DEFB DEFB +31,duplicate +0F,addition +A1,stk-one +03,subtract W, W 2*W 2*W, 1 2*W-1 = Z +03,subtract Y-N = W +31,duplicate +27,int,1C46 +C3,st-mem-3 Y, Y Y, INT Y = N Y, N mem-3 holds N 0028,FP-CALC +3D,re-stack +34,stk-data +F1,exponent+81 +38,+AA,+3B,+29 +04,multiply X X (in full floating-point form) X, 1/LN 2 X/LN 2 = Y
Perform step v, passing to the SERIES GENERATOR the parameter '8' and the eight constants required. 1. 2. 3. 4. 5. 6. 7. 8. DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB +88,series-08 +13,exponent+63 +36,(+00,+00,+00) +58,exponent+68 +65,+66,(+00,+00) +9D,exponent+6D +78,+65,+40,(+00) +A2,exponent+72 +60,+32,+C9,(+00) +E7,exponent+77 +21,+F7,+AF,+24 +EB,exponent+7B +2F,+B0,+B0,+14 +EE,exponent +7E +7E,+BB,+94,+58 +F1,exponent+81 +3A,+7E,+F8,+CF Z
At the end of the last loop the 'last value' is 2**W. Perform step vi. DEFB DEFB CALL JR JR ADD JR +E3,get-mem-3 +38,end-calc 2DD5,FP-TO-A NZ,3705,N-NEGTV C,3703,REPORT-6 A,(HL) NC,370C,RESULT-OK 2**W, N The absolute value of N mod 256 decimal, is put into the A register. Jump forward if N was negative. Error if ABS N greater than 255 dec. Now add ABS N to the exponent. Jump unless e greater than 255 dec.
212
Report 6 - Number too big 3703 3705 REPORT-6 N-NEGTV RST DEFB JR SUB JR NEG LD RET RST DEFB DEFB DEFB RET 0008,ERROR-1 +05 C,370E,RSLT-ZERO (HL) NC,370E,RSLT-ZERO (HL),A 0028,FP-CALC +02,delete +A0,stk-zero +38,end-calc Call the error handling routine. The result is to be zero if N is less than -255 decimal. Subtract ABS N from the exponent as N was negative. Zero result if e less than zero. Minus e is changed to e. The exponent, e, is entered. Finished: last value is EXP X Use the calculator to make the 'last value' zero. Finished, with EXP X = 0.
370C 370E
RESULT-OK RSLT-ZERO
Perform step i. DEFB DEFB DEFB DEFB DEFB DEFB Report A - Invalid argument 371A REPORT-A RST DEFB 0008,ERROR-1 +09 Call the error handling routine. +3D,re-stack +31,duplicate +37,greater-0 +00,jump-true +04,to 371C, VALID +38,end-calc X (in full floating-point form) X, X X, (1/0) X X X
Perform step ii. 371C VALID DEFB DEFB DEFB LD LD CALL RST DEFB DEFB +A0,stk-zero +02,delete +38,end-calc A,(HL) (HL),+80 2D28,STACK-A 0028,FP-CALC +34,stk-data +38,exponent+88 X,0 The deleted 1 is X overwritten with zero. X The exponent, e, goes into A. X is reduced to X'. The stack holds: X', e. X', e X', e, 128 (decimal)
213
DEFB DEFB Perform step iii. DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB INC RST DEFB DEFB DEFB DEFB DEFB Perform step iv. DEFB DEFB DEFB DEFB DEFB Perform step v. DEFB DEFB DEFB DEFB DEFB DEFB DEFB
+00,(+00,+00,+00) +03,subtract
X', e'
373D
GRE.8
+01,exchange +31,duplicate +34,stk-data +F0,exponent+80 +4C,+CC,+CC,+CD +03,subtract +37,greater-0 +00,jump-true +08,to 373D, GRE.8 +01,exchange +A1,stk-one +03,subtract +01,exchange +38,end-calc (HL) 0028,FP-CALC +01,exchange +34,stk-data +F0,exponent+80 +31,+72,+17,+F8 +04,multiply
e', X' e', X', X' e', X', X',0.8 (decimal) e', X', X'-0.8 e', X', (1/0) e', X' e', X' X', e' X', e', 1 X', e'-1 e'-1, X' e'-1, X' Double X' to give 2*X'. e'-1,2*X' X',e' - X' large. 2*X',e'-1 - X' small. X',e',LN 2 2*X',e'-1, LN 2 X',e'*LN 2 = Y1 2*X', (e'-1)*LN 2 = Y2
Y1, X' - X' large. Y2, 2*X' - X' small. Y1, X', .5 (decimal) Y2, 2*X', .5 Y1, X'-.5 Y2, 2*X'-.5 Y1, X'-.5, .5 Y2, 2*X'-.5, .5 Y1, X'-1 Y2, 2*X'-1
Y, X'-1, X'-1 Y2, 2*X'-1, 2*X'-1 Y1, X'-1, X'-1, 2.5 (decimal) Y2, 2*X'-1, 2*X'-1, 2.5 Y1, X'-1, 2.5*X'-2.5 Y2, 2*X'-1, 5*X'-2.5 Y1,X-1, 2.5*X-2.5, .5 Y2, 2*X-1, 5*X-2.5, .5 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. 1. 2. 3. DEFB DEFB DEFB DEFB DEFB DEFB DEFB +8C,series-0C +11,exponent+61 +AC,(+00,+00,+00) +14,exponent+64 +09,(+00,+00,+00) +56,exponent+66 +DA,+A5,(+00,+00) Y1, X'-1, Z or Y2, 2*X'-1, Z
214
DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB
+59,exponent+69 +30,+C5,(+00,+00) +5C,exponent+6C +90,+AA,(+00,+00) +9E,exponent+6E +70,+6F,+61,(+00) +A1,exponent+71 +CB,+DA,+96,(+00) +A4,exponent+74 +31,+9F,+B4,(+00) +E7,exponent+77 +A0,+FE,+5C,+FC +EA,exponent+7A +1B,+43,+CA,+36 +ED,exponent+7D +A7,+9C,+7E,+5E +F0,exponent+80 +6E,+23,+80,+93
At the end of the last loop the 'last value' is: either or Perform step vii. DEFB DEFB DEFB RET +04,multiply +0F,addition +38,end-calc Y1=LN (2**e'), LN X' Y2=LN (2**(e'-1)), LN (2*X') LN (2**e')*X') = LN X LN (2**(e'-1)*2*X') = LN X LN X Finished: 'last value' is LN X. LN X'/(X'-1) for the larger values of X' LN (2*X')/(2*X'-1) for the smaller values of X'.
215
Note: Adding 0.5 and taking INT rounds the result to the nearest integer. DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB RET If the jump was made then continue. 37A1 ZPLUS DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB RET +A1,stk-one +03,subtract +01,exchange +36,less-0 +00,jump-true +02,to 37A8,YNEG +1B,negate +38,end-calc 4*Y, Z, 1 4*Y, Z-1 Z-1,4*Y Z-1,(1/0) Z-1 Z-1 1-Z 1-Z = V - case ii. Z-1 = V - case iii. Finished. +31,duplicate +0F,addition +31,duplicate +0F,addition +31,duplicate +2A,abs +A1,stk-one +03,subtract +31,duplicate +37,greater-0 +C0,st-mem-0 +00,jump-true +04, to 37A1,ZPLUS +02,delete +38,end-calc Y, Y 2*Y 2*Y, 2*Y 4*Y 4*Y, 4*Y 4*Y, ABS (4*Y) 4*Y, ABS (4*Y), 1 4*Y, ABS (4*Y)-1 = Z 4*Y, Z, Z 4*Y, Z, (1/0) Mem-0 holds the result of the test. 4*Y, Z 4*Y, Z 4*Y 4*Y = V - case i. Finished.
37A8
YNEG
If the jump was not made then continue. DEFB +1B,negate DEFB +33,jump DEFB +03, to 37B7,C-ENT
216
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. 37B5 sin RST 0028 FP-CALC X
Perform step ii. The subroutine from now on is common to both the SINE and COSINE functions. 37B7 C-ENT DEFB DEFB DEFB DEFB DEFB DEFB DEFB +31,duplicate +31,duplicate +04,multiply +31,duplicate +0F,addition +A1,stk-one +03,subtract W, W W, W, W W, W*W W, W*W, W*W W, 2*W*W W, 2*W*W, 1 W, 2*W*W-1 = Z
Perform step iii, passing to the SERIES GENERATOR the parameter '6' and the six constants required. 1. 2. 3. 4. 5. 6. DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB +86,series-06 +14,exponent+64 +E6,(+00,+00,+00) +5C,exponent+6C +1F,+0B,(+00,+00) +A3,exponent+73 +8F,+38,+EE,(+00) +E9,exponent+79 +15,+63,+BB,+23 +EE,exponent+7E +92,+0D,+CD,+ED +F1,exponent+81 +23,+5D,+1B,+EA W, Z
At the end of the last loop the 'last value' is (SIN (PI*W/2))/W. Perform step v. DEFB DEFB RET +04,multiply +38,end-calc Finished: 'last value' = SIN X. or ('last value' = COS X) SIN (PI*W/2) = SIN X (or = COS X)
217
iii. The SERIES GENERATOR is used to produce the required function. iv. Finally a simple multiplication and addition give ATN X. Perform stage i. 37E2 atn CALL LD CP JR RST DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB 37F8 SMALL RST DEFB 3297,RE-STACK A,(HL) +81 C,37F8,SMALL 0028,FP-CALC +A1,stk-one +1B,negate +01,exchange +05,division +31,duplicate +36,less-0 +A3,stk-pi/2 +01,exchange +00,jump-true +06, to 37FA,CASES +1B,negate +33,jump +03,to 37FA,CASES 0028,FP-CALC +A0,stk-zero Use the full floating-point form of X. Fetch the exponent of X. Jump forward for case i: Y = X. X X, 1 X,-1 -1, X -1/X -1/X, -1/X -1/X, (1/0) -1/X, (1/0), PI/2 -1/X, PI/2, (1/0) -1/X, PI/2 Jump forward for case ii: Y = -1/X W = PI/2 -1/X, -PI/2 -1/X, -PI/2 Jump forward for case iii: Y = -1/X W = -PI/2 Y Y, 0 Continue for case i: W = 0
Perform step ii. 37FA CASES DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB +01,exchange +31,duplicate +31,duplicate +04,multiply +31,duplicate +0F,addition +A1,stk-one +03,subtract W, Y W, Y, Y W, Y, Y, Y W, Y, Y*Y W, Y, Y*Y, Y*Y W, Y, 2*Y*Y W, Y, 2*Y*Y, 1 W, Y, 2*Y*Y-1 = Z
Perform step iii, passing to the SERIES GENERATOR the parameter '12' decimal, and the twelve constants required. 1. DEFB DEFB DEFB +8C,series-0C +10,exponent+60 +B2,(+00,+00,+00) W, Y, Z
218
DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB DEFB
+13,exponent+63 +0E,(+00,+00,+00) +55,exponent+65 +E4,+8D,(+00,+00) +58,exponent+68 +39,+BC,(+00,+00) +5B,exponent+6B +98,+FD,(+00,+00) +9E,exponent+6E +00,+36,+75,(+00) +A0,exponent+70 +DB,+E8,+B4,(+00) +63,exponent+73 +42,+C4,(+00,+00) +E6,exponent+76 +B5,+09,+36,+BE +E9,exponent+79 +36,+73,+1B,+5D +EC,exponent+7C +D8,+DE,+63,+BE +F0,exponent+80 +61,+A1,+B3,+0C
At the end of the last loop the 'last value' is: ATN X/X - case i. ATN (-1/X)/(-1/X) ATN (-1/X)/(-1/X) Perform step iv. DEFB DEFB DEFB RET +04,multiply +0F,addition +38,end-calc W, ATN X - case i. W, ATN (-1/X) - case ii. W, ATN (-1/X) - case iii. ATN X - all cases now. Finished: 'last value' = ATN X. - case ii. - case iii.
219
The jump is made if X = 0, otherwise continue with: DEFB DEFB and then find the result of X**.5. +A2,stk-half +38,end-calc X,.5
The jump is made if X = 0, otherwise EXP (Y*LN X) is formed. DEFB Giving report A if X is negative. DEFB DEFB JP +04,multiply +38,end-calc 36C4,EXP Y*LN X Exit via EXP to form EXP (Y*LN X). +25,ln Y,LN X
The value of X is zero so consider the three possible cases involved. 385D XIS0 DEFB DEFB DEFB DEFB +02,delete +31,duplicate +30,not +00,jump-true Y Y,Y Y,(1/0) Y
220
DEFB
+09,to 386A,ONE
Y Y,0 0,Y 0,(1/0) 0 0 0,1 1,0 Exit via 'division' as dividing by zero gives 'arithmetic overflow'.
The jump is made if X = 0 and Y = 0, otherwise proceed. DEFB +A0,stk-zero DEFB +01,exchange DEFB +37,greater-0 DEFB +00,jump-true DEFB +06,to 386C,LAST The jump is made if X = 0 and Y is positive, otherwise proceed. DEFB +A1,stk-one DEFB +01,exchange DEFB +05,division The result is to be 1 for the operation. 386A ONE DEFB +02,delete DEFB +A1,stk-one
Now return with the 'last value' on the stack being 0**Y. 386C LAST DEFB +38,end-calc RET (1/0) Finished: 'last value' is 0 or 1.
386E - 3CFF These locations are 'spare'. They all hold +FF. 3D00 - 3FFF These locations hold the 'character set'. There are 8 byte representations for all the characters with codes +20 (space) to +7F (). e.g. the letter 'A' has the representation 00 3C 42 42 7E 42 42 00 and thereby the form: 00000000 00111100 01000010 01000010 01111110 01000010 01000010 00000000
221
APPENDIX
BASIC PROGRAMS FOR THE MAIN SERIES
The following BASIC programs have been included as they give a good illustration of how Chebyshev polynomials are used to produce the approximations to the functions SIN, EXP, LN and ATN. The series generator: This subroutine is called by all the 'function' programs. 500 510 520 530 540 550 560 570 580 590 600 610 620 630 640 650 660 REM SERIES GENERATOR, ENTER REM USING THE COUNTER BREG REM AND ARRAY-A HOLDING THE REM CONSTANTS. REM FIRST VALUE IN Z. LET M0=2*Z LET M2=0 LET T=0 FOR I=BREG TO 1 STEP -1 LET M1=M2 LET U=T*M0-M2+A(BREG+1-I) LET M2=T LET T=U NEXT I LET T=T-M1 RETURN REM LAST VALUE IN T.
In the above subroutine the variable are: Z the entry value. T the exit value. M0 mem-0 M1 mem-1 M2 mem-2 I the counter for BREG. U a temporary variable for T. A(1) to A(BREG) the constants. BREG the number of constants to be used. To see how the Chebyshev polynomials are generated, record on paper the values of U, M1, M2 and T through the lines 550 to 630, passing, say, 6 times through the loop, and keeping the algebraic expressions for A(1) to A(6) without substituting numerical values. Then record T-M1. The multipliers of the constants A(1) to A(6) will then be the required Chebyshev polynomials. More precisely, the multiplier of A(1) will be 2*T5(Z), for A(2) it will be 2*T4(Z) and so on to 2*T1(Z) for A(5) and finally T0(Z) for A(6). Note that T0(Z)=1, T1(Z)=Z and, for n>=2, Tn(Z)=2*Z*Tn-1(Z)-Tn-2(Z).
222
SIN X
10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300 310 320 330 REM DEMONSTRATION FOR SIN X REM USING THE 'SERIES GENERATOR'. DIM A(6) LET A(1)=-.000000003 LET A(2)=0.000000592 LET A(3)=-.000068294 LET A(4)=0.004559008 LET A(5)=-.142630785 LET A(6)=1.276278962 PRINT PRINT "ENTER START VALUE IN DEGREES" INPUT C CLS LET C=C-10 PRINT "BASIC PROGRAM","ROM PROGRAM" PRINT "-------------","-----------" PRINT FOR J=1 TO 4 LET C=C+10 LET Y=C/360-INT (C/360+.5) LET W=4*Y IF W > 1 THEN LET W=2-W IF W < -1 THEN LET W=-W-2 LET Z=2*W*W-1 LET BREG=6 REM USE 'SERIES GENERATOR' GO SUB 550 PRINT TAB 6; "SIN ";C;" DEGREES" PRINT PRINT T*W,SIN (PI*C/180) PRINT NEXT J GO TO 100
NOTES:
I. When C is entered this program calculates and prints SIN C degrees, SIN (C+10) degrees, SIN (C+20) degrees and SIN (C+30) degrees. It also prints the values obtained by using the ROM program. For a specimen of results, try entering these values in degrees: 0; 5; 100; -80; -260; 3600; -7200. The constants A(1) to A(6) in lines 40 to 90 are given (apart from a factor of 1/2) in Abramowitz and Stegun Handbook of Mathematical Functions (Dover 1965) page 76. They can be checked by integrating (SIN (PI*X/2))/X over the interval U=0 to PI, after first multiplying by COS (N*U) for each constant (i.e. N=1,2,...,6) and substituting COS U=2*X*X-1. Each result should then be divided by PI. (This integration can be performed by approximate methods e.g. using Simpson's Rule if there is a reasonable computer or programmable calculator to hand.)
II.
223
EXP X
10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300 310 320 330 340 350 360 370 380 390 400 410 420 REM DEMONSTRATION FOR EXP X REM USING THE 'SERIES GENERATOR' LET T=0 (This makes T the first variable.) DIM A(8) LET A(1)=0.000000001 LET A(2)=0.000000053 LET A(3)=0.000001851 LET A(4)=0.000053453 LET A(5)=0.001235714 LET A(6)=0.021446556 LET A(7)=0.248762434 LET A(8)=1.456999875 PRINT PRINT "ENTER START VALUE" INPUT C CLS LET C=C-10 PRINT "BASIC PROGRAM", "ROM PROGRAM" PRINT "-------------", "-----------" PRINT FOR J=1 TO 4 LET C=C+10 LET D=C*1.442695041 (D=C*(1/LN 2);EXP C=2**D). LET N=INT D LET Z=D-N (2**(N+Z) is now required). LET Z=2*Z-1 LET BREG=8 REM USE "SERIES GENERATOR" GO SUB 550 LET V=PEEK 23627+256*PEEK 23628+1 (V=(VARS)+1) LET N=N+PEEK V IF N > 255 THEN STOP (STOP with arithmetic overflow). IF N < 0 THEN GO TO 360 POKE V,N GO TO 370 LET T=0 PRINT TAB 11;"EXP ";C PRINT PRINT T,EXP C PRINT NEXT J GO TO 130
NOTES:
I. When C is entered this program calculates and prints EXP C, EXP (C+10), EXP (C+20) and EXP (C+30). It also prints the values obtained by using the ROM program. For a specimen of results, try entering these values: 0; 15; 65 (with overflow at the end); -100; -40. The exponent is tested for overflow and for a zero result in lines 320 and 330. These tests are simpler in BASIC than in machine code, since the variable N, unlike the A register, is not confined to one byte. The constants A(1) to A(8) in lines 50 to 120 can be obtained by integrating 2**X over the interval U=0 to PI, after first multiplying the COS (N*U) for each constant (i.e. for N=1,2,...,8) and substituting COS U = 2*X-1. Each result should then be divided by PI.
II. III.
224
LN X:
10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300 310 320 330 340 350 360 370 380 390 400 410 420 430 440 450 460 470 480 REM DEMONSTRATION FOR LN X REM USING THE 'SERIES GENERATOR' LET D=0 (This makes D the first variable). DIM A(12) LET A(1)= -.0000000003 LET A(2)=0.0000000020 LET A(3)= -.0000000127 LET A(4)=-0.0000000823 LET A(5)= -.0000005389 LET A(6)=0.0000035828 LET A(7)= -.0000243013 LET A(8)=0.0001693953 LET A(9)= -.0012282837 LET A(10)=0.0094766116 LET A(11)= -.0818414567 LET A(12)=0.9302292213 PRINT PRINT "ENTER START VALUE" INPUT C CLS PRINT "BASIC PROGRAM", "ROM PROGRAM" PRINT "-------------", "-----------" PRINT LET C=SQR C FOR J=1 TO 4 LET C=C*C IF C=0 THEN STOP (STOP with 'invalid argument'.) LET D=C LET V=PEEK 23627+256*PEEK 23628+1 LET N=PEEK V-128 (N holds e'). POKE V,128 IF D<=0.8 THEN GO TO 360 (D holds X'). LET S=D-1 LET Z=2.5*D-3 GO TO 390 LET N=N-1 LET S=2*D-1 LET Z=5*D-3 LET R=N*0.6931471806 (R holds N*LN 2). LET BREG=12 REM USE 'SERIES GENERATOR' GO SUB 550 PRINT TAB 8;"LN ";C PRINT PRINT S*T+R,LN C PRINT NEXT J GO TO 170
NOTES:
I. When C is entered this program calculates and prints LN C, LN (C**2), LN (C**4) and LN (C**8). It also prints the values obtained by using the ROM program. For a specimen of results, try entering these values: 1.1; 0.9; 300; 0.004; 1E5 (for overflow) and 1E-5 (STOP as 'invalid argument'). The constants A(1) to A(12) in lines 50 to 160 can be obtained by integrating 5*LN (4* (X+1)/5)/(4*X-1) over the interval U=0 to PI, after first multiplying by COS (N*U) for each constant (i.e. for N=1,2,...,12) and substituting COS U=2*X-1. Each result should then be divided by PI.
II.
225
ATN X:
10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300 310 320 330 340 350 360 370 380 390 REM DEMONSTRATION FOR ATN X REM USING THE 'SERIES GENERATOR' DIM A(12) LET A(1)= -.0000000002 LET A(2)=0.0000000010 LET A(3)= -.0000000066 LET A(4)=0.0000000432 LET A(5)= -.0000002850 LET A(6)=0.0000019105 LET A(7)= -.0000131076 LET A(8)=0.0000928715 LET A(9)= -.0006905975 LET A(10)=0.0055679210 LET A(11)= -.0529464623 LET A(12)=0.8813735870 PRINT PRINT "ENTER START VALUE" INPUT C CLS PRINT "BASIC PROGRAM", "ROM PROGRAM" PRINT "-------------", "-----------" PRINT FOR J=1 TO 4 LET B=J*C LET D=B IF ABS B>=1 THEN LET D= -1/B LET Z=2*D*D-1 LET BREG=12 REM USE "SERIES GENERATOR" GO SUB 550 LET T=D*T IF B > =1 THEN LET T=T+PI/2 IF B < =-1 THEN LET T=T-PI/2 PRINT TAB 8;"ATN ";B PRINT PRINT T,ATN B (or PRINT T*180/PI,ATN B*180/PI PRINT to obtain the answers in degrees) NEXT J GO TO 160
NOTES:
I. When C is entered this program calculates and prints ATN C, ATN (C*2), ATN (C*3) and ATN (C*4). For a specimen of results, try entering these values: 0.2; -1; 10 and -100. The results may be found more interesting if converted to yield degrees by multiplying the answers in line 360 by 180/PI. The constants A(1) to A(12) in lines 40 to 150 are given (apart from a factor of 1/2) in Abramowitz and Stegun Handbook of Mathematical Functions (Dover 1965) page 82. They can be checked by integrating ATN X/X over the interval U=0 to PI, after first multiplying by COS (N*U) for each parameter (i.e. for n=1,2,...,12) and substituting COS U=2*X*X-1. Each result should then be divided by PI.
II.
226
An alternative subroutine for SIN X: It is straightforward to produce the full expansion of the Chebyshev polynomials and this can be written in BASIC as follows: 550 LET T =(32*Z*Z*Z*Z*Z-40*Z*Z*Z+10*Z)*A(1) +(16*Z*Z*Z*Z-16*Z*Z+2)*A(2) +(8*Z*Z*Z-6*Z)*A(3) +(4*Z*Z-2)*A(4) +2*Z *A(5) +A(6) 560 RETURN This subroutine is called instead of the SERIES GENERATOR and can be seen to be of a similar accuracy. An alternative subroutine for EXP X: The full expansion for EXP X is: 550 LET T =(128*Z*Z*Z*Z*Z*Z*Z-224*Z*Z*Z*Z*Z+112*Z*Z*Z-14*Z)*A(1) +(64*Z*Z*Z*Z*Z*Z-96*Z*Z*Z*Z+36*Z*Z-2)*A(2) +(32*Z*Z*Z*Z*Z-40*Z*Z*Z+10*Z)*A(3) +(16*Z*Z*Z*Z-16*Z*Z+2)*A(4) +(8*Z*Z*Z-6*Z)*A(5) +(4*Z*Z-2)*A(6) +2*Z*A(7) +A(8) 560 RETURN The expansion for LN X and A TN X, given algebraically, will be: (2048z11-5632z9+5632z7-2464z5+440z3-22z) * A (1) + + + + + + + + + + + (1024z10-2560z8+2240z6-800z4+100z2-2) * A(2) (512z9-1152z7+864z5-240z3+18z) * A(3) (256z8-512z6+320z4-64z2+2) * A(4) (128z7-224z5+112z3-14z) * A(5) (64z6-96z4+36z2-2) * A(6) (32z5-40z3+10z) * A(7) (16z4-16z2+2) * A(8) (8z3-6z) * A(9) (4z2-2) * A(10) (2z) * A(11) A(12)
227
A complete algorithm is to found in the following program, as a subroutine that will 'DRAW A LINE' from the last position to X,Y.
228
250 260 270 280 290 300 500 510 520 530 540 550 560 570 580 590 600 610 620 630 640 650 660 670 680 690 700 710 720 730 740 750 760
GO SUB 510 LET Arcs=Arcs-1: IF Arcs=0 THEN STOP LET MM1=M1 LET M1=M1*M3-M2*M4 LET M2=MM1*M4+M2*M3 GO TO 210 REM 'DRAW A LINE' from last position to X,Y LET PLOTx=PEEK 23677: LET PLOTy=PEEK 23678 LET dx=SGN X: LET dy=SGN Y LET X=ABS X: LET Y=ABS Y IF X> =Y THEN GO TO 580 LET L=X: LET B=Y LET ddx=0: LET ddy=dy GO TO 610 IF X+Y=0 THEN STOP LET L=Y: LET B=X LET ddx=dx: LET ddy=0 LET H=B LET i=INT (B/2) FOR N=B TO 1 STEP -1 LET i=i+L IF i < H THEN GO TO 690 LET i=i-H LET ix=dx: LET iy=dy GO TO 700 LET ix=ddx: LET iy=ddy LET PLOTy=PLOTy+iy IF PLOTy <0 OR PLOTy > 175 THEN STOP LET PLOTx=PLOTx+ix IF PLOTx <0 OR PLOTx > 255 THEN STOP PLOT PLOTx,PLOTy NEXT N RETURN
229
6. The above amendment (i.e. 15 extra bytes) with the omission of bytes 3223 to 323E inclusive from 'truncate' should solve the problems. It would be nice to be able to test this. The calls of INT-STORE should not lead to 00 FF 00 00 00 being stacked. In 'multiply' the number will lead to overflow if it occurs, since 65536 will set the carry flag; so 'long' multiplication will be used. As noted at 30E5, the 5 bytes starting there could probably be omitted if the above amendments were made. 'Negate' avoids stacking 00 FF 00 00 00 by treating zero separately and returning it unaltered. Truncate deals separately with -65536, as noted above. SGN stores only 1 and -1.
230
INDEX TO ROUTINES
address routine page
231
address routine 0C55 0CF8 0D4D 0D6B 0DAF 0DD9 0DFE 0E44 0E88 0E9B 0EAC 0ECD 0EF4 0F2C 0F81 0FA0 0FA9 0FF3 1007 100C 1015 101E 1024 1031 1059 1076 107F 1097 10A8 111D 1190 11A7 Test for scroll 'scroll?' message Temporary colour items CLS command Clearing the whole display area CL-SET Scrolling Clear lines CL-ATTR CL-ADDR COPY command COPY-BUFF COPY-LINE EDITOR ADD-CHAR Editing keys table EDIT key Cursor down editing Cursor left editing Cursor right editing DELETE editing ED-IGNORE ENTER editing ED-EDGE Cursor up editing ED-SYMBOL ED-ERROR CLEAR-SP Keyboard input Lower screen copying SET-HL REMOVE-FP
page 40 42 43 43 44 45 45 46 48 48 48 49 49 50 51 52 52 53 53 53 53 53 53 53 54 54 54 55 55 56 57 58
232
address routine 1716 171E 1736 177A 1793 1795 1795 17F5 17F9 1855 18B6 18C1 18E1 190F 1925 196E 1980 1988 19B8 19DD 19E5 19FB 1A1B CLOSE stream look-up table Stream data OPEN # command OPEN stream look-up table CAT, ERASE, FORMAT & MOVE commands LIST & LLIST commands AUTO-LIST LLIST LIST Print a whole BASIC line NUMBER Print a flashing character Print the cursor LN-FETCH Printing characters in a BASIC line LINE-ADDR Compare line numbers Find each statement NEXT-ONE Difference Reclaiming E-LINE-NO Report and line number printing
page 71 71 71 72 73 73 73 74 74 75 76 77 77 77 78 79 79 79 80 81 81 82 82
233
address routine 1E4F 1E5F 1E67 1E7A 1E80 1E85 1E94 1EA1 1EAC 1EED 1F05 1F1A 1F23 1F3A 1F54 1F60 1FC3 1FC9 1FCF 1FF5 1FFC 2045 204E 2070 2089 21B9 21D6 21E1 226C 2294 22AA 22CB 22DC 2307 2314 2320 2382 247D 24B7 RANDOMIZE command CONTINUE command GO TO command OUT command POKE command TWO-PARAM Find integers RUN command CLEAR command GO SUB command TEST-ROOM Free memory RETURN command PAUSE command BREAK-KEY DEF FN command UNSTACK-Z LPRINT command PRINT command Print a carriage return Print items End of printing Print position Alter stream INPUT command IN-ASSIGN IN-CHAN-K Colour item routines CO-CHANGE BORDER command Pixel address Point PLOT command STK-TO-BC STK-TO-A CIRCLE command DRAW command Initial parameters Line drawing
page 100 101 101 101 101 101 101 102 102 103 103 103 104 104 104 105 106 106 106 107 107 108 108 108 109 111 112 112 114 115 115 116 116 117 117 117 119 123 124
EXPRESSION EVALUATION
24FB 2530 2535 2580 2596 25AF 26C9 2734 2795 27B0 27BD 28AB 28B2 2951 2996 2A52 2AB6 2ACC 2AEE SCANNING SYNTAX-Z Scanning SCREEN$ Scanning ATTR Scanning function table Scanning function routines Scanning variable routine Scanning main loop Table of operators Table of priorities Scanning function (FN) FN-SKPOVR LOOK-VARS Stack function argument STK-VAR SLICING STK-STORE INT-EXP DE,(DE+1) 127 128 128 129 129 130 133 135 137 137 137 141 141 144 145 148 150 150 151
234
address routine 2AF4 2AFF 2BF1 2C02 2C88 2C8D 2C9B 2D1B 2D22 2D28 2D2B 2D3B GET-HL*DE LET command STK-FETCH DIM command ALPHANUM ALPHA Decimal to floating-point NUMERIC STK-DIGIT STACK-A STACK-BC Integer to floating-point
page 151 151 157 157 159 159 160 161 162 162 162 162
235
address routine 34E9 34F9 3501 3506 350B 351B 3524 352D 353B 359C 35BF 35C9 35DE 361F 3645 3669 3674 367A 3686 368F 369B 36A0 36AF 36C4 3713 3783 37AA 37B5 37DA 37E2 3833 3843 384A 3851 TEST-ZERO Greater than zero (37) NOT (30) Less than zero (36) Zero or one OR (07) Number AND number (08) String AND number (10) Comparison (09-0E, 11-16) String concatenation (17) STK-PNTRS CHR$ (2F) VAL and VAL$ (1D,18) STR$ (2E) Read-in (1A) CODE (1C) LEN (1E) Decrease the counter (35) Jump (33) Jump on true (00) END-CALC (38) Modulus (32) INT (27) Exponential (26) Natural logarithm (25) Reduce argument (39) Cosine (20) SINE (1 F) Tan (21) ARCTAN (24) Arcsin (22) Arccos (23) Square root (28) Exponentiation (06)
page 202 202 202 203 203 203 203 204 204 205 206 206 207 208 208 209 209 209 209 210 210 210 211 211 213 215 216 216 217 218 219 220 220 220
APPENDIX
BASIC programs for the main series. - Series generator - SIN X - EXP X - LN X - ATN X The 'DRAW' algorithm The 'CIRCLE' algorithm Note on Small Integers and -65536 222 223 224 226 228 228 229 229
236
237
238
O MAGAZINE (give name) ............................ O OTHER (specify) ................................. Age? O 10-15 O 16-19 O 20-24 O 25 and over
How would you rate this book? QUALITY: O Excellent O Good VALUE: O Overpriced O Poor O Good O Underpriced
What other books and software would you like to see produced for your computer? ..................................................................... ....................................................................................... .......................................................................................
EDITION 7 6 5 4 3 2 1
239
United Kingdom
Melbourne House (Publishers) Ltd Castle Yard House Castle Yard Richmond, TW10 6TF
240