C 64/C 128
Kurs

Von Basic zu Assembler (Teil 7)

Neben einer Anzahl von Integer-Routinen und einer Technik zum Schreiben von Basic-Erweiterungen beschäftigen wir uns mit Assembler-Programmen, die sich selbst verändern.

Selbstmodifizierende Programme — also Programme, die sich im Verlauf der Abarbeitung selbst verändern — sind dem einen ein Graus, dem andern aber die Essenz der Raffinesse. Welcher Ansicht man auch immer sein mag: Es sind mit dieser Technik recht interessante Dinge möglich, die auf andere Weise nicht oder nur schwer realisierbar wären.

Programm und Daten

Wie unterscheidet unser Computer Programme und Daten? Sehen wir uns zuerst einmal an, wie das in Basic aussieht: Beide (Daten und Programme) werden im RAM streng voneinander getrennt (beim C 128 liegen sie sogar in unterschiedlichen Speicherbänken) und völlig unterschiedlich verwaltet. Deshalb ist die Selbstveränderung von Basic-Programmen auch mit allerlei Tricks verbunden, die entweder über POKEs den Programmspeicher beeinflussen oder im programmierten Direktmodus arbeiten. Ein simples Basic-Beispiel zeigt Listing 1:

10 rem *** selbstmodifikation prg.1 ***
20 print chr$(147):i=0
30 print "a";
40 i=i+1:if i=62 then end
50 poke2112,peek(2112)+1
60 goto 30
Listing 1. Selbstmodifikation mit Hilfe des POKE-Befehls im Basic-Programm

Dieses Programm für den C 64 (bei anderen Computern muß die Adresse in Zeile 50 entsprechend geändert werden) verändert während des Programmablaufes die Speicherstelle 2112. Dort befindet sich der Buchstabe A im PRINT-Befehl der Zeile 30. Durch den POKE-Befehl gelangt nach dem A ein B, dann ein C und so weiter in das PRINT-Argument. Das sehen Sie dann, wenn Sie sich nach dem Ablauf des Programms mit LIST noch einmal die PRINT-Anweisung ansehen: Das A ist verschwunden, statt dessen ist dort ein Grafikzeichen (bei eingeschalteter Groß- und Kleinschreibung) oder der griechische Buchstabe Pi (bei Großschreibung) zu finden. Die andere Technik, also die, die im programmierten Direktmodus arbeitet, bedient sich des Tastaturpuffers. Falls Sie darüber mehr wissen möchten, dann lesen Sie bitte den Artikel »Lernen Sie Ihren Commodore 64 kennen«, Teil 4, in der Ausgabe 8/85 der Zeitschrift Happy-Computer, Seite 45ff. C 128-Benutzer sollten die Ausgabe 7/86 des 64'er-Magazins auf Seite 85 aufschlagen: Dort sind allerlei Verwendungsmöglichkeiten dieser Technik für den großen Bruder des C 64 vorgeführt. Soweit also das Ganze in Basic, wie verhält es sich in Assembler?

Hier existiert für den Computer nur eine lange Straße aufeinanderfolgender Speicherzellen. Der Zentralprozesser orientiert sich am Programmzähler, in dem sich die gerade aktuelle Anschrift befindet. In jeder Hausnummer findet die CPU irgendeinen Code, der sie veranlaßt, darauf zu reagieren. Alle derartigen Codes führen zu Veränderungen von Speicherinhalten — und sei es auch nur das Hochzählen des Programmzählers beim NOP-Befehl, das Chaos beim Programmabsturz oder auch das Eintragen von ASCII-Werten in den Bildschirmspeicher. Mal liegen diese Veränderungen weit weg vom Programm-Code, mal näher dran: Nichts hindert uns, auch in dem Speicherteil Änderungen vorzunehmen, in dem das Programm abgelegt ist, was uns mit Assemblern wie dem Hypra-Ass leicht fällt. Listing 2 zeigt, wie man Vergleichbares in Maschinensprache erreichen kann:

10  -.BA $3000
20  -;********************************
30  -; PROGRAMM 2: SELBSTMODIFIKATION
40  -;********************************
50  -;
60  -CODE      LDA #$01       ;BUCHSTABE A
70  -          LDX #$01       ;FARBE WEISS
80  -BILD      STA $0400      ;BILDSCHIRMSPEICHER
90  -FARB      STX $D800      ;FARBRAM
100 -          INC FARB+1
110 -          INC BILD+1
120 -          INC CODE+1
130 -          BNE CODE
140 -          BRK
Listing 2. Zum Prinzip der Selbstmodifikation in Assemblerprogrammen

Das Programm ist für die älteren Versionen des C 64 geschrieben — daher die Belegung des Bildschirmfarbspeichers —, läuft aber auch auf den anderen Versionen, bei denen man die Zeilen, die sich auf die Farben beziehen, weglassen kann. Erinnern Sie sich bitte an die Art, wie der 6502 und seine kompatiblen Nachkommen Adressen im Speicher ablegen: Wenn wir ein Assemblerprogramm schreiben:

STX $D800

dann findet sich im Speicher die Code-Folge:

Speicherstelle Code Bedeutung
Farb 8E Code für absolutes STX
Farb+1 00 LSB der Adresse $D800
Farb+2 D8 MSB der Adresse $D800

Deshalb erhöhen wir Farb + 1 und Bild + 1.

Ebenso wie im Basic-Beispiel zeigt sich auch im Listing 2 ein Nachteil dieser Art der Programmierung: Das Programm kann kein zweites Mal gestartet werden — eben weil wir es verändert haben. Jedenfalls leistet es beim Neustart nicht mehr genau dasselbe. Sehen Sie sich nach dem Programmdurchlauf einmal das Disassemblerlisting an, dann finden Sie in den veränderten Zeilen:

CODE  LDA  #$00
BILD  STA  $04FF
FARB  STX  $D8FF

Beim Starten dieses veränderten Programms wird zuerst der Klammeraffe (das ist das Zeichen mit dem Code 00) in die Bildschirmspeicherstelle $04FF geschrieben. Erst danach läuft alles seinen gewohnten Gang, weil $FF+1 als $00 verstanden wird. Im Falle dieses Programms hätten wir die Schwierigkeit leicht umgehen können: Wenn wir nämlich anstelle des A mit dem Klammeraffen angefangen hätten, sähe unser Programm nach dem Ablauf genauso aus wie vorher.

