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.