Viele Arbeitsweisen von Forth sind bis hierher schon vorgestellt worden. Eine Eigenschaft von Forth, und mit Sicherheit nicht ihre unwichtigste, ist allerdings noch nicht erwähnt worden. Bevor sie aber erklärt werden kann, sind einige Vorbemerkungen erforderlich.
Das erste, was deutlich gemacht werden muss, ist die extreme
Einfachheit des Forth-Kompilers. Kompiler anderer Sprachen sind
recht umfangreiche Programme, denen es gelingt sehr viele
Konstruktionen in einer Hochsprache in Maschinensprache zu
übersetzen. Im Gegensatz dazu besteht der Forth-Kompiler aus einer,
noch dazu sehr kleinen, Definition. Alle Konstuktionen, wie IF
... ELSE ... THEN
oder DO ... LOOP
werden nicht
vom Kompiler bereit gestellt, sondern sind in diese Worte
ausgelagert.
Die zweite Sache, die angesprochen werden muss ist, dass Wörter in
Forth zwei unterschiedliche Verhalten haben können, die
Laufzeitverhalten und Kompilierzeitverhalten genannt werden. Das ist
nun etwas schwierig und sollte etwas eingehender erklärt werden. Am
einfachsten geht das an einem Beispiel. Ich nehme dafür das Wort
VARIABLE
. Wie bekannt ist, kann mit diesem Wort eine
Variable angelegt werden, aber das dabei zwei
Verhalten verknüpft sind, ist bisher noch nicht thematisiert worden.
Schauen wir uns dazu einmal die Verwendung dieses Wortes an:
VARIABLE foo ok 5 foo ! ok
Im ersten Fall wurde die Variable foo
angelegt, sie
wurde in das Wörterbuch kompiliert. Was dabei
passiert, nennt man das Kompilierzeitverhalten von
VARIABLE
. Dabei passiert folgendes:
VARIABLE
stehen kann und ausgeführt
wird, wenn die Variable 'läuft', nennt man dies
das Laufzeitverhalten.
Im zweiten Fall wird also das Laufzeitverhalten der Variablen
foo
ausgenützt; die Adresse des Speicherplatzes wird
auf den Stack gelegt.
Es gibt zwei Arten von Wörtern in Forth, die sowohl ein Laufzeit- als auch ein Kompilierzeitverhalten haben: Die Definitionswörter und die Kompilerwörter. Ich beginne mit den einfacherern.
Es sind schon einige Definitionsworte vorgestellt worden:
VARIABLE
, CONSTANT
aber auch
CREATE
. Was ist diesen Worten gemeinsam? Nun, ihnen ist
gemeinsam, dass sie alle Wörter im Wörterbuch erzeugen, die ein
ähnliches oder sogar gleiches Verhalten haben.
Das Wort VARIABLE
erzeugt immer ein Wort im Wörterbuch,
das beim Aufruf die Adresse seines Parameterbereichs auf den Stack
legt. Analog legen alle Wörter, die mit CONSTANT
erzeugt wurden, den Inhalt ihres Parameterbereichs auf den Stack.
Bei CREATE
ist es eigentlich genau so, wie bei
Variable
. Ein mit CREATE
erzeugtes Wort,
legt die Adresse seines Parameterbereichs auf den Stack, wenn es
aufgerufen wird. Der Unterschied zu VARIABLE
besteht
eigentlich nur darin, dass mit CREATE
erzeugte Worte
dort keinen Platz reservieren. Die Definition von
VARIABLE
ist daher übrigens ausgesprochen einfach:
: VARIABLE CREATE 2 ALLOT ;
Man sieht, dass VARIABLE
indirekt CREATE
verwendet. Da VARIABLE
den gleichen Laufzeitkode wie
CREATE
hat, braucht kein eigener Laufzeitkode
festgelegt zu werden. Will man ein solches Verhalten allerdings
angeben, dann braucht man: DOES>
Das Vorgehen ist dabei eigentlich wieder recht einfach:
: DEFINITION CREATE (Kompilzeittätigkeit) DOES> (Laufzeittätigkeit) ;
Am einfachsten macht man sich das einmal an einem einfachen Beispiel
klar. Die Definition von CONSTANT
lautet vereinfacht:
: CONSTANT CREATE , DOES> @ ;
Nun kann man sich ansehen, was passiert, wenn man
CONSTANT
aufruft und was passiert, wenn man ein damit
erzeugtes Wort aufruft. Stellen sie sich vor, sie definieren:
314159 CONSTANT PI
Genau so einfach kann man nun auch eigene Definitionswörter erstellen. Fangen wir mit etwas einfachem an, einem eindimensionalen Array von Bytes, wobei die Anzahl der Bytes beim Aufruf angegeben werden soll.
: BYTES CREATE DUP , ALLOT DOES> 2+ + ;
Wenn wir nun ein neues Wort erzeugen, passiert folgendes:
10 BYTES fritz ok
Es wird ein Bytefeld 'fritz' angelegt, das Platz für 10 Bytes enthält. Vor diesem Platz wird abgelegt, wieviel Platz vorhanden ist. Will man auf eines dieser Bytes zugreifen, dann reicht:
3 fritz C@
Was passiert: Das einkompilierte DOES>
legt zur
Laufzeit die Adresse des Parameterbereichs auf den Stack, dazu
werden erst mal 2 addiert, um die Zelle zu überspringen, in der die
Länge des zur Verfügung stehenden Platzes gespeichert ist. Damit hat
man die Adresse des 1. Bytes. Dann werden 3 addiert, was die Adresse
des vierten Bytes bedeutet. Mit C@
wird der dortige
Wert ausgelesen.
Nun kommt der eigentliche Vorteil: Man kann das Laufzeitverhalten
aller mit BYTES
definierten Worte
sofort ändern, wenn man die Definition von
BYTES
ändert! Angenommen, man ändert die Definition
folgendermaßen:
: BYTES CREATE DUP , ALLOT DOES> 2DUP @ U< NOT ABORT" Falscher Index" 2+ + ;
Nun holt man den gewünschten Index und den zur Verfügung stehenden Platz auf den Stack und prüft, ob der Index noch zum Platz passt. Ist das nicht der Fall, wird eine Fehlermeldung ausgegeben und ansonsten wie gewohnt die gewünschte Adresse zurück gegeben.