© 1999 Peter Karlsson. Übersetzung © 1999 Alexander Klock & Go64/Commodore World. Im Go64/Commodore World 12/1999 veröffentlicht.
Eine der am meistem unterschätzten und oft übersehenen
Funktionen des Commodore-BASIC ist USR()
, die
benutzerdefinierte Funktion. Vielleicht liegt es daran, dass sie von Haus
nichts bewirkt, vielleicht auch, weil die meisten Leute noch nie etwas von
ihr gehört habeln.
Von Peter Karlsson
Wie dem auch sei, es ist sehr nützlich, Funktionen dem BASIC hinzufügen zu können, ohne das BASIC selbst gleich ändern zu müssen.
In seinem zweiten BASIC-Projekt in GO64! Ausgabe 8/1999, beschäftigte
sich Arndt Dettke mit dem Bestimmten des Rests einer Division (also mit dem
Modulo-Operator MOD). In seinem Beispiel löste er das Problem ohne eine
Funktion, aber was, wenn es nicht anders gegangen wäre?
Natürlich könnte man eine DEF FN()
-Funktion definieren oder
direkt durch verwendung von INT()
zusammen mit anderen
interessanten Befehlen zum Ziel kommen (wie Arndt), jedoch ist die
Programmgeschwindigkeit dann eher niedrig.
Um das Geschwindigkeitsproblem aus dem Weg zu räumen, könnten wir
versuchen, die Routine in Maschinencode zu schreiben und sie mit
SYS
aufzurufen. Dabei entsteht aber das Problem, der Routine
Parameter zu übergeben und ein in BASIC zu verarbeitendes Resultat zu
erhalten, was mit dem SYS
-Befehl nur schwer möglich ist.
Hier steht die USR()
-Funktion an, den sie übergibt einfach ein
Argument an die Routine und liefert ein in BASIC verwendbares Ergebnis
züruck.
Jetzt haben wir noch ein Problem. Der Modulo-Operator benötigt zwei
Wertem den Zähler und den Nenner. Wie erwähnt, wertet die
USR()
-Funktion von sich aus ein Argument aus, das innerhalb
der Klammern. Wenn das so ist, gibt es eigentlich keinen Grund, warum man
nicht außerhalb der Klammern weitere, eigene Argumente wervenden sollte.
Wir müssen nur wissen, welche Routinen im C64-ROM für das Einlesen von
Parametern zuständig sind, damit wir sie aufrufen können.
Das Argument in der Klamer wird der USR()
-Routine in
FAC1 übergeben, dem ersten der drei Fließkomma-Akkumulatoren in
BASIC, zu finden bei den Adressen 97-102 (hex $61-66). Dort wird dann auch
das Ergebnis abgelegt.
Der Vektor für die USR()
-Funktion befindet sich bei den
Adressen 785-786 (hex $0311-$0312) im normalen Low/High-Format. Bei einem
Kaltstart wird dieser Vektor auf 45640 (hex $b248) gestzt, was einem
"ILLEGAL QUANTITY ERROR" auf dem Bildschirm ausgibt. Um den
Vektor zu testen, können wir ihn auf irgendeine eine RTS-Anweisung richten,
wodurch ledigleich der Wert in der USR
-Klammern ausgeben
würde.
POKE 49152,96 | schreibe eine RTS-Anweisung nach 49152 (hex $c000) |
POKE 785,0:POKE 786,192 | den USR() -Vektor auf $c000 setzen
|
PRINT USR(42) | sollte 42 als Ergebnis ausgeben |
Eine sehr gute Eigenshaft der USR()
-Funktion ist, dass
man sie jederzeit umdefinieren kann, selbst wenn BASIC-Erweiterungen
verwendet werden. Das heißt, dass die USR()
-Funktion maximale
Kompatibilität gewährleistet.
Das Datum des (christlichen) Ostersonntags wird vom Zeitpunkt des ersten Vollmonds im Frühjahr bestimmt. Dank der Regelmäßigkeit der Umlaufbahn des Mondes um Erde kann dieses Datum durch eine mathematische Formel berechnet werden, und - was für ein Zufall - man kann dazu den Modulo-Operator sehr gut gebrauchen:
Die Summe von D und ergibt das gesuchte Datum, wobei Null als 22. März festgelegt ist. Es gibt zwei Ausnahmen: Wenn das Ergebnis der 26. April ist, oder es ist der 25. April, wobei D=28, E=6 und A größer als 10 sein müssen, ziehen wir eine Woche ab. Die Ziffern für M und N hängen vom Jahrhundert ab, wie untenstehende Tabelle zeigt:
Jahrhundert | 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 |
Der Modul-Operator MOD(X,Y)
kann umgesetzt werden, indem man
X-INT(X/Y)*Y berechnet (siehe BASIC-Projekt in 8/99), was genau
im Maschinencode getan wird, den wir nun implementieren. Wir verwenden darin
einige Fließkomma-Routinen des BASIC-ROM, da sowohl die Argumente als auch
die Rückgabewerte als Fließkommazahlen behandelt werden.
Um das zweite Argument, den Nenner, zur Weiterverarbeitung nicht zu verlieren, rufen wir zunächst die MOV2F-Routine des C64-ROM auf, um den Eingabewert in den temporären Zwischenspeicher des BASIC bei Adresse 87-91 (hex $57-5b, FAC3) zu schicken. Dann rufen wir die FRMNUM-Routine auf, die das nächste Argument des BASIC-Sourcecodes lädt und in FAC1 ableft. Ist das Argument unzulässig, ergibt das einen "SYNTAX ERROR", und wenn es keine Zahl, ein Ausdruck oder eine numerische Variabel ist, ergibt es einen "TYPE MISMATCH ERROR".
Aus dieser Behandlung der Eingabewerte ergibt sich die Syntax
USR(X)Y
für unsere MOD(X,Y)
-Funktion. Ein Problem
bei dieser Lösung ist die Vorrangigkeit der Operatoren. Um
MOD(X,Y)*2 zu beschreiben, brauchen wir ein Paar Klammern extra:
(USR(X)Y)*2), weil USR(X)Y*2 als MOD(X,Y*2)
interpretiert würde. Das sieht zwar nicht so gut aus wie eine "echte"
Umsetzung einer MOD
-Funktion, aber wir können eine wirkliche
MOD
-Funktion mit korrekter Syntax nich in nur 33 Bytes
realisieren. Wir müssen mit unserer Lösung leben.
Der Assember-Code ist auf unserer Heftdiskette
zu finden, genauso wie eine Umsetzung des Algorithmus zur Bestimmung des
Ostersonntags. Bitte beachtet, dass ihr das
Programm einmal mit RUN starten oder die Befehle POKE
43,1:POKE 44,16 eingeben müsst, um das BASIC-Listung zu sehen, da dass
Programm selbst einem eigenen Zeichensatz an den regulären BASIC-Start bei
2048 (hex $0800) lädt. Die USR()
-Routine wird mit SYS
<Startadresse> aktiviert, in unseren Fall mit SYS
49152.
Im Mapping the Commodore 64 von Compute! könnt Ihr viele Informationen über ROM-Routinen finden. Ebenfalls empfehlenswert ist Marko Mäkeläs "Commodore 64 ROM disassembly". Beide können im Internet bei Project 64 heruntergeladen werden. Die Adresse lautet http://project64.c64.org/