back : next : content =

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 $0110
Ihm 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
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 8
Sind 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:
 
  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 
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.

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:

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:
 
CH.IO 0   Kanalidentifikation (ID des Qdos-Kanals) 
-1 bei nicht mehr existierendem Kanal
CH.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
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.

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
%.000.... 0 ohne Trennzeichen
%.001.... 1 Komma
%.010.... 2 Semicolon
%.011.... 3 Bachslash ("\")
%.100.... 4 Ausrufezeichen
%.101.... 5 "TO"
Typ in Bits 0 bis 3 wie oben beschrieben.

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:
 
1 String (Zeichenkette)
2 Fließkommazahl
3 Ganzzahl (16-Bit-Zahl)
memoryzuweisung:

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:
 
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!
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.

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

CA.GTINT  $0112
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.

Der Aufruf <getint> holt eine 16-Bit-Ganzzahl in Wortlänge nach D5:
v_vec equ $0112 Vector CA.GTINT 
v_len equ 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
Zur Rückgabe aus einer Funktion sind anzugeben

D4 = 3 für den Variablentyp Integer
D0 = 0 meldet fehlerfreien Verlauf
Es sei die Zahl in D5.w gegeben. Dann folgt etwa:
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.)

CA.GTFP $0114
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.

Die 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,5
Bit 21: 2^-10 = 1/1024 = 0,00097656
Dabei gibt es die Ausnahme für das Vorzeichen
Bit 31: 2^0 = 1 zählt negativ mit
Alle Bits 0 = Mantisse der Zahl Null
Und die Ausnahme beim Exponenten ist
Alle Bits 0 = Exponent der Zahl Null
Die ganze Zahl
Zahl = 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 definiert
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
...
Zur Rückgabe aus einer Funktion sind anzugeben
D4 = 2 für den Variablentyp f.p.
D0 = 0 meldet fehlerfreien Verlauf
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.
 
 

4.5.4.4 * 32-Bit-Zahlen

CA.GTLIN $0118
Fü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
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)
...
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.

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 $0116
Strings 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 gilt

D4 = 1 für den Variablentyp String
D0 = 0 für fehlerfreien Verlauf
Der 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.
 

4.5.4.6 * Wertzuweisung

BP.LET $0120
Ggf. 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.
 
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
...
Strings haben besondere Angaben in der letzten 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 mit
DIM 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.
A5 erste Adresse nach der letzten Eintragung dto.
A6 aktuelle Basisadresse für den Basic-Job
A7 User-Stackptr
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.

Kaum Einschränkungen gibt es in der Registerverwendung, die fast alle frei veränderbar sind:

D0 muß Null oder den Fehlercode zurückgeben.
D1 - D7, A0 - A5 dürfen beliebig verändert werden.
A6 Basisadresse des Interpreters.
A7, A7' USER- und SUPERVISOR-Stackptr sind zu erhalten.
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.

Ü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 $011A
dem 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) aus
RI.EXECB $011E dto., Operationen nach Tabelle (A3)+
Zu übergeben sind
D0 Op-Code bei RI.EXEC
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.-Variablenbuffers
Zurück kommen
D0 evtl. mit dem Fehlercode ERR.OV
A1 dem Verlauf der Berechnungen angepaßt
Alle anderen Register bleiben erhalten.
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 ist
A6 + 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
 
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
Manchmal ist auch noch RI.MAXFP = 48 definiert.

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 0
RI.STORE 1
Definiert man nun etwa
VAR1 EQU -6
hat man die Operationen
VAR1+RI.LOAD Laden der Variablen in den Stack
VAR1+RI.STORE Ablegen des ersten Stack-Elements
 

  top : back : next : content 

= (count)