4.5 *** Basic
Die Basic-programe selbst sind nicht Thema. Dazu gibt es das Handbuch, den "User Guide", und noch viele andere Schriften. Wir betrachten ausschließlich die vorgesehenen Methoden zur Erweiterung des Basic-Befehlssatzes.
4.5.1 ** Residente Erweiterungen
Das SuperBASIC des QL dürfte kaum von Konkurrenz bedroht sein. Doch, wie in allen interpretierenden program(ier)-sprachen, gibt es hier soviele Einzelheiten zu verwalten, daß der direkt programierte Processor-Code wesentlich schneller zum Ziel kommt. Es ist gelegentlich ein Faktor um 1000 erreichbar. In vielen Fällen ist der Aufwand für das Schreiben besonderer Basic-Befehle nicht allzu hoch, der Gewinn dagegen ganz außerordentlich.
Auch ist manche Einrichtung des QL sonst unzugänglich.Schließlich sind Editoren, direkte Befehlseingabe und narrensichere Fehlerkontrolle sehr angenehm bei der programentwicklung, jedoch in einem ausgetesteten program als Ballast entbehrlich. Was zum Compilieren führt, womit allein sie schon entschieden schneller werden.
Daß Basic-programe compilierbar sind, wäre ansich kaum der Rede wert, ist das doch allgemein üblich. Im Ql werden sie aber dadurch zugleich selbständige programe. Sie sind tauglich zum Multitasking und den anderen Jobs völlig gleichgestellt. Bedenkt man noch, daß diese vorher als Basic-programe Stück für Stück aufgebaut und in Teilen bereits vollständig geprüft werden können, so wird die Erweiterbarkeit des Basic zu einer besonders wichtigen und hilfreichen Einrichtung des QL.
Genaues Einhalten der im Basic angewandten Verfahren ist Voraussetzung für die Compilierbarkeit von programen, in denen auch Basic-Erweiterungen enthalten sind. Natürlich gilt das für sämtliche Anwendungsfälle. Dabei ist das System aber so außerordentlich fehlertolerant, daß so mancher Erweiterungscode unter einfachen Umständen anwendbar ist, ohne daß irgendeine Fehlfunktion aufträte. Darum der Rat, sich strikt an die dokumentierten Konventionen zu halten, und notfalls die programe unter möglichst irrwitzigen Bedingungen zu prüfen, damit nicht doch einmal eine Fehlermeldung das Ganze anhält.
4.5.2 ** Anmeldung
Irgendein (nahezu) beliebiger Code kann dem Interpreter als Befehl verfügbar gemacht werden. Das ist bereits bekannt. Jetzt soll das Verfahren dazu beschrieben werden, den Angaben zu den eigentlichen Bedingungen vorgreifend, unter denen solche Erweiterungen zu arbeiten haben.Das Hilfsmittel hierzu ist der Vector
BP.INIT $0110Ihm ist eine Tabelle mitzugeben, in der alle neuen Befehle verzeichnet sind. A1 übergibt die Adresse dieser Tabelle. Bei der Ausführung werden D2 und A1 smashed, die anderen Register bleiben erhalten.
Der Stack wird nie mit mehr als 48 Bytes beansprucht.Die Tabelle enthält:
proc_num DC.W {anzahl.proceduren} DC.W erste.proc - pc DC.B erste.len,"{erster.name}" DS.W 0 ... DC.W letzte.proc - pc DC.B letzte.len,"{letzter.name}" DS.W 0 func_num DC.W {anzahl.funktionen} ... DC.W 0 Alle Eintragungen müssen auf geradzahliger Adresse beginnen, darum sind die DS.W-Angaben gemacht. Assembler justieren dann gewöhnlich entsprechend. Bei anderen als dem GST-Assembler können andere Mnemonics erforderlich sein. Natürlich kann die Angleichung auch durch Anfügen irgendeines Füllzeichens an den Namen geschehen. Der Autor hat sich obige Schreibweise zerstreutheitshalber für die Sicherheit angewöhnt. Eine Macro-Definition dazu, der nur noch die Namen anzugeben sind, ist {13} beigefügt.
Die erste Eintragung der beiden Tabellenteile gibt die Anzahl der folgenden Namen an. Sie sollte genau deren Zahl entsprechen. Nur, wenn die durchschnittliche Länge der Namen Sieben überschreitet, ist jeweils eine größere Zahl einzutragen:
Zahl = (Summe aller Schriftzeichen + Anzahl aller Funktionen + 7) DIV 8Sind von einer Sorte keine Definitionen anzugeben, trägt man als Anzahl Null ein, danach gleich wieder Null als Marke für das Ende des Bereiches. Die vor den Namen angeordneten Zahlen sind Pointer auf die zugehörigen Aufrufe relativ zur eigenen Adresse.Eine übliche Notation ist etwa auch
DC.W startlabel-*4.5.3 ** Struktur des Basic
SuperBASIC, genauer der Basic-Interpreter, ist ein Job. Im Überblick findet er sich solchermaßen geordnet:
Der Interpreter enthält daneben noch ein Anzahl Stacks und Tabellen, wie die im Tabellenteil verzeichneten Basic- Variablen andeuten. Da wir uns hier nicht mit dem Compilerbau befassen, auch nicht einen weiteren Basic-Interpreter schaffen wollen, sind diese Angaben ohne belang, ebenso o.a. Posten 1 und 8 bis 11.
1. Job Header Kopf mit einigen Job-Daten bp 2. Work Area Arbeitsbereich des SuperBASIC 3. Name Table Tabelle mit Daten aller Namen 4. Name List Die Namen selbst 5. Variable Values Bereich der Variablenwerte 6. Channel Table Kanaltabelle 7. Arithmetic Stack (dieses) 8. Token List Token 9. Line Number Table Zeilennummern 10. Program File tokenisiertes program 11. Return List (eine Art Buchführung mit Stack) 12. Buffer Datenbuffer Wenn nun das Wort Basic fällt, ist damit seine innere Gestalt als program im Processorcode gemeint. Auf die Relation zur Jobbasis A6 deutet die gelegentlich angefügte Abkürzung (bp) - basic pointer - hin. Grundsätzlich sollen hier aber alle Adressen relativ zu diesem Ptr gelten. Auf absolute Adressierung wird immer besonders hingewiesen.
Insbesondere verbietet es sich von selbst, bei irgendwelchen Codes nur kurz einmal A6 auf Null zu setzen, um mit einer relativ bewerteten Adresse absolut arbeiten zu können {8}:
Der Basicjob kann sich zu beliebigem Zeitpunkt zwischen zwei Befehlen verlagern. Dabei ist es ohne jede Bedeutung, wie häufig das geschehen kann oder mit welcher Wahrscheinlichkeit.
Es sei auf die Möglichkeiten hingewiesen, die das System bietet. Da sind relativ adressierbare buffer vorhanden oder einzurichten und es gibt die Subtraktion. Man kann auch dafür sorgen, daß alle Interrupts abgeschaltet sind und das program selber eine Bereichserweiterung mit Sicherheit nicht auslöst.
Auch die Qdos-Trap #4 wird des öfteren von Nutzen sein.Arbeitsbereich BV.START 00(bp)
Der Arbeitsbereich wird mit A6 und Pointern adressiert, die den Abstand dazu angeben. Sein Aufbau ist den Tabellen der Basic-Variablen zu entnehmen.
Die Basisadresse A6 wird nur vom Sytem angerührt, wenn nämlich der Job an eine andere Stelle zu schieben ist. Durch programierung äußerstenfalls mittels der beiden Traps für memoryzuweisung und -Freigabe, MT.ALBAS (1/22) und MT.REBAS (1/23). Diese verlagern damit zugleich auch als Einzige den Stackpointer.
Name Table BV.NBAS 24(bp) bis vor BV.NT 28(bp)
Alle seine Referenzen in procedural aufgebaute program-teile (Strukturen mit GO TO und dergleichen lassen wir hier völlig außer Acht) sowie auf Variable bezieht der Interpreter auf die Daten der Name Table.
Für jeden irgendwann einmal auch nur genannten Namen enthält diese eine Eintragung von 8 Bytes Länge. Zusätzlich werden in Reihenfolge der Übergabe bei jeder Parameterdeklaration für Funktionen und Proceduren Copien der betreffenden Daten am Ende der Tabelle angelegt, mit denen dann die eigentliche Arbeit erfolgt. Nach Beendigung des Aufrufs ordnet das System die möglicherweise auch veränderten Daten wieder in die Tabelle ein.
Locale Variable sind dadurch realisiert, daß der alte Wert wieder zurückgememoryt wird.
Die 8 Bytes einer Eintragung bedeuten:
- 0 Verwendung
- 0 Variable nicht besetzt (use)
- 1 Ausdruck
- 2 Einfache Variable
- 3 Dimensionierte Variable
- 4 Procedur in Basic
- 5Funktion in Basic
- 6 Schleifenindex in REPeat
- 7 Schleifenindex in FOR
- 8 residente Procedur
- 9 residente Funktion
- 1 Typ
- 0 abhängig von der Verwendung:
- 3 Teilstring
- 4 Procedur in Basic
- 8 residente Procedur
- 9 residente Funktion
- sonst Fehler
- 1 String
- 2 f.p.-Zahl
- 3 Integer
- 2 - 3 Ptr
- auf den Namen innerhalb der Name List.
- sonst:
- -1 bei Ausdrücken, geschnittenen Strings,
Bytes 4-7 rel (BV.VVBAS(bp)) zeigen auf den Ausdruck.- Andere negative Angabe bedeutet einen Ptr auf den Originaleintrag, von dem der Vorgefundene copiert ist.
- 4 - 7
- Basic-Definitionszeile im höherwertigen Wort (Bytes 6 und 7).
- sonst:
- Bei Ausdrücken der Ptr in den RI-Stack.
- Bei residenten Befehlen deren absolute Aufrufadresse.
- In allen anderen Fällen der Ptr auf den Variablenwert.
- -1 steht hier, wenn der Wert noch undefiniert ist.
Proceduren sind immer Typ 0.
Name List BV.NLBAS 32(bp) bis vor BV.NLP 36(bp)Hier stehen sämtliche Namen verzeichnet. Die Längenangabe ist in nur einem Byte vor dem Namen gememoryt und die Grenzen zwischen den Eintragungen können auch auf ungerader Adresse stehen.
Bytes 2 und 3 der Eintragung in der Name Table enthalten den Abstand der Basisadresse aus BV.NLBAS zum Längenbyte des zugehörigen Namen. Umgekehrt läßt sich aus einem Namen die zugehörige Eintragung nur durch Absuchen der gesamten Name Table finden. An wievielter Stelle der Name steht, ist gleich dem Index in die Name Table. Dieser mit 8 multipliziert gibt den Abstand zur richtigen Eintragung an.
Variablenwerte BV.VVBAS 40(bp) bis vor BV.VVP 44(bp)
Diese stehen in einem memory-Heap. Sie sind ohne weitere Zusatzangaben zu holen mit einem zugehörigen Vector und den beim Einsprung aus dem Basic mitkommenden Werten in A3 & A5 als Ptr in die erweiterte Name Table. Neben A1 als Ptr auf den Anfang des ersten Parameters kommt in D3 deren Anzahl zurück.
Die Übergabe eines Wertes an Basic-programe geschieht mit der Rückkehr (RTS) aus einer Funktion, muß aber einem Protokoll folgen, das den Fehlercode, die Typenmarkierung und die Art der memoryung bestimmt. Weitere Parameter oder die einer Procedur können mittels BP.LET auf neue Werte eingestellt werden.
Anordnung der Werte im memory und die Möglichkeiten des Zugriffs darauf werden genauer im Anschluß an diese Zusammenstellung beschrieben.
Kanaltabelle BV.CHBAS 48(bp) bis vor BV.CHP 52(bp)
Enthält die einem Basic-Job zugeordneten Kanäle samt einigen Zusatzangaben:
Die Tabelle ist - "derzeit" nach {1,2} - 40 Bytes lang, in den dem Verfasser bekannten Systemen JM bis SMSQ 2.76 wurde das nicht geändert.
CH.IO 0 Kanalidentifikation (ID des Qdos-Kanals)
-1 bei nicht mehr existierendem KanalCH.CCPY 4 f.p. Horizontale Graphik-Cursorposition (y) CH.CCPX 10 f.p. Vertikale Graphik-Cursorposition (x) CH.ANGLE 16 f.p. Winkel der Turtle-Graphik CH.PEN 22 .b PEN-Status, =/= 0 nach PENDOWN ... CH.PPOS 32 .w laufend bis WIDTH-Grenze gezählte Zeichenposition CH.WIDTH 34 .w mit WIDTH gesetzte Breite einer Ausgabezeile Ausgehend von der Basis in BV.CHBAS wird der Tabellenplatz eines Kanals folgendermaßen ermittelt:
ofs = Kanalnummer * 40 ist der Offset in die Tabelle bse = BV.CHBAS(bp) Tabellenbasis aus der Variablen tab = (ofs + bse)(bp) Ptr auf die Kanaldaten
Hat der Ptr den Wert aus BV.CHP(bp) erreicht, gibt es einen Kanal der angegebenen Nummer nicht. Ist das erste Langwort der Tabelle negativ, wurde er zuvor geschlossen.Also z.B. mit der Basic-Kanalnummer in D0:
bchan mulu #40,d0 kabellenposition add.l 48(a6),d0 bv.chbas kanaltabelle cmp.l 52(a6),d0 Obergrenze bge.s kanal_nf ? erreicht move.l 0(a6,d0.l),d0 sollte die ID sein not.l d0 auf flag -1 prüfen bne.s ist_okay ? ist kanal-ID moveq #-6,d0 sonst ERR.NO rts kanal_nf moveq #6,d0 kein Kanal, ERR.NF ist_okay not.l d0 rts
Arithmetik-Stack BV.RIP $58(bp)
Hier ist die Stelle verankert, die den Rechneraufrufen als Basisadresse für ihren Stack dient. Zugleich werden von hier aus Werte aus residenten Funktionen an Basic übergeben. Aber auch für sonstige memoryoperationen kann dieser Bereich als Kurzzeitmemory benutzt werden.
Dabei ist zu beachten, daß er, ggf. mit Hilfe der Trap #4, nur A6-relativ adressiert werden darf, und daß sich seine Adresse durch jeden Aufruf eines QDOS-Vectors, der irgendwelche Aktionen im SBasic erledigt, verändern kann, Sichern seiner Adresse in solchen Fällen also zwecklos ist. Gleiches gilt für den Fall, daß inzwischen ein neuer Job eingerichtet wurde - womit jederzeit gerechnet werden muß, da das auch durch beliebige andere programe geschehen kann.
Alle Basic-Pointer müssen nach solchen Vorgängen bedarfsweise immer wieder neu aus ihrer Variablenposition gelesen werden.Platzreservierung geschieht hier mittels BV.CHRIX, wie anschließend und bei der Variablenbeschreibung näher erläutert. Der Ptr in BV.RIP ist dabei immer durch das program zu aktualisieren, wenn der Bereich als reserviert anerkannt werden soll.
bufferbereich BV.BFBAS 00(bp)
Nur soviel, daß in BV.BFBAS auf 00(bp) die Adresse eines kurzfristig frei nutzbaren bufferbereiches steht. BV.BFP auf 04(bp) enthält einen Laufptr dorthinein. Die meisten SBasic- und TK2-Aufrufe, die irgendwie mit Zeichenketten umgehen, verwenden diesen buffer
4.5.4 ** Parameter
4.5.4.1 * Allgemein
Parameterübergabe:Zum Holen von Parameterwerten, deren Gestalt im Folgenden beschrieben werden wird, gibt es eigene Vectoren für vier Parametertypen. Ihre Verwendung ist dort angegeben. Weiter einen, mit dem Variablen ein neuer Wert zugewiesen werden kann. Variablenfelder entziehen sich diesem Zugriff. Deren Aufbau ist am Schluß genauer dargestellt.
Die Trennzeichen zwischen den übergebenen Parametern, Separatoren, sind im zweiten Byte (Byte 1) der mit A3 / A5 adressierten Tabelle codiert:
'#'-Marke "Hash" durch gesetztes Bit 7 angezeigt.
Separator in Bits 4 bis 6 mit
Typ in Bits 0 bis 3 wie oben beschrieben.
%.000.... 0 ohne Trennzeichen %.001.... 1 Komma %.010.... 2 Semicolon %.011.... 3 Bachslash ("\") %.100.... 4 Ausrufezeichen %.101.... 5 "TO" Somit kann, auch zur Laufzeitersparnis, schon vor dem Holen die Art eines Parameters leicht ermittelt werden. Die Anzahl Parameter ist die durch acht geteilte Differenz A5 - A3, steht also auch bereits vorher zur Verfügung. Hiervon wird etwa im Beispiel "DEFVAL" Gebrauch gemacht.
Leider spielt auch hier das SMSQ wieder einmal nicht so recht mit. Ein Geistesblitz der besonderen Art muß wohl zu der Wiedereinführung der schon in uralten Basicvarianten lästigen automatischen Initiierung von Variablen auf einen festen Inhalt geführt haben (Null rsp. Leerstring). "Undefinierte" Variable im Sinne des SuperBASIC gibt es dort nur außerhalb von FuNctionen oder PROCeduren, also dort gerade nicht, wo entsprechende Initiierungsaktionen in programierter Form ihren größten Nutzen entfalten würden.
Eine besondere Einrichtung des QL bewirkt, daß die übergebenen Parameter wenn irgend möglich stets in die angeforderte Form gebracht werden. So kann eine Zahl auch als String geholt werden, jede 32-Bit-Zahl als f.p.-Zahl (in welcher Form sie ohnehin gememoryt ist) und jede Integer-Zahl in einer der anderen Formen.
Umgekehrt müssen die Variablen "aufwärtscompatibel" sein:
Eine f.p.-Zahl, die als Integer geholt werden soll, muß in passendem Wertebereich angegeben sein, ein String muß mit einer Ziffer beginnen, einem "e" muß stets eine Ziffer folgen.Der erste Parameterwert steht an der Adresse 0(A1,a6.L).
Falls sich ein Fehler ergibt, der auf falscher Typenwahl beruht, kann ohne weiteres ein anderer Vector aufgerufen werden.
Wenn nicht das program selbst A3 & A5 verändert hat, ist der Wert bis dahin nur zerstörungsfrei gelesen worden. Allerdings nur dieser:
Trennzeichen und "hash"-Marke werden beim ersten Vectoraufruf überschrieben, müssen also ggf. vorher gesichert werden (s.u.). Auch wird der Rechnerstack nicht immer richtig wieder freigegeben, wogegen nur vorheriges Sichern der Stacktiefe hilft, und Rückstellen auf diesen Wert, wenn der Vector eine Fehler zurückgegeben hat. Da dies in jedem Falle das zuverlässigere Verfahren ist, im Gegensatz zur Freigabe nach Abzählen, wird es weiter unten geanuer beschrieben.Typvorgabe für Variable:
Funktionen geben einen Wert zurück. Deren Typ ist in Basicprogramen durch die Endung des Namen gegeben, residente Funktionen haben dagegen keinen vorgegebenen Typen. Die Endung hat hier nur kennzeichnenden Wert. Eine Funktion MÜLL% kann also durchaus den String "EIMER" zurückgeben (Umlaute im Namen übrigens erst ab MGG).
Die Rückgabetypen residenter Funktionen sind in D4 wie folgt anzugeben:
memoryzuweisung:
1 String (Zeichenkette) 2 Fließkommazahl 3 Ganzzahl (16-Bit-Zahl) Ein zurückzugebender Wert wird zunächst in die gewünschte Form gebracht. Die Basicvariable BV.RIP(bp) bekommt die Adresse, auf der der Wert steht. Dies darf jedoch keine beliebige (relative) Adresse irgendeines buffers sein. Vielmehr ist das der Basispointer auf den Rechnerstack, der z.B. mit dem Vector RI.EXEC benutzt wird. Es ist zugleich der allgemeine Parameterbereich und Hilfsmemory für alle arithmetischen Operationen.
Platz wird dort auf verschiedene Weise reserviert. Die genannten Vectoren bewirken das für alle von ihnen geholten Parameter bereits genau passend.
Der gesamte Bereich mag vollständig aufgegeben werden, nachdem alle Daten entnommen wurden. Die Anzahl der freizugebenden Bytes kann mit Hilfe der wirklichen Anzahl und des Wissens um die Länge der angekommenen Parameter ermittelt werden. Die Basic-Variable BV.RIP wird um den entsprechenden Betrag (geradzahlig!) erhöht, womit der Bereich bereits wieder allgemein verfügbar ist.
Bei Strings wird das gelegentlich allzu umständlich werden, deren Bereiche gibt man am Einfachsten einzeln für jeden String frei.
Eine von allen (Fehler-)Situationen unabhängig zuverlässige Form der Freigabe besteht im Rückstellen auf die vor dem Aufruf ermittelte Stacktiefe:
Der Umweg über die Stacktiefe ist notwendig, da der Zahlenwert von BV.RIP sich auch bei gleichbleibendem Stack ändern kann. Ausgelöst werden solche Änderungen u.a. beim Holen sehr langer Strings, wenn deren Länge dazu führt, daß der Basic-Job selbst neuen memory aufnimmt und sich dafür verlagern muß, oder dadurch, daß während gerade ein SBasic-Vector durchlaufen wird irgendein anderes program einen neuen Job einrichtet.
parm_holen move.l $58(a6),a4 bv.rip holen und mit sub.l $5c(a6),a4 bv.ribas stacktiefe ermitteln .... (hier die parameter holen) add.l $5c(a6),a4 stacktiefe und bv.ribas move.l a4,$58(a6) wird bv.rip für leeren stack ... tst.l d0 move in den memory hat die flags verändert! Für zusätzlich benötigten memory und zum Platzmachen für die Rückgabe eines Funktionswertes gibt es den Vector BV.CHRIX, $011A dessen Einsatz weiter unten ausführlich beschrieben ist.
Damit Funktionen Ergebnisse liefern, mit denen gleich auch gerechnet werden kann, und die bei Verlagerung des Basic immernoch sicher arbeiten, ist BV.RIP unbedingt auf aktuellem Stand zu halten. Und stets ist dafür zu sorgen, daß alle Operationen innerhalb des fixierten Bereiches stattfinden.
4.5.4.2 * Integer-Zahlen
Sie stehen als vorzeichenbehaftete Zahlen im Zweiercomplement an der angegebenen Adresse. Trotz der eigentlich nur benötigten 2 Bytes liegen diese Zahlen in einem Feld von 6 Bytes Größe im Variablenbereich.CA.GTINT $0112Der Aufruf <getint> holt eine 16-Bit-Ganzzahl in Wortlänge nach D5:
Zur Rückgabe aus einer Funktion sind anzugeben
v_vec equ $0112 Vector CA.GTINT v_len equ 2 Länge einer Zahl getint move.l $58(a6),a4 bv.rip sub.l $5c(a6),a4 bv.ribas move.w v_vec,a2 QDOS-vector jsr (a2) > aufrufen bne.s errxit ? fehler moveq #-15,d0 err.bp vorbereiten subq.w #1,d3 ein parameter bmi.s errxit ? reicht nicht moveq #0,d0 flag: fehlerfrei fetchp move.w 0(a1,a6.l),d5 wert holen, überzählige sind egal errxit add.l $5c(a6),a4 mit bv.ribas move.l a4,$58(a6) bv.rip zurückstellen tst.l d0 flags setzen rts D4 = 3 für den Variablentyp IntegerEs sei die Zahl in D5.w gegeben. Dann folgt etwa:
D0 = 0 meldet fehlerfreien Verlauf
retint moveq #2,d1 zwei bytes platz move.w $11a,a1 mit bv.chrix jsr (a1) > sicherstellen move.l $58(a6),a1 bv.rip subq.l #2,a1 justieren move.l a1,$58(a6) platz fixieren move.w d5,0(a1,a6.l) wert ablegen moveq #3,d4 typ integer markieren moveq #0,d0 und fehlercode rts Wobei auch hier die frei definierten Fehlermeldungen angegeben werden können, wie das bei der Beschreibung der Access Layer einfacher Device Handler (3.3.2.3.2) angegeben wurde.
4.5.4.3 * Flieszkommazahlen (f.p.)
Diese liegen mit einem 12-Bit-Exponenten in zwei Bytes und weiteren vier Bytes mit 32-Bit-Mantisse vor. Die Mantisse ist vorzeichenbehaftet im Zweiercomplement notiert. Die Notation des Exponenten erfolgt mit einem "Offset" von $0800, wo dann Bit 11 eines negativen Exponenten Null ist.CA.GTFP $0114Die Zahl wird so normiert, daß die Mantisse im Bereich 1/2 bis -1/2 liegt. Bit 31 zählt darin mit -1. Weitere Konventionen gibt es nicht.
Die Bits haben die Wertigkeit 2^(Bitnummer-31), also etwa
Bit 30: 2^-1 = 1/2 = 0,5Dabei gibt es die Ausnahme für das Vorzeichen
Bit 21: 2^-10 = 1/1024 = 0,00097656Bit 31: 2^0 = 1 zählt negativ mitUnd die Ausnahme beim Exponenten ist
Alle Bits 0 = Mantisse der Zahl NullAlle Bits 0 = Exponent der Zahl NullDie ganze ZahlZahl = Mantisse * 2 ^ (exponent - $81F)Als Aufruf zum Holen einer f.p.-Zahl läßt sich das Muster <getint> verwenden, wenn man den Lesebefehl anpaßt und definiertZur Rückgabe aus einer Funktion sind anzugeben
v_vec equ $0114 Vector CA.GTFP v_len equ 6 Anzahl Bytes einer Zahl ... move.w 0(a1,a6.l),d5 (erster) exponent move.l 2(a1,a6.l),d7 (erste) mantisse ... D4 = 2 für den Variablentyp f.p.Nach Anforderung und Reservierung von 6 Bytes Platz im RI-Stack wird die Zahl analog o.a. Beispiel <retint> aus der SBasic-Funktion zurückgegeben.
D0 = 0 meldet fehlerfreien Verlauf
4.5.4.4 * 32-Bit-Zahlen
CA.GTLIN $0118Für Zahlen innerhalb des Wertebereiches -2^31 bis 2^31-2 gibt es diesen Vector, mit dem 32-Bit-Zahlen, f.p. oder Integer passend gemacht, geholt werden.Am oberen Ende des Wertebereichs wäre 2^31-1 ansich die richtige Grenze. Akzeptiert wird dafür aber nur die Eingabeform 2^30-1+2^30.
Zum Holen einer Zahl gilt das Beispiel zu ca.gtint entsprechend mit
Rückgabe solcher Daten ist direkt nicht möglich, da ein entsprechender Parametertyp nicht existiert. Die Zahl kann entweder, wenn der Wertebereich im erlaubten Rahmen bleibt (- 32768 bis 32767) als Integer abgeliefert werden, oder normiert als f.p.-Zahl.
v_vec equ $0118 Vector CA.GTLIN v_len equ 4 Anzahl Bytes einer Zahl ... move.l 0(a1,a6.l),d5 lange ganzzahl (integer) ... Eine einfache Routine zur Normierung und Rückgabe der Zahl in D5:
retfp moveq #6,d1 RI-memory zur rückgabe move.w $11a,a1 bv.chrix jsr (a1) move.l $58(a6),a1 bv.rip subq.l #6,a1 move.l a1,$58(a6) move.l d5,0(a1,a6.l) beq.s retfnul ? fertig bei null move.w #$081f,d0 höchster exponent normfp add.l d5,d5 dbvs d0,normfp vorzeichenbits hochschieben roxr.l #1,d5 normiert mit vorzeichen move.w d0,0(a1,a6.l) exponenten eintragen retfnul move.l d5,2(a1,a6.l) mantisse moveq #2,d4 typ f.p. moveq #0,d0 ohne fehler rts
4.5.4.5 * Strings
CA.GTSTR $0116Strings sind immer auf geradzahliger Adresse beginnend mit dem Längenwort gememoryt. Bei ungerader Länge steht am Ende ein Füllbyte.Nach dem Holen eines String kann im Gegensatz zu den anderen Aufrufen der Übergabestack nur freigegeben werden, wenn der String in einen anderen Bereich übernommen wurde. Sonst gehen die Daten verloren. Man übernehmimmt nun zwar die Länge in D5 und passt den Pointer A1 an, läßt den memory aber zweckmäßigerweise zunächst unberührt.
Einen String holen, Länge nach D5, Daten unberührt ab 0(a1,a6.l):
v_vec equ $0116 Vector CA.GTSTR getstr move.l $58(a6),a4 bv.rip sub.l $5c(a6),a4 stacktiefe merken move.w v_vec,a1 ca.gtstr jsr (a1) string holen bne.s erxit ? warnix moveq #-15,d0 err.bp vorbereiten subq.w #1,d3 bmi.s erxit ? nichts angekommen move.w 0(a1,a6.l),d5 stringlänge addq.l #2,a1 ptr auf erstes zeichen justiert ... string in eigenen memory übertrgaen ... der vom Basic unabhängig sein muß! moveq #0,d0 ordentlichen verlauf markieren erxit add.l $5c(a6),a4 bv.ribas move.l a4,$58(a6) RI-stack aufräumen tst.l d0 flags setzen rts
Rückgabe eines String in der schon bekannten Weise. Hier giltD4 = 1 für den Variablentyp StringDer String selbst beginnt an geradzahliger Adresse mit dem Zähler in Wortlänge, es folgen die Zeichen. Auffüllen bei ungerader Länge ist nicht nötig.
D0 = 0 für fehlerfreien Verlauf
4.5.4.6 * Wertzuweisung
BP.LET $0120Ggf. bereits beim Holen des Wertes sind die zugehörigen Pointer in die Name Table zu sichern. Für den ersten Parameter gilt A3 unverändert, sonst ist entsprechend um je 8 Bytes weiterzuzählen.Nachdem im aufgerufenen Code alle nötigen Operationen erledigt sind, wird der neue Wert in der bereits beschriebenen Form im Arithmetik-Stack abglegt. Dies muß im Typen der zu ändernden Variablen erfolgen. Der Vector wird aufgerufen, nachdem BV.RIP auf den aktuellen Wert des Arithmetik-Stack eingestellt wurde, d.h. er muß auf die adressenmäßig untere Grenze der in Betracht kommenden Variablendaten gerichtet sein. Solange noch irgendwelche Posten übergeben werden sollen, ist dies stets der für BP.LET gültige Pointer auf den zu übernehmenden Variablenwert, der wirkliche Stackpointer, wenn er denn hiervon abweicht, muß erst vor der Rückkehr zum Basic wiederhergestellt werden.
A3 muß auf die Adresse des Ptr in die Name Table der zu besetzenden Variablen zeigen, das ist derselbe Wert, wie er bei der Parameterübergabe an eine Procedur oder Funktion vom Basic her ankommt.
Rückkehr zum Basic kann aus einer Procedur unmittelbar danach erfolgen, weil dann zusätzlichen Angaben unnötig sind.
D0 kommt mit der üblichen Fehlermeldung,
D1 - D3, A0 - A2 werden smashed.
4.5.4.7 * Arrays (Variablenfelder)
Während für das Holen und Verändern einfacher Variabler das Betriebssystem eine ausreichende Anzahl Vectoren enthält, gibt es für Array-Variable keinerlei Unterstützung. Die Vectoren im MEM-Device aus IO2 sind so erweitert, daß sie, wenn eine Anzahl gleichartiger Parameter angefordert wird, auch Arrays annehmen. Es ist also durchaus möglich, dergleichen mit relativ geringen Aufwand nachzusetzen, seine Beschreibung würde hier aber weit über das Thema hinausgehen. Interessenten seien auf die vollständige IO2-Sammlung verwiesen.Die Werte konne einzeln nacheinander direkt gelesen werden.
Einer Basisadresse, auf der das erste Element der Anordnung zu finden ist, folgen Zusatzdaten über Lage und Anzahl der einzelnen Elemente. Sie sind in einem Kopfeintrag verzeichnet.
Strings haben besondere Angaben in der letzten Dimension:
Langwort Ptr auf erstes Element rel. zum Inhalt von BV.VVBAS und A6 1. Wort Anzahl Dimensionen, ab 1 zählend 2. Wort höchster Index Dimension 0 3. Wort Index-Multiplikator (Postengröße)
zur nächsten Dimensionenbeschreibung... (2n+2). Wort höchster Index der n-ten Dimension (2n+3). Wort Index-Multilikator der n-ten Dimension ...
vorletztes Wort maximale Länge, geradzahlig aufgerundet letztes Wort immer Eins
Die einzelnen Elemente sind durchgehend regulär wie bei einer Matrix abgelegt, die der Reihe nach Zeile für Zeile in den memory geschrieben wird, mehrere Dimensionen ebenfalls der Reihe nach. Der Index-Multiplikator gibt den Abstand in Bytes zum nächsten Element derselben Ordnung an, multipliziert mit dessen Länge.Nur bei Variablenfeldern wird Integer-Zahlen platzsparend der tatsächlich benötigte memorybereich von zwei Bytes je Element zugeordnet.
Das Element Nr.0 der letzten Dimension eines String enthält seine Länge im memory. Liefert also das Ergebnis der Basic-Funktion LEN, ggf. aber geradzahlig aufgerundet:
PRINT a$(1 TO ,1 TO ,0)!\gibt die memory-Länge aller Eintragungen in das Array a$ aus, welches mitDIM a$(erste_dim,zweite_dim,max_len)definiert wurde.
4.5.5 ** Laufzeit-Konventionen
Einige wenige Regeln gelten innerhalb der vom Interpreter aufgerufenen Basic-Erweiterungen:Beim Einsprung haben die Register den Inhalt
A3 erste Eintragung der Name Table einer Parameterliste.Nach dem Aufruf befindet sich der Processor stets im User-Mode. Wurden Parameter angegeben, läßt sich deren Zahl aus der durch Acht geteilten Differenz von A3 und A5 ermitteln. Auf A6 sind alle Daten-Referenzen des Basic zu beziehen, seien sie nun vorhanden oder erst neu ermittelt worden. Dieses Register und die beiden Stackptr dürfen nur durch das System verändert werden, da der Basic-Bereich jederzeit zwischen zwei Befehlen, auch das nur durch das System, verschoben werden kann.
A5 erste Adresse nach der letzten Eintragung dto.
A6 aktuelle Basisadresse für den Basic-Job
A7 User-StackptrKaum Einschränkungen gibt es in der Registerverwendung, die fast alle frei veränderbar sind:
D0 muß Null oder den Fehlercode zurückgeben.Rückkehr muß im User-Mode des Processors erfolgen. Andernfalls bleibt als einzige und nur noch manuell programierbare Stelle die RESET-Leitung. Besonders nach solchen Irrläufern reinigt der Kaltstart durch Reset nicht immer die gesamten Systemdaten, sodaß danach durchaus noch Reste der alten Fehlersituation stören können. Hier hilft nur Abschalten des ganzen Geräts für einige Sekunden.
D1 - D7, A0 - A5 dürfen beliebig verändert werden.
A6 Basisadresse des Interpreters.
A7, A7' USER- und SUPERVISOR-Stackptr sind zu erhalten.Übergang in den SUPERVISOR-Mode macht ein program "atomic", nicht teilbar, sperrt also den Scheduler, sodaß die anderen Jobs ruhen. Zugleich gilt dann der Supervisor- Stack (irgendwo in der Gegend der Systemvariablen). Nach {2} sind dort maximal 64 Bytes frei nutzbar, das wären etwa 16 Adressen oder Aufrufebenen. Auf den User-Stack greift der Processor als USP zu. Hier läßt sich nun wacker an den Registern herumspielen. Solcherweise wagemutig muß man aber dafür sorgen, daß besagte drei Adressregister am Ende richtig wiederhergestellt sind. Es sei auf die Ausführungen zur Trap #0 verwiesen.
Der Systemstack heißt stets A7. Innerhalb einer residenten Basic-Erweiterung sind im USER-Mode dort bis zu 128 Bytes verwendbar - das sind z.B. nur 32 Adressen ! Was Besondere Beachtung bei recursiven Verfahren erfordert. Auch, wer gewohnt ist, Daten im Stack zu übergeben, sollte hier wachsam sein. Eine Systemfunktion, mit Hilfe derer der nutzbare Bereich direkt erweitert werden könnte, ist nicht dokumentiert. Bei sorgfältiger Beobachtung der sonstigen Gegebenheiten können aber MT.ALBAS und MT.REBAS helfen (hoppala: NICHT im SMSQ, wie könnte es anders sein...).
4.5.6 ** Arithmetik-memory
Dieser relativ zu A6 adressierte memorybereich ist dem Basic-Job zueigen und in der Verwendung den Arithmetik- Routinen sowie der Parameterübergabe zugeordnet. Er kann davon unabhängig aber für jede beliebige Operation, bei Bedarf mit Hilfe der Trap #4, als Kurzzeit-memory eingesetzt werden. Mit Rückkehr zum Basic geht sein Inhalt verloren, schon allein, weil (bei Funktionen) sein Pointer entsprechend justiert werden muß.Platz wird dort auf verschiedene Weise reserviert. Einmal sorgen die Vectoren zum Holen der Parameter dafür, daß genügend Platz vorhanden ist. Dann gibt es den Vector
BV.CHRIX $011Adem in D1.l die gewünschte Anzahl Bytes (geradzahlig!) mitzugeben ist. Außer, daß einige Register smashed werden, bewirkt der aber nur, daß die Verfügbarkeit eines entsprechenden Bereiches sichergestellt wird. Ein Pointer darauf wird nicht gesetzt. Die Variable BV.RIP fixiert und schützt solche Bereiche, der neue Wert muß dazu aber erst dort eingetragen werden. Nur die Vectoren, die die Parameter holen, bewirken das automatisch.Man schreibt etwa
moveq #8,d1 Platz für vier Integer chrix move.l d1,-(sp) move.w $011A,a1 mit bv.chrix jsr (a1) sicherstellen move.l $58(a6),a1 bv.rip sub.l (sp)+,a1 justiert move.l a1,$58(a6) bereich fixieren rts kein fehlercode (seit SMSQ...)
Nun stehen ab 0(a6,a1.l) acht Bytes sicher geschützt beliebigen Operationen zur Verfügung. Vor Rückkehr zum Basic noch folgende Aufrufe, auch der Vectoren zum Holen der Parameter, schließen sich mit den von ihnen belegten Bereichen dort lückenlos an. So besteht nach mehreren solcher Aufrufe ein geschlossener Bereich, in dem auch andere Arbeiten verrichtet werden können.Freigabe erfolgt einfach durch Addition der reservierten Anzahl zu BV.RIP, die Einordnung übernimmt das System.
4.5.7 ** Arithmetik-Vector
Aufrufbar durch zwei Vectoren ist dem QL eine Sammlung Arithmetik-Routinen beigegeben, mit deren Hilfe sich verschiedene Rechenoperationen an f.p.-Zahlen durchführen lassen. Mit einer Tabelle wird dem Vector RI.EXECB eine Art "program" übergeben. Die weiter unten aufgeführten Codes veranlassen die einzelnen Operationen. Auch Adressierung eines eigenen Variablenbereichs ist möglich.RI.EXEC $011C führt eine einzelne Operation (D0) ausZu übergeben sind
RI.EXECB $011E dto., Operationen nach Tabelle (A3)+D0 Op-Code bei RI.EXECZurück kommen
D7 := 0 RI.EXP liefert sonst falsches Ergebnis {2}
A1 rel A6 Arithmetik-Stack
A3 Liste der Op-Codes bei RI.EXECB
A4 rel A6 Basis des f.p.-VariablenbuffersD0 evtl. mit dem Fehlercode ERR.OVAlle anderen Register bleiben erhalten.
A1 dem Verlauf der Berechnungen angepaßt
Die Adresse A3 ist absolut zu übergeben (Angabe in {2} und {15} ist falsch!).A1 arbeitet als Stack. Partner für die memorybefehle ist der Variablenmemory, adressiert mit Offset zu A4. Beide Bereiche sind vorher auf geeignete Weise zu reservieren, etwa mit Hilfe des schon beschriebenen Vectors BV.CHRIX.
Zwei Arten Op-Codes werden erkannt. Neben den aufgeführten eigentlichen Rechenoperationen noch die memory-Befehle bezüglich eines Variablenmemorys: Ein Op-Code außerhalb des Bereiches 0 bis 48 gilt als memorybefehl: Geradzahlig Laden der adressierten Variablen in den Stack mit Predecrement, ungeradzahlig Ablegen aus dem Stack mit Postincrement an die adressierte Position im Variablenbereich. Die Adresse ist
A6 + A4.l + ((op-code OR $FF00) AND $FFFE).Ein etwas anderes Vorgehen ist in {2} angegeben: Ein negativer Op-Code im Bereich von $FF31 bis $FFFF ruft zum memorytransport auf. Die Operationen erfolgen, wie oben beschrieben. Die Adresse istA6 + A4.l + (op-code AND $FFFE)In den untersuchten Roms (auch Gold-Card) wird die zuerst angegebene Methode benutzt. 34 Variable sind adressierbar.Bei MINERVA gibt es hierfür einen wesentlich erweiterten Befehlssatz, dessen Beschreibung deren Handbuch entnommen werden kann. Die Codes liegen ungerade im Bereich von 1 bis 47 und sind im Tabellenteil verzeichnet.
Abgekürzt:
tos Top Of Stack, das unmittelbar zugängliche Element,
nos Next On Stack, das danach eingetragene Element.
top Top Of Stack nach einer Operation
Manchmal ist auch noch RI.MAXFP = 48 definiert.
Name Code Stack Funktion RI.TERM 00 0 Beendet die Rechenoperationen. ri.one 01 -6 Constante Eins ablegen RI.NINT 02 +4 gerundete Integer ri.zero 03 -6 Constante Null ablegen RI.INT 04 +4 gekappte Integer ri.n 05 -6 Byte in f.p.-Zahl -128 bis 127 RI,.NLINT 06 +2 f.p. in gerundete lange Integer ri.k 07 -6 (?) s. Handbuch RI.FLOAT 08 -4 Integer nach f.p. ri.flong 09 -2 lange Integer nach f.p. RI.ADD 10 +6 top := tos + nos RI.SUB 12 +6 top := nos - tos ri.halve 13 0 top := tos / 2 RI.MULT 14 +6 top := tos * nos ri.doubl 15 0 top := tos * 2 RI.DIV 16 -6 top := nos / tos ri.recip 17 0 tos := 1 / tos RI.ABS 18 0 tos := | tos | ri.roll 19 0 tos: fp1 fp2 fp3 => tos: fp2 fp3 fp1 (?) RI.NEG 20 0 tos := - tos ri.over 21 -6 tos: fp1 fp2 => tos: fp2 fp1 fp2 RI.DUP 22 -6 tos: fp1 => tos: fp1 fp1 ri.swap 23 0 tos: fp1 fp2 => tos: fp2 fp1 RI.COS 24 0 cosinus RI.SIN 26 0 sinus RI.TAN 28 0 tangens RI.ASIN 30 0 arcus sinus RI.ACOS 32 0 arcus cosinu RI.ATAN 34 0 arcus tangens ri.arg 35 +6 tos, nos: k * cis( arg ) RI.ACOT 36 0 arcus cotangens ri.mod 37 +6 sqrt( tos^2 + nos°2 ) RI.SQRT 38 0 Quadratwurzel ri.sqar 39 0 Quadrat RI.LN 40 0 logarithmus naturalis (braucht ca 60 Bytes RI-Stack!) RI.LOG10 42 0 lg RI.EXP 44 0 Exponentialfunktion ri.power 45 +2 nos(fp) ^ tos(Integer) RI.POWFP 46 +6 nos ^ tos Als Hilfsgrößen für die Assemblerschreibweise werden in {2} noch ein paar ziemlich sinnlose Operatoren für Stackbewegungen genannt, die zum Variablenoffset zu A4 zu addieren sind:
RI.LOAD 0Definiert man nun etwa
RI.STORE 1VAR1 EQU -6hat man die OperationenVAR1+RI.LOAD Laden der Variablen in den Stack
VAR1+RI.STORE Ablegen des ersten Stack-Elements