----------------------------------------------------
Kapitel
7
ATARI
BASIC
----------------------------------------------------
Dieses
Kapitel behandelt den BASIC-Interpreter des ATARI™ Personal Computer Systems. Die
vier Hauptthemen sind:
1. WAS IST ATARI BASIC - Eine Beschreibung dessen, was erforderlich ist, um das BASIC laufen zu lassen und eine Erläuterung seiner Stärken und Schwächen.
2. WIE DAS ATARI BASIC ARBEITET - Eine detaillierte Analyse über die "Tokenisierung" und Ausführung von Programmen.
3. VERBESSERUNG DER PROGRAMMAUSF‹HRUNG - Eine Liste von Methoden, um die Ausführungsgeschwindigkeit eines Programmes zu erhöhen und seine Länge zu verkürzen.
4. FORTGESCHRITTENE PROGRAMMIER-TECHNIKEN - Eine Reihe von Möglichkeiten, um verschiedenen Programm-Erfordernissen besser gerecht zu werden.
WAS
IST ATARI BASIC?
ATARI
BASIC ist wie andere BASICs eine interpretierte Sprache. Das bedeutet, Programme
können gestartet werden, sobald sie eingegeben wurden, und nicht erst, nachdem
sie "compiliert" und "gelinkt" (d.h. in Maschinensprache übersetzt
und im Speicher plaziert) worden sind. Der ATARI BASIC INTERPRETER liegt in einem
8K-ROM-Modul, das in den linken Schacht des Systems gesteckt wird. Dieses Modul belegt
die Adressen $A000 bis $BFFF. Der BASIC-Interpreter speichert die Programme des Benutzers
im RAM, wofür mindestens 8K-RAM benötigt werden.
Um
das ATARI BASIC effektiv benutzen zu können, muß man seine Stärken
und Schwächen kennen. Mit folgenden Informationen ist es möglich, Programme
zu schreiben, die sämtliche Vorteile aus den Fähigkeiten und Möglichkeiten
des ATARI BASICs ziehen.
Stärken
des ATARI BASICs:
1. ANSPRECHEN DER GRAPHIK-ROUTINEN DES OPERATING SYSTEMS - Mit einfachen Graphik-Befehlen ist es möglich, ansprechende Bilder auf den Schirm zu bringen.
2. ANSPRECHEN EINES TEILS DER HARDWARE - Dieses betrifft z.B. die Befehle SOUND, STICK oder PADDLE.
3. EINFACHER AUFRUF VON ASSEMBLER-ROUTINEN - Die USR-Funktion gestattet dem Benutzer den Zugriff auf Assembler-Routinen.
4. DER INTERPRETER LIEGT im ROM - Dieses schützt vor versehentlichen ƒnderungen des BASIC-Interpreters durch den Programmierer oder sein Programm.
5. ZUGRIFF AUF DAS DOS - Spezielle Befehle, wie z.B. NOTE oder POINT (DOS 2.0S) gestatten dem Benutzer über das Disk-Operating-System einen freien Zugriff auf die Diskettenstationen.
6. ZUGRIFF AUF DIE PERIPHERIE - Jedes vom OS erkannte und akzeptierte Peripheriegerät kann über BASIC angesteuert werden.
Schwächen
des ATARI BASICs:
1. KEINE INTEGER-ARITHMETIK - Alle Zahlen werden als 6 Byte-BCD Fließkomma-Zahlen gespeichert.
2. LANGSAME MATHEMATISCHE BERECHNUNGEN - Da alle Zahlen im oben genannten Format gespeichert werden, sind die Berechnungen entsprechend langsam.
3. KEINE STRING-ARRAYS - Es können nur eindimensionale Strings aufgebaut werden.
WIE
DAS ATARI-BASIC ARBEITET
Ein
kurzer ‹berblick über die Arbeit des BASIC-Interpreters.
1.
Der
Interpreter bekommt vom Benutzer eine Zeile eingegeben und wandelt diese in eine
Token-Form um.
2.
Diese
Token-Zeile wird in das bisherige Token-Programm eingefügt.
3.
Das
Programm wird ausgeführt.
Die
Details dieser Operationen werden in den folgenden 4 Abschnitten besprochen:
A.
Tokenisierung
B.
Struktur der tokenisierten Dateien
C.
Programmausführung
D.
BASIC und das Betriebssystem
A.
TOKENISIERUNG
Die Tokenisierung
einer Zeile in BASIC sieht wie folgt aus:
1.
BASIC nimmt eine Zeile
entgegen.
2.
Die Zeilensyntax wird
überprüft (SYNTAX CHECK).
3.
Während dieses
Checks wird die Zeile tokenisiert.
4.
Die tokenisierte Zeile
wird in das Token-Programm eingefügt.
5.
Wurde die Zeile im
Direkt-Modus, d.h. ohne Zeilennummer eingegeben, so wird sie sofort ausgeführt.
Um
den Tokenisierungs-Vorgang besser zu verstehen, müßen als erstes einige
Begriffe geklärt werden:
TOKEN
Ein
8-Bit-Zeichen, das einen speziellen interpretierbaren Code enthält.
STATEMENT
Eine
vollständige Folge von Tokens, die BASIC veranlassen, etwas auszuführen.
Beim LISTen werden die einzelnen Statements durch Doppelpunkte voneinander getrennt.
ZEILE
Ein
oder mehrere Statements, denen entweder eine Nummer von 0 bis 32767, oder keine (im
Falle des direkten Ausführens) vorangeht.
KOMMANDO
Der
erste ausführbare Token eines Statements, das BASIC
mitteilt, wie die folgenden Tokens zu interpretieren sind.
VARIABLE
Ein
Token, der ein indirekter Zeiger zu seinem augenblicklichen Wert ist; der Wert kann
geändert werden, ohne den Token zu ändern.
KONSTANTE
Ein
6-Byte-BCD-Wert, dem ein spezieller Token vorangeht. Dieser Wert wird während
der Programmausführung nicht geändert.
OPERATOR
Einer
von 46 Tokens, die auf irgendeine Weise den folgenden Wert ändern oder bewegen.
FUNKTION
Ein
Token, der bei Ausführung einen bestimmten Wert für das Programm erzeugt.
EOL
-End
of Line- (Ende der Zeile). Ein Zeichen mit dem hexadezimalen Wert 98.
Der
BASIC-Interpreter beginnt den Tokenisierungs-Vorgang mit Empfang einer vom Benutzer
geschriebenen Zeile. Diese Eingabe geschieht über einen Handler des OS. Normalerweise
ist dieses der Bildschirm-Editor, mit dem ENTER-Kommando ist allerdings die Festlegung
eines beliebigen Gerätes möglich. Der von BASIC an irgendein Gerät
gesendete Befehl wird als GET RECORD-Kommando bezeichnet.
Die
hierdurch erhaltenen Daten stehen im ATASCII-Format und werden durch ein EOL-Zeichen
abgeschloßen. Sie werden mittels CIO in den sog. Eingabe-Zeilen-Buffer ($580
bis $5FF) gespeichert.
Nachdem
das GET RECORD-Kommando aus und ein Syntax-Check durchgeführt wurde, beginnt
der Tokenisierungs-Vorgang. Als erstes überprüft der BASIC-Interpreter,
ob eine Zeilennummer angegeben wurde. Ist dieses der Fall, so wird sie in eine 2-Byte-Integer-Zahl
umgewandelt. Ist keine Zeilennummer vorhanden, so wird Direktmodus angenommen und
die Zeilennummer wird intern auf $8000 gesetzt. Diese beiden Bytes sind die ersten
Tokens der tokenisierten Zeile, die in den Token-Ausgabe-Puffer gebracht wird. Dieser
Puffer besitzt eine Länge von 256 Bytes und liegt am Ende des fürs Operating-System
reservierten RAMs.
Das
nächste Token ist ein sog. "Dummy"-Byte. Es ist für das Zählen
der Bytes vom Anfang dieser Zeile bis zum Anfang der nächsten (=Offset) nötig.
Danach folgt ein weiteres Dummy-Byte, das die Anzahl der Bytes vom Anfang dieser
Zeile bis zum Anfang des nächsten Statements abgeschloßen wurde. Der Sinn
dieser Parameter wird im Abschnitt zum Programm-Ausführungs-Prozeß erläutert.
Der
BASIC-Interpreter überprüft nun den Befehl des ersten Statements der eingegebenen
Zeile mit Hilfe der im ROM befindlichen Liste aller zugelassenen Kommandos. Wird
ein passendes Kommando gefunden, dann wird das nächste Byte der tokenisierten
Zeile die Nummer des zum Befehl passenden Eintrages der Liste im ROM. Wird kein passender
Befehl gefunden, so wird das "Syntax-Error"- (Syntax-Fehler)-Token eingesetzt
und der Interpreter stoppt den Tokenisierungs-Vorgang. Die restliche Zeile im Eingabe-Buffer
wird im ATASCII-Format in den Token-Ausgabe-Puffer kopiert, woraufhin die fehlerhafte
Zeile ausgedruckt wird.
In
einer zulässigen Zeile muß hinter einem Kommando eines der folgenden Dinge
stehen: eine Variable, eine Konstante, ein Operator, eine Funktion, ein Anführungszeichen,
ein anderes Statement oder ein EOL-Zeichen. Der BASIC-Interpreter testet, ob das
nächste eingegebene Zeichen numerisch ist. Ist dieses nicht der Fall, dann wird
das Zeichen (sowie die nachfolgenden) mit den bisherigen Einträgen in eine Variablenliste
verglichen. (Bei der ersten eingegebenen Zeile kann hierbei natürlich nichts
gefunden werden, da es noch keine Einträge gibt.) Die Zeichen werden dann mit
den Funktions- und Operatortafeln verglichen. Wird hierbei ebenfalls nichts gefunden,
so nimmt der Interpreter an, daß es sich um eine neue Variable handelt. (Da
dieses in der ersten Zeile die erste Variable ist, ist es auch der erste Eintrag
in die Variablenliste.) Die Zeichen werde aus dem Eingabe-Buffer in die Variablenliste
kopiert, wobei das höchstwertigste Bit (MSB) des letzten Bytes vom Variablen-Namen
gesetzt wird. Danach werden für diesen Eintrag 8 Bytes in der Variablentafel
reserviert. (Siehe Erklärung der Variablentafel im Abschnitt über die Struktur
der tokenisierten File) Das Token, das für eine Variable in eine tokenisierte
Zeile eingesetzt wird, ist die Nummer derselben minus 1, wobei das MSB gesetzt ist.
Das bedeutet, das Token der ersten eingegebenen Variablen wäre $80, der zweiten
$81 usw., bis $FF, was insgesamt 128 verschiedene Variablen-Namen zuläßt.
Wird
eine Funktion gefunden, dann nimmt das erste Token den zugehörigen Wert der
Funktionstafel an. Funktionen erfordern bestimmte Folgen von Parameter; diese sind
in Syntax-Tafeln enthalten. Stimmen sie nicht überein, so wird eine Syntax-Fehlermeldung
ausgegeben.
Wird
ein Operator gefunden, so enthält das Token die zugehörige Nummer der Operatortabelle.
Operatoren können in sehr komplexer Form aufeinander folgen (z.B. verschachtelte
Klammern), daher ist der entsprechende Syntax-Check etwas komplizierter.
Im
Falle von Anführungszeichen nimmt der Interpreter an, daß ein String (=
Reihe, Folge) von Zeichen folgt. Das Token enthält den hexadezimalen Wert 0F,
wobei ein Dummy-Byte für die Länge des Strings reserviert wird. Die Zeichen
werden dann aufeinanderfolgend von Eingabe-Puffer in den Ausgabe-Puffer übertragen,
bis ein weiteres Anführungszeichen gefunden wird. Das Dummy-Byte erhält
dann einen Wert, die Anzahl der Zeichen dieses Strings.
Ist
das folgende Zeichen im Eingabe-Puffer numerisch, so wird es vom BASIC-Interpreter
in eine 6-Byte-BCD-Konstante umgewandelt. Ein Token mit dem wert $0E, dem die 6-Byte-Konstante
folgt, wird danach in den Ausgabe-Puffer gebracht.
Stößt
der Interpreter auf einen Doppelpunkt, so wird eine $l4 als Token eingesetzt und
in den Ausgabe-Puffer gebracht. Das vorher bereitgestellte Dummy-Byte erhält
ebenfalls seinen Wert, d.h. die Bytes vom Anfang der Zeile bis zum Anfang des nächsten
Statements. Danach wird ein weiteres Dummy-Byte reserviert und der BASIC-Interpreter
beginnt wieder mit dem Abfragen eines Befehls.
Wenn
ein EOL-Zeichen gefunden wird, dann wird ein $16-Token gespeichert und die Anzahl
der Bytes vom Zeilenanfang in das für den Zeilen-Offset reservierte eingesetzt.
Zu diesem Zeitpunkt ist die Tokenisierung abgeschloßen, und der BASIC-Interpreter
bringt die tokenisierte Zeile in das Token-Programm. Als erstes wird hierbei nach
der Nummer der eben tokenisierten Zeile gesucht. Wird die Nummer gefunden, so wird
die betreffende Zeile durch die neue ersetzt. Andernfalls wird die neue Zeile an
der (numerisch) richtigen Stelle in das tokenisierte Programm eingefügt. In
beiden Fällen werden die Daten, die der Zeile folgen, im Speicher auf- oder
abbewegt, wodurch das Programm weiter ausgedehnt bzw. verkleinert wird.
Der
Interpreter prüft nun, ob die tokenisierte Zeile direkt ausgeführt werden
soll. Ist dieses der Fall, dann führt er diese wie unter Interpretationsprozeß
beschrieben aus. Andernfalls wartet der BASIC-Interpreter auf eine neue Zeile.
‹berschreitet
die Länge der tokenisierten Zeile zu irgendeinem Zeitpunkt 256 Bytes, so wird
eine "Fehler 14"-Meldung auf dem Bildschirm ausgegeben. Danach wartet der
Interpreter wieder auf eine neue Zeile.
Die
Tokenisierung einer Zeile sieht wie folgt aus (alle Werte stehen in hexadezimaler
Notation.):
10 LET X=1:PRINT X
KOMMANDO
OPERANDEN
FUNKTIONEN
Hex
Dez
Hex Dez
Hex Dez
---------------------------------------------------------------
00
0 REM
0E 14
(numer. Konstante) 3D
61 STR$
01
1 DATA
0F 15
(String-Konstante) 3E
62 CHR$
02
2 INPUT
10 16
"
3F 63
USR
03
3 COLOR
11 17
(nicht belegt)
40 64
ASC
04
4 LIST
12 18
,
41 65
VAL
05
5 ENTER
13 19
$
42 66
LEN
06
6 LET
14 20
: (Ende d. Statem.) 43
67 ADR
07
7 IF
15 21
;
44 68
ATN
08
8 FOR
16 22
(Ende der Zeile)
45 69
COS
09
9 NEXT
17 23
GOTO
46 70
PEEK
0A
10 GOTO
18 24
GOSUB
47 71
SIN
0B
11 GO TO
19 25
TO
48 72
RND
0C
12 GOSUB
1A 26
STEP
49 73
FRE
0D
13 TRAP
1B 27
THEN
4A 74
EXP
0E
14 BYE
1C 28
=
4B 75
LOG
0F
15 CONT
1D 29
<= (Numerische)
4C 76
CLOG
10
16 COM
1E 30
<>
4D 77
SGR
11
17 CLOSE
1F 31
>=
4E 78
SGN
12
18 CLR
20 32
<
4F 79
ABS
13
19 DEG
21 33
>
50 80
INT
14
20 DIM
22 34
=
51 81
PADDLE
15
21 END
23 35
.
52 82
STICK
16
22 NEW
24 36
*
53 83
PTRIG
17
23 OPEN
25 37
+
54 84
STRIG
18
24 LOAD
26 38
-
19
25 SAVE
27 39
/
1A
26 STATUS
28 40
NOT
1B
27 NOTE
29 41
OR
1C
28 POINT
2A 42
AND
1D
29 XIO
2B 43
(
lE
30 ON
2C 44
)
1F
31 POKE
2D 45
= (Arithmetische)
20
32 PRINT
2E 46
= (Strings)
21
33 RAD
2F 47
<=(Strings)
22
34 READ
30 48
<>
23
35 RESTORE
31 49
>=
24
36 RETURN
32 50
<
25
37 RUN
33 51
>
26
38 STOP
34 52
=
27
39 POP
35 53
+ (Vorzeichen)
28
40 ?
36 54
- (Vorzeichen)
29
41 GET
37 55
( (String)
2A
42 PUT
38 56
( (Array)
2B
43 GRAPHICS
39 57
( (DIM Array)
2C
44 PLOT
3A 58
( (Sonstige)
2D
45 POSITION
3B 59
( (DIM String)
2E
46 DOS
3C 60
, (Array-Komma)
2F
47 DRAWTO
30
48 SETCOLOR
31
49 LOCATE
32
50 SOUND
33
51 LPRINT
34
52 CSAVE
35
53 CLOAD
36
54 (implizites LET)
37
55 ERROR (Syntax)
B.
STRUKTUR DER TOKENISIERTEN DATEI
Die
tokenisierte Datei enthält zwei Hauptbestandteile: 1) eine Gruppe von Zero-Page-Zeigern,
die in die tokenisierte Datei zeigen und 2) die tokenisierte Datei selbst. Die Zero-Page-Zeiger
sind 2-Byte-Werte, die auf verschiedene Abschnitte der Token-Datei zeigen. Es gibt
9 dieser 2-Byte-Zeiger. Sie liegen bei den Adressen $80 bis $91. Die folgende Liste
reiht die Zeiger und die Abschnitte der Token-Datei auf, auf welche sie zeigen.
Zeiger
(Hex) Abschnitt der Token-Datei (Fortlaufende
Blöcke)
------------
-----------------------------------------------
LOMEM
80,81 Token Ausgabe-Puffer
- Diesen Puffer benutzt der
BASIC-Interpreter, um eine Zeile zu codieren. Er
ist 256 Bytes lang und liegt am Ende des vom OS
benutzten RAMs.
VNTP
82,83 Variablen-Namentafel
- Eine Liste aller in das
Programm eingegebenen Variablen. Diese werden als
ATASCII-Zeichen gespeichert und stehen in der
gleichen Reihenfolge, in der sie in das Programm
eingegeben wurden. Es gibt drei verschiedene
Arten von Einträgen:
1.numerische Variablen
- MSB des letzten Zeichens
des Namens gesetzt.
2.String-Variablen -
Das letzte Zeichen ist ein
"$", dessen MSB gesetzt ist.
3.Variablenfelder -
Das letzte Zeichen ist ein
"(", dessen MSB gesetzt ist.
VNTD
84,85 Ende der Variablen-Namenstafel
- Der BASIC-
Interpreter benutzt diesen Zeiger zum Anzeigen
des Namenstafel-Endes. Dieser zeigt normalerweise
auf ein Dummy-Byte mit dem Wert Null (weniger als
128 Variablennamen). Gibt es 128 Variablen, so
zeigt er auf das letzte Byte des letzten
Variablennamens.
VVTP
86,87 Variablen-Wertetafel
- Diese Tafel enthält den
augenblicklichen Wert jeder Variablen. Die
Information für den jeweiligen Variablentyp sieht
wie folgt aus:
Eine
numerische Variable enthält einen numerischen Wert. Ein Beispiel hierfür
wäre X=l. X ist die numerische Variable und 1 ihr Wert (6-Byte BCD-Format).
Ein Feld wird aus numerischen Werten zusammengesetzt, die im String/Array-Bereich
gespeichert werden. Er besitzt einen Eintrag in die Wertetafel. Ein String, bestehend
aus im String/Array-Bereich gespeicherten Zeichen, besitzt ebenfalls einen Eintrag
in der Wertetafel.
Das
erste Byte jedes Eintrages benennt den Variablentyp: 00 steht für eine Variable,
40 für ein Feld und 90 für einen String. Wurde das Feld oder der String
dimensioniert, dann wird das LSB des ersten Bytes gesetzt.
Im
Falle der numerischen Variablen enthalten die Bytes 3 bis 8 die 6-Byte-BCD-Zahl,
die ihrem augenblicklichen Werten entsprechen.
Für
Strings und Felder enthalten die Bytes 3 und 4 den Offset von Anfang des Strings/Array-Bereiches
bis zum Anfang der Daten (siehe unten).
Das
fünfte und sechste Byte eines Arrays enthalten den ersten Parameter des DIM-Befehls.
Die Zahl ist ein 2-Byte-Integer, dessen Wert um 1 größer ist, als der
vom Benutzer eigegebene. Das siebte und, achte Byte enthalten den zweiten Parameter
des DIM-Befehls, dessen Wert ebenfalls um 1 größer ist, als der vom Benutzer
eingegebene.
STMTAB
88,89 Statement-Tafel
- Dieser Datenblock enthält
alle vom Benutzer eingegebenen Zeilen, die von
BASIC tokenisiert wurden. Zeilen im Direktmodus
werden ebenfalls hier abgelegt. Das Format dieser
Zeile wird im Abschnitt über den Tokenisierungs-
prozeß beschrieben.
STMCUR
8A,8B Augenblickliches Statement
- Dieser Zeiger wird
vom BASIC-Interpreter benutzt, um auf bestimmte
Tokens innerhalb einer Zeile der Statement-Tafel
hinzuweisen. Erwartet der Interpreter eine
Eingabe, so wird dieser Zeiger auf den Anfang der
Direktmodus-Zeile gesetzt.
STARP
8C,8D String/Array-Bereich
- Dieser Block enthält alle
String- und Felddaten. String-Zeichen werden als
jeweils ein Byte ATASCII-Code gespeichert, d.h.
ein String von 20 Zeichen Länge benötigt 20 Bytes
des Speichers. Felder werden im 6-Byte-BCD-Format
gespeichert. Ein Feld mit 10 Elementen benötigt
demnach 60 Bytes des RAMs.
Dieser Bereich wird bei jedem neuen DIM-Befehl um
die Länge bzw. das 6-fache der Feldgröße
vergrößert.
RUNSTK
8E,8F RUN-TIME-STACK
- Dieser Software-Stapel enthält
GOSUB- und FOR/NEXT-Einträge. Der GOSUB-Eintrag
besteht aus 4 Bytes. Das erste ist eines mit dem
Wert 0, welches das GOSUB anzeigt. Danach folgt
als 2-Byte-Integer die Zeilennummer, in welcher
die Anweisung steht. Schließlich folgt ein Byte,
welches den Offset des GOSUBs zum Zeilenanfang
angibt, so daß der Interpreter wieder dorthin
zurückspringen kann (RETURN), um das nächste
Statement auszuführen.
Jeder FOR/NEXT-Eintrag besteht aus 16 Bytes. Der
erste Eintrag enthält den Endwert für den Zähler.
Der zweite ist die Schrittweite, d.h. der Wert,
um den der Zähler jedesmal erhöht wird. Jeder
dieser beiden Werte wird im 6-Byte-BCD-Format
gespeichert. Das 13. Byte ist die Nummer der
Zählervariablen, deren MSB gesetzt ist. Das 14.
und 15. Byte enthalten die Zeilennummer, und das
16. Byte ist der Zeilenoffset des FOR-Statements.
MEMTOP
90,91 Oberstes Ende des verwendeten
RAMs - Dieses ist
das Ende des BASIC-Programms. Die Ausdehnung des
Programms kann von hier aus bis zum Ende des
freien RAMs, das durch den Anfang der
Display-List festgelegt wird, geschehen. Die
FRE-Funktion berechnet die Größe des freien RAMs,
in dem MEMTOP von HIMEM ($2E5,$2E6) abgezogen
wird. Anmerkung: MEMTOP des BASICs ist nicht
identisch mit der OS-Variablen MEMTOP!
C.
PROGRAMMAUSF‹HRUNG
Um
eine Programmzeile auszuführen, ist es erforderlich, die, Tokens, die während
dem Tokenisierungs-Vorgangs erzeugt werden, zu lesen. Jedes Token hat eine bestimmte
Bedeutung und veranlaßt den Interpreter zur Ausführung einer Folge von
Operationen. Hierfür ist es erforderlich, daß sich der BASIC-Interpreter
jeweils ein Token aus dem tokenisierten Programm holt und dieses ausführt. Das
Token ist ein Index zu einer Sprungtafel von Routinen. Das bedeutet, ein PRINT-Token
zeigt auf eine Routine zur Durchführung eines solchen Befehls. Nach Abschluß
der Routine(n) holt sich der Interpreter das nächste Token. Der Zeiger, der
auf das neue Token zeigt, heißt STMCUR und liegt bei $8A und $8B.
Die
erste Zeile, die innerhalb eines Programmes ausgeführt wird, ist die Direktmodus-Zeile.
Sie enthält gewöhnlich ein RUN oder GOTO xxxxx. Im Falle eines RUN-Befehls
holt der Interpreter sich die erste tokenisierte Zeile aus der Statement-Tafel und
führt diese aus. Weisen die Tokens in der Zeile nicht auf eine andere, dann
fährt der BASIC-Interpreter nach Durchführung dieser Zeile mit der Abarbeitung
der nächsten fort.
Wird
auf ein GOTO gestoßen, dann muß die anzuspringende Zeile gefunden werden;
Die Statement-Tafel enthält eine verkettete Liste von Zeilennummern und Statements,
wobei die niedrigste Nummer an erster und die höchste an letzter Stelle steht.
Wird eine andere Zeile in der Mitte der Tafel benötigt, dann läuft der
folgende Prozeß ab:
Die
Adresse der ersten Zeile wird im STMTAB-Zeiger ($88,$89) gefunden und in einem Hilfszeiger
gespeichert. Die ersten zwei Bytes der Zeile sind ihre Nummer. Diese wird mit der
anzuspringenden Zeilennummer verglichen. Ist die Nummer kleiner, dann holt sich der
Interpreter die nächste Zeile, indem der Wert des dritten Bytes der ersten Zeile
zum Wert des Hilfszeigers addiert wird. Hierdurch zeigt der Hilfszeiger auf die zweite
Zeile. Nun werden wieder die ersten beiden Bytes der neuen Zeile mit dem Wert der
anzuspringenden verglichen. Sind die ersten beiden Bytes kleiner, so wird das dritte
Byte zum Hilfszeiger addiert.
Ist
die anzuspringende Zeilennummer gefunden, dann wird der Hilfszeiger nach STMCUR übertragen.
Der Interpreter holt sich dann das nächste Token aus der neuen Zeile.
Wenn
die gesuchte Zeile nicht gefunden wird, dann gibt der BASIC-Interpreter eine "ERROR
12"-Meldung aus ("LINE NOT FOUND" - Zeile nicht gefunden).
Die Ausführung
eine GOSUBs erfordert mehr Arbeit als die eines
GOTOs.
Die Routine für das Auffinden der gewünschten Zeile ist die gleiche. Bevor
der Interpreter aber zur neuen Zeile geht, wird ein Eintrag auf den RUN-TIME-STACK
gebracht. Dieser Eintrag umfaßt 4 Bytes am Ende den Stapels und speichert eine
0 im ersten Byte, um anzuzeigen, daß es sich um einen GOSUB-Befehl handelt.
Als nächstes werden die beiden Bytes, welche die Zeilennummer angeben, in der
der GOSUS-Befehl steht, auf den Stapel gebracht. Das letzte Byte enthält den
Offset vom Anfang der Zeile bis zum GOSUB-Token. Danach führt der BASIC-Interpreter
die angesprungene Zeile aus. Wenn er auf eine RETURN-Anweinung stößt,
dann holt er den letzten Eintrag vom Stapel und geht zu der Zeile zurück, in
welcher die GOSUB-Anweisung stand. Danach wird der nächste Befehl ausgeführt.
Das
FOR-Kommando veranlaßt den Interpreter zu einer Reservierung von 16 Bytes auf
dem RUN-TIME-STACK. Die ersten 6 Bytes geben den Endwert an, den die Zählervariable
erreichen kann (6-Byte-BCD-Format). Die nächsten 6 Bytes enthalten die Schrittweite
im gleichen Format. Danach wird die Variablennummer (mit gesetztem MSB) gespeichert.
Als nächstes folgt die augenblickliche Zeilennummer (2 Bytes) und der Offset
in die Zeile. schließlich wird der Rest der Zeile ausgeführt.
Findet
der Interpreter das NEXT-Kommando, so ließt er den letzten Stapeleintrag. Es
wird überprüft, ob die Variable, die durch das NEXT ausgegeben wird, die
gleiche ist, die als letztes auf den Stapel gebracht wurde. Außerdem wird überprüft,
ab der Zähler den Endwert erreicht oder überschritten hat. Ist dieses nicht
der Fall, dann kehrt der BASIC-Interpreter zur Zeile mit der FOR-Anweisung zurück
und fährt mit der Programm-Ausführung fort. Wird der Endwert des FOR-Befehls
erreicht oder überschritten, so wird der FOR/NEXT-Eintrag vom Stapel geholt
und die Programmausführung von diesem Punkt an fortgesetzt.
Stößt
der Interpreter auf einen mathematischen Ausdruck, dann werden die Operatoren auf
einen Operatoren-Stapel gebracht. Danach werden sie nacheinander wieder vom Stapel
geholt und ausgeführt. Die Reihenfolge, in der sie auf den Stapel gebracht werden,
kann beeinflußt werden. Werden die einzelnen Operatoren durch Klammern eingeschlossen
so werden sie in der Reihenfolge, in der sie abgearbeitet worden sollen, auch auf
den Stapel gebracht. Andernfalls "schaut" der Interpreter in einer ROM-Tafel
"nach", in der die Prioritäten der einzelnen Operatoren verzeichnet
sind, und bringt sie dementsprechend auf den Stapel (z.B. Punkt- vor Strichrechnung).
Wird
zu irgendeinem Zeitpunkt die BREAK-Taste gedrückt dann wird vom OS ein Flag
gesetzt, um dieses Ereignis festzuhalten. Der BASIC-Interpreter überprüft
dieses Flag jeweils nach der Ausführung eines Tokens. Wenn dieses Flag gesetzt
ist, wird die Nummer der Zeile, in der die Taste gedrückt wurde, gespeichert
und eine "STOPPED AT LINE XXXXX"-Meldung ausgegeben, bei der dieser Wert
eingesetzt wird. Danach wird das BREAK-Flag gelöscht und auf eine Eingabe vom
Benutzer gewartet. Durch Eingabe eines "CONT"-Befehls kann der Benutzer
erreichen, daß die Programmausführung ab der nächsten Zeile wieder
aufgenommen wird.
D.
BASIC UND DAS BETRIEBSSYSTEM
Der BASIC-Interpreter
benutzt das OS hauptsächlich durch I/O-Aufrufe des CIO. Die folgende Liste zeigt
eine Reihe durch den Benutzer ausführbare Aufrufe und die zugehörigen IOCBs
des OS.
BASIC
OS
-------------------
---------------------------
OPEN #1,12,0,E:
IOCB=l
Kommando=3 (OPEN)
Aux1=12 (Ein-/Ausgabe)
Aux2=0
Pufferadresse=ADR("E:")
GET #1,X
IOCB=!
Kommando=7 (GET CHARACTER)
Pufferlänge=0
=> Zeichen wird im Akku-
mulator übergeben
PUT #1,X
IOCB=1
Kommando=ll (PUT CHARACTER)
Pufferlänge=0
=> Zeichenausgabe aus dem
Akkumulator
INPUT #1,A$
IOCB=l
Kommando=5 (GET RECORD)
Pufferlänge=Größe von A$
(nicht über 120)
Pufferadresse=Eingabe-Zei-
len-Puffer
PRINT #I,A$
IOCB=l
Der Interpreter benutzt
einen speziellen PUT BYTE-
Vektor im IOCB, um direkt
mit dem Handler zu
kommunizieren.
XIO 18,#6,12,0,"S:"
IOCB=6
Kommando=18 (FILL-Kommando)
Aux1=12
Aux2=0
SAVE/LOAD:
Wenn ein Token-Programm auf einem Gerät mit SAVE gespeichert wird, dann werden
zwei Informationsblöcke geschrieben. Der erste Block besteht aus 7 von 9 Zero-Page-Zeigern,
die der Interpreter benutzt, um die Token-Files zu verwalten und aufrecht zu erhalten.
Diese Zeiger sind LOMEM ($80,$81) bis STARP ($8C,$8D). Bei diesem Schreiben wird
allerdings eine ƒnderung gemacht: die 2-Byte-Zeiger werden erst auf ein Gerät
geschrieben, nachdem der Wert des LOMEM-Zeigers von jedem subtrahiert wurde. Dieses
bedeutet, daß die ersten beiden gespeicherten Werte immer 0 und 0 sind.
Der
zweite gespeicherte Informationsblock besteht aus folgenden Abschnitten der tokenisierten
Datei:
1) der Namenstafal
der Variablen,
2) der Wertetafel
der Variablen,
3) dem tokenisierten
Programm und
4) der Direktmodus-Zeile.
Wird
dieses Programm wieder in den Speicher geladen (LOAD), dann "sieht" der
Interpreter "auf" die OS-Variable MEMLO ($2E7,$2E8), und addiert deren
Wert zu jedem der 2-Byte-Zero-Page-Zeiger, sobald diese gelesen werden. Die Zeiger
werden wieder in der Zero-Page des Speichers plaziert, wonach die Werte von RUNSTK
($8E,$8F) und MEMTOP ($90,$91) auf den Wert von STARP gebracht werden.
Als
nächstes werden 256 Bytes ab der Speicherstelle reserviert, die von MEMLO angegeben
wird, um Platz für den Token-Ausgabe-Puffer zu schaffen. Danach wird die Information
der tokenisierten Datei eingelesen. Diese Daten werden genau hinter dem Token-Ausgabe-Puffer
plaziert.
OS-
und BASIC-Zeiger (ohne DOS)
VERBESSERUNG
DER PROGRAMMAUSF‹HRUNG
Um
die Effektivität eines Programms zu steigern, können zwei Dinge getan werden.
Zum einen kann die Ausführungszeit, zum anderen der benötigte Speicherplatz
verringert werden. Um diese beiden Ziele zu erreichen, kann der Benutzer die nachfolgend
Tips verwenden. Die einzelnen Methoden sind in einer Folge aufgereiht, bei der die
erste die größte und die letzte die geringste Effektivität besitzt.
Methoden
zur Erhöhung der Ausführungsgeschwindigkeit von BASIC-Programmen:
1. NEUCODIEREN - Da BASIC keine strukturierte Sprache ist, werden in dieser Sprache geschriebene Programme nicht ineffizient. Ein Programm kann auch nach mehreren ‹berarbeitungen noch immer nicht optimal sein. Es lohnt sich also immer, ein Programm noch einmal durchzusehen.
2. UBERPR‹FEN DER ALSORITHMISCHEN LOGIK - Der Programmteil für die Durchführung einer Operation muß so durchschlagend wie nur irgendmöglich sein.
3. Oft BENUTZTE UNTERPROGRAMME UND FOR/NEXT-SCHLEIFEN M÷GLICHST AN DEN ANFANG DES PROGRAMMES STELLEN - Der Interpreter beginnt mit der Suche nach einer Zeilennummer immer am Anfang des Programms, so daß weiter hinten stehende Zeilen erst sehr viel später erreicht werden.
4. MEHRFACH BENUTZTE OPERATIONEN INNERHLAB EINER SCHLEIFE DIREKT ALS UNTERPROGRAMM EINGEBEN - Der BASIC-Interpreter benötigt sehr viel Zeit, um den RUN-TIME-STACK zu aktualisieren.
5. DIE SICH AM HƒUFIGSTEN ƒNDERNDE SCHLEIFE SOLLTE BEI VERSCHACHTELTEN SCHLEIFEN DIE INNERSTE SEIN - Der RUN-STACK-TIME wird dadurch seltener benutzt.
6. VEREINFACHEN DER FLIEßKOMMA-ARITHMETIK INNERHALB VON SCHLEIFEN - Wird ein Ergebnis durch Multiplikation mit dem Index erreicht, so sollte diese statt dessen in eine Addition von Konstanten umgewandelt werden.
7. SCHLEIFEN IN EINE ZEILE BRINGEN - Der BASIC-Interpreter muß dadurch nicht erst in die nächste Zeile springen, um die Schleife fortzusetzen.
9. ABSCHALTEN DES BIDSCHIRMS - Ist eine Bildschirmanzeige (über einen Zeitraum) nicht unbedingt erforderlich, so können 30% der Ausführungszeit durch Setzen einer 0 in die Speicherstelle 559 gespart werden (POKE 559,0).
9. BENUTZEN EINES SCHNELLEREN GRAPHIKMODUS¥ BZW. EINER K‹RZEREN DISPLAY LIST - Ist nicht unbedingt ein voller Bildschirm erforderlich, so können hierdurch bis zu 25% der Ausführungszeit gespart werden.
10. BENUTZEN VON ASSEMBLER-UNTERPROGRAMMEN - Durch Aufrufen von Assembler-Unterprogrammen über die USR-Funktion kann die Ausführungszeit eines Programms verkürzt werden.
Sparen von Speicherplatz:
1. NEUCODIEREN - Wie schon genannt. Durch Neuorganisation das Programms kann Speicherplatz eingespart werden.
2. L÷SCHEN VON BEMERKUNGEN - REMs werden als ATASCII-Daten gespeichert und verbrauchen dementsprechend Speicherplatz.
3. ERSETZEN VON KONSTANTEN, DIE MEHR ALS DREIMAL BENUTZT WERDEN - Der BASIC-Interpreter benötigt für eine Konstante 7 Bytes, für einen Variablenaufruf aber nur 1 Byte. Durch das Einsetzen von Variablen können also jeweils 6 Bytes gespart werden.
4. INITIALISIEREN VON VARIABLEN MIT DER READ-ANWEISUNG - Ein Data-Statement wird als ATASCII-Code gespeichert, d.h. ein Byte pro Zeichen. Durch Gleichsetzen mit einer Konstanten gehen Jedesmal 7 Bytes verloren.
5. ERSETZEN VON KONSTANTEN DURCH AUSDRUCKE AUS BEREITS BENUTZTEN VARIABLEN - so kann eine 1 durch Zl, und eine 2 durch Z2 ersetzt werden. Wird die Zahl 3 benötigt, so schreibt man Z1+Z2.
6. HƒUFIG BENUTZTE ZEILENNUMMERN IN VARIABLEN UMWANDELN - Wird z.B. die Zeile 100 über einen GOSUB- oder GOTO-Befehl 50 mal angesprungen, so können ca. 300 Bytes gespart worden, indem man die 100 jedes Befehls durch ein Z100 ersetzt (GOTO Z100).
7. DIE ANZAHL DER VARIABLEN M÷GLICHST KLEIN HALTEN - Jeder neue Eintrag in die Namenstafel für Variablen verbraucht 8 Bytes, zuzüglich der Bytes des Namens.
8. LöSCHEN DER NICHT MEHR BEN÷TISTEN VARIABLEN - Variablen werden nicht durch Löschen aus dem Programm aus der Namenstafel entfernt. Um sie aus letzterer zu entfernen, muß das Programm auf Diskette oder Cassette geLISTet werden. Danach wird NEW eingegeben und das Programm über ENTER wieder geladen.
9. VARIABLENNAMEN KURZ HALTEN - Jeder Variablenname wird in der Namenstafel als ATASCII-Daten gespeichert. Je kürzer die einzelnen Namen sind, desto kürzer ist die Tafel, d.h. umso um so weniger Speicher wird verbraucht.
10. ERSETZEN VON MEHRFACH BENUTZTEM TEXT DURCH STRINGS - Durch diese Methode kann viel Speicherplatz gespart werden, da jeder Buchstabe eines Textes einem Byte im Speicher entspricht.
11. INITIALISIEREN EINES STRINGS DURCH GLEICHSETZEN - Ein Gleichsetzen mit Zeichen innerhalb zweier Anführungszeichen benötigt weniger Speicherplatz, als eine READ-Anweisung oder eine CHR$-Funktion.
12. ZUSAMMENFASSEN VON ZEILEN - 3 Bytes können gespart werden, wenn anstelle von zwei Zeilen mit jeweils einem Statement eine Zeile mit zwei durch Doppelpunkt getrennten Statements geschrieben wird.
13. VERMEIDEN NUR EINMAL BENUTZTER UNTERPROGRAMME - Nur einmal aufgerufene Unterprogramme sollten anstelle ihres Aufrufes direkt in den Programmcode eingefügt werden (GOSUB und RETURN verschwenden Bytes, wenn sie nur einmal benutzt werden).
14. ERSETZEN VON NUMERISCHEN FELDERN DURCH STRINGS, SOFERN DIE DATENWERTE NICHT GR÷ßER ALS 255 WERDEN - Einträge für numerische Felder verbrauchen jeweils 5 Bytes, wogegen Stringelemente nur ein Byte benötigen.
15. ERSETZEN VON SETCOLOR-BEFEHLEN DURCH POKE-KOMMANDOS - Dieses erspart jedesmal 9 Bytes.
16. ERSETZEN VON POSITION-ANWEISUNGEN DURCH CURSOR-STEUERCODES Die POSITION-Anweisung benötigt 15 Bytes für X- und Y-Parameter, wogegen ein Cursor-Steuerzeichen nur ein Byte verbraucht.
17. L÷SCHEN VON ZEILEN DURCH DAS PROGRAMM SELBST - Siehe Abschnitt "Fortgeschrittene Programmier-Techniken".
18. MODIFIZIEREN DES STRING/ARRAY-ZEIGBERS ZUM LADEN VON VORHER DEFINIERTEN DATEN - Siehe Abschnitt über fortgeschrittenel Programmier-Techniken.
19. VERKETTEN VON PROGRAMMEN - Ein Programmteil wird vom Benutzer gestartet und lädt dann nach seiner Ausführung selbsttätig den nächsten Teil von Diskette oder Cassette. Diese Technik läßt sich beliebig oft wiederholen.
FORTGESCHRITTENE
PROGRAMMIER-TECHNIKEN
Wenn
die Grundprinzipien des ATARI BASICs verstanden worden sind, können einige interessante
Programme geschrieben werden, die diese Prinzipien anwenden. Es können sowohl
reine BASIC-Programme sein, als auch solche, die die Möglichkeiten des OS mit
einbeziehen.
BEISPIEL 1 -String-Initialisierung - Dieses Progamm setzt alle Bytes eines Strings (bliebiger Länge) auf den gleichen Wert. Der BASIC-Interpeter kopiert das erste Byte des Quellenstrings in das erste Byte des Zielstrings. Danach folgt das zweite, das dritte usw. Da der Zielstring das zweite Byte des Quellenstrings ist, wird das gleiche Zeichen in den gesamten String geschrieben.
BEISPIEL 2 - Löschen von Programmzeilen - Durch Benutzen einer Möglichkeit des OS kann ein Programm Zeilen selbst löschen oder ändern. Der Bildschirm-Editor kann so geschaltet werden, daß er Informationen vom Bildschirm annimmt, ohne auf die Eingabe des Benutzers warten zu müßen. Als erstes wird der Cursor am oberen Bildschirmrand positioniert und das Programm gestoppt. Hierdurch liest der Interpreter die auf dem Bildschirm stehenden Kommandos ein.
BEISPIEL 3 - Sichern eines String/Array-Bereiches - Besitzt ein String oder Array immer die gleiche Größe und den gleichen Inhalt, so kann ein großer Betrag des Speicherbereiches gespart werden, indem die entsprechende Information während des SAVEns gespeichert und die Initialisierung beim nächsten Programmstart gelöscht wird.
BEISPIEL 4 - Sichern von BCD-Zahlen auf Diskette - Immer wenn numerische Daten auf ein Gerät geschrieben werden, geschieht dies im ATASCII-Format. Das heißt: die Zahl 10 wird als ATASCII-Information 1, gefolgt von einer 0 ausgeschrieben. Dieses kann bei Records mit festgelegter Länge problematisch sein. Eine Möglichkeit, um dieses zu umgehen, wäre, die Zahlen in 6-Byte-BCD-Zahlen umzuformen und in Strings zu speichern. Letztere werden dann zum Lesen und Schreiben benutzt.
BEISPIEL 5 - Player/Missile-Graphik mit Strings - In diesem Beispiel wird eine Möglichkeit der schnellen Bewegung von Player/Missile-Bilddaten gezeigt. Man ändert den String/Array-Offset eines Strings, so daß er auf den Bereich für die Player/Missile-Bilddaten zeigt. Durch Schreiben von Daten in diesen String (Gleichsetzen) können die Player/Missile-Bilddaten mit einer Geschwindigkeit bewegt werden, die der von Maschinensprache gleichkommt.
10 REM Beispiel 1:
String-Initialisierung
20 DIM A$(1000)
30 A$(1)="*":
A$(1000)="*"
40 A$(2)=A$
10 REM Beispiel 2:
Löschen von Zeilen
20 GRAPHICS 0:POSITION
2,4
30 ? 70:? 80:? 90:?
"CONT"
40 POSITION 2,0
50 POKE 842,13:STOP
60 POKE 842,12
70 REM Diese Zeilen
80 REM werden
90 REM gelöscht
10 REM Beispiel 3:
Sichern von Strings/Arrays
20 REM Beim ersten
Lauf ein GOTO 50 ausführen
30 REM Beim zweiten
Lauf Zeile 50 löschen
40 GOTO 110
50 DIM A$(10):A$="WWWWWWWWWW"
60 STARP=PEEK(140)+PEEK(141)*256
70 STARP=STARP+10
80 HI=INT(STARP/256):LO=STARP-HI*256
90 POKE 140,LO:POKE
141,HI
100 SAVE"0:STRING":STOP
110 STARP=PEEK(140)+PEEK(141)*256
120 STARP=STARP-10
130 HI=INT(STARP/256):LO=STARP-HI*256
140 POKE 140,LO:POKE
142,LO:POKE 144,LO
150 POKE 141,HI:POKE
142,HI:POKE 145,HI
160 DIM A$(10)
170 A$(10)="W"
180 STOP
10 REM Beispiel 4:
Sichern und Laden von
20 REM BCD-Zahlen
über die Diskettenstation
30 DIM A(0),B$(6)
40 B$(6,6)=CHR$(32)
50 VTAB=PEEK(134)+PEEK(135)*256
60 POKE VTAB+1,0
70 OPEN #1,8,0,"D:TEST"
80 FOR C=1 TO 15:A(0)=C:?
#1;B$:NEXT C
90 CLOSE #1
100 OPEN #1,4,0,"D:TEST"
110 FOR C=1 TO 15:INPUT
#1,B$:? A(0):NEXT C
120 CLOSE #1:END
10 REM Beispiel 5:
Player/Missile-Graphik über Strings
20 DIM A$(512),B$(20)
30 X=X+1:READ A:IF
A<>-l THEN B$(X,X)=CHR$(A):GOTO 30
40 DATA 0,255,129,129,129,129,129,129,129,129,255,0,-l
50 POKE 559,62:POKE
704,88
60 I=PEEK(106)-16:POKE
54279,I
70 POKE 53277,3:POKE
710,244
80 VTAB=PEEK(134)+PEEK(135)*256
90 ATAB=PEEK(140)+PEEK(141)*256
100 OFFS=I*256+1024-ATAB
110 HI=INT(OFFS/256):LO=OFFS-HI*256
130 Y=60:Z=100:V=1:H=l
140 A$(Y,Y+11)=B$:POKE
53248,Z
150 Y=Y+V:Z=Z+H
160 IF Y>213 OR
Y<33 THEN V=-V
170 IF Z>206 OR
Z<49 THEN H=-H
180 GOTO 140