Die USR-Funktion - wozu soll die gut sein?

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

Ein Beispiel

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.

Wertübergabe

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.

Ostersonntag

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:

JahrhundertMN
1583-1599222
1600-1699222
1700-1799233
1800-1899234
1900-1999245
2000-2099245
2100-2199246
2200-2299250
2300-2399261
2400-2499251
2500-2599262

Implementation

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.

Literaturhinweise

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/


Diese Artikel auf English (Englisch), Svenska (Schwedish).

Zurück zum Index