First the bad news - you'll need to do
some actual thinking if you want to get
the full benefit from reading this article.
But the good news is that it's worth it
because, once you've 'taught' the Spectrum a new statement, then you'll be
able to use it in any Basic program you
care to write. So, let's move on and look
at some fundamental concepts ... |
The first concept is the fact that the Interface 1 contains a brand new ROM (called the Shadow ROM) which is totally separate from the normal Spectrum ROM. Both ROMs use the same addresses, so you can only ever have one of them in place at once. The Shadow ROM occupies addresses 0000 to 1FFF Hex - so as soon as you start using it, every CALL instruction in this range will actually CALL a routine in the Shadow ROM, not the Spectrum ROM. This includes the re-starts - all of the RST instructions do different things when the Shadow ROM is in place. And here's where the crunch comes in: an Extended Basic handling routine operates with the Shadow ROM in place.
A machine code routine to cope with a new Basic command has to scan through such a line of Basic to make sure all is well, and then carry out the required task. The second fundamental concept then is that of the 'current character' in a Basic line. The gist of it is that we can only check one character at a time (although in practice, there are some ROM subroutines we can use to speed things up) and so the 'current character' is a pointer to the character we are currently checking. (This pointer is stored as the system variable (CH_ADD).)
To avoid all confusion, we'll precede all Shadow ROM addresses with the capital letter 'X'. So '1234' means address 1234 in the Spectrum ROM, and 'X1234' means address l234 in the Shadow ROM. Because all the re-starts are different, it'll be useful to spend some time explaining just one of them - RST X1O. The byte 'D7' (meaning RST X1O) must be followed by two bytes of data. These bytes represent an address in the Spectrum ROM (that's the Spectrum ROM, not the Shadow ROM) with, as ever, the bytes in reverse order. The purpose of this instruction is to call a subroutine in the Spectrum ROM at the address given. So 'D72000' - which is Hex for 'RST X10/0020' - actually means CALL 0020 in the Spectrum ROM. This is, of course, very handy.
Let's consider a routine which extends Basic, making 'USR' a command instead of just a function. Thus, the line:
is now a valid Basic line. The purpose of this Basic line is to call a machine code routine (in the above case at address 40000), but it avoids all that messing around with RANDOMIZE USR and PRINT USR and so on. In other words, the final value of the BC register is
'forgotten' at the end of the routine -
there are no 'side effects' like changing the random number seed. The
routine at address 40000 (or
wherever) must assume that the Spectrum ROM - so in other words my
USR command is exactly the same as
the normal USR function. |
The machine code necessary to create this new command is very short, and may be placed at absolutely any address in RAM. To activate it, however, you must not CALL it in the usual way. Instead you must activate it by loading the system variable VECTOR (address 5CB7, or decimal 23735) with the address of the label START in my program - that's all you need to do.
Having typed in the machine code to any suitable address (remembering to fill in the address that's labelled SUB_ADDR in the listing, and having POKEd the system variable VECTOR with the address of the label START) you'll find that BASIC has now been extended. Try this Basic program to see:
USR 3435 is the subroutine for CLS in the ROM, so you should expect the screen to clear every time you input zero. Does it work?
You'll notice that my machine code listing contains rather a lot of calls to
subroutines in the ROMs. Some of
these are important for you to know.
Address X01F0 is the address you
have to jump to if the Basic line is not
recognised. Address X05B7 is a sub-
routine which tests for the end of a Basic
line - that is for a colon or a carriage
return; if this is not found then a syntax
error results. But more importantly, if
we are only checking the syntax, and not
actually carrying out the statement,
then this routine will insert the line into a
Basic program if it has a line number -
or prepare to execute it if not. Control
will not return from subroutine X05B7
unless we are actually carrying out the
statement. Finally, address X05C1 is
the address you must jump to when all
the work has been carried out. |
When extending Basic on your own, it will be useful to know lots of other sub- routine addresses in the Spectrum ROM. There are too many of them to list here, so either to grab hold of a copy of The Complete Spectrum ROM Disassembly by Dr Ian Logan and Dr Frank O'Hara or, alternatively, get in touch with the ZX Machine Code Users' Club, nn xxxxxxxxx xxxx, xxxxxxxxx, xxxxxx xxxxxx xxnn nxx.
Incidentally, there's one nice little advantage to using my USR command instead of Uncle Clive's USR function. And that is that with my routine the final value of HL' is unimportant, whereas Sinclair Research's routine necessitates that HL' must contain 2758 on return.