Next: Calls and returns, Previous: Counted Loops, Up: Control Structures
ANS Forth permits and supports using control structures in a non-nested way. Information about incomplete control structures is stored on the control-flow stack. This stack may be implemented on the Forth data stack, and this is what we have done in Gforth.
An orig entry represents an unresolved forward branch, a dest entry represents a backward branch target. A few words are the basis for building any control structure possible (except control structures that need storage, like calls, coroutines, and backtracking).
IF
compilation – orig ; run-time f – core “IF”
AHEAD
compilation – orig ; run-time – tools-ext “AHEAD”
THEN
compilation orig – ; run-time – core “THEN”
BEGIN
compilation – dest ; run-time – core “BEGIN”
UNTIL
compilation dest – ; run-time f – core “UNTIL”
AGAIN
compilation dest – ; run-time – core-ext “AGAIN”
CS-PICK
... u – ... destu tools-ext “c-s-pick”
CS-ROLL
destu/origu .. dest0/orig0 u – .. dest0/orig0 destu/origu tools-ext “c-s-roll”
The Standard words CS-PICK
and CS-ROLL
allow you to
manipulate the control-flow stack in a portable way. Without them, you
would need to know how many stack items are occupied by a control-flow
entry (many systems use one cell. In Gforth they currently take three,
but this may change in the future).
Some standard control structure words are built from these words:
ELSE
compilation orig1 – orig2 ; run-time – core “ELSE”
WHILE
compilation dest – orig dest ; run-time f – core “WHILE”
REPEAT
compilation orig dest – ; run-time – core “REPEAT”
Gforth adds some more control-structure words:
ENDIF
compilation orig – ; run-time – gforth “ENDIF”
?DUP-IF
compilation – orig ; run-time n – n| gforth “question-dupe-if”
This is the preferred alternative to the idiom "?DUP IF
", since it can be
better handled by tools like stack checkers. Besides, it's faster.
?DUP-0=-IF
compilation – orig ; run-time n – n| gforth “question-dupe-zero-equals-if”
Counted loop words constitute a separate group of words:
?DO
compilation – do-sys ; run-time w1 w2 – | loop-sys core-ext “question-do”
+DO
compilation – do-sys ; run-time n1 n2 – | loop-sys gforth “plus-do”
U+DO
compilation – do-sys ; run-time u1 u2 – | loop-sys gforth “u-plus-do”
-DO
compilation – do-sys ; run-time n1 n2 – | loop-sys gforth “minus-do”
U-DO
compilation – do-sys ; run-time u1 u2 – | loop-sys gforth “u-minus-do”
DO
compilation – do-sys ; run-time w1 w2 – loop-sys core “DO”
FOR
compilation – do-sys ; run-time u – loop-sys gforth “FOR”
LOOP
compilation do-sys – ; run-time loop-sys1 – | loop-sys2 core “LOOP”
+LOOP
compilation do-sys – ; run-time loop-sys1 n – | loop-sys2 core “plus-loop”
-LOOP
compilation do-sys – ; run-time loop-sys1 u – | loop-sys2 gforth “minus-loop”
NEXT
compilation do-sys – ; run-time loop-sys1 – | loop-sys2 gforth “NEXT”
LEAVE
compilation – ; run-time loop-sys – core “LEAVE”
?LEAVE
compilation – ; run-time f | f loop-sys – gforth “question-leave”
unloop
R:w1 R:w2 – core “unloop”
DONE
compilation orig – ; run-time – gforth “DONE”
The standard does not allow using CS-PICK
and CS-ROLL
on
do-sys. Gforth allows it, but it's your job to ensure that for
every ?DO
etc. there is exactly one UNLOOP
on any path
through the definition (LOOP
etc. compile an UNLOOP
on the
fall-through path). Also, you have to ensure that all LEAVE
s are
resolved (by using one of the loop-ending words or DONE
).
Another group of control structure words are:
case
compilation – case-sys ; run-time – core-ext “case”
endcase
compilation case-sys – ; run-time x – core-ext “end-case”
of
compilation – of-sys ; run-time x1 x2 – |x1 core-ext “of”
endof
compilation case-sys1 of-sys – case-sys2 ; run-time – core-ext “end-of”
case-sys and of-sys cannot be processed using CS-PICK
and
CS-ROLL
.
In order to ensure readability we recommend that you do not create arbitrary control structures directly, but define new control structure words for the control structure you want and use these words in your program. For example, instead of writing:
BEGIN ... IF [ 1 CS-ROLL ] ... AGAIN THEN
we recommend defining control structure words, e.g.,
: WHILE ( DEST -- ORIG DEST ) POSTPONE IF 1 CS-ROLL ; immediate : REPEAT ( orig dest -- ) POSTPONE AGAIN POSTPONE THEN ; immediate
and then using these to create the control structure:
BEGIN ... WHILE ... REPEAT
That's much easier to read, isn't it? Of course, REPEAT
and
WHILE
are predefined, so in this example it would not be
necessary to define them.