hex \ Alle Wertangaben sind hexadezimal zu verstehen. \ In [FB-4] hatte ich die folgenden Kontrollstruktur-Elemente von Coos Haak \ [CH93] verwendet, die ohne 'immediate' und ohne die Zuhilfenahme von \ Assembler auskommen und die mit den allereinfachsten Primitives drop r> r@ \ >r arbeiten: : begin ( -- ) r@ >r ; \ Kopiert (in der Colon-Definition, die die Schleife \ enthaelt) die Adresse nach begin auf den R-Stack. : again ( -- ) r> drop \ Ersetzt auf dem R-Stack die Adresse nach again durch r@ >r ; \ die Adresse nach begin, springt also dann dorthin. : exit ( -- ) r> drop \ Entfernt die Adresse nach begin vom Returnstack und r> drop ; \ wirkt dann genau so wie das urspruengliche exit. \ In [FB-4] hatte ich im Uebrigen geschrieben: \ "Schleifen, die sich nur auf schon durchlaufene Stellen im Quelltext \ beziehen, muessten eigentlich alle nach dem hier besprochenen Schema \ behandelbar sein. Dazu gehoeren (neben dem oben erwaehnten begin-again \ aus [CH93]) auch begin-until und begin-while-repeat. Bei begin-until \ haette ich es beinah schon geschafft. Aber nur beinahe! Die weitere \ Beschaeftigungsrichtung ist damit jedoch bereits vorgegeben." \ Nun, im vorliegenden Artikel moechte ich ueber das inzwischen Erreichte \ berichten. Die hier verwendete Returnstack-Akrobatik ist 'unendlich \ schwoer', aber Forth macht es auch einem Nicht-Guru moeglich, sich daran zu \ versuchen - zugegeben, manchmal mit fast nicht mehr vertretbarem Aufwand an \ Geduld. \ Ich war jedoch in meinen Erwartungen zu optimistisch. Bei begin-until habe \ ich es inzwischen geschafft - ich werde gleich darueber berichten. \ begin-while-repeat jedoch enthaelt einen Teil, bei welchem ein \ Vorwaerts-Sprung-Ziel bekannt sein muss, das man sich nach meiner jetzigen \ Sicht nicht anders beschaffen kann als schon waehrend der Compilation. \ Dasselbe Problem besteht bei if-then und if-else-then. Ich habe mir dazu \ einen Trick ueberlegt. Zunaechst aber darf ich begin-until erledigen. \ Bevor ich mit meinen Vorschlaegen zu begin-until beginne, schnell diejenigen \ Worte in Kurzdarstellung, die ich schon in [FB-4] vorgeschlagen hatte und \ von denen ich auch hier wieder Gebrauch machen moechte. \ begin und again aus [CH93] habe ich gerade erwaehnt. Sodann brauche ich: : 0= ( n1 -- n2 ) \ n1=0 -> n2=-1 ; sonst n2=0 0 -1 0 d+ swap drop 1 - ; : ?branch2 ( fl -- ) \ Sprung um 2 Byte bei fl=0, kein Sprung sonst. 0= 0 -1 0 d+ swap drop dup + r> + >r ; code d+ ( d1 d2 -- d1+d2 ) \ Das habe ich in [FB-1] vorgeschlagen. ax pop bx pop cx pop dx pop cx push dx push ax push bx push 66 c, ax pop 66 c, bx pop 66 c, ax bx add 66 c, bx push ax pop bx pop ax push bx push next end-code \ Und nun zu begin-until. Dazu fuehre ich -begin und (again) ein: : -begin ( -- ) \ Dieses -begin entfernt die von (again) in until stammende \ Ruecksprung-Adresse vom Returnstack. r> r> \ Zwei Returnadressen-Ebenen tiefer springen. r> drop \ Dort Adresse nach begin vom R-Stack nehmen und die noch auf >r >r ; \ dem D-Stack geparkten Rsp-Adressen wieder auf den R-Stack \ legen (zum Ruecksprung zur aufrufenden Colon-Definition). : (again) ( -- ) \ Dieses (again) wird ausgefuehrt, wenn in until fl=0 ist. r> \ Ruecksprung-Adresse auf dem Datenstack parken. r> drop \ Beim Ausfuehren wieder zur Adresse nach begin springen (was r@ >r \ also dem reinen Schleifenwiederholer again entspricht). 2 + \ Geparkte Ruecksprung-Adresse um 2 erhoeht auf den R-Stack >r ; \ legen, so dass dann nach Rueckkehr zu until das Wort -begin \ uebersprungen und also nicht mehr ausgefuehrt wird. : until ( fl -- ) \ Das Wort 0= wirkt auf das ?branch2 als logische Negation. 0= \ Wenn bei diesem until (in der Colon-Definition) fl<>0 ist, ?branch2 \ dann wird das folgende (again) uebersprungen und -begin (again) \ ausgefuehrt (in der Colon-Definition weiter nach until). -begin ; \ Wenn dagegen fl=0 ist, dann wird das (again) ausgefuehrt \ und zur Adresse nach begin zurueckgesprungen. \ Die Bezeichnung (again), mit den Klammern, ist eine Verlegenheitsloesung, da \ die Bezeichnung again schon fuer das herkoemmliche begin-again vergeben ist. \ Ueberlegenswert waere es, ob man nicht mit der Funktion des Wortes again \ auch hier durchkommt. \ Es folgt ein Beispiel fuer begin-until. : yy1 0 begin 1 + dup . dup 47 = until drop cr ." Das war's." ; : xx1 cr ." Beispiel 1: " cr yy1 cr ; \ Und wie war das eigentlich mit if-then (ohne Assembler und ohne immediate)? \ Es sollte keine Moeglichkeit bestehen, (schon waehrend der Compilation des \ Quelltextes) Immediate-Worte auszufuehren. Wie soll dann aber das Sprungziel \ fuer if bei fl=0 schon am Anfang von if-then bereitgestellt werden? Es ist \ wirklich schwierig! Hier ein Kompromissvorschlag zur Loesung des Konflikts: \ if-then kapselt den von Fall zu Fall (bei nicht erfuellter if-Bedingung) zu \ ueberspringenden Quelltextteil ein. Eine ganz einfache Einkapselung, die \ gleichzeitig schon gleich nach der Compilation das Sprungziel bereitstellt \ (also wenn das Compilat bereits vorliegt, aber vom Runtime-Durchgang noch \ keine Rede ist), kann ueber ein Forth-Wort erzielt werden, das man extra zu \ diesem Zweck in die aufrufende Colon-Definition einbringt. (Der Sprung - so \ er ueberhaupt anfaellt - findet ja in der rufenden Colon-Definition statt.) \ Der Colon-Compiler tut uns den Gefallen, die Adresse unmittelbar hinter dem \ extra eingefuehrten Wort (in der Colon-Definition) schon zur Compile-Zeit \ als Ruecksprung-Adresse auf den Returnstack zu legen. \ Ich beginne mit if-then, benoetige aber gleich noch einen Nebengedanken, \ wenn ich if in ueblicher Art auch fuer if-else-then verwenden moechte. \ Aufgrund der Syntax-Modifikation (siehe Textteil), von welcher ich hoffe, \ dass sie sich im Endeffekt als nicht ganz so eigenwillig herausstellt, wie \ sie zunaechst erscheinen mag, braucht zu keinem Zeitpunkt ein Sprungziel \ explizit bekannt zu sein. Gesprungen wird (auf der entsprechenden Ebene der \ Compilation in der Colon-Definition) nur immer um hoechstens ein einziges \ Forth-Wort (ich will es Huellwort nennen). Die Sprungweite ist also immer \ nur 2 oder noetigenfalls 0 (wenn naemlich fl=0), und das ist ein typischer \ Anwendungsfall fuer meinen Vorschlag ?branch2. Das eigentliche Sprungziel \ von if hin zu then oder von if hin zu else und dann von else hin zu then \ kann als Ruecksprung-Adresse (per r> oder r@) dem Returnstack entnommen \ werden. \ -------------------------------------------------------------------- \ Achtung! Hier und im weiteren Verlauf moege gelten: \ Huellworte duerfen leer sein, aber nicht einfach weggelassen werden. \ -------------------------------------------------------------------- \ So und nicht anders haette ich mir das eigentlich vorgestellt. Mit ?branch2 \ komme ich aber leider nicht durch. Bei if-else-then wird entweder der Part \ if-else durchlaufen und dann von else nach then gesprungen oder es wird von \ if nach else gesprungen und der Part else-then durchlaufen. else muesste \ genau wie if einen Flag-Wert tragen - und der Flagwert von else waere mit \ dem Flagwert von if zu koppeln. Nun sind aber die Flagwerte von if und else \ logisch komplementaer: entweder das eine oder aber das andere. Das nutze ich \ aus. \ Mit anderen Worten, mit ?branch2 komme ich nicht durch. Ich benoetige ein \ Wort, das sich analog zu ?branch2 verhaelt, bei dem sich aber der Sprung, \ wenn er stattfindet, ueber vier Byte (nicht nur ueber zwei) erstreckt. Ich \ darf also konstruieren: : ?branch4 ( fl -- ) \ Dieses Wort sorgt in einer Colon-Definition dafuer, 0= 0 -1 0 d+ swap \ dass die 2 Worte, die unmittelbar auf eben dieses drop dup + dup + \ ?branch4 folgen, bei fl=0 uebersprungen werden. r> + >r ; \ Bei fl<>0 erfolgt kein Sprung. \ Die Sequenz dup + dup + haette natuerlich durch 4 * ersetzt werden koennen. \ Ich moechte aber mit der gewaehlten Darstellung schon mal darauf aufmerksam \ machen, dass ich ja bei meinen ganzen Ueberlegungen im Vorliegenden immer \ davon ausgehe, dass das Einbringen von Ganzzahlen ins Compilat vom Compiler \ klaglos erledigt wird. \ Beim Ausprobieren beschraenke ich mich nicht auf den Colon-Compiler, sondern \ mache vom gesamten (Turbo-)Forth-System Gebrauch. Ich mache mir also hier \ insbesondere keine Gedanken ueber die Erfassung von (Ganz-)Zahlen durch das \ System - eigentlich durch den Colon-Compiler. \ Man koennte sich aber doch auch ueberlegen, ob nicht eventuell alle Zahlen \ ueber dup und + und vielleicht noch ein weiteres Primitive (welches?) \ erfasst werden koennen. Doch das ist ein anderes Thema, das hier nicht weiter \ verfolgt werden soll. \ Jetzt ein Beispiel: : xx3 ?branch4 5 . 6 . 7 . 8 . ; \ 9 0 xx3 [ret] 9 6 7 8 ok ( -- ) \ 9 -1 xx3 [ret] 5 6 7 8 ok ( -- 9 ) \ Ich habe das Beispiel mit Absicht komplizierter gestaltet, als es vielleicht \ noetig gewesen waere. 0 und -1 sind die Flagwerte fuer ?branch4. Die 9 wird \ bei fl=0 vom ersten Punkt konsumiert (und angezeigt), nachdem ja die 32 Bits \ von (lit) 5 uebersprungen worden waren. Bei fl=-1 kommt die 5, die ja im \ Compilat als (lit) 5 erscheint, zum Tragen und wird angezeigt; die 9 bleibt \ dann auf dem Datenstack. \ Fuer das jetzt folgende if-else-then schien es mir 'einfacher', nicht das \ fertige Wort ?branch4 zu verwenden, sondern 'nur' dessen Wortkoerper explizit \ und in voller Laenge direkt einzubauen. Damit bin ich dem Uebel aus dem Weg \ gegangen, dass ich sonst mit unterschiedlichen Ruecksprung-Ebenen zu kaempfen \ gehabt haette. : if ( fl -- ) 0= 0 -1 0 d+ swap drop dup + dup + r> + >r ; : else ( -- ) r> 2 + >r ; : then ( -- ) ; \ Jetzt ein Beispiel fuer if-then und if-else-then mit Erlaeuterung. Es sei : aaa 4 . 5 . 6 . ; : bbb 7 . 8 . 9 . ; \ Und hier das Beipiel mit den Huellworten aaa und bbb: : ww1 if aaa then ; : ww2 if aaa else bbb then ; \ In ww1 ist es sicher nicht auf Anhieb erkenntlich, warum das Kontrollwort if \ auf Spruenge ueber 4 Byte gefasst sein muss - wenn naemlich fl=0. Der Grund \ fuer Spruenge ueber 4 statt 2 Byte erhellt aus ww2. In der Konstruktion \ if-else-then soll if natuerlich dasselbe if sein wie bei if-then. In if-then \ richtet ein Sprung ueber 4 Byte (statt 2) keinen Schaden an: then ist ein \ funktionsloses Wort. \ Und wie ist es bei if-else-then (vergleiche ww2)? Wenn da if auf fl=0 trifft, \ wird nicht nur aaa uebersprungen, sondern auch else. Fuer den Sprung ist das \ Kontrollwort else aber ein genau so schoenes Forth-Wort wie aaa. Also wird \ als Antwort auf fl=0 das Wort aaa ausgelassen, das Wort else uebersprungen \ und dann direkt bei bbb fortgefahren. (Es lebe die Programmierfreiheit und \ die Vermischbarkeit von System- und Anwendungsworten in Forth!) \ Andererseits wird bei Aufruf von if mit fl<>0 erst das Wort aaa ausgefuehrt, \ und dann das Kontrollwort else. Dass dann das Wort bbb uebersprungen wird, \ ist ganz einfach dadurch zu erreichen, dass else (in der aufrufenden \ Colon-Definition) einen Sprung ueber 2 Byte einleitet. Nichts anderes. Eine \ Alternative gibt es im vorliegenden Kontrollwort-Geruest bei else nicht: \ Waere beim Aufruf von if der Flagwert fl=0, dann wuerde mit aaa auch else \ uebersprungen werden. else wuerde also fuer diesen Fall, naemlich fuer fl=0, \ gar nicht erst in Betracht gezogen werden. \ Es hat alles seinen Preis. Ich bezahle meine Syntax-Modifikation damit, dass \ then im Compilat tatsaechlich auftritt. Das ist etwas gewoehnungsbeduerftig, \ da das ja in der ueblichen Forth-Syntax nicht geschieht (Ueberpruefung per \ see). Man kann aber auf das Dummy then nicht verzichten - wenn man das auf \ 4-Byte-Spruenge eingerichtete if auch bei if-then (ohne else) verwenden will \ (bei if-else-then waren 4-Byte-Spruenge noetig, um die Moeglichkeit, else \ zusammen mit dem Huellwort aaa zu ueberspringen, auszuschoepfen). \ Es gibt bei den im Vorliegenden besprochenen Kontrollworten im Compilat auch \ kein ?branch. Und im Compilat zeigt das uebliche see die Worte if und else \ an... und andere (kleinere oder groessere) Abweichungen vom Ueblichen. \ Es folgt ein Beispiel, das nicht funktioniert (nachpruefen!). Was ist daran \ falsch? Falsch im Sinne meiner Syntax-Modifikationen. Mit welch einfacher \ Veraenderung bringt man es zum Laufen? : ww3 if 4 if aaa then then 47 . ; \ Ich muss zugeben, dass ich das Durchprobieren aller Moeglichkeiten (und \ Unmoeglichkeiten) der Verschachtelungen nicht bis zum allerletzten denkbaren \ Ende durchgefuehrt habe. Irgendwann muss aber jeder VD-Artikel auch mal sein \ Ende finden - und wenn ich einen Schnitzer begangen haben sollte, waere ich \ um Mitteilung dankbar. \ Und hier noch ein funktionierendes komplexeres Beispiel, das sich aus den \ bisher betrachteten (funktionierenden) Beispielen zusammensetzt. Ich fasse \ es das eine Mal mit meiner Syntax-Modifikation und spreche es dann unter xx4 \ an, ein zweites Mal erfasse ich alles in der ueblichen Schreibweise des \ if-else-then-Konstrukts unter xx5. Das Wort xx4 ist mit xx5 funktionsgleich \ (nachpruefen!). : xx4 ww2 ww1 xx1 cr ." Alles klar?" cr ; : xx5 if aaa else bbb then if aaa then cr ." Beispiel 1: " cr 0 begin 1 + dup . dup 47 = until drop cr ." Das war's." cr cr ." Alles klar?" cr ; \ Sowohl xx4 wie auch xx5 koennen mit 4 verschiedenen Parameterpaaren \ aufgerufen werden (0=false, -1=true): \ 0 -1 xx4/xx5 [ret] \ 0 0 xx4/xx5 [ret] \ -1 0 xx4/xx5 [ret] \ -1 -1 xx4/xx5 [ret] \ Ich verzichte auf die Wiedergabe der Ergebnisse im Einzelnen. \ Und jetzt die noch ausstehende Konstruktion begin-while-repeat . Mit meiner \ Vermutung in [FB-4] habe ich mich leider vertan. Es ist denn doch nicht so \ leicht wie bei begin-again und begin-until. Der Teil while-repeat entspricht, \ wenn das Dazwischenstehende nicht ausgefuehrt wird, wenn also while mit fl=0 \ aufgerufen wird, einem Vorwaertssprung. Das leuchtet auch ein, wenn man \ bedenkt, dass while in der herkoemmlichen Forth-Syntax nichts anderes bewirkt \ als if. Und diese Aequivalenz von if und while kann ich auch, und werde ich \ auch, hier ausnuetzen. Allerdings muss ich dazu meine Modifikationsforderung \ an die Forth-Syntax ausdehnen - indem ich sie auch auf while-repeat anwende. \ ---------------------------------------------------------------------------- \ Forderung: Die Forth-Bestandteile zwischen while und repeat muessen (wie bei \ if-then und if-else und else-then) in ein Huellwort eingekapselt sein. Fuer \ die Bestandteile zwischen begin und while ist ein Huellwort nicht noetig, \ kann aber ebenso gut auch eingesetzt werden. \ ---------------------------------------------------------------------------- \ Das fuer begin-while-repeat vorzusehende Wort begin bleibt dasselbe wie bei \ begin-again und begin-until. Es legt die Adresse, die in der aufrufenden \ Colon-Definition auf begin folgt, auf den Returnstack (siehe weiter oben im \ Listing). \ Und jetzt das Kontrollwort while. Wegen der Aequivalenz mit if (siehe weiter \ oben im Listing) nimmt es nicht wunder, dass ich auch while mit dem \ Wortkoerper von ?branch4 ausstatte - ?branch4, nicht ?branch2. Andererseits \ muss bei while-repeat die mit begin eingeleitete Schleife (fuer while mit \ fl=0) beruecksichtigt werden. Es nimmt also gleichermassen nicht wunder, wenn \ ich mich auch stark an begin-until (siehe weiter oben im Listing) ausrichte. : while ( fl -- ) 0= 0 -1 0 d+ \ Diese, dann die zweite, die dritte und die fuenfte Zeile swap drop \ entsprechen dem Wortkoerper von ?branch4 (siehe unter dup + dup + \ if-else-then im Listing). dup + dup + entspricht 4 * . dup \ Duplikat von 0/4 zum Ausschalten von -begin bei fl<>0 . r> + >r \ Ruecksprung-Adresse + 4 Byte bei fl=0, + 0 Byte bei fl<>0. ?branch2 \ -begin wird (in while) bei fl<>0 uebersprungen; bei fl=0 -begin ; \ wird es ausgefuehrt. : repeat ( -- ) \ Dieses repeat wird ausgefuehrt, wenn while auf fl<>0 r> drop \ trifft. Es entspricht dem weiter oben besprochenen again: r@ >r ; \ Ruecksprung-Adresse vom R-Stack nehmen und durch Adresse \ nach begin in der aufrufenden Colon-Definition ersetzen. \ Erlaeuterung: Sei : ss0 begin 0 while aaa repeat 7 . ; : ss1 begin 1 while bbb repeat 7 . ; \ In ss0 wird das Paar aaa repeat uebersprungen und dann wird 7 . ausgefuehrt. \ In ss1 wird das Wort bbb ausgefuehrt und die Schleife dann ab 1 wiederholt. \ Zur Abrundung ein verhaeltnismaessig einfaches Beispiel. Es stellt die Zahl \ 8 durch mehrstufige Potenzierung der Basis 2 her. Natuerlich wird dieses \ (viel zu umstaendliche) Beipiel nicht zur Anwendung empfohlen. Es soll nur \ das Prinzip der begin-while-repeat-Schleife ohne Assembler und ohne immediate \ erhellen. Man achte auf ccc als Huellwort! Ohne ein solches Huellwort geht \ mein Vorschlag nicht. Mit dup + vermeide ich das Malzeichen bei 2 * . : ccc dup + ; : acht ( -- ) 1 begin dup 8 <> while ccc repeat . ; \ Die Schleife ist bei repeat zu Ende. Der Anzeige-Punkt gehoert schon zur \ Schleifen-Aussenwelt. \ Vorsicht: Urspruenglich hatte ich beim Austesten der Schleife acht den \ Bestandteil dup + ohne das eben eingesetzte Huellwort ccc wie folgt \ geschrieben und mich darueber gewundert, dass das Ergebnis nicht meinen \ Erwartungen entsprach: \ : acht ( -- ) 1 begin dup 8 <> while dup + repeat . ; \ Und nun noch das weiter oben im Listing (unter xx1/yy1) schon angefuehrte \ Beispiel, diesmal aber ueber begin-while-repeat statt ueber begin-until \ konstruiert. : ddd 1 + dup . ; : yy2 0 begin dup 47 < while ddd repeat drop cr ." Das war's." ; : xx2 cr ." Beispiel 2: " cr yy2 cr ;