Einführung in FORTH Teil III

Nachdem im letzten Teil der Aufbau einer Doppelpunktdefinition im Vordergrund stand, geht es diesmal um Entscheidungen und Wiederholungen. Außerdem wird gezeigt, daß für das Programmieren in FORTH ein Editor unerläßlich ist.

Entscheidungen

Jede Programmiersprache bietet die Möglichkeit, die Ausführung von Befehlen von einer Bedingung abhängig zu machen. In FORTH werden solche Entscheidungen denkbar einfach realisiert. Einer Entscheidung in FORTH geht in der Regel ein Vergleich zwischen zwei Zahlen voraus. FORTH-83 stellt hierzu eine Reihe von Vergleichsoperatoren zur Verfügung. Ein solcher Vergleichsoperator ist z.B. ’=‘. Um ihn anwenden zu können, müssen die beiden zu vergleichenden Zahlen zuvor auf dem Stack abgelegt werden:

12 34 ok

Aufgrund des Prinzips (”Last in — First Out“), nach dem der Stack in FORTH verwaltet wird, befindet sich die zuletzt eingegebene Zahl ‘34’ an oberster Stelle im Stack und die davor eingegebene ’12‘ eine Position darunter. Durch Eingabe von

= ok

wird der Vergleich durchgeführt. Für das Ergebnis, welches entweder wahr (beide Zahlen sind gleich) oder falsch (beide Zahlen sind ungleich) sein kann, wird ein entsprechendes Flag im Stack abgelegt. In FORTH-83 wird ein Wahrflag durch eine ’-l‘ und ein Falschflag durch eine ‘0’ dargestellt. Es gilt zu beachten, daß die beiden verglichenen Zahlen nach dem Vergleich vom Stack verschwunden sind. Sollten beiden Zahlen nach dem Vergleich für weitere Operationen benötigt werden, müssen sie vor dem Vergleich auf dem Stack kopiert werden. In unserem Beispiel befindet sich auf dem Stack nun ein Falschflag, da die beiden verglichenen Zahlen ja nicht gleich waren:

Abb. 1 Stackdiagramm 1
. 0 Ok

Abb. 1 zeigt den Zustand des Stacks nach den i izelnen Operationen. Neben ’=‘ stellt FORTH-83 folgende Vergleichsoperatoren zur Verfügung:

< > 0= 0< 0>

Es fällt auf, daß ein ’< >‘ Operator fehlt. Dieser läßt sich bei Bedarf jedoch leicht selbst definieren, wie die folgende Doppelpunktdefinition zeigt:

: <> = 0= ;

Die neue Wortdefinition wird durch einen Doppelpunkt eingeleitet. Dieser schaltet FORTH vom interpretierenden Modus in den kompilierenden Modus. (In volksFORTH-83 wird dies durch die Meldung „compiling“ angezeigt) Alle nun folgenden Eingaben werden nicht mehr direkt ausgeführt, sondern in die neue Definition eingetragen. Danach folgt ’< der Name der neuen Definition. Dem Wortnamen folgen die Worte, die beim Aufruf der neuen Definition ausgeführt werden sollen. Da wäre zunächst einmal ’=‘, welches die beiden obersten Zahlen im Stack auf Gleichheit prüft und ein entsprechendes Flag auf dem Stack ablegt. Diese Flag wird vom nachfolgenden ’0=‘ Operator gestestet. Bei diesem Vergleichsoperator wird die oberste Zahl im Stack mit Null verglichen. ’0=‘ kehrt ein Flag in der obersten Speicherzelle des Stacks (dem ”Top of Stack“) um, da eine 0 im Stack in ein Wahrflag bzw. eine Zahl ungleich Null ist eine Null umgewandelt wird.

Bsp.

4 5 ok
<> ok
. -1 ok

Abb. 2 zeigt den Inhalt des Stacks während der Ausführung des Wortes.

Abb. 2 Stackdiagramm 2

Ein Vergleich macht allerdings noch keine Entscheidung. Schauen wir einmal, wie eine einfache Entscheidung in FORTH realisiert werden kann. Stellen Sie sich vor, Sie sollten ein Wort schreiben, welches feststellt, ob die oberste Zahl im Stack kleiner als Null ist.

