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
                       |              |       |---------------|
                       +--------------+       |               |
                                              |               |
                                              |               |
                                              |               |
                                              |               |
                                              +---------------+