© 1999 Peter Karlsson. As published in Go64/Commodore World issue 12/1999.
One of the most over-looked functions in the Commodore
BASIC repertoire is USR()
, the user-defined function. Perhaps
its because it by default does not do anything, perhaps it is because most
people never heard of it.
By Peter Karlsson
It is, however, very useful for adding functions to the BASIC, without having to modify the whole of the BASIC.
In his 2nd Basic Project, starting in the 8/1999 issue, Arndt Dettke
mentioned the lack of a modulo operator, one that calculates the remainder
of an integer division. In his example, he managed to do without it, but
what if it were really necessary? Sure, it could be implemented in BASIC by
using a DEF FN
user-defined function, or directly in-line using
INT()
and
some other interesting commands, but that is slow.
To alleviate the speed problem, we could always try to write the routine
in assembly code, and call it with SYS
. The problem is how to
pass along the arguments, and how to get the result back from the routine.
This is where USR()
comes in; it implicitly takes one argument
and makes it available for your assembly routine, and allows you to return a
result back to BASIC.
Now, there is just one problem, the modulo operator takes two input values, the numerator and the denominator. Well, I said that it implicitly takes one argument, the one within the parentheses, and while this is true, nothing stops us from taking our own arguments outside of the parentheses. We just have to know our way around the BASIC ROM to call the correct routines.
The implicit argument is passed to the USR()
routine in
FAC1, the first of BASICs two floating point accumulators, located
at addresses 97-102 (hex 61-66), where also the result is to be located.
The vector for the USR()
function is located at addresses
785-786 (hex 311-312) in the standard low/high format. Upon startup, the
contents of this vector is initialized to 45640 (hex B248), which will just
print the "ILLEGAL QUANTITY" error message on the screen. To test
it out, we can point it to an RTS instruction, which would just return the
argument passed to it. Try this:
POKE 49152,96 | put a RTS instruction at 49152 (hex C000) |
POKE 785,0:POKE 786,192 | set the USR() vector
|
PRINT USR(42) | should print 42 back to the screen |
A very nice property about the USR()
function is that
it is always available for redefinition, even if other BASIC externsions are
available. Thus, using USR()
when all you need is one function
ensures maximum compatibility.
The date of the (Christian) Easter Sunday is determined by the time when the full moon appears in early spring. Thanks to the nice regularity of the moon's orbit around the sun, this date can be calculated by a mathematical formula (and as a nice coincidence, it heavily relies on the modulo operator):
The sum of D and E gives the date, counting zero as 22nd March, with two exceptions: If you get the result 26th April, or if you get 25th April with D=28, E=6 and A greater than ten, you should subtract one week. The numbers for M and N depend on the century in question, according to the table below:
Century | M | N |
---|---|---|
1583-1599 | 22 | 2 |
1600-1699 | 22 | 2 |
1700-1799 | 23 | 3 |
1800-1899 | 23 | 4 |
1900-1999 | 24 | 5 |
2000-2099 | 24 | 5 |
2100-2199 | 24 | 6 |
2200-2299 | 25 | 0 |
2300-2399 | 26 | 1 |
2400-2499 | 25 | 1 |
2500-2599 | 26 | 2 |
The modulo operator MOD(X,Y)
can be implemented by
calculating X-INT(X/Y)*Y, which is exactly the assembly code we
are implementing. We are using a number of the BASIC ROM floating point
routines to implement this, since we both receive the arguments and have to
give the return values in floating point.
To get the second argument (the denominator) we first call the MOV2F routine to move the input argument out to memory, in the BASIC temporary storage area, addresses 87-91 (hex 57-5B). We then call the FRMNUM routine, which loads the next numeric argument in the BASIC source code and puts it in FAC1. If the argument is omitted, it will generate a SYNTAX ERROR, and if it is not a number, an expression or a numeric variable, it will generate a TYPE MISMATCH. This suits our purposes well.
This tweaking of the input arguments will result in the syntax
USR(X)Y
for the MOD(X,Y)
function. A problem with
this implementation is the operator precedence, to write
MOD(X,Y)*2 you need to use an extra set of parentheses:
(USR(X)Y)*2, because USR(X)Y*2 will be interpreted as
MOD(X,Y*2). While this does not look as good as a "real"
implementation of a MOD
function would do, you cannot really
add a MOD
function with a correct syntax in only 33 bytes, so
we have to live with it.
The assembly code is available on this month's
cover-disk, as well as an implementation of the
Easter Sunday algorithm. Please note that you
need to RUN the program once, or issue the command POKE
43,1:POKE 44,16 to see the BASIC listing, as it loads a custom
character set at the normal start of BASIC (address 2048, hex 800). The
USR()
routine is activated by SYSing the start
address, in this case, SYS 49152.
Mapping the Commodore 64 from Compute! is a good source to find information about what ROM calls are available, and so is Marko Mäkelä's Commodore 64 ROM disassembly. Both are available for download from the Project 64 website, http://project64.c64.org/