EXTENDED SPECTRUM BASIC (48K/+) (ZX Computing May 1987) Stuart Nicholls demonstrates how to add some new commands to Basic. [See end of this file for Tables 1-3, Diagram 1 & Program 2.] [See DOTCMNDS.TAP for Program 1 and CODE (Listing 4). ] [See DOTCMNDS.ASM for Listings 1-3. JimG] I suppose that most of you have at some time or another wished that there were more commands available in ZX BASIC. Commands such as BOX or FILL maybe. Well, with this article I hope to show the competent machine code programmer how to achieve this. Yes you've guessed it, another BASIC extension article. But before you stop reading I believe that this one is different from any other you may have read in that it does not use PRINT or LPRINT to access the new commands, and more importantly, works without Interface I. I will be explaining how you can add 49 new commands that will be checked for syntax before entry into the listings and checked for parameter errors in run time in exactly the same way as normal ZX Spectrum commands. In order to understand how new commands can be added to ZX BASIC we must first examine how the Spectrum handles the existing 50 commands; that is, commands DEF FN (code CEh) to COPY (code FFh). Commands When the Spectrum is first switched on it goes through an initial setting up procedure to check the RAM available and give initial values to the system variables. Once this has been carried out we enter what is known as the "main execution loop" which allows commands to be entered either as a program listing or as direct commands to be executed. But before a line can be accepted a check is made to see if the commands within the line conform to the correct syntax. If there is any error in the syntax then this is highlighted with a question mark at the position of the error so that it can be corrected. Only when the syntax is correct will the line be either added to the listing or RUN. There may of course still be parameter errors in the line which will cause run-time errors stopping the program with an error report. With this generalised view of the Basic Interpreter in mind we can proceed to analyse each step in more detail. It is easier to follow the workings of the interpreter by using a simple BASIC program and follow it stage by stage as it is interpreted, so for our example I will use the direct command: POKE 65535,8 As outlined above, the first step is to get the line into the Spectrum. This is done by means of the editor which is called after the initialisation routine. The function of the editor is to 'read' key presses and build up a line in the editing area and also echo the line to the screen so that we can see what is being typed. The lower screen is used for this purpose. So, if we type POKE (keyword key O) 65535,8 this will appear on the screen and in the editing area. We can continue to type as much as we like, although the Spectrum will complain if the line is too long, and the editor will continue to accept key presses until ENTER is pressed. As soon as ENTER is pressed a return from the editor is made, the Basic interpreter is called and the line is 'run'. This is, however, a dummy run to check the syntax of the line in the editing area; only when the line passes this check is it run again to perform the command (assuming a direct command). There are nine main steps taken during syntax checking: 1. Reset bit 7, FLAGS to signal syntax checking. 2. Check the first byte of the statement for one of the command codes, CEh to FFh, and if OK move to step 3, otherwise signal an editor error. 3. Subtract CEh from code. 4. Add the result of '3' to the base address of an offset table. 5. Use the offset value found at this computed address and add this value to the computed address. 6. The result of '5' produces an address in the command class parameters table. 7. Use the address produced by '6' as the start to a series of class codes. 8. Work through each class code in turn using a class code offset table to call the class code routines. 9. If each class code routine is passed with no errors then move on the to the next statement in the editing area. From this more detailed breakdown above you will realise that the syntax of each command depends on its class codes, and hence the class code routines. Table 2 gives the method of calculating the class routine from a class code and Table 3 gives a brief description of the function of the class routines. For our purposes it is not necessary to go into how the class routines work but it is essential to understand the result of each class routine. We can forget Class 01, 02, 07 and 0B routines as these are specific to certain commands and are of little use to us. The remaining class routines, except one (05), are concerned with evaluation of the expression following the command code, be it a numeric or string expression (for example a$, "help", a, 900, (a=b), NOT c, SIN x). Providing the expression conforms to the requirements given in Table 3 the syntax will be correct. Coming back to the example POKE 65535,8 the syntax checking will arrive at class code 08 and 00 (followed by a command routine address 1E80h); the route followed is given in Diagram 1. Looking at Table 3 shows that Class 08 will, during syntax, check for two numeric expressions separated by a comma. The syntax check will then move on to the class 00 routine which simply means that there must be no more expressions before the end of statement/line marker, and a return is made in this case to the main execution loop. If, however, we had typed in POKE 65535;8 the class 08 routine would have spotted the syntax error of the semicolon in place of the comma and returned to the editor with a '?' after the semicolon. Similarly, POKE "help",8 would give a class 08 error as the first expression is a string, but POKE a,b would be accepted since a and b are numeric variables and in syntax checking the actual values are not calculated. Our command has now passed its syntax check and a return made to the main execution loop. The next step is to RUN the command. This involves the same process as for syntax checking except that BIT 7, FLAGS is SET to indicate "RUN TIME", and we enter the interpreter at a slightly different point. However this time after performing class routines 08 and 00 the actual values of the two numeric expressions will be placed on the calculator stack. We then go on to call the command routine given by the two byte number following the class codes in the parameter table; ie. address 1E80h. Stack If we just take a little time out to briefly discuss 'values' on the calculator stack then perhaps the following will be a little clearer. The calculator stores two different types of value: 1. Numeric values of one form or another. 2. String parameters. To get at these values there are several ROM routines that can prove extremely useful. 1. STK.TO.A Address = 2314h. 0-255 into A. 2. STK.TO.BC Address = 2307h. Top value to B, next value to C. 3. TWO.PARAM Address = 1E85h. Top value to A, next value to BC. 4. FIND.INT2 Address = 1E99h. Top value to BC. 5. STK.FETCH Address = 2BF1h. DE = start of string, BC = length. Now to get back to the POKE command 1E80h. This is a very short program that uses 1E85h to take two numbers from the stack putting the first value (8) into the A register and the second value (65535) Into the BC register pair. The instruction LD (BC),A is then used to POKE 65535,8 and then a Ret is made to find the next statement, in our case there is no next statement so a return to the main execution loop is made via the report code 'OK' routine. If, however, we had typed POKE 65535,300 this would have passed syntax checking but would produce a run time error report from the POKE command routine. The error would have been caused by trying to compress a number greater than 255 into the A register. Similarly, POKE 655356,8 would give an error report as the BC register pair cannot hold numbers greater than 65535. These errors are dealt with automatically by the RST 8 routine and need not concern us. I include Table 1 to give you all the commands available with their class codes and command routines where applicable. From Table 3 you will see that (leaving out 01, 02, 07 and 0B as "dedicated classes") classes 00, 03, 04, 06, 08, 09 and 0A 'evaluate' expressions following the command and in fact do not call the command routine at all during syntax checking. The exception to this is class 05 which is used in several commands, such as PRINT, READ, CIRCLE in which syntax is checked by calling the command routine following the class 05 code. In other words any expression following the command has to be checked for the syntax required within the command routine without actually performing the command. For example, PRINT "Hello";a$ has to be syntax checked by the PRINT command routine at 1FCD without actually printing to the screen, and if you typed PRINT "Hello";ab$ then the error must be spotted by the PRINT command routine, and flagged with a '?' after a. New commands Having got a grasp of the method of calculating a command class group we can dream up our own commands. How about a FILL command that uses the format: FILL colour; start co-ords x,y eg. FILL INK 2; PAPER 3; 127,88; this would have class codes 09 00 xxxx (xxxx being the FILL routine address) Or, a LINE command which will draw a line from a point x,y to a point a,b in any colour: LINE INK 5; OVER 1; 100,35; 150,80 this would have class codes 09 3B 08 00 xxxx. The 3B code is included in the above CLASS CODES to indicate that a semicolon is required between the two sets of co-ordinates, (We could of course have used any separator by placing its code between 09 and 08). To achieve this we must set up an offset table and parameter class code table for our new commands and, what is more important, find a method of diverting the Basic interpreter to consider using this new set of tables when encountering a new command. Keywords With the existing commands the Spectrum uses a single code/keyword combination so that command checking is made easier. For our extended Basic commands we will use a similar method, but to make it even easier we will precede a "new command code" with the code 2Eh (FULL STOP). To save code space we will have single letter commands, any other method would entail having a routine to expand the new commands into keywords. In this extended Basic the upper and lower case letters A to Z will be used so that a new command will be in the form .a (Dot A) .m (Dot m); ie. "Dot commands". Now that we have the form of our new commands we can proceed to examine the way in which the ROM interpreter so that we can retain control of all the Spectrum commands as well as our own new one. [There seems to be something missing from that sentence. JimG] The routines required to do this are held in the code F08C to F39D. You can take my word for it that these routines work or disassemble them for yourself and using a suitable book on the Spectrum ROM, follow the routines chosen. There is one small point to note with this method and this is to do with syntax checking of certain class code routines. Several Spectrum commands when checked for syntax will pass control back to the ROM interpreter for the rest of the basic line only. This means that in some cases you cannot place new commands after normal commands as the syntax will fail. Should this occur then transfer the new command to another line. This does not affect new commands preceding normal ones. The reason for this problem is a little routine in the ROM called CHECK-END at 1BEEh, which is called many times when checking syntax to stop the execution of the actual command. This does not normally cause any problem but when syntax checking is carried out within the command routine as in class 05 then a call to this routine will cause a continuation into the STMT-NEXT ROM routine. In run-time this call will have no effect as a direct return will be made back to the calling routine. Dot commands Now that we have control of the Basic interpreter we can modify it as we like to enable our dot commands to be recognised. I include sections of the assembly listing to demonstrate how this is achieved. Listing 1: Lines 250 to 350 show these modifications. In line 270 a statement beginning with a DOT command is diverted to use our new offset table whereas standard commands are handled in the normal manner using the ROM offset table. You will notice that three normal commands REM, POKE and IF are also diverted to use our new offset table and are treated as dot commands: REM = .c POKE = .m IF = .s The REM and IF commands are special cases which if allowed to be handled by the ROM would cause control of the Basic interpreter to be lost. POKE has been diverted so that the command can be modified to error trap any POKES which would corrupt our interpreter. Our new offset table is shown in my Assembly Listing 2 as LINE 600 and the Dot Command parameter table at LINE 650. As an example of the method of setting up new commands I have included two new BASIC commands .A and .I. The .A command is a new text command and will print text on the screen in any size at any pixel position and in any colour (including INVERSE, BRIGHT and OVER etc) and if you look at the new parameter table the class codes for the command are: 09 3B 0A 3B 08 00 (this shows that you can string any number of class codes together to get the parameters you require). An example of the command would be: .A INK 0; PAPER 3; BRIGHT 1; 128,25; "HELLO";3,2 which will print "HELLO" three characters by two characters wide, starting at pixel 128,25 using BLACK ink on MAGENTA paper, BRIGHT. W=0 is a special case that will print at 42 characters per line and H=0 will only print the first seven bytes of the character form. The .I command is an INPUT (line) command to the main screen and has class codes: 28 06 29 05 For example: .I (5) INK 0; PAPER 5; FLASH 1; AT 3,5;"Name ? ";a$ will print an input prompt "Name ? " at line 3, column 5, wait for an input in the normal way and allocate the input (if the syntax is correct) to the string variable a$. The routine will allow inputs of codes 32 to 127 and DELETE. The maximum number of characters to be accepted is given by the numeric variable enclosed by the brackets immediately after the .I; in the above example this is '5'. The .A routine occupies F408 to F51A and the .I command F51B to F72B. Offset values Finally, the routine in assembly Listing 3 was used to set up the correct offset table values for all the dot commands. I included this because it saved calculating values every time a change was made to the command routines. The Laser Genius assembler allows mathematical calculations based on the label address values as long as the result is within the range of the machine code command; ie. LD A,prem - REM - 1 means; LD A,(address prem) - (address REM) - 1 and must be a value less than 256. The code has a start address of F000h (61440d) and should be called from this address; a fanfare BEEP will be played to indicate the routine has been installed. My dual purpose HEXLOADER/HEXDUMP program (program 1) should be used to enter the code as Listing 4. Once entered it should be saved as "DOTCODE" CODE 61440,1836. After the CODE has been saved it can be loaded at the start of your extended Basic program such that it will AUTO RUN using something along the lines of the example in Program 2: PROGRAM 2 Example Basic program 10 REM ** START OF EXTENDED BASIC PROGRAM ** 20 . . ETC. . . 9000 STOP 9900 CLEAR 61439 9910 LOAD "DOTCODE" CODE,61440 9920 RANDOMIZE USE 61440: GO TO 10 9930 SAVE "BASICPROG" LINE 9900 9940 SAVE "DOTCODE" CODE 61440,1836 9950 PRINT "REWIND TAPE TO VERIFY..." 9960 VERIFY "": VERIFY "" CODE 9970 PRINT "VERIFY O.K...." Should you wish to exit extended BASIC use the NEW command which will revert back to the ROM interpreter. And that as they [say] is all there is to it. I hope that you have got this far with some understanding of the methods discussed and that you now feel able to tackle one of your own new commands. How about the LINE command touched upon earlier? TABLE 1: COMMAND TO CLASS CODE CONVERSION COMMAND CODE-CEh OFFSET OFFSET ADDRESS IN CLASS CODES ROUTINE ADDRESS ADDRESS VALUE PARAM TABLE ---------------------------------------------------------------------------- DEF FN 00 1A48 B1 1AF9 05 1F60 CAT 01 1A49 CB 1B14 00 1793 FORMAT 02 1A4A BC 1B06 0A 00 1793 MOVE 03 1A4B BF 1B0A 0A 2C 0A 00 1793 ERASE 04 1A4C C4 1B10 0A 00 1793 OPEN 05 1A4D AF 1AFC 06 2C 0A 00 1736 CLOSE 06 1A4E B4 1B02 06 00 16E5 MERGE 07 1A4F 93 1AE2 0B VERIFY 08 1A50 91 1AE1 0B BEEP 09 1A51 92 1AE3 08 00 03F8 CIRCLE 0A 1A52 95 1AE7 09 05 2320 INK 0B 1A53 98 1AEB 07 PAPER 0C 1A54 98 1AEC 07 FLASH 0D 1A55 98 1AED 07 ---------------------------------------------------------------------------- BRIGHT 0E 1A56 98 1AEE 07 INVERSE 0F 1A57 98 1AEF 07 OVER 10 1A58 98 1AF0 07 OUT 11 1A59 98 1AF1 08 00 1E7A LPRINT 12 1A5A 7F 1AD9 05 1FC9 LLIST 13 1A5B 81 1ADC 05 17F5 STOP 14 1A5C 2E 1A8A 00 1CEE READ 15 1A5D 6C 1AC8 05 1DED DATA 16 1A5E 6E 1ACC 05 1E27 RESTORE 17 1A5F 70 1ACF 03 1E42 NEW 18 1A60 48 1AA8 00 11B7 BORDER 19 1A61 94 1AF5 06 00 2294 CONTINUE 1A 1A62 56 1AB8 00 1E5F DIM 1B 1A63 3F 1AA2 05 2C02 ---------------------------------------------------------------------------- REM 1C 1A64 41 1AA5 05 1BB2 FOR 1D 1A65 2B 1A90 04 3D 06 CC 06 05 1D03 GOTO 1E 1A66 17 1A7D 06 00 1E67 GOSUB 1F 1A67 1F 1A86 06 00 1EED INPUT 20 1A68 37 1A9F 05 2089 LOAD 21 1A69 77 1AE0 0B LIST 22 1A6A 44 1AAE 05 17F9 LET 23 1A6B 0F 1A7A 01 3D 02 PAUSE 24 1A6C 59 1AC5 06 00 1F3A NEXT 25 1A6D 2B 1A98 04 00 1DAB POKE 26 1A6E 43 1AB1 08 00 1E80 PRINT 27 1A6F 2D 1A9C 05 1FCD PLOT 28 1A70 51 1AC1 09 00 22DC RUN 29 1A71 3A 1AAB 03 1EA1 ---------------------------------------------------------------------------- SAVE 2A 1A72 6D 1ADF 0B RANDOMIZE 2B 1A73 42 1AB5 03 1E4F IF 2C 1A74 0D 1A81 06 CB 05 1CF0 CLS 2D 1A75 49 1ABE 00 0D6B DRAW 2E 1A76 5C 1AD2 09 05 2382 CLEAR 2F 1A77 44 1ABB 03 1EAC RETURN 30 1A78 15 1A8D 00 1F23 COPY 31 1A79 5D 1AD6 00 0EAC TABLE 2: CLASS CODE TO CLASS ROUTINE CONVERSION CLASS OFFSET OFFSET CLASS CODE ADDRESS VALUE ROUTINE ------------------------------- 00 1C01 0F 1C10 01 1C02 1D 1C1F 02 1C03 4B 1C4E 03 1C04 09 1C0D 04 1C05 67 1C6C 05 1C06 0B 1C11 06 1C07 7B 1C82 07 1C08 8E 1C96 08 1C09 71 1C7A 09 1C0A B4 1CBE 0A 1C0B 81 1C8C 0B 1C0C CF 1CDB TABLE 3: FUNCTION OF CLASS CODE ROUTINES 00 no expression to follow 01 'LET' 02 'LET' 03 evaluate numeric expression (default zero) 04 evaluate single character variable 05 evaluate "a set of items" 06 evaluate numeric expression 07 "colour commands" 08 evaluate two numeric expressions (comma separator) 09 handle colours, then as 08 0A evaluate string expression 0B "tape routines" NOTE: Evaluation of expressions takes place during syntax checking as well as "run time". The value ends up as the last 'value' on the calculator stack. If the expression does not conform to the required type then an error is flagged. In syntax checks this results in a return to the editor with a ? at the point of error. REMEMBER Class Routine 05 is a special case in which the syntax is checked within the command routine and must be used with care. DIAGRAM 1: FLOW DIAGRAM OF BASIC COMMAND INTERPRETER +--------------+ +--------------+ +---------------+ +------------------+ | COMMAND CODE | | OFFSET TABLE | |PARAMETER TABLE| |CLASS OFFSET TABLE| |--------------| | BASE 1A48h | |BASE 1A7Ah | |BASE 1C01h | | POKE F4 |---+ |--------------| |---------------| |------------------| +--------------+ | | | | | +--00| 0F |-+1C01 = 1C10 | | | | | | |------------------| | | | | | | | | -CE | | | | | | | | | | | | | |------------------| =26 | | | | | | 71 |-+1C09 = 1C7A | | | | | | +08|------------------| +1A48 |--------------| | | | | | | =1A6E-| OFFSET VALUE |---+ | | | | | | | =43 | +1A6E | | | | | | |--------------| =1AB1 | | | | +------------------+ | | | |---------------| | | | | +---| CLASS CODES 08|----+ SYNTAX & RUN TIME | | | 00|--+ | | | ROUTINE 1E80|-------------- RUN TIME ONLY 1E80 | | |---------------| +--------------+ | | | | | | | | | | +---------------+