Der FORTH Ausdruck ’0< IF ZAHL NEGATIV“ THEN“ prüft, ob die oberste Zahl im Stack kleiner als Null ist. Für den Fall, daß die Zahl kleiner als Null ist, wird die Anweisung zwischen IF und THEN ausgeführt. Ansonsten wird die Programmausführung bei dem ersten Wort nach THEN fortgesetzt. Dieser Ausdruck muß nun noch in eine Doppelpunktdefinition eingebaut werden, und das gesuchte Wort ist fertig:

: ?NEGATIV ( n-------) 0<
IF .” Zahl ist negativ“ THEN ;

Das Wort ’?NEGAT1V‘ erwartet vor dem Aufruf eine Zahl auf dem Stack. Dies ist aus dem Wort selbst nicht unbedingt ersichtlich. Deswegen ist es sinnvoll, sich einer Notation zu bedienen, aus der hervorgeht, welche Parameter vor dem Aufruf eines Wortes auf dem Stack zu übergeben sind und welche Parameter sich nach der Ausführung eines Wortes auf dem Stack befinden. Die konsequente Anwendung einer solchen Notation, bei der der Stackinhalt vor und nach der Ausführung des Wortes in Klammern geschrieben wird, gehört einfach zu einem guten Programmierstil und verbessert die Lesbarkeit von Programmlistings erheblich. Bei der Eingabe der obigen Definition können Sie diese Stacknotation aber getrost weglassen, da sie auf die Funktion des Wortes keinerlei Einfluß hat.

Ungewohnt für FORTH Anfänger ist sicher der Aufbau einer TF ... THEN“ Entscheidung und vor allem die Tatsache, daß die Bedingung vor dem Wort TF‘ aufgeführt wird. Dies liegt einfach daran, daß alle FORTH Worte ihre Parameter vor dem Aufruf auf dem Stack erwarten, auch TF‘ macht da keine Ausnahme. Der Aufbau einer Alternativentscheidung ist ebenfalls möglich, wie folgendes Beispiel zeigt:

: »NEGATIV
0<
IF .” Zahl negativ“ ELSE
.” Zahl positiv“ THEN ;

( die Meldung '.»NEGATIV already exists“ besagt lediglich, daß ein Wort mit diesem Namen bereits existiert)

Bsp.

-4 ?NEGATIV 

Zahl negativ ok

55 ?NEGATIV 

Zahl positiv ok

Alle Anweisungen zwischen ’ELSE“ und ’THEN“ werden ausgeführt, wenn die Bedingung vor TF‘ nicht erfüllt ist. Übrigens ist TF“ nicht unbedingt auf einen vorherigen Vergleich angewiesen (außer Null natürlich) an oberster Stelle im Stack, damit der TF“ — Teil ausgeführt wird — Probieren Sie es ruhig einmal aus.

Bliebe noch zu erwähnen, daß TF ... THEN* / TF ... ELSE ... THEN* Anweisungen beliebig verschachtelt werden können. Aus Gründen der Übersichtlichkeit ist es allerdings ratsamer, ab einer bestimmten Verschachtelungstiefe auf eine Mehrfachentscheidung wie z.B. ’CASE ... OF‘ zurückzugreifen.

Programmieren in FORTH heißt den Sprachkern um neue Wortdefinitionen zu erweitern. Die Verarbeitung des Quelltextes (das ist der Oberbegriff für alles, was zur Ausführung gebracht werden soll) übernimmt der Textinterpreter. Grundsätzlich gibt es zwei Möglichkeiten, dem Textinterpreter den Quellkode zuzuführen. Einmal kann der Quelltext direkt über die Tastatur eingegeben werden. Alle Eingaben über die Tastatur (Zahlen, Befehle und auch Wortdefinitionen) werden unter dem Begriff Eingabestrom (engl. Input Stream) zusammengefaßt. Nach dem Betätigen der Return Taste wird der gesamte Eingabestrom, der im Eingabepuffer gesammelt wurde, vom Textinterpreter verarbeitet. Normalerweise wird jeder Befehl, der im Eingabestrom auftaucht, vom Textinterpreter sofort zur Ausführung gebracht (interpretierender Modus). Stößt der Textinterpreter allerdings auf einen V, der ja bekanntlich eine Wortdefinition einleitet, werden alle weiteren Eingaben nicht mehr direkt ausgeführt, sondern in den Arbeitsspeicher ( genauer gesagt in das Wörterbuch) kompiliert (kompilierender Modus). Stören Sie sich im Moment noch nicht an dem Begriff „kompilieren“. Ich werde später dazu eine Definition nachliefern. Dieser kompilierende Modus kann z.B. durch ein wieder aufgehoben werden. (Sie werden sich erinnern, die Doppelpunktdefinition wird durch ein wieder beendet).

Die direkte Eingabe über die Tastatur weist aber zwei Nachteile auf. Zum einen wird der eingegebene Quelltext sofort umgesetzt und kann später nicht mehr gelistet, geschweige denn editiert werden. Zum anderen führt jeder Eingabefehler dazu, daß der Textinterpreter den kompilierenden Modus abbricht und in den interpretierenden Modus zurückkehrt. Alle bis dahin gemachten Eingaben der begonnenen Doppelpunktdefinition sind verloren. Es ist deswegen wesentlich effektiver, den Quellkode zunächst mit Hilfe eines Editors einzugeben und ihn gegebenenfalls auf Diskette zu speichern.

Der FORTH Editor

Bei einem Editor handelt es sich allgemein um ein Programm, welches die Eingabe, Verarbeitung und Abspeicherung von Programmtext (Quelltext) ermöglicht. Es existiert in FORTH kein "Standard Editor“. Vielmehr sind die Bedienung und der Komfort des Editors von System zu System stark verschieden. Bevor der Editor des volksFORTH-83 3.7 vorgestellt wird, noch einige Erläuterungen zu dem Prinzip, nach dem FORTH den Massenspeicher verwaltet.

Der gesamte zur Verfügung stehende Massenspeicher (auf Diskette oder Festplatte) wird in log. Einheiten, sog Blocks, eingeteilt. Ein Block umfaßt 1024 Bytes und ist sozusagen die kleinste Einheit, die vom Editor bearbeitet werden kann. Jeder Block wird über seine Blocknummer angesprochen. In einem Block kann man nun den Programmtext oder Daten, mit denen das Programm arbeiten soll, eingeben. Damit ein einzelner Block vom Benutzer bearbeitet werden kann, muß dieser Block zunächst in einen speziellen Bereich des Arbeitsspeichers, des Diskettenpuffer, geladen werden. Dies kann explizit durch den Benutzer veranlaßt werden (z.B. durch Eingabe von ’n Block“, wobei n die Blocknummer darstellt), oder indirekt beim Aufruf des Editors. In volksFORTH-83 wird der Editor zum Bearbeiten eines Blocks durch Eingabe von

3 1

aufgerufen, wobei ’3‘ die Nummer des zu bearbeitenden Blockes angibt. (Ignorieren Sie die Aufforderung „Geben Sie Ihre ID ein...“ einfach durch Drücken der Return Taste). Nun erscheint ein leerer Rahmen, der aus 16 Zeilen mit jeweils 64 Zeichen besteht, auf dem Bildschirm (Sollte aus irgendeinem Grund kein leerer Rahmen erscheinen, so verlassen Sie entweder den Editor mit ’CTRL-F“ und wählen Sie einen anderen Block oder überschreiben Sie einfach den Programmtext, der sich in diesem Block befindet). Zur Übung können Sie am besten einmal das Beispiel aus Abb. 3 eingeben. (Machen Sie sich zunächst keine Gedanken über die Bedeutung dieses Beispiels, wir werden später noch darauf zurückkommen). Ist der vollständige Programmtext eingegeben, ist es sinnvoll, diesen auch abzuspeichern. Dies geschieht wieder durch ’CTRL-F*. Der gesamte Inhalt des Blocks wurde damit auf Diskette zurückgeschrieben.

Ein einzelner Block kann durch 'LOAD* interpretiert werden. So wird in unserem Fall durch

3 LOAD ok

(vorausgesetzt natürlich, Sie haben für die Eingabe des Programmbeispiels den Block mit der Nummer 3 ausgewählt) der Inhalt von Block 3 interpretiert. Mit WORDS können Sie sich davon überzeugen, daß das Wörterbuch tatsächlich um ein Wort mit dem Namen 'WARTE* ergänzt wurde. 'LOAD* macht eigentlich nicht viel mehr, als den Inhalt des angegebenen Blocks zuerst in den Diskettenpuffer zu laden (sofern er sich bereits dort befindet) und ihn dann dem Textinterpreter zu übergeben. Dieser behandelt die vom Diskettenpuffer kommenden Daten genauso, als würden sie direkt über die Tastatur eingegeben. Nach Eingabe von '3 LOAD* wurde der gesamte Inhalt von Block 3 interpretiert. Mit 'WORDS* können Sie sich davon überzeugen, daß das Wörterbuch tatsächlich um ein Wort mit dem Namen 'WARTE* ergänzt wurde.

Das Laden eines Blocks ist nicht auf einen einzelnen Block beschränkt. Als Alternative bietet es sich an, an das Ende eines einzelnen Blockes das Wort ’----->* anzuhängen, welches den darauffolgenden Block ebenfalls lädt. Eine weitere Möglichkeit, mehrere Blöcke auf einmal zu laden, bietet ’THRU*. So werden beispielsweise durch ’ 1 10 TH-RU* die Blöcke 1 bis 10 nacheinander von Diskette geladen und interpretiert.

Damit soll die Beschreibung des Editors an dieser Stelle beendet sein. Da der Ed-tior im Grunde ständig benötigt wird, werden Sie weitere Erläuterungen noch an mehreren Stellen im Laufe dieser Einführung finden.

Wiederholungen

Neben den Entscheidungen gehören die Wiederholungen zu den wichtigsten Elementen einer Programmiersprache. Eine einfache Wiederholungsanweisung haben Sie mit der 'DO ... LOOP* Schleife, bereits in der letzten Folge kennengelernt. Neben der 'DO ... LOOP* Schleife, bei der der Anfangs- und der Endwert vor dem Aufruf auf dem Stack abgelegt werden, müssen die Anzahl der Wiederholungen also im voraus feststeht, ist dies bei der Wiederholungsanweisung ’BEGIN ... UNTIL* nicht der Fall. Die Anzahl der Durchläufe hängt vielmehr von einer Bedingung ab. In Abb. 3 finden Sie ein Beispiel für eine ’BEGIN ... UNTIL* Anweisung. Sofern Sie den letzten Abschnitt durchgearbeitet und das Wort 'WARTE* mit Hilfe des Editors eingegeben und mit 'LOAD* kompiliert haben, können Sie 'WARTE* direkt aufrufen. Ansonsten holen Sie nun die Eingabe der Definition in Abb. 3 (entweder mit Hilfe des Editors oder direkt über die Tastatur) nach.

WARTE
BEGIN
	" DRÜCKE EINE TASTE" 
KEY?
UNTIL ;

Abb. 3

Die Anweisungen innerhalb von ’BEGIN ... UNTIL* (die Ausgabe eines Textes bzw. das Prüfen einer Tasteneingabe durch ’KEY?) werden solange wiederholt, bis eine Taste gedrückt wird. Solange keine Taste gedrückt wird, liefert KEY? eine Null. Erst beim Betätigen einer Taste wird ein Wahrflag im Stack abgelegt. 'UNTIL erwartet ein Flag im Stack, wie es z.B. von KEY? geliefert wird. Liegt ein Wahrflag an oberster Stelle im Stack, wird die Wiederholung abgebrochen, ansonsten läuft die Schleife ein weiteres mal durch. Die Anzahl der Wiederholungen hängt also in diesem Beispiel von dem Flag ab, welches von KEY? im Stack abgelegt wird.

EWIG 
	BEGIN 
		42 EMIT 
	AGAIN ;
( EMIT gibt das Zeichen, dessen ASCII 
Code sich an oberster Stelle im Stack 
befindet aus )

Abb. 4

Für viele Anwendungen ist die ’BEGIN ... UNTIL* Anweisung nicht ausreichend. So wäre es denkbar, daß eine Anweisung nur ausgeführt werden soll, während eine Bedingung erfüllt ist. Auch bei der ’BEGIN ... WHTI F ... REPEAT* Anweisung hängt die Anzahl der Durchläufe vo neinem Flag ab, das sich vor der Ausführung von 'WEHLE* an oberster Stelle im Stack befindet. Dieses Flag wird von 'WEHLE* geprüft. Handelt es sich um ein Wahrflag, werden zunächst die Anweisungen zwischen 'WEHLE* und ’RE-PEAT ausgeführt und dann die Schleife ein weiteres mal durchlaufen. Falls sich vor der Ausführung von 'WEHLE* ein Falschflag an oberster Stelle im Flag befindet, wird die Schleife abgebrochen. (War die Bedingung vor ’WHILE* bereits beim erstenmal nicht erfüllt, so werden die Anweisungen zwischen ’WHILE und REPEAT* kein einziges Mal ausgeführt.) Mit Worten ließen sich ,BEGIN ... UNTIL* als eine „Tue solange bis eine Bedingung erfüllt ist“ - und ’BEGIN ... WEHLE ... REPEAT* als eine „Tue solange während eine Bedingung erfüllt ist“ Schleife beschreiben.

Erwähnenswert ist schließlich noch die ,BEGIN ... AGAIN* Schleife. Hierbei handelt es sich um eine sog. Endlosschleife. Abb. 4 zeigt ein einfaches Beispiel Beim Aufruf von 'EWIG* werden solange Sternchen produziert, bis Sie entweder den Rechner neu starten oder bei Ihnen der Strom ausfällt. Solche Endlosschleifen haben durchaus ihre Berechtigung. So besteht beispielsweise der Textinterpreter in FORTH, welcher ständig Eingaben von der Tastatur entgegennimmt und verarbeitet, im Kern aus einer solchen ,BEGIN ... AGIN* Schleife.

Konstanten und Variablen

Ein Thema, welches bislang noch gar nicht erwähnt wurde, sind Konstanten und Variablen. Allerdings auch nicht ganz grundlos, denn sowohl Konstanten als auch Variablen spielen in FORTH eher eine untergeordnete Rolle. Der Grund dafür liegt in der Tatsache begründet, daß die Parameter eines FORTH Programms in den meisten auf dem Stack verwaltet werden. Dennoch haben auch Konstanten und Variablen in FORTH ihre Berechtigung, da sie den Stack entlasten und vor allem die Lesbarkeit und Übersichtlichkeit von Programmen beträchtlich steigern — eine Notwendigkeit sind sie allerdings nicht.

Abb. 5 Stackdiagramm
	VARIABLE ZAEHLER
	0 ZAEHLER !

	: QUADRAT 
	  DUP * ;

	: ALLE_QUADRATE 
	   BEGIN
	    ZAEHLER @ QUADRAT DUP .
	    255 < WHILE 
	    ZAEHLER @ 1+ ZAEHLER !
	   REPEAT ;

Das Wort 'QUADRAT* ist ja noch aus der 
letzten Folge bekannt. Beachten Sie, 
daß das Ergebnis diesmal nicht ausgegeben wird, 
sondern auf dem Stack für weitere Berechnungen 
zur Verfügung steht. Die Sequenz ’ZAEHLER @ 1+ ZAEHLER !’ 
liese sich besser durch die Sequenz '1 ZAEHLER +! ’ 
ersetzen, wobei ’+!’ z.B. eine Variable, deren Adresse 
sich an oberster Stelle im Stack befindet um den 
Wert erhöht, der sich auf dem Stack darunter befindet.

Abb. 6 Auflösung der Übungsaufgabe

Ähnlich wie in PASCAL oder C müssen Konstanten bzw. Variablen vor ihrer Verwendung erst einmal definiert werden. Dies geschieht durch die Definitionsworte ’CONSTANT bzw. 'VARIABLE* in der folgenden Form:

VARIABLE ZAHL ok
123 CONSTANT NOCH_NE_ZAHL

(Bei Variablen wird kein Initialisierungswert angegeben. Sie weisen daher zunächst irgendeinen Wert 0 auf). Bei Aufruf eines durch CONSTANT definierten Wortes wird der Wert der Konstanten an oberster Stelle im Stack algelegt:

NOCH_NE_____ZAHL < Return >
. 123 ok

Bei Variablen sieht es ein wenig anders aus. Beim Aufruf einer Variablen erscheint im Stack allerdings nicht der Wert der Variablen, sondern vielmehr die Adresse, unter der dieser Wert zu finden ist. Dementsprechend erhalten wir nach Eingabe von 'ZAHL*:

ZAHL . -31830 ok

nicht den Wert der Variablen 'ZAHL*, sondern die Adresse, unter der dieser Wert zu finden ist. Wie wir in der nächsten Folge sehen werden, wird eine Variable in einer ähnlichen Weise wie eine Doppelpunktdefinition in das Wörterbuch eingetragen. Die Adresse der Variablen 'ZAHL* hängt daher von der Anzahl und dem Umfang der davor gemachten Definitionen ab. Bevor Sie sich jetzt den Kopf darüber zerbrechen, wieso eine negative Zahl ausgeben, versuchen Sie es einfach noch einmal aber mit ’U.‘ anstelle von ’.*:

ZAHL U. 33706 ok

Der Grund für dieses unterschiedliche Verhalten liegt einfach darin, daß ’.* nur vorzeichenbehaftete 16 Bit Zahlen ausgibt. Jede Zahl größer als ,32768* wird daher mit einem Minuszeichen ausgegeben. Anders bei ’U.‘, welches jede Zahl als 16 Bit Zahl ohne Vorzeichen ausgibt.

Um auf den Inhalt einer Variablen zugreifen bzw. einen Wert in einer Variablen speichern zu können, benötigen wir ein Wort, welches den Inhalt einer beliebigen Speicherzelle in den Stack holt bzw. ein Wort, welches die umgekehrte Operation durchführt und einen Wert in einer beliebigen Speicherzelle ablegt. FORTH bietet dazu die Worte ’ * bzw. ’!* an. (Auch wenn es sich hier nur um einzelne Zeichen handelt, werden auch ’ * bzw. ’!* als Worte bezeichnet) Um mit diesen beiden Worten arbeiten zu können, werfen Sie zunächst einmal einen Blick auf das Stackverhalten der beiden Worte:

! ( n addr------)
( addr-------n )

Um T sinnvoll anwenden zu können, muß sich sowohl die Adresse der Variablen, als auch der abzuspeichernde Wert im Stack befinden und zwar in der angegebenen Reihenfolge. Durch eingabe von

123 ok

wird zunächst der Wert, der der Variablen zugewiesen werden soll, im Stack abgelegt und durch

ZAHL ok

schließlich die Adresse der Variablen. Die Eingabe von

! ok

erledigt den Rest und speichert ’123‘ in der Variablen 'Zahl* ab. Es sei an dieser Stelle daraufhingewiesen, daß FORTH hier, wie auch in fast allen anderen Situationen keinerlei Überprüfung bezüglich der Korrektheit von Daten durchführt. Es liegt also an Ihnen, dafür zu sotgen, daß Daten auf dem Stack auch einen Sinn ergeben. Sollten Sie im obigen Beispiel etwa aus Versehen die Reihenfolge der beiden Zahlen vertauschen (was am Anfang sicher häufig der Fall sein wird), so wird "!" die Adresse als die zu speichernde Zahl als die Adresse auffassen. T macht nichts anderes, als die zweitoberste Zahl im Stack unter der Adresse, die sich an oberster Stelle im Stack befindet, abzuspeichern, (siehe auch Abb. 5)

Nun wird sich sicher der eine oder andere Leser die Frage stellen, warum das alles so umständlich geht. Schließlich wäre es doch wesentlich naheliegender, eine Variable in der Form ’ ZAHL = 123‘ zuzuweisen. Die Antwort lautet: selbstverständlich wäre dies auch in FORTH möglich. Nur würde dafür (unötige) Rechenzeit geopfert werden, die zu Lasten der Performance geht. Gerade diese, zunächst vielleicht ein wenig merkwürdig anmutende Schreibweise, führt dazu, daß in FORTH Probleme in vielen Fällen eleganter und vor allem schneller gelöst werden können als in anderen Sprachen.

Zum Abschluß dieser Einführungsfolge noch eine kleine Übungsaufgabe. Schreiben Sie ein Programm, welches alle Quadratzahlen bis max. 255 ausgibt. Wenn Sie diese (und auch die letzte) Folge aufmerksam durchgearbeitet haben, dürfte Ihnen die Lösung dieses Problems nicht allzu schwer fallen. Die (oder besser gesagt eine mögliche) Lösung finden Sie in Abb. 6.

(PM)



Aus: ST-Computer 06 / 1987, Seite 62

Links

Copyright-Bestimmungen: siehe Über diese Seite