!!!Programm-Strukturen

[{TableOfContents }] \\


[Wil Baden|http://home.earthlink.net/~neilbawd/], auf den Sie in der englischsprachigen Literatur oft stoßen, hat in
seinem Beitrag ESCAPING FORTH folgendes dargelegt: Es gibt vier Arten von Steueranweisungen :

* die Abfolge von Anweisungen,
* die Auswahl von Programmteilen,
* die Wiederholung von Anweisungen und Programmteilen,
* den Abbruch.

Die ersten drei Möglichkeiten sind zwingend notwendig und in den älteren Sprachen wie PASCAL	ausschließlich	vorhanden. Entsprechend	steht im volksFORTH eine Anweisung für die Auswahl von Programmteilen zur Verfügung, wobei die Ausführung vom Resultat eines logischen Ausdrucks abhängig gemacht wird:

{{{
flag IF <Anweisungen> THEN 
flag IF <Anweisungen> ELSE <Anweisungen> THEN
}}}

Soll dagegen im Programm ein Rücksprung erfolgen, um Anweisungen wiederho1t auszuführen, wird bei einer gegebenen Anzahl von Durchläufen diese Anweisung eingesetzt, wobei der aktuelle Index über I und J zur Verfügung steht:

{{{
<Grenzen> DO / ?DO <Anweisungen> LOOP 
<Grenzen> DO / ?DO <Anweisungen> <Schrittweite> +LOOP
}}}

Wenn eine Wiederholung von Anweisungen ausgeführt werden soll, ohne daß die Anzahl der Durchläufe bekannt 1st, so ist eine Indexvariable mitzuführen oder sonstwie zum Resultat eines logischen Ausdrucks zu kommen. Die folgende Konstruktion ermöglicht eine Endlos-Schleife:

{{{
BEGIN <Anweisungen> REPEAT
}}}

Die Wiederholungsanweisungen sind insoweit symmetrisch, daß eine Anweisung so­ lange (while) ausgeführt wird, wie ein Ausdruck wahr ist, oder eine Anweisung wiederholt wird, bis (until) ein Ausdruck wahr wird.

{{{
BEGIN <Anweisungen> flag UNTIL 
BEGIN <Anweisungen> flag WHILE <Anweisungen> REPEAT
}}}

Beide Möglichkeiten lassen sich in volksFORTH auch kombinieren, wobei auch mehrere (multiple) WHILE in einer Steueranweisung auftreten dürfen.

{{{
BEGIN <Anweisungen> flag WHILE <Anweisungen> flag UNTIL
}}}

Nun tritt in Anwendungen häufig der Fall auf, daß eine Steueranweisung verlassen werden soll, weil sich etwas ereignet hat.

Dann ist die vierte Situation, der Abbruch, gegeben, Die Programmiersprache "C" stellt dafür die Funktionen: ''break'', ''continue'', ''return'' und ''exit'' zur Verfügung; volksFORTH bietet hier ''exit'', ''leave'', ''endloop'', ''quit'', ''abort'', ''abort"'' und ''abort('' an. In FORTH wird EXIT dazu benutzt, um die Definition zu verlassen, in der es erscheint; LEAVE dagegen verläßt die kleinste umschließende DO...LOOP-Schleife.

! Glossary

Ab der Version 3.81.8 verfügt volksFORTH über eine zusätzliche Steueranweisung für den Compiler, die bedingte Kompilierung in der Form:

{{{
have <word> not .IF <action1> .ELSE <action2> .THEN
}}}

DieseWorte werden außerhalb von Colon-Definitionen eingesetzt und ersetzen das ''\needs'' früherer Versionen.

* [have]
* [exit]
* [?exit|question-exit]
* [0=exit|zero-equals-exit]
* [if]
* [.IF]
* [then]
* [.THEN]
* [else]
* [.ELSE]
* [do]
* [?do|question-do]
* [loop]
* [+loop|plus-loop]
* [I]
* [J]
* [leave]
* [endloop]
* [bounds]
* [begin]
* [repeat]
* [until]
* [while]
* [execute]
* [perform]
* [case?|case-question]
* [stop]

!!! Worte zur Fehlerbehandlung

Diese arbeiten auch wie Steueranweisungen, wie die Definitionen von ''ARGUMENTS'' und ''IS-DEPTH'' zeigen: : 

{{{
is-depth ( n — )
  depth 1- — abort" falsche Parameterzahl!" ; 
}}}

IS-DEPTH überprüft den Stack auf eine gegebene Anzahl Stackelemente (depth) hin.

* [abort]
* ['abort|tick-abort]
* [abort"|abort-quote]
* [error"|error-quote]
* [errorhandler]
* [(error|paren-error]
* [r#|r-sharp]
* [scr]
* [quit]
* [?pairs|question-pairs]

!!! Fallunterscheidung in FORTH

!!Strukturierung mit IF ELSE THEN / ENDIF

An dieser Stelle soll kurz die vielfältigen Möglichkeiten gezeigt werden, mit denen eine Fallunterscheidung in FORTH getroffen merden kann. Kennzeichnend für eine solche Programmsituation ist, daß von verschiedenen Möglichkeiten des Programmflusses genau eine ausgesucht werden soll.

Ausgehend von einer übersichtlichen Problemstellung, einem Spiel, werden die notwendigen Grundlagendefinitionen und die Entwicklung der oben beschriebenen Kontrollstruktur beschrieben.

Als Beispiel dient ein Spiel mit einfachen Regeln:

Bei diesem Trinkspiel, das nach dem Artikel "Ultimate CASE-Statement" ([Vierte Dimension 2/87|http://forth-ev.de/filemgmt/visit.php?lid=116], Seite 40 ff) auch CRAPS genannt wird, geht es darum, einen Vorrat von gefüllten Gläsern unter den Mitspielern mit Hilfe des Würfels zu verteilen und leerzutrinken:

* Bei einer EINS wurde ein Glas aus dem Vorrat in der Tischmitte genommen und vor sich gestellt.
* Bei einer ZWEI oder einer DREI bekam der Nachbar/ die Nachbarin links ein Glas des eigenen Vorrates zugesehoben.
* Bei einer VIER oder einer FÜNF wurde dem Nachbarn/ der Nachbarin rechts ein Glas des eigenen Vorrates vorgesetzt.
* Bei einer SECHS wurden alle Gläser, die der Spieler / die Spielerin vor sich stehen hatte, leergetrunken.

Zuordnung ist also: 1=nehmen, 2/3=links, 4/5=rechts, 6=trinken und entsprechend der Augenzahl des Würfels soll eine der 6 möglichen Aktionen ausgeführt werden. Das Programm soll sich darauf beschränken, das Ergebnis dieses Würfelns einzulesen und auszuwerten. Daraufhin wird eine Meldung ausgegeben, welche der sechs Handlungen auszuführen ist.

Für ein solches Programm ist eine Zahleneingabe notwendig. Diese wurde hier mit dem Wort F83-NUMBERS realisiert:

{{{
: F83-number?  ( string -- d f )
   number? ?dup
   IF
     0<  IF extend THEN
     true exit
   THEN
   drop 0 0 false ;
   
: input#   (  string -- n )
   pad c/l 1- >expect
   pad F83-number? 2drop ;
}}}

Die Definition der Worter, die sechs oben genannten Aktionen symbolisch aus­führen sollen, richtet sich nach den Spielregeln, die für jedes Würfelergebnis genau eine Handlung vorschreiben:

{{{
\ nehmen  trinken  links  rechts  schieben

: nehmen  bright ." ein Glas nehmen"          normal  2 spaces ;
: trinken bright ." alle Gläser austrinken"   normal  2 spaces ;
: links   bright ." ein Glas nach links"      normal  2 spaces ;
: rechts  bright ." ein Glas nach rechts"     normal  2 spaces ;

: schieben ;

}}}

''SCHIEBEN'' ist eine Dummyprozedur, ein Füllsel, dessen Notwendigkeit sich erst sehr spät ergibt. Für den Dialog mit dern Anwender wird deflniert:

{{{
: Anfrage    cr ." Sollen Sie nehmen, trinken oder schieben?" 
             cr ." Bitte Ihre Augenzahl und <cr> : " ;
             
: Glückwunsch  cr ." Viel Glück beim nächsten Wurf ..." ;
}}}

Das Wort ''AUSWERTUNG'' soll entsprechend einem Selektor genau eine von 6 möglichen Prozeduren ausführen. Also wird man prüfen, ob diese oder diese oder ... der Mogiichkeiten in Frage kommt. Hinzu kommt noch die Prüfung, ob der übergebene Parameter zwischen (between) 1 und 6 lag.

Die Def!nit!on von ''BETWEEN'' ist volksFORTH-gemäß recht kurz:

{{{
( Wert Untergrenze Obergrenze -- false   oder )
(                             -- true    wenn Untergrenze <= Wert <= Obergrenze )
: between   1+ uwithin ;

: Auswertung.1   ( wurfergebnis -- )
  dup 1 = IF  nehmen  ELSE
          dup 2 = IF links schieben ELSE
          dup 3 = IF links schieben ELSE
                  dup 4 IF rechts schieben ELSE
                  dup 5 IF rechts schieben ELSE
                        dup 6 = IF trinken THEN
                        THEN
                        THEN
                  THEN
                  THEN
          THEN
  1 6 between not IF invers ." Betrug!" normal THEN ;
}}}

Da eine solche Prüfung auf Gleichheit in der Programmierpraxis oft vorkommt, stellt das volks4TH dafür das Wort case? zur Verfügung. case? vergleicht die obersten beiden Stackwerte miteinander. Bei Ungleichheit bleibt der Testwert (Selektor) erhalten, so daß die Worte DUP und = dadurch ersetzt werden.

{{{
: Auswertung.3  ( Wurfergebnis -- )

   1 case? IF  nehmen           exit THEN
   2 case? IF  links  schieben  exit THEN
   3 case? IF  links  schieben  exit THEN
   4 case? IF  rechts schieben  exit THEN
   5 case? IF  rechts schieben  exit THEN
   6 case? IF  trinken          exit THEN
   
   drop    invers  ." Betrug!"  normal ;
}}}

Bei dieser Auswertung wird aus dem QueHtext zu wenig deutlich, daß bei ZWEI und DREI dieselbe Handlung ausgeführt wird, wie auch VIER und FÜNF die gleichen Aktionen zur Folge haben.

=OR prüft deshalb einen Testwert n2 auf Gleichheit mit einer unter einem Flag f1 liegenden Zahl n2. Das Ergebnis dieses Tests wird mit dem bereits vorliegenden Flag OR-verknüpft. Dieses neue Flag f2 und der Testwert nl werden Übergeben:

=OR Definition in Forth
{{{
  : =or  ( n1 f1 n2 -- n1 f2 )
    2 pick
    = or ;
}}}

=OR Definition in 8086 Assembler
{{{
   code 0or     ( n1 f1 n2 -- n1 f2 )
      A D xchg    D pop
      S W mov
        W ) A cmp
        0= ?[ -1 # D mov ]?
      next
   end-code
}}}

Dieses Wort bringt im Quelltext eine deutliche Verbesserung:

{{{
: Auswertung.4  ( Wurfergebnis -- )
  dup
  1 6 between IF
              dup 1 =           IF nehmen          THEN
              dup 2 = 3 =or     IF links  schieben THEN
              dup 4 = 5 =or     IF rechts schieben THEN
              dup 6 =           IF trinken         THEN
            ELSE
              invers ." Betrug!" normal
            THEN
   drop ;
}}}

Damit wurde ohne eine CASE-Anweisung eine sehr übersichtliche Steuerung des Programm-Flusses geschaffen. Die Plausibilitatsprüfung, ob die eingegeben Zahl zwischen 1 und 6 lag, ist hier an den Anfang gerückt und wird in einem einzigen ELSE-Zweig abgearbeitet.

!!Behandlung einer CASE — Situation

!Strukturelles CASE

Viele Programmiersprachen stellen eine CASE-Anweisung zur Verfügung, die wie in PASCAL mit Hilfe eines Fall-Indices eine Liste von Fall-Konstanten auswertet und eine entsprechende Anweisung ausführt.
 
Obwohl ein solches CASE-Konstrukt — wie oben gezeigt — nicht notwendig ist, macht es Programme besser lesbar und liegt bei Problemstellungen wie der Auswertung eines gegebenen Index eigentlich näher. Dies ist in ["Wil Baden - Ultimate CASE-Statement - VD 2/87, S.40 ff."|Ultimate CASE Statement] ausführlich diskutiert worden, , wobei aber der ältere Esker-CASE (Dr. Charles Eaker -- Just in CASE (FORTH DIM II/3)) von Dr. Charles Eaker sicherlich der bekanntere ist, der auch in der Literatur und in Quelltexten häufig Erwähnung und Verwendung findet.

Herr H. Sehnitter hat diesen Eaker-CASE für das volks4TH implementiert und dabei Veränderungen in der Struktur und Verbesserungen in der Anwendung vorgenommen.

{{{
\ caselist initlist >marklist >resolvlist 

| variable caselist
| : initlist	( list — addr ) 
    dup @ swap off ;

| : >marklist	( list — ) 
    here over @ , swap ! ;
    
| : >resolvelist ( addr list— ) 
    BEGIN dup @
    WHILE dup dup @ dup @ rot ! >resolve 
    REPEAT ! ;

\ case elsecase endcase

: CASE    caselist initlist 4 ; immediate restrict
: ELSECASE   4 ?pairs compile drop 6 ; immediate restrict
: ENDCASE    dup 4 = 
                IF  drop compile drop
                ELSE 6 ?pairs
                THEN caselist >resolvelist
  ; immediate restrict
  
\ of endof

: OF    4 ?pairs  compile over  compile = compile ?branch >mark compile drop 5 ; immediate restrict
: ENDOF 5 ?pairs  compile branch caselist >marklist >resolve 4 ; immediate restrict
}}}