Das letzte Kapitel mag ihnen ein bisschen 'arm' vorgekommen sein. Nur eine einzige Möglichkeit Entscheidungen zu realisieren, wenn man davon absieht, dass es Befehle gibt, die eine Entscheidung 'eingebaut' haben. Nun, ich habe ihnen versprochen, dass ich dieses Problem noch angehen werde, aber hier erst mal ein Kapitel, in dem es mehr Auswahl gibt: Ein Kapitel über Schleifen, für die Forth wesentlich mehr Möglichkeiten bietet, als bei den Entscheidungen.
Unter festgelegten Schleifen versteht man in Forth Schleifen, deren Anzahl von Durchläufen vor Eintritt in die Schleife festgelegt wird. Die einfachste Form der Schleife ist:
... Grenze Index DO ... LOOP ...
Eine solche Schleife kann nur in einer
Doppelpunktdefinition verwendet werden, also nur in der Definition
eines neuen Wortes. Bevor DO dort erscheinen darf,
müssen zwei Werte auf dem Stack liegen, die als Grenze und
Index interpretiert werden. Wie wird mit ihnen umgegangen?
Zunächst 'schaufelt' DO die beiden Werte auf den
Returnstack, das ist ein weiterer Stack, den Forth nutzt und der
später noch eingehender besprochen werden wird. Danach werden die
Wörter, die hinter DO stehen ganz normal ausgeführt
(Eine DO ... LOOP-Schleife wird also
immer einmal ausgeführt!) Erreicht die Ausführung
LOOP, dann geschieht folgendes: LOOP
erhöht den Index um Eins und testet dann, ob der Index größer oder
zumindest gleich der Grenze geworden ist. Ist das der Fall, dann
werden Index und Grenze vom Returnstack wieder entfernt und hinter
LOOP mit der Ausführung der Wörter weiter gemacht. Ist
es hingegen nicht der Fall, dann verzweigt Forth
auf das erste Wort hinter DO und die Scheife wird ein
zweites Mal durchlaufen.
Kompliziert? Eigentlich nicht! Am einfachsten sieht man sich erst mal ein einfaches Beispiel an:
: BALKEN 10 0 DO 61 EMIT LOOP ; BALKEN ========== ok
Wie man sieht wird der bereich zwischen DO und
LOOP einfach zehn mal durchlaufen und das war es. Die
Grenze ist hier die '10', der (Anfangs)Index die '0'.
Nachdem das erste Gleichheitszeichen ausgegeben wurde, das erste Mal
LOOP erreicht wird, erhöht LOOP den Index
zunächst von 0 auf 1. Dann wird getestet, ob der Index gleich oder
größer als die 10 ist. Das ist natürlich nicht der Fall und es geht
zurück hinter das DO, wo die Sache von vorne beginnt.
Nachdem das 10te Gleichheitszeichen ausgegeben wurde, ist der Index
'9'. Auch der wird zunächst erhöht und dann getestet. Nun ist er
gleich der Grenze, welche die ganze Zeit hindurch unverändert
geblieben ist. Daraufhin entfernt LOOP Grenze und Index
vom Returnstack und es geht hinter dem LOOP weiter,
wobei bei diesem Beispiel da natürlich nichts mehr passiert.
Kann man in einer DO...LOOP-Schleife auf
den Index zugreifen? 'Weiß' man also innerhalb der Schleife, der
wievielte Durchlauf gerade 'dran' ist? Ja, man weiß es! Es gibt in
Forth natürlich Möglichkeiten auch den Returnstack zu verändern und
auszulesen, im Zusammenhang mit
DO...LOOP-Schleifen ist es aber einfacher
auf ein anderes Wort zu setzen: I. I
kopiert einfach den obersten Wert vom Returnstack auf den
Zahlenstack. Da das der aktuelle Index ist, hat man ihn dann zur
Verfügung.
: LAUF 10 0 DO I . LOOP ; ok LAUF 0 1 2 3 4 5 6 7 8 9 ok
Man kann den Schleifenindex, wenn man ihn mit I auf den
Zahlenstack geholt hat, wie jeden anderen Wert verwenden. Man kann
mit ihm rechnen, ihn als Flag verwenden und so weiter:
: SIEBENER 11 1 DO I 7 * . LOOP ; SIEBENER 7 14 21 28 35 42 49 56 63 70 ok
DO-Schleifen lassen sich auch schachteln. Und das in
zweifacher Hinsicht. Die einfache Variante geht folgendermaßen:
: ERREIHE 11 1 DO DUP I * 5 U.R LOOP DROP ; : KLEINES 11 1 DO I ERREIHE CR LOOP ;
Das Wort U.D ist eine Sonderform des Ausgabewortes
.. Hiermit können Zahlen rechtsbündig in einem Feld
ausgegeben werden, dessen Breite vorher angegeben werden muss (hier
ist das Feld 5 Zeichen breit).
Man erkennt, dass das Wort KLEINES eine Schleife
enthält, die ihrerseits ein Wort (ERREIHE) aufruft, das
wiederum eine Scheife enthält.
KLEINES 1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100
ok
Man kann das Ganze aber auch in eine Definition packen. Das sieht dann so aus:
: GROSSES ( -- )
CR
21 11 DO
21 11 DO
I J * 5 U.R
LOOP
CR LOOP ;
grosses
121 132 143 154 165 176 187 198 209 220
132 144 156 168 180 192 204 216 228 240
143 156 169 182 195 208 221 234 247 260
154 168 182 196 210 224 238 252 266 280
165 180 195 210 225 240 255 270 285 300
176 192 208 224 240 256 272 288 304 320
187 204 221 238 255 272 289 306 323 340
198 216 234 252 270 288 306 324 342 360
209 228 247 266 285 304 323 342 361 380
220 240 260 280 300 320 340 360 380 400
ok
Alles, was man zum Verständnis wissen muss ist die Bedeutung des
Wortes J. Es holt einfach den drittobersten Wert vom
Returnstack auf den Datenstack und da liegt der Index der inneren
Schleife. Auch auf diese Art können Schleifen verschachtelt werden.
Was passiert nun, wenn man in einer solchen Schleife feststellt, dass eine Bedingung erreicht ist und man die Schleife vorzeitig beenden will? Nach meiner Meinung sollte man dann zwar direkt eine bedingte Schleife verwenden (kommt gleich), aber Forth bietet auch für diesen Fall eine Möglichkeit:
LEAVE
Trifft der Ablauf innerhalb einer Schleife auf auf
LEAVE, dann wird die Schleife abgebrochen. Das
genaue Vorgehen ist dabei allerdings
Implementationsabhängig. Manche Versionen von Forth gehen so vor,
dass LEAVE einfach die Grenze so ändert, dass beim
nächsten Erreichen von LOOP oder +LOOP die
Schleife beendet wird. Bei diesem Verfahren werden alle Worte
bis zu diesem LOOP oder
+LOOP noch abgearbeitet. Andere Implementation von
Forth sorgen bei LEAVE für einen
sofortigen Abbruch der Schleife und die weitere
Bearbeitung hinter LOOP oder +LOOP. In
jedem Fall wäre mit diesem Wort folgendes möglich:
: URLAUB
21 1 DO
STRAND ESSEN DISCO
GELDALLE? IF LEAVE THEN
LOOP
HEIMREISE ;
Ich hoffe natürlich für sie, dass die Bedingung nie wahr wird!
Im Gegensatz zu Schleifen, bei denen die Anzahl der Durchläufe vor Beginn der Schleife festgelegt wird, gibt es in nahezu allen Computersprachen auch Schleifen, bei denen das nicht der Fall ist. Sie werden so lange durchlaufen, bis eine Bedingung eintritt (oder nicht mehr der Fall ist); man spricht hier auch von bedingten Schleifen. Forth bietet zwei Konstruktionen für derartig bedingte Schleifen. Die erste hat die Form:
BEGIN ... UNTIL
Der Körper der Schleife, also der Bereich zwischen
BEGIN und UNTIL wird in jedem Fall einmal
durchlaufen. Das Wort UNTIL wertet dann die oberste
Zahl auf dem Stack als Flag aus. Ist dieses Flag 'wahr', dann wird
hinter UNTIL weiter gemacht. Ist es 'falsch', dann wird
zurück hinter BEGIN gesprungen.
Eine andere Variante unterscheidet sich von der BEGIN
... UNTIL-Schleife durch zwei Unterschiede: Zum einen
erfolgt der Test, ob die Schleife abgebrochen werden soll
innerhalb der Schleife und nicht erst am Ende, zum
Anderen wird die Schleife beenet, wenn die getestete Bedingung
'falsch' ist und nicht, wenn sie 'wahr' ist. Wie funktioniert diese
Schleifenkonstruktion?
Zunächst wird die Schleife bis WHILE durchlaufen.
WHILE interpretiert die auf dem Stack liegende Zahl als
Flag und bricht die Schleife ab, wenn das Flag 'falsch', also die
Zahl Null ist. In diesem Fall werden die Worte hinter
REPEAT weiter ausgeführt.
Ist das Flag hingegen 'wahr', dann wird die Ausführung hinter
WHILE weitergeführt, bis zum REPEAT. Von
dort aus verzweigt die Ausführung dann wieder auf das erste Wort
hinter BEGIN.
Ein schönes Beispiel ist die Berechnung der Halbwertzeit. Hier wird eine Größe so lange verringert, bis sie kleiner oder gleich der Hälfte der ursprünglichen Zahl ist. Kommentiert sieht das folgendermaßen aus:
: % 10 */ 5 + 10 / ;
: HALBWERT \ Übergeben wird der Prozentsatz
1000 \ Ein Startwert, der im Prinzip beliebig ist (s.u.)
BEGIN \
OVER % \ Vom aktuellen Wert der Prozentwert
DUP 500 > \ ist der noch größer als 500 (Halber Startwert)
WHILE \ wenn nicht, wars das!
DUP . \ Sonst mal ausgeben (zum Zählen)
REPEAT ; \ und weiter
Eine letzte Schleife wird nicht durchlaufen.
Allerdings nur dann, wenn Index und Grenze gleich sind. Das Wort
?DO arbeitet exakt wie DO, abgesehen eben
davon, dass der Schleifenkörper nicht durchlaufen wird, wenn Index
und Grenze gleich sind.
FIBONACCI. Dieses Wort soll
eine Zahl auf dem Stack erwarten und die entsprechende Anzahl von
Fibonacci-Zahlen ausgeben. Sie brauchen nicht darauf zu achten,
dass diese Zahlen sehr schnell sehr groß werden. Es reicht, wenn
es die ersten zehn Fibonacci-Zahlen ausgibt. [Lösung]
1 AND 'wahr' wenn die Zahl ungerade
war.[Lösung]