Es ist also erforderlich, in solche selbstmodifizierenden Programme einen Reparaturmechanismus einzubauen, der die veränderten Speicherinhalte wieder auf einen definierten Startwert bringt. Das geschieht durch eine Initialisierung vor dem eigentlichen Programm oder durch Rückstellen aller beeinflußten Speicherplätze nach dem Arbeitsteil — was eine weitere Selbstmodifikation wäre. Anstelle des BRK im Listing 2 stünde dann beispielsweise:

STX  CODE + 1
DEX
STX  BILD + 1
STX  FARB + 1
BRK

Zur Übung können Sie ja mal die andere Möglichkeit — also die Initialisierung vor dem eigentlichen Programm — einbauen.

Anwendung der Selbstmodifikation

Vielleicht haben Sie nun schon eine Vorstellung davon, was für ein mächtiges Programmierinstrument man mit dieser Technik in der Hand hat. Wir haben ja schon im Listing 2 eine Schleife geschrieben und sind dabei ohne die indirekte Adressierung ausgekommen. Der Schritt zur 16-Bit-Schleife ist nun nicht mehr weit: Man veranlaßt einfach, daß nicht nur die LSBs der Adressen (BILD und FARB) anders eingetragen werden, sondern auch die MSBs nach jedem kompletten 8-Bit-Schleifen-Durchlauf. Florian Müller hat sich die Mühe gemacht, in seinem Kurs »Effektives Programmieren in Assembler«, Kapitel 10 (erschienen im Assembler-Sonderheft des 64'er-Magazins, Sonderheft 8/85, Seite 97ff.) allerlei Varianten der Anwendung von Selbstmodifikation in Programmen vorzustellen. Deshalb soll hier nur ein kleiner Überblick gegeben werden.

So ist es beispielsweise möglich, eine ganze Reihe von Befehlen zu simulieren, die es im Sprachschatz des 6502-Assemblers nicht gibt: indirekte JSR-Sprünge (es gibt nur den indirekten JMP-Befehl), indirekte Schiebe-, Dekrementier- und Inkrementierbefehle. Befehle mit unmittelbarer Adressierung (beispielsweise CMP #$20)können veränderliche Argumente erhalten, man kann auf diese Weise beispielsweise den Inhalt des Akku und des X-Registers addieren:

      STX  ADD+1   ;X-Register hinter ADC-Befehl ablegen
      ...          ;eventuell weiteres Programm
      CLC          ;Carry-Bit freimachen vor Addition
ADD   ADC  #$FF    ;$FF ist nur ein Füllwert (Dummy)

Komplette Befehle kann man durch Eintragen des Befehls-Codes umändern, beispielsweise aus einem BCS (Code $BO) ein BCC (Code $90) erzeugen, Unterprogrammaufrufe verhindern oder erlauben (durch Eintragen des Codes für den BIT Befehl anstelle des JSR-Codes). Ganze Programmsequenzen lassen sich durch das Programm selbst umschreiben. Sie sehen: Der Möglichkeiten gibt es viele und der Programmiererfantasie sind nur wenige Grenzen gesetzt.

Ein kurzer Blick in die CHRGET-Routine

Eine andere Anwendung selbstmodifizierender Programmtechniken befindet sich schon fix und fertig in unserem Computer (hier ist speziell der C 64 gemeint): die sogenannte CHRGET-Routine Laden Sie doch einmal den SMON und blicken Sie mittels

D 0073 008B

in den unteren RAM-Bereich hinein. Was Sie dann auf dem Bildschirm sehen, ist dieses kleine Programm, das die Aufgabe hat den Inhalt des Basic-Speichers Byte für Byte zu lesen und mit bestimmten Markierungen an den Basic-Interpreter zu übergeben. Es handelt sich um eines der wichtigsten Werkzeuge des Interpreters. Wie es genau funktioniert, sollten Sie einmal nachlesen im Kapitel 25 des Assembler-Kurses (Sonderheft 8/85, Seite 26), hier würde uns die Besprechung zu weit vom Thema wegführen. Zum Thema aber passen die ersten vier Zeilen:

0073      INC $7A
0075      BNE $0079
0077      INC $7B
0079      LDA $0225   ;$0225 steht hier nur als Dummy
007C      ...

Wie Sie sicherlich bemerken, steht die Adresse, aus der etwas in den Akku geladen werden soll (Zeile 0079), bei $7A (das LSB) und $7B (das MSB). Was also in der ersten Zeile passiert, ist das Hochzählen der Ladeadresse, die gleich benutzt werden soll. Die nächste Zeile prüft, ob dabei ein Überlauf ($FF+1) stattgefunden hat. In dem Fall ist das Zero-Flag gesetzt, der Sprung nach 0079 findet nicht statt. Zuerst wird noch das MSB der Ladeadresse erhöht. Wie auch immer, die Adresse in $7A/$7B ist nun um 1 größer geworden und der Inhalt der so angezeigten Speicherstelle wird in den Akku geladen.

Bevor wir uns dem zweiten Beispiel zuwenden, noch eine Bemerkung zu einem Nachteil der selbstverändernden Programme: Wie Sie sehen, steht die CHRGET-Routine im RAM — ganz im Gegensatz zur ganzen sonstigen im ROM stehenden Software des C 64. Das hört sich vielleicht trivial an, ist aber schon vorgekommen: Eben weil man aus dem ROM nur lesen, nicht aber hineinschreiben kann, darf auch kein Programm oder auch nur ein Teil davon dort vorhanden sein, das selbstverändernde Techniken benutzt. Wenn Sie EPROMs selbst brennen, sind Sie vielleicht schon einmal über diese Falle gestolpert.

Programmierer einer Befehlserweiterung

Dies ist ein umfangreiches Thema, bei dem wir eine Anwendung der selbstmodifizierenden Programmtechnik kennenlernen, aber auch ein Verfahren, wie man neue Basic-Befehle einbinden kann. Außerdem wird uns eine ganze Palette von Interpreter-Routinen geläufig. Wir werden erstmalig mit Tabellen arbeiten und auch die eben erwähnte CHRGET-Routine bewußt einsetzen. Weil viele Leser wissen wollen, wie man die verschiedenen mathematischen Interpreter-Routinen ansteuert, werden wir dem Basic des C 64 noch einige mathematische Funktionen hinzufügen. Als Listing 3 finden Sie es weiter unten abgedruckt. Listing 4 ist das fertige Programm, das Sie mit dem MSE eingeben müssen.

Flußdiagramm mit Entscheidungsrauten und Verbindungslinien auf grünem Grund
Bild 1. Das Flußdiagramm zum Modul 1 der Befehlserweiterung

Vielen Benutzern ist das Basic 2.0 zu dürftig. Auch wenn man nach mathematischen Funktionen sucht, sind es relativ wenige. So stört es beispielsweise, daß man vom gewohnten Gradmaß der Winkel bei Winkelfunktionen wie SIN, COS und TAN abweichen und erst noch auf Bogenmaß umrechnen muß. Außerdem sind es zu wenig Winkelfunktionen und die Umkehrfunktionen (arcus...)sind gar nur in einer einzigen Form vertreten: ATN. Wenn man mit Logarithmen arbeiten möchte, muß man sich immer auf die natürlichen (LOG ist nämlich In) umstellen, statt mit den normalen dekadischen arbeiten zu können. Unser aus zehn Modulen bestehendes Programm erweitert nun das Basic um neun Befehle.

Der erste davon heißt AUS. Damit kann man diese Erweiterung abschalten, falls sie nicht benötigt wird.

Es folgen zwei Funktionen zur Umrechnung von Gradmaß in Bogenmaß und umgekehrt. BOG ermittelt das Bogenmaß eines Winkels:

Aufruf: BOG,Winkel

GRD geht den umgekehrten Weg der Berechnung des Gradmaßes eines im Bogenmaß angegebenen Winkels:

Aufruf: GRD,Winkel

Sind Sie das Rechnen mit dem durch LOG erzeugten natürlichen Logarithmus leid, dann verwenden Sie DLGR für den normalen dekadischen Logarithmus:

Aufruf: DLGR,Argument

Bei den trigonometrischen Funktionen steht Ihnen nun neben SIN, COS und TAN auch der Kotangens COT zur Verfügung:

Aufruf: COT,Winkel im Bogenmaß

Die bislang nur durch recht komplizierte Formeln zu ermittelnden Umkehrfunktionen (die im Handbuch sogar teilweise falsch angegeben sind) des Sinus, Cosinus und Kotangens erreichen Sie durch die nächsten drei Funktionen ARCS, ARCC und ACOT:

Aufruf: ARCS,Argument
ARCC,Argument
ACOT,Argument

Ein kleines Bonbon noch am Schluß: Ein Polynom ist ein Ausdruck der Form:

y = a0 + a1x + a2x2 + a3x3 + ... anxn

Dabei ist n der Grad des Polynoms. Die einzelnen »a« nennt man Koeffizienten. Durch den neuen Befehl POLY kann solch ein Polynom schnell berechnet werden, indem man angibt, für welchen Wert »x« man die Berechnung ausführt, welchen Grad das Polynom hat und wie die Koeffizienten heißen:

Aufruf: POLY,x,n,an,an-1,...,a1,a0

Solche Polynome spielen in vielen Bereichen der Mathematik und der Statistik eine wichtige Rolle.

Noch zu einer Besonderheit all dieser Funktionen, die ihren Aufruf betrifft. Bei der Beschreibung des ersten Moduls werden Sie sehen, daß die hier gewählte Methode der Befehlserweiterung relativ einfach ist. Das bietet zwar den Vorzug (der im Rahmen dieses Kurses erst einmal Vorrang genießt), daß man leicht verstehen kann, wie das Ganze funktioniert, hat aber in der Handhabung der neuen Befehle einige Nachteile Ein Nachteil betrifft die Ausgabe der durch die neuen Funktionen ermittelten Ergebnisse. Während man beim Sinus beispielsweise gewohnt ist, A=SIN(x) oder B=SQR(SIN(x)) zu schreiben, die Funktion selbst also wie einen Variablenwert verwenden Kann, geht das bei unseren Funktionen nicht. Das hätte einen tieferen Eingriff in die Interpreterschleife erfordert. Andererseits war es uns zu primitiv, lediglich das Ergebnis nach der Berechnung auf dem Bildschirm ausgeben zu lassen: Man sollte schon damit weiterrechnen können. Der Kompromiß sieht etwas merkwürdig aus, funktioniert aber (und später, wenn wir weitere Formen der Befehlserweiterung kennengelernt haben, können wir das Programm auch umbauen). Das Ergebnis steht immer in der Variablen, die als letzte vor dem Funktionsaufruf genannt worden ist. Um also in der Variablen A das Bogenmaß eines Winkels zu speichern, ruft man auf:

A=A:BOG,Winkel

oder um den dekadischen Logarithmus eines Ausdruckes in A abzulegen beispielsweise:

A=0:DLGR,SQR(x)

Wenn man zwei von unseren neuen Funktionen nacheinander verwendet, beispielsweise hier den Kotangens eines Winkels, der zuvor ins Bogenmaß umgerechnet wurde, dann kann man schreiben:

B=0:BOG,Winkel:A=A:COT,B

Es ist aber auch ein abgekürzter Weg möglich, denn ein interner Zeiger weist weiterhin auf die bezeichnete Variable:

A=A:BOG,Winkel:COT,A

In beiden Fällen steht hinterher der Ergebniswert in der Variablen A, die nun ganz normal weiterverwendet werden kann.

Wie startet man diese Erweiterung? Das kommt ganz darauf an, wohin Sie sie im Speicher legen. Im Modul 1 wurde in Zeile 110 willkürlich der Start nach $5000 gelegt, was den Start durch SYS 20480 ermöglicht. Falls Sie diesem Vorschlag folgen, oder die Erweiterung statt nach $C000 (dann erfolgt der Start durch SYS 49152) in den Basic-Speicherraum legen, dann achten Sie bitte darauf, den betreffenden Speicherbereich vor dem Überschreiben durch Basic-Text, Variable oder Strings zu schützen.

Modul 1 unseres Programmes

Sehen wir uns zunächst die Label an, die im gesamten Programm benutzt werden. Die ersten fünf Adressen sind Zeiger in der Form LSB/MSB, von denen hier immer nur die niedrigere Adresse genannt wird, weil man im Programm mit LABEL und LABEL+1 arbeiten kann. Genaue Beschreibungen dieser Vektoren finden Sie im Kurs »Memory Map« von Dr. Hauck (komplett veröffentlicht im Sonderheft 7/86 des 64'er-Magazins). Deshalb soll hier nur eine kurze Erläuterung dieser Vektoren folgen.

Die Betriebssystemroutine ORPNT ist für uns nicht interessant, weil auch immer mit einem Zeiger auf die zuletzt angesprochene Variable gearbeitet wird. Erinnern Sie sich an unsere etwas ungewöhnliche Ausgabeform? Dazu brauchen wir diesen Zeiger.

CHRGET, CHRGOT und TXTPTR gehören alle zur CHRGET-Routine und' dienen dazu, das jeweils nächste Byte aus dem Basic-Programmtext zu holen und zu identifizieren. Hauck beschreibt diese Funktion recht gut.

Auch IGONE ist von Hauck erklärt worden. Das ist ein Zeiger, der normalerweise nach $ATE4 zeigt (von uns in Zeile 240 als GONEI bezeichnet) und für die Auswertung des Basic-Textes bedeutsam ist. Wir verbiegen diesen Vektor auf unser eigenes Programm, zu dem wir noch kommen werden.

Die folgenden Adressen sind Interpreter-Routinen, die wir uns nutzbar machen, meist solche mathematischer Art. Dazu noch einige Anmerkungen: Wir werden in einer späteren Folge noch genau auf die sogenannten Fließkommazahlen, ihre verschiedenen Speicherformate und die beiden Fließkomma-Akkumulatoren FAC und ARG eingehen. Sie können all das aber auch noch nachlesen im Assembler-Kurs (vollständig erschienen im Sonderheft 8/85). Die übliche Art der Zahlenverarbeitung im C 64 (und auch im C 128) ist die Verarbeitung im Fließkommaformat. Dabei spielt der sogenannte Fließkomma-Akkumulator 1, der allgemein FAC genannt wird und der in den Speicherstellen $61 bis $66 steht, eine ähnlich zentrale Rolle wie der Akkumulator bei den einfachen Assembler-Programmen. Die meisten mathematischen Routinen erwarten das Argument im FAC und geben das Ergebnis im FAC aus. Manchmal ist die Verwendung eines Hilfsakkumulators sinnvoll, der sogenannte ARG ($69 und $6E). Es gibt im Prinzip zwei Formate für Fließkommazahlen in unserem Computer: Als FLPT-Format bezeichne ich die Speicherung der Daten im FAC und ARG in 6 Byte, als MFLPT-Format die im normalen Speicherraum, die nur 5 Byte beansprucht.

Damit ergibt sich die Notwendigkeit folgender Routinen:

  1. Routinen, die Werte als Zahlen, Variable oder mathematische Ausdrücke aus dem Basic-Text lesen, ins FLPT-Format bringen und im FAC ablegen.
  2. Routinen, die Zahlen aus dem FAC in den normalen Speicher transportieren und dabei die Übersetzung ins MFLPT-Format leisten und Routinen, die den umgekehrten Weg gehen.
  3. Routinen, die die nötigen mathematischen Operationen an der Zahl ausführen, die im FAC steht und das Ergebnis im FAC ablegen.
  4. Routinen, die dasselbe wie in 3) ausgedrückt leisten, dazu aber noch weitere Zahlen verwenden, die im normalen Speicherraum im MFLPT-Format vorhanden sind.

Damit beenden wir diesen Teil des Kurses. In der nächsten Ausgabe werden wir das Modul 1 fertig besprechen und uns die Label näher ansehen.

(Heimo Ponath/dm)
10   -;******************************
20   -;*                            *
30   -;*    PROGRAMM 3 / MODUL 1    *
40   -;*      Erweiterung der       *
50   -;*    Interpreterschleife     *
60   -;*                            *
70   -;*  Heimo Ponnath  HH  1986   *
80   -;*                            *
90   -;******************************
100  -;
110  -          .ba $5000
120  -;
130  -;------ Labels ----------------
140  -;
150  -          .eq forpnt=$49 ;Variablenzeiger
160  -          .eq chrget=$73 ;chrget-Routine
170  -          .eq chrgot=$79 ;chrgot-Routine
180  -          .eq txtptr=$7a ;chrget-Zeiger
190  -;
200  -          .eq igone=$0308;Vektor zum Routinenaufruf
210  -;
220  -          .eq error=$a437;Fehlermeldung und READY
230  -          .eq newstt=$a7ae;interpreterschleife
240  -          .eq gone1=$a7e4;alter Inhalt von igone
250  -          .eq intend=$a7e7;Ende interpreterschleife
260  -          .eq frmnum=$ad8a;Numerischen Wert einlesen
270  -          .eq chkcom=$aefd;Komma ueberlesen
280  -          .eq facinx=$b1aa;FAC zu Integer in Y/A
290  -          .eq getbytc=$b79b;Byte in X-Register einlesen
300  -          .eq fsub=$b850 ;FAC=Mem-FAC
310  -          .eq eins=$b9bc ;das ist 1
320  -          .eq log=$b9ea  ;FAC=log(FAC)
330  -          .eq fmult=$ba28;FAC=FAC*Mem
340  -          .eq fdiv=$bb0f ;FAC=Mem/FAC
350  -          .eq movfm=$bba2;Mem in FAC
360  -          .eq movmf=$bbd4;FAC in Speicher
370  -          .eq abs=$bc58  ;FAC=abs(FAC)
380  -          .eq fcomp=$bc5b;Vergleich FAC mit Mem
390  -          .eq sqr=$bf71  ;FAC=sqr(FAC)
400  -          .eq polyx=$e059;Polynomauswertung
410  -          .eq cos=$e264  ;FAC=cos(FAC)
420  -          .eq sin=$e26b  ;FAC=sin(FAC)
430  -          .eq pihalb=$e2e0;das ist Pi/2
440  -          .eq atn=$e30e  ;FAC=atn(FAC)
450  -;
460  -          .eq polyvar=polytab-4
470  -;
480  -;------ Initialisierung -------
490  -;
500  -init      lda #<(start)  ;lsb eigene Routine
510  -          sta igone      ;in vektor schreiben
520  -          lda #>(start)  ;msb
530  -          sta igone+1
540  -          rts
550  -;
560  -;------ Abschalten ------------
570  -;
580  -aus       lda #<(gone1)  ;vektor auf
590  -          sta igone      ;Normalwert
600  -          lda #>(gone1)  ;zurueckstellen
610  -          sta igone+1
620  -          rts
630  -;
640  -;-Erweiterte Interpreterschleife-
650  -;
660  -start     jsr chrget     ;Zeichen holen
670  -          cmp #$60       ;Buchstabe?
680  -          bcs ende       ;Basic-Code
690  -          cmp #$41       ;Buchstabe A ?
700  -          bcc ende       ;Sonderzeichen
710  -          sta akku       ;Akku sichern
720  -          ldx #$00
730  -          stx befnr      ;Befehlsnr. auf 0
740  -int1      ldy #$00
750  -          inc befnr      ;Befehlsnr. + 1
760  -          lda beftab,x   ;Zeichen aus Befehlstabelle
770  -          bne int2       ;kein Trennzeichen
780  -;
790  -          lda akku       ;Zurueck ins
800  -ende      jsr chrgot     ;normale Basic
810  -          jmp intend     ;springen
820  -;
830  -;--- Adresse suchen -----------
840  -;
850  -int2      cmp (txtptr),y ;Vergleich mit Basictext
860  -          bne rest       ;ungleich
870  -          iny            ;Basictextindex+1
880  -          inx            ;Befehltab.-Index+1
890  -          lda beftab,x   ;naechstes Zeichen
900  -          bne int2       ;pruefen
910  -          clc
920  -          tya            ;Befehlsindex um
930  -          adc txtptr     ;Befehlslaenge
940  -          sta txtptr     ;erhoehen
950  -          bcc lab1       ;Uebertrag?
960  -          inc txtptr+1   ;msb erhoehen
970  -lab1      lda befnr      ;Befehlsnr.
980  -          asl            ;verdoppeln
990  -          tax            ;und als Index in
1000 -          lda sprtab,x   ;Sprungtabelle
1010 -          sta sprung+1   ;lsb Sprung
1020 -          lda sprtab1,x  ;msb lesen
1030 -          sta sprung+2   ;msb
1040 -;
1050 -;-- Selbstmodifizierender Teil --
1060 -;
1070 -sprung    jsr $ffff      ;Dummy
1080 -;
1090 -;-- Zurueck zum Interpreter -----
1100 -;
1110 -          jmp ende
1120 -;
1130 -;-restl. Befehlstext ueberlesen--
1140 -;
1150 -rest      inx
1160 -          lda beftab,x
1170 -          bne rest       ;bis Trennzeichen
1180 -          inx
1190 -          jmp int1       ;naechster Befehl
1200 -;
1210 -;
1220 -;*********************************
1230 -;*                               *
1240 -;*      Programm 3  Modul 2      *
1250 -;* Umrechnung in Bogenmass (BOG) *
1260 -;*                               *
1270 -;*********************************
1280 -;
1290 -bog       lda forpnt     ;Variablenzeiger auf Stapel
1300 -          pha
1310 -          lda forpnt+1
1320 -          pha
1330 -          jsr chkcom     ;Komma pruefen
1340 -          jsr frmnum     ;Numerischen Ausdruck holen
1350 -          lda #<(bogfak) ;Faktor Pi/180
1360 -          ldy #>(bogfak)
1370 -          jsr fmult      ;Multiplikation
1380 -          pla            ;x/y auf Variable
1390 -          tay
1400 -          pla
1410 -          tax
1420 -          jsr movmf      ;FAC in Variable
1430 -          rts
1440 -;
1450 -;
1460 -;*********************************
1470 -;*                               *
1480 -;*      Programm 3  Modul 3      *
1490 -;* Umrechnung in Gradmass (GRD)  *
1500 -;*                               *
1510 -;*********************************
1520 -;
1530 -grd       lda forpnt     ;Variablenzeiger auf Stapel
1540 -          pha
1550 -          lda forpnt+1
1560 -          pha
1570 -          jsr chkcom     ;Komma pruefen
1580 -          jsr frmnum     ;Numerischen Ausdruck holen
1590 -          lda #<(grdfak) ;Faktor 180/Pi
1600 -          ldy #>(grdfak)
1610 -          jsr fmult      ;Multiplikation
1620 -          pla            ;x/y auf Variable
1630 -          tay
1640 -          pla
1650 -          tax
1660 -          jsr movmf      ;FAC in Variable
1670 -          rts
1680 -;
1690 -;
1700 -;*********************************
1710 -;*                               *
1720 -;*      Programm 3  Modul 4      *
1730 -;* Dekadischer Logarithmus(DLGR) *
1740 -;*                               *
1750 -;*********************************
1760 -;
1770 -dlog      lda forpnt     ;Variablenzeiger auf Stapel
1780 -          pha
1790 -          lda forpnt+1
1800 -          pha
1810 -          jsr chkcom     ;Komma pruefen
1820 -          jsr frmnum     ;Numerischen Ausdruck holen
1830 -          jsr log        ;Logarithmieren
1840 -          lda #<(logfak) ;Faktor 1/ln10
1850 -          ldy #>(logfak)
1860 -          jsr fmult      ;Multiplikation
1870 -          pla            ;x/y auf Variable
1880 -          tay
1890 -          pla
1900 -          tax
1910 -          jsr movmf      ;FAC in Variable
1920 -          rts
1930 -;
1940 -;
1950 -;*********************************
1960 -;*                               *
1970 -;*      Programm 3  Modul 5      *
1980 -;*    Kotangensfunktion (COT)    *
1990 -;*                               *
2000 -;*********************************
2010 -;
2020 -cot       lda forpnt     ;Variablenzeiger auf Stapel
2030 -          pha
2040 -          lda forpnt+1
2050 -          pha
2060 -          jsr chkcom     ;Komma pruefen
2070 -          jsr frmnum     ;Numerischen Ausdruck holen
2080 -          ldx #<(zwsp1)  ;und beiseite legen
2090 -          ldy #>(zwsp1)
2100 -          jsr movmf
2110 -          jsr cos        ;Cosinus bilden
2120 -          ldx #<(zwsp2)  ;und sichern
2130 -          ldy #>(zwsp2)
2140 -          jsr movmf
2150 -          lda #<(zwsp1)  ;Wert zurueckholen
2160 -          ldy #>(zwsp1)
2170 -          jsr movfm
2180 -          jsr sin        ;Sinus bilden
2190 -          lda #<(zwsp2)  ;Division
2200 -          ldy #>(zwsp2)  ;FAC=zwsp2/FAC
2210 -          jsr fdiv
2220 -          pla            ;x/y auf Variable
2230 -          tay
2240 -          pla
2250 -          tax
2260 -          jsr movmf      ;FAC in Variable
2270 -          rts
2280 -;
2290 -;
2300 -;*********************************
2310 -;*                               *
2320 -;*      Programm 3  Modul 6      *
2330 -;* Arcuscotangensfunktion (ACOT) *
2340 -;*                               *
2350 -;*********************************
2360 -;
2370 -acot      lda forpnt     ;Variablenzeiger auf Stapel
2380 -          pha
2390 -          lda forpnt+1
2400 -          pha
2410 -          jsr chkcom     ;Komma pruefen
2420 -          jsr frmnum     ;Numerischen Ausdruck holen
2430 -          jsr atn        ;Arcustangens bilden
2440 -          lda #<(pihalb) ;Zeiger auf Pi/2
2450 -          ldy #>(pihalb)
2460 -          jsr fsub       ;FAC=pihalb-FAC
2470 -          pla            ;x/y auf Variable
2480 -          tay
2490 -          pla
2500 -          tax
2510 -          jsr movmf      ;FAC in Variable
2520 -          rts
2530 -;
2540 -;
2550 -;*********************************
2560 -;*                               *
2570 -;*      Programm 3  Modul 7      *
2580 -;*   Arcussinusfunktion (ARCS)   *
2590 -;*                               *
2600 -;*********************************
2610 -;
2620 -asin      lda forpnt     ;Variablenzeiger auf Stapel
2630 -          pha
2640 -          lda forpnt+1
2650 -          pha
2660 -          lda #$00       ;Flagge auf Null
2670 -          sta flag       ;setzen
2680 -          jsr chkcom     ;Komma pruefen
2690 -          jsr frmnum     ;Numerischen Ausdruck holen
2700 -easin     ldx #<(zwsp1)  ;und sichern
2710 -          ldy #>(zwsp1)
2720 -          jsr movmf
2730 -          jsr abs        ;Absolutwert berechnen
2740 -          lda #<(eins)   ;Vergleich mit
2750 -          ldy #>(eins)   ;Fliesskommawert
2760 -          jsr fcomp      ;von 1
2770 -          beq argok      ;gleich 1
2780 -          rol            ;Bit 7 in Carry
2790 -          bcs argok      ;kleiner 1
2800 -          pla            ;Stapel leeren
2810 -          pla
2820 -          ldx #$0e       ;Fehlernummer
2830 -          jmp error      ;Fehler und Ready
2840 -argok     lda #<(zwsp1)  ;Wert zurueck
2850 -          ldy #>(zwsp1)  ;in FAC
2860 -          jsr movfm
2870 -          lda #<(zwsp1)  ;Bilden von
2880 -          ldy #>(zwsp1)  ;x*x
2890 -          jsr fmult      ;FAC=x^2
2900 -          lda #<(eins)   ;Bilden von
2910 -          ldy #>(eins)   ;1-FAC
2920 -          jsr fsub       ;FAC=1-x^2
2930 -          jsr sqr        ;FAC=SQR(1-x^2)
2940 -          lda #<(zwsp1)  ;Bilden von
2950 -          ldy #>(zwsp1)  ;x/FAC
2960 -          jsr fdiv       ;FAC=x/SQR(1-x^2)
2970 -          jsr atn        ;FAC=ASIN!
2980 -          lda flag       ;Flagge pruefen
2990 -          bne retour     ;zurueck zu ACOS
3000 -          pla            ;in Variable
3010 -          tay            ;schreiben
3020 -          pla
3030 -          tax
3040 -          jsr movmf      ;FAC in Variable
3050 -retour    rts
3060 -;
3070 -;
3080 -;*********************************
3090 -;*                               *
3100 -;*      Programm 3  Modul 8      *
3110 -;*  Arcuscosinusfunktion (ARCC)  *
3120 -;*                               *
3130 -;*********************************
3140 -;
3150 -acos      lda forpnt     ;Variablenzeiger auf Stapel
3160 -          pha
3170 -          lda forpnt+1
3180 -          pha
3190 -          lda #$ff       ;Flagge auf 255
3200 -          sta flag       ;setzen
3210 -          jsr chkcom     ;Komma pruefen
3220 -          jsr frmnum     ;Numerischen Ausdruck holen
3230 -          jsr easin      ;Berechnen des asin
3240 -          lda #<(pihalb) ;Bilden der
3250 -          ldy #>(pihalb) ;Differenz
3260 -          jsr fsub       ;FAC=pihalb-asin=acos
3270 -          lda #$00       ;Flagge zurueckstellen
3280 -          sta flag
3290 -          pla            ;in Variable
3300 -          tay            ;schreiben
3310 -          pla
3320 -          tax
3330 -          jsr movmf      ;FAC in Variable
3340 -          rts
3350 -;
3360 -;
3370 -;*********************************
3380 -;*                               *
3390 -;*      Programm 3  Modul 9      *
3400 -;*  Polynomberechnung    (POLY)  *
3410 -;*                               *
3420 -;*********************************
3430 -;
3440 -poly      lda forpnt     ;Variablenzeiger auf Stapel
3450 -          pha
3460 -          lda forpnt+1
3470 -          pha
3480 -          jsr chkcom     ;Komma pruefen
3490 -          jsr frmnum     ;Numerischen Ausdruck holen
3500 -          ldx #<(zwsp1)  ;und sichern
3510 -          ldy #>(zwsp1)
3520 -          jsr movmf
3530 -          jsr chkcom     ;Polynomgrad
3540 -          jsr frmnum     ;naechsteZahlholen
3550 -          jsr facinx     ;in Integer wandeln in Y/A
3560 -          sty polytab    ;und ablegen
3570 -          iny            ;Koeffizientenzahl
3580 -m0        sty flag       ;sichern
3590 -          clc            ;Addieren
3600 -          lda m1+1       ;von 5 zur
3610 -          adc #$05       ;Ablegeadresse
3620 -          sta m1+1
3630 -          lda m2+1
3640 -          adc #$00
3650 -          sta m2+1
3660 -          jsr chkcom     ;naechster
3670 -          jsr frmnum     ;Koeffizient
3680 -m1        ldx #<(polyvar);lsb Zieladresse
3690 -m2        ldy #>(polyvar);msb
3700 -          jsr movmf      ;ablegen
3710 -          ldy flag       ;Zaehler laden
3720 -          dey
3730 -          bne m0         ;noch Koeffizienten?
3740 -          lda #<(polyvar);restaurieren
3750 -          sta m1+1       ;der Zieladresse
3760 -          lda #>(polyvar)
3770 -          sta m2+1
3780 -          lda #<(zwsp1)  ;Argument
3790 -          ldy #>(zwsp1)  ;zurueck
3800 -          jsr movfm      ;in FAC
3810 -          lda #<(polytab);Aufruf
3820 -          ldy #>(polytab);der Routine
3830 -          jsr polyx      ;FAC=POLY(x)
3840 -          pla            ;in Variable
3850 -          tay            ;schreiben
3860 -          pla
3870 -          tax
3880 -          jsr movmf      ;FAC in Variable
3890 -          rts
3900 -;
3910 -;
3920 -;********************************
3930 -;*                              *
3940 -;*   Programm 3 Tabellenmodul   *
3950 -;*   Tabellen und Hilfszellen   *
3960 -;*                              *
3970 -;********************************
3980 -;
3990 -;------ Konstanten ------------
4000 -;
4010 -bogfak    .by$7b,$0e,$fa,$35,$0f;Pi/180
4020 -grdfak    .by$86,$65,$2e,$e0,$d2;180/Pi
4030 -logfak    .by$7f,$5e,$5b,$d8,$aa;1/ln10
4040 -zwsp1     .by$00,$00,$00,$00,$00;Zwischenspeicher 1
4050 -zwsp2     .by$00,$00,$00,$00,$00;und 2
4060 -;------ Hilfszellen -----------
4070 -;
4080 -befnr     .by$00         ;Befehlsnummer
4090 -akku      .by$00         ;Zw'Speicher f. Akku
4100 -flag      .by$00         ;Marke fuer ACOS, Zaehler fuer POLY
4110 -;
4120 -;---- Sprungtabelle -------------
4130 -;
4140 -sprtab    .by$e7
4150 -sprtab1   .by$a7
4160 -          .wo aus
4170 -          .wo bog
4180 -          .wo grd
4190 -          .wo dlog
4200 -          .wo cot
4210 -          .wo acot
4220 -          .wo asin
4230 -          .wo acos
4240 -          .wo poly
4250 -          .by 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
4260 -;
4270 -;---- Befehlstabelle ------------
4280 -;
4290 -beftab    .tx"aus"
4300 -          .by 0
4310 -          .tx"bog"
4320 -          .by 0
4330 -          .tx"grd"
4340 -          .by 0
4350 -          .tx"dlgr"
4360 -          .by 0
4370 -          .tx"cot"
4380 -          .by 0
4390 -          .tx"acot"
4400 -          .by 0
4410 -          .tx"arcs"
4420 -          .by 0
4430 -          .tx"arcc"
4440 -          .by 0
4450 -          .tx"poly"
4460 -          .by 0,0
4470 -;
4480 -;Hiernach noch Platz fuer weitere
4490 -;8 Befehlstexte lassen.
4500 -;
4510 -          .by 0,0,0,0,0,0,0,0,0,0
4520 -          .by 0,0,0,0,0,0,0,0,0,0
4530 -          .by 0,0,0,0,0,0,0,0,0,0
4540 -          .by 0,0,0,0,0,0,0,0,0,0
4550 -          .by 0,0,0,0,0,0,0,0,0,0
4560 -          .by 0,0,0,0,0,0,0,0,0,0
4570 -;
4580 -;--- Tabelle fuer Polynome ---
4590 -;
4600 -polytab   .by0,0,0,0,0,0,0,0,0,0
4610 -          .by 0,0,0,0,0,0,0,0,0,0
4620 -          .by 0,0,0,0,0,0,0,0,0,0
4630 -          .by 0,0,0,0,0,0,0,0,0,0
4640 -          .by 0,0,0,0,0,0,0,0,0,0
4650 -          .by 0,0,0,0,0,0,0,0,0,0
4660 -          .by 0,0,0,0,0,0,0,0,0,0
4670 -          .by 0,0,0,0,0,0,0,0,0,0
4680 -;
Listing 3. Eine Basic-Befehlserweiterung in 10 Modulen, die den Wortschatz um einige mathematische Befehle ergänzt
PROGRAMM : MODUL 1-10     5000 52FD
-----------------------------------
5000 : A9 16 8D 08 03 A9 50 8D   F3
5008 : 09 03 60 A9 E4 8D 08 03   C1
5010 : A9 A7 8D 09 03 60 20 73   AC
5018 : 00 C9 60 B0 19 C9 41 90   31
5020 : 15 8D 20 52 A2 00 8E 1F   F1
5028 : 52 A0 00 EE 1F 52 BD 46   B0
5030 : 52 D0 09 AD 20 52 20 79   EA
5038 : 00 4C E7 A7 D1 7A D0 28   D2
5040 : C8 E8 BD 46 52 D0 F5 18   68
5048 : 98 65 7A 85 7A 90 02 E6   E4
5050 : 7B AD 1F 52 0A AA BD 22   E5
5058 : 52 8D 63 50 BD 23 52 8D   AD
5060 : 64 50 20 FF FF 4C 36 50   D0
5068 : E8 BD 46 52 D0 FA E8 4C   2C
5070 : 29 50 A5 49 48 A5 4A 48   BF
5078 : 20 FD AE 20 8A AD A9 06   0F
5080 : A0 52 20 28 BA 68 A8 68   B9
5088 : AA 20 D4 BB 60 A5 49 48   D8
5090 : A5 4A 48 20 FD AE 20 8A   5B
5098 : AD A9 0B A0 52 20 28 BA   2D
50A0 : 68 A8 68 AA 20 D4 BB 60   24
50A8 : A5 49 48 A5 4A 48 20 FD   1C
50B0 : AE 20 8A AD 20 EA B9 A9   5A
50B8 : 10 A0 52 20 28 BA 68 A8   FC
50C0 : 68 AA 20 D4 BB 60 A5 49   08
50C8 : 48 A5 4A 48 20 FD AE 20   6B
50D0 : 8A AD A2 15 A0 52 20 D4   43
50D8 : BB 20 64 E2 A2 1A A0 52   3B
50E0 : 20 D4 BB A9 15 A0 52 20   6E
50E8 : A2 BB 20 6B E2 A9 1A A0   02
50F0 : 52 20 0F BB 68 A8 68 AA   50
50F8 : 20 D4 BB 60 A5 49 48 A5   8E
5100 : 4A 48 20 FD AE 20 8A AD   A8
5108 : 20 0E E3 A9 E0 A0 E2 20   3C
5110 : 50 B8 68 A8 68 AA 20 D4   F1
5118 : BB 60 A5 49 48 A5 4A 48   01
5120 : A9 00 8D 21 52 20 FD AE   CC
5128 : 20 8A AD A2 15 A0 52 20   2D
5130 : D4 BB 20 58 BC A9 BC A0   42
5138 : B9 20 5B BC F0 0A 2A B0   D9
5140 : 07 68 68 A2 0E 4C 37 A4   53
5148 : A9 15 A0 52 20 A2 BB A9   48
5150 : 15 A0 52 20 28 BA A9 BC   C6
5158 : A0 B9 20 50 B8 20 71 BF   B9
5160 : A9 15 A0 52 20 0F BB 20   B0
5168 : 0E E3 AD 21 52 D0 07 68   90
5170 : A8 68 AA 20 D4 BB 60 A5   F3
5178 : 49 48 A5 4A 48 A9 FF 8D   85
5180 : 21 52 20 FD AE 20 8A AD   04
5188 : 20 2B 51 A9 E0 A0 E2 20   A6
5190 : 50 B8 A9 00 8D 21 52 68   A3
5198 : A8 68 AA 20 D4 BB 60 A5   1B
51A0 : 49 48 A5 4A 48 20 FD AE   9B
51A8 : 20 8A AD A2 15 A0 52 20   AD
51B0 : D4 BB 20 FD AE 20 8A AD   9B
51B8 : 20 AA B1 8C AC 52 C8 8C   C5
51C0 : 21 52 18 AD DA 51 69 05   AE
51C8 : 8D DA 51 AD DC 51 69 00   CA
51D0 : 8D DC 51 20 FD AE 20 8A   0F
51D8 : AD A2 A8 A0 52 20 D4 BB   05
51E0 : AC 21 52 88 D0 D9 A9 A8   96
51E8 : 8D DA 51 A9 52 8D DC 51   13
51F0 : A9 15 A0 52 20 A2 BB A9   F0
51F8 : AC A0 52 20 59 E0 68 A8   1D
5200 : 68 AA 20 D4 BB 60 7B 0E   29
5208 : FA 35 0F 86 65 2E E0 D2   22
5210 : 7F 5E 5B D8 AA 00 00 00   5B
5218 : 00 00 00 00 00 00 00 00   19
5220 : 00 00 E7 A7 0B 50 72 50   AD
5228 : 8D 50 A8 50 C6 50 FC 50   95
5230 : 1A 51 77 51 9F 51 00 00   7F
5238 : 00 00 00 00 00 00 00 00   39
5240 : 00 00 00 00 00 00 41 55   F0
5248 : 53 00 42 4F 47 00 47 52   4C
5250 : 44 00 44 4C 47 52 00 43   BD
5258 : 4F 54 00 41 43 4F 54 00   FA
5260 : 41 52 43 53 00 41 52 43   DF
5268 : 43 00 50 4F 4C 59 00 00   39
5270 : 00 00 00 00 00 00 00 00   71
5278 : 00 00 00 00 00 00 00 00   79
5280 : 00 00 00 00 00 00 00 00   81
5288 : 00 00 00 00 00 00 00 00   89
5290 : 00 00 00 00 00 00 00 00   91
5298 : 00 00 00 00 00 00 00 00   99
52A0 : 00 00 00 00 00 00 00 00   A1
52A8 : 00 00 00 00 00 00 00 00   A9
52B0 : 00 00 00 00 00 00 00 00   B1
52B8 : 00 00 00 00 00 00 00 00   B9
52C0 : 00 00 00 00 00 00 00 00   C1
52C8 : 00 00 00 00 00 00 00 00   C9
52D0 : 00 00 00 00 00 00 00 00   D1
52D8 : 00 00 00 00 00 00 00 00   D9
52E0 : 00 00 00 00 00 00 00 00   E1
52E8 : 00 00 00 00 00 00 00 00   E9
52F0 : 00 00 00 00 00 00 00 00   F1
52F8 : 00 00 00 00 4A            B4
Listing 4. Das fertig assemblierte Listing 3. Bitte mit dem MSE eingeben.
PDF Diesen Artikel als PDF herunterladen
Mastodon Diesen Artikel auf Mastodon teilen
← Vorheriger ArtikelNächster Artikel →