Commodore BASIC's USR Function

The USR function - what is it good for?
© 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.

An Example
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.

Argument Passing
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.

Easter Sunday
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):

A = YEAR MOD 19
B = YEAR MOD 4
C = YEAR MOD 7
D = (19*A + M) MOD 30
E = (2*B + 4*C + 6*D + N) MOD 7
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

Implementation
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.

Recommended Reading
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/


--------------------------------------------------------------------------------

This article in Deutsch (German), Svenska (Swedish).

Return to the article index