Go to the first, previous, next, last section, table of contents.


Arbitrary 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       ``CS-PICK''

CS-ROLL       destu/origu .. dest0/orig0 u -- .. dest0/orig0 destu/origu         tools-ext       ``CS-ROLL''

On many systems control-flow stack items take one word, in Gforth they currently take three (this may change in the future). Therefore it is a really good idea to manipulate the control flow stack with cs-pick and cs-roll, not with data stack manipulation words.

Some standard control structure words are built from these words:

ELSE       compilation orig1 -- orig2 ; run-time f --         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       --       core       ``unloop''

DONE       compilation orig -- ; run-time --         gforth       ``DONE''

The standard does not allow using cs-pick and cs-roll on do-sys. Our system 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 LEAVEs 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.

Programming Style

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.

E.g., 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.


Go to the first, previous, next, last section, table of contents.