Wednesday, September 21, 2005

Mixing and Matching Control Structures

Forth uses IMMEDIATE words to build control structures at compile time. The various opening and closing words communicate with each other by passing items on a control stack to ensure proper nesting.

What goes on the control stack



  • BEGIN leaves dest to be consumed by UNTIL or AGAIN

  • AHEAD and IF leave orig to be consumed by THEN

  • (AHEAD compiles an unconditional forward jump)

  • DO and ?DO leave do-sys to be consumed by LOOP or +LOOP

  • CASE leaves case-sys to be consumed by ENDCASE

  • OF leaves of-sys to be consumed by ENDOF

Sometimes, however, strict nesting is not what is needed, and the Standard provides the words CS-PICK and CS-ROLL to change the
order of items on the stack. (0 CS-ROLL is a no-op, 1 CS-ROLL is a swap; 0 CS-PICK is a dup, 1 CS-PICK is an over, and so on...). The details of the control stack and the size and format of items is implementation-dependant, but by far the most common choice is to use the data stack, with either one or two items for each item.

In the examples below I shall use CS-PICK and CS-ROLL explicitly, but they are intended to be used for the definition of new control structure words, as in the examples of ELSE and WHILE below:

: ELSE \ orig1 -- orig2
POSTPONE AHEAD \ orig1 orig2
1 CS-ROLL POSTPONE THEN ; \ consumes orig1
IMMEDIATE

: WHILE \ dest -- orig dest
POSTPONE IF 1 CS-ROLL ; IMMEDIATE

Using WHILE
BEGIN ... WHILE is normally resolved by REPEAT, which is another way of saying AGAIN THEN - but could also be closed with

... UNTIL do this on normal exit only THEN ...

and you can use multiple WHILEs within the loop, each resolved by its own THEN outside

BEGIN ... WHILE ... WHILE ... UNTIL ... THEN ... THEN

But you’ll notice that WHILE does not modify the dest on the top of the control stack. In fact, unless your Forth is overly pedantic, any other item will do as well. For example, in:

IF ... WHILE ... THEN ... THEN

(I don't recommend this - each time I look at it I have to work out again what it actually does).

Generally it is best to strictly nest both forward branches and loops, but it is sometimes useful to branch out of a loop (as in the examples above) or into the middle of a loop:

IF BEGIN maybe ignore first time [ 1 CS-ROLL ] THEN the rest of the loop UNTIL

WHILE may equally well be used to exit a DO ... LOOP, but here you need to use UNLOOP to get rid of the redundant loop index.

DO ... WHILE ... LOOP normal exit ELSE early exit UNLOOP THEN

is very messy, even more so with multiple WHILEs. It would be better to place the ELSE clauses within the loop, changing the sense of the test:

DO ... IF early exit UNLOOP ELSE [ 1 CS-ROLL ] ... LOOP normal exit THEN ...

Of course. if the definition ends at THEN, you can simply do an early return

DO ... IF early exit UNLOOP EXIT THEN ... LOOP normal exit

Or if you just want to exit the loop and perform whatever code follows in all cases:

DO ... IF early exit LEAVE THEN ... LOOP all exits

Branching to a Common Start


This can be done using CS-PICK. The simplest example mimics the Java’s continue keyword:

BEGIN ... WHILE ... [ 0 CS-PICK ] REPEAT ... UNTIL

DO ... WHILE ...[ 0 CS-PICK ] REPEAT ... LOOP

may possibly compile too, but is not guaranteed to work, since do-sys may not be identical to dest. In this case, since the loop counter is decremented by LOOP rather than DO, each time the program hits REPEAT it effectively repeats the same iteration.

The same trick can be used within a CASE statement. Suppose you want the same action for a range of values, with one exception:

CASE
exception OF do-exception ENDOF
DUP range WITHIN IF DROP do-range ELSE [ 1 CS-ROLL ]
...
ENDCASE
THEN

On some (perhaps most) Forths this is unnecessary, since OF is the strict equivalent of OVER IF DROP and so can be paired with THEN, or IF paired with ENDOF.

Of course, if you find this uselful, it's better to define and document your own control words:

   : DONE  \ branch out of the current loop (resolved by implicit or explicit THEN

        POSTPONE ELSE 1 CS-ROLL ;  IMMEDIATE

   :  REPRISE  \  return control to nearest BEGIN
 
        0 CS-PICK  POSTPONE REPEAT ;  IMMEDIATE

Thursday, September 15, 2005

All the Standard Words

Somehow this puts me in mind of C K Ogden and his 850 words of Basic English...

Neal Bridges has produced a four-sheet reference for Standard Forth -- all 371 words with their respective Standard stack diagrams. It's in PDF format, and if printed '4-up' makes a pretty handy single-sheet reference (if a bit hard on the eyes).

I haven't seen this anywhere else in so compact a form. It's very useful for reminding yourself what words are in the Standard and how they are spelt.