Standard 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”
At run-time, if f=0, execution continues after the
THEN
(or ELSE
) that consumes the orig,
otherwise right after the IF
(see Selection).
AHEAD
( compilation – orig ; run-time – ) tools-ext “AHEAD”
At run-time, execution continues after the THEN
that
consumes the orig.
THEN
( compilation orig – ; run-time – ) core “THEN”
The IF
, AHEAD
, ELSE
or WHILE
that
pushed orig jumps right after the THEN
(see Selection).
BEGIN
( compilation – dest ; run-time – ) core “BEGIN”
The UNTIL
, AGAIN
or REPEAT
that consumes
the dest jumps right behind the BEGIN
(see Simple Loops).
UNTIL
( compilation dest – ; run-time f – ) core “UNTIL”
At run-time, if f=0, execution continues after the
BEGIN
that produced dest, otherwise right after
the UNTIL
(see Simple Loops).
AGAIN
( compilation dest – ; run-time – ) core-ext “AGAIN”
At run-time, execution continues after the BEGIN
that
produced the dest (see Simple Loops).
CS-PICK
( orig0/dest0 orig1/dest1 ... origu/destu u – ... orig0/dest0 ) tools-ext “c-s-pick”
CS-ROLL
( destu/origu .. dest0/orig0 u – .. dest0/orig0 destu/origu ) tools-ext “c-s-roll”
CS-DROP
( dest – ) gforth-1.0 “CS-DROP”
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).
CS-PICK
can only pick a dest and CS-DROP
can only drop a
dest, because an orig must be resolved exactly once.
Some standard control structure words are built from these words:
ELSE
( compilation orig1 – orig2 ; run-time – ) core “ELSE”
At run-time, execution continues after the THEN
that
consumes the orig; the IF
, AHEAD
, ELSE
or WHILE
that pushed orig1 jumps right after the
ELSE
. (see Selection).
WHILE
( compilation dest – orig dest ; run-time f – ) core “WHILE”
At run-time, if f=0, execution continues after the
REPEAT
(or THEN
or ELSE
) that consumes the
orig, otherwise right after the WHILE
(see Simple Loops).
REPEAT
( compilation orig dest – ; run-time – ) core “REPEAT”
At run-time, execution continues after the BEGIN
that
produced the dest; the WHILE
, IF
,
AHEAD
or ELSE
that pushed orig jumps right
after the REPEAT
. (see Simple Loops).
Gforth adds some more control-structure words:
ENDIF
( compilation orig – ; run-time – ) gforth-0.2 “ENDIF”
Same as THEN
.
?dup-IF
( compilation – orig ; run-time n – n| ) gforth-0.2 “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-0.2 “question-dupe-zero-equals-if”
Another group of control structure words are:
case
( compilation – case-sys ; run-time – ) core-ext “case”
Start a case
structure.
endcase
( compilation case-sys – ; run-time x – ) core-ext “end-case”
Finish the case
structure; drop x, and continue behind
the endcase
. Dropping x is useful in the original
case
construct (with only of
s), but you may have
to supply an x in other cases (especially when using
?of
).
next-case
( compilation case-sys – ; run-time – ) gforth-1.0 “next-case”
Restart the case
loop by jumping to the matching
case
. Note that next-case
does not drop a cell,
unlike endcase
.
of
( compilation – of-sys ; run-time x1 x2 – |x1 ) core-ext “of”
If x1=x2, continue (dropping both); otherwise, leave x1 on the
stack and jump behind endof
or contof
.
?of
( compilation – of-sys ; run-time f – ) gforth-1.0 “question-of”
If f is true, continue; otherwise, jump behind endof
or
contof
.
endof
( compilation case-sys1 of-sys – case-sys2 ; run-time – ) core-ext “end-of”
Exit the enclosing case
structure by jumping behind
endcase
/next-case
.
contof
( compilation case-sys1 of-sys – case-sys2 ; run-time – ) gforth-1.0 “cont-of”
Restart the case
loop by jumping to the enclosing
case
.
Internally, of-sys is an orig
; and case-sys is a cell
and some stack-depth information, 0 or more orig
s, and a
dest
.
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.