% This file was converted to LaTeX by Writer2LaTeX ver. 1.0.2 % see http://writer2latex.sourceforge.net for more info \title{Forth--Compiler--Hilfsbefehle als High--Level--Befehle} \author{Willi Stricker} \date{2011-07-10} \begin{document} \maketitle \begin{multicols}{2} Der Forth--Compiler benötigt verschiedene Hilfsbefehle, die er für den Programm--Ablauf compilieren muss, die der Programmierer aber nicht „sieht.“ Sie werden compiliert anstelle „sichtbarer“ Befehle, die wiederum nicht direkt umgesetzt werden können. Einfachstes Beispiel sind die Befehle BRANCH und ?BRANCH, die der Compiler für sämtliche Strukturen in Forth einsetzt (IF\dots{}ELSE\dots{}THEN, BEGIN\dots{}UNTIL, BEGIN\dots{}WHILE\dots{}REPEAT). Der Programmierer benötigt die beiden Befehle normalerweise nicht. Bei Forth-Systemen werden die im Folgenden beschriebenen Hilfsbefehle in Assembler geschrieben (aus Gründen der Geschwindigkeit). Wenn aber, wie beim STRIP--Forth--Prozessor, keine Assembler-Befehle zur Verfügung stehen, müssen sie in High--Level geschrieben werden. Die Befehle beeinflussen alle den Instruction Pointer (IP), dazu folgen einige Vorbemerkungen: Bei Aufruf eines High--Level--Befehles (also eines Unterprogramms) liegt immer die Adresse des nachfolgenden Befehls auf dem Return--Stack (Return--Adresse), die ist aber gleich dem jeweils aktuellen Wert des IP. Man könnte damit die Befehle IP@ und IP! definieren: \begin{verbatim} : IP@ ( -- ip ) R> DUP >R ; : IP! ( ip -- ) R> DROP >R ; \end{verbatim} Der Befehl IP! wäre dann ein indirekter Sprung. Man erhält demnach durch den Befehl R> den aktuellen IP-Wert, muss aber dafür sorgen, dass eine gültige Return--Adresse zurückgelegt wird, anderenfalls wird beim nachfolgenden Return in die nächsthöhere Programm (Struktur)--Ebene zurückgesprungen. Anmerkungen zur Schreibweise: Wie in der Forth--Literatur üblich, werden die Compiler--High--Level--Hilfsbefehle in Klammern geschrieben. Bei der Darstellung des Speichers werden das Name--Field und das Link--Field weggelassen. \section{Befehle VARIABLE und CONSTANT} Die Variable (VARIABLE) benutzt normalerweise die Hilfs--Routine „dovar:“ Anordnung im Speicher: [dovar] [Speicherplatz für die Variable] Funktion der „dovar“-Routine: Sie gibt als Ergebnis die Adresse des nachfolgenden Speicherplatzes aus ( -- address), deren Inhalt dann z.\,B. mit dem @-Befehl ausgelesen werden kann, außerdem reserviert der Compiler den nachfolgenden Speicherplatz für die Variable. Wichtig: Das Programm wird an der Stelle fortgeführt, an der die Variable aufgerufen wurde (übergeordnete Ebene)! Daraus folgt die Realisierung der Routine dovar in High-Level sehr einfach (address = Return--Adresse des den Befehl (VAR) aufrufenden Programms): \begin{verbatim} : (VAR) ( -- address ) \ return stack: ( address -- ) R> ; \end{verbatim} Die Konstante (CONSTANT) benutzt normalerweise die Hilfs-Routine “docon:“ Anordnung im Speicher: [docon] [konstanter Wert] Funktion der „docon“-Routine: Sie gibt als Ergebnis den auf dem nachfolgenden Speicherplatz liegenden konstanten Wert aus ( -- constant). Wichtig auch hier: Das Programm wird an der Stelle fortgeführt, an der die Konstante aufgerufen wurde! (übergeordnete Ebene). Daraus folgt die Realisierung der Routine „docon“ in High-Level: \begin{verbatim} : (CON) ( -- constant ) \ return stack: ( address -- ) R> @ ; \end{verbatim} \section{Die Befehle CREATE und CREATE\allowbreak\dots\allowbreak{}DOES>} Der Befehl CREATE hat in Forth eine Doppelbedeutung: \begin{enumerate} \item Er markiert die Anfangsadresse eines beliebig großen Daten--Feldes \item Er wird benutzt in der Kombination CREATE\dots\allowbreak{}DOES> \end{enumerate} Zu 1. Datenfeld: Der Hilfsbefehl ist hier identisch mit dem der Variablen: „dovar,“ es wird also kein neuer Befehl benötigt. Anordnung im Speicher: [dovar] [Speicherplatz beliebiger Größe] Im Unterschied zur Variablen wird hier vom CREATE--Befehl kein Speicherplatz reserviert, sondern explizit vom Programmierer z.\,B. durch den ALLOT--Befehl. Zu 2. Kombination CREATE und DOES> Die Kombination aus CREATE und DOES> kann nur als „Definierender Befehl“ benutzt werden, z.\,B. \begin{verbatim} : XYZ CREATE ... DOES> ... ; \end{verbatim} Der Befehl XYZ erzeugt eine Gruppe von Befehlen, bei denen der „Create“--Teil ein Feld mit Daten beliebiger Art definiert und der „Does“--Teil ein Programm zur Bearbeitung dieser Daten. Wird ein Befehl des Typs XYZ programmiert, dann wird lediglich das zugehörige Feld definiert und das Programm des Does--Teils aufgerufen. Funktion: der Create--Befehl muss das Programm des Does-Teils aufrufen und gleichzeitig die Adresse des nachfolgenden Feldes übergeben. Anordnung im Programm: Create--Teil: [Adresse des Does--Teils] [Datenfeld beliebiger Größe] Does--Teil: [DOES-Befehl] [Does-Programm] Demgemäß muss lediglich ein Befehl (DOES) definiert werden, der dem „Does“--Teil die Daten--Adresse des „Create“--Teils übergibt (cadr = Adresse des „Create“--Teils, radr = Return--Adresse des den Create-Teil aufrufenden Befehls): \begin{verbatim} : (DOES) ( -- cadr ) \ return stack: ( cadr radr -- radr ) R> R> SWAP >R ; \end{verbatim} Funktion: Beim Aufruf des Does--Programms liegt die (Daten--)Adresse des Create--Teils auf dem Return--Stack (erster R>--Befehl), darüber die Adresse des aufrufenden Programms (zweiter R>--Befehl). Der SWAP--Befehl vertauscht die Adressen, der >R--Befehl legt die Return--Adresse auf den Return--Stack zurück für den abschließenden Return, so dass nun die Adresse des Create--Teils für das Does--Programm zur Verfügung steht. \section{Die Befehle DO \dots{} LOOP/+LOOP} Die Loop-Befehle sind, wie der Name besagt, Programmschleifen, die jeweils mit dem Befehl DO eingeleitet und mit dem Befehl LOOP oder mit dem Befehl +LOOP abgeschlossen werden. Es werden jeweils 2 Eingagsparameter erwartet: Der Startindex (Index) und der Endindex (Limit). Der Compiler benutzt dazu die Hilfsbefehle (DO), (LOOP) und (+LOOP) folgendermaßen: [(DO)] [Schleifenbefehle beliebiger Anzahl] [(LOOP/+LOOP)] [start-address] Der Befehl (DO) markiert den Anfang, der Befehl (LOOP/+LOOP) das Ende der Schleife, die Adresse „start address“ ist die auf den Befehl (DO) folgende Adresse (1. Befehl der Schleife). Der vom Compiler anstelle des DO--Befehls compilierte Befehl (DO) verschiebt die beiden Parameter vom Parameter-Stack auf den Return--Stack, damit sie innerhalb der Schleife nicht „stören“ aber ständig zugreifbar sind. Auch hierbei ist zu beachten, dass es sich um einen High--Level--Befehl handelt und deswegen der oberste Wert auf dem Return--Stack die Return--Adresse (radr) enthält, die vor dem Rücksprung wieder dort liegen muss: \begin{verbatim} : (DO) ( limit index -- ) \ return-stack: ( radr -- limit index radr ) SWAP R> SWAP >R SWAP >R >R ; \end{verbatim} Die Verschiebung erfolgt derart, dass der Index als oberstes Wort auf dem Return--Stack liegt. Er wird als Variable benutzt, die mit jedem Schleifendurchlauf verändert wird. Anstelle des LOOP--Befehls wird vom Compiler zunächst der Befehl (LOOP) compiliert und anschließend die Start--Adresse der Schleife. Der (LOOP)--Befehl muss zunächst den Index weiterzählen (inkrementieren), dann prüfen, ob der Index kleiner als der Limit ist. In dem Fall muss er auf die Startadresse zurückspringen (nächster Schleifendurchlauf), anderenfalls die Schleife verlassen, indem er auf den nachfolgenden Befehl springt (die Startadresse überspringt). \begin{verbatim} : (LOOP) ( -- ) \ return stack: ( limit index radr -- \ limit index startadr / radr+2 ) R> R> 1+ DUP R@ < \ index+1 < limit ? IF >R @ \ next loop -- return to start ELSE DROP R> DROP 2+ \ loop end -- branch to address+2 THEN >R ; \end{verbatim} Anmerkung: In Forth wird die Schleife durchlaufen, solange der Index kleiner als der Limit ist, anderenfalls wird die Schleife abgebrochen! Die +LOOP unterscheidet sich von der einfachen LOOP dadurch, dass der Index nicht um 1 pro Schleifendurchlauf erhöht wird, sondern um einen programmierten Wert „data:“ \begin{verbatim} : (+LOOP) ( data -- ) \ return stack: ( limit index radr -- \ limit index startadr / radr+2 ) R> SWAP DUP R> + SWAP OVER R@ < \ index+data < limit ? IF >R @ \ next loop - return to start ELSE DROP R> DROP 2+ \ loop end - branch to address+2 THEN >R ; \end{verbatim} Forth stellt weitere spezielle Befehle für die Schleifenbearbeitung zur Verfügung. Zwei werden kurz beschrieben: Der Schleifenbefehl I (für Index) greift auf den Index im Return--Stack zu, ohne ihn zu verändern. \begin{verbatim} : I ( -- index ) \ return stack ( limit index radr -- limit index radr ) R> R@ SWAP >R ; \end{verbatim} Der Schleifenbefehl LEAVE bewirkt, dass die Schleife beim nachfolgenden LOOP oder +LOOP--Befehl unabhängig vom aktuellen Index verlassen wird. Das wird erreicht, indem der Index gleich dem Limit gesetzt wird. \begin{verbatim} : LEAVE ( -- ) \ return stack: ( limit index radr -- limit limit radr ) R> R> DROP R@ >R >R ; \end{verbatim} Anmerkung zu den Befehlen (VAR) und (CON) anstelle von „dovar“ und „docon:“ Die in Assembler geschriebenen Routinen „dovar“ und „docon“ werden direkt von der next--Routine aufgerufen und benutzen deren Word--Pointer (WP), ohne auf den Return--Stack zuzugreifen. Wenn man sie durch die High-Level-Befehle (VAR) und (CON) ersetzt, dann muss ihnen eine „docol“--Routine vorangestellt werden (nicht nötig beim STRIP--Forth--Prozessor)! Die Teil--Programme „docol,“ „dovar“ und „docon“ werden als Routinen bezeichnet, da sie keine Forth--Befehle sind. \end{multicols} \end{document}