Skip to content

eForth FOR .. NEXT

Thomas edited this page Feb 25, 2022 · 10 revisions

Discussion of eForth FOR .. NEXT

Among "small" Forth implementations the eForth FOR .. NEXT loop is unique - it's powerful but some of its properties and idioms are difficult to understand. The following discussion deals with the main properties, idioms and limitations of this "eForth-ism".

Basic properties of eForth FOR .. NEXT

A the basic FOR .. NEXT loop accepts a loop count parameter n:

: test-for ( n -- )
   FOR I . NEXT ;

NEXT decrements the loop counter on the return stack (accessible through I or R@) and loops while the counter is positive. 4 test-for prints 4 3 2 1 0, 0 test-for prints 0, and -1 test-for prints -1.

The following FOR .. NEXT properties are noteworthy:

  • at least one iteration will be performed (the loop counter is equal to the input of FOR)
  • the loop count will be "one-too-many" compared to standard loop structures (e.g. DO .. LOOP)
  • the number of iterations is limited to 32768 (from the largest positive integer down to zero)

The implementations of the word FOR and NEXT are very simple: at compile time FOR puts the next code address on the data stack. The compiled code puts the loop counter on the return stack.

An alternative way to use a FOR .. NEXT loop is this:

: alt-for ( n -- )
   >R BEGIN I . NEXT ;

At compile-time the word BEGIN puts the loop address for the NEXT run-time code on the data stack. NEXT pops that address and compiles the runtime code donxt. During runtime, >R (or FOR) puts the loop counter on the return stack. The runtime code donxt decrements the loop counter and jumps to the loop address unless the counter is negative. In that case it removes the loop counter from the return stack.

eForth FOR .. AFT .. THEN .. NEXT

The word AFT is maybe the most unusual feature of eForth:

: test-aft1 ( n -- )
  FOR
    ."  for"     \ first iteration
    AFT
      ."  aft"   \ following iterations
    THEN
    I .          \ all iterations
  NEXT ; 

2 test-aft1 prints for 2 aft 1 aft 0.

Why one would need such a control structure isn't immediately obvious. The eForth core implementation use of AFT shows that it not only works around the "one-too-many" iterations in FOR .. NEXT, but it also provides loop count testing if the actual "loop code" is in the AFT .. THEN block:

: test-aft2 ( n -- )
    FOR AFT
       I .
    THEN NEXT ;

Running 3 test-aft2 prints 2 1 0, and 0 test-aft2 or -1 test-aft2 print nothing at all. This structure is widely used in the eForth core, e.g. in CMOVE, SAME? and TYPE. The effect is that input testing around FOR .. NEXT isn't needed, especially for 0 counts.

The code for AFT on page 42..43 of eForth Overview looks a bit cryptic:

: >MARK ( --A ) HERE 0 , ;
: AHEAD ( --A ) COMPILE branch >MARK ; IMMEDIATE
: AFT ( a --a A ) DROP [COMPILE] AHEAD [COMPILE] BEGIN SWAP ; IMMEDIATE

After the explanation of FOR ... NEXT above the implementation is easy to understand :

  • drop the loop address from FOR (or >R BEGIN)
  • insert an unconditional jump to THEN (which will be executed once)
  • put a new loop address for NEXT on the stack

This example also shows how, in Forth, the compiler can be extended on the fly.

eForth FOR .. WHILE .. NEXT .. ELSE .. THEN

After the introduction above, we'll look at a loop structure in the eForth core that's similar to DO .. LEAVE .. LOOP. It's not the most readable control structure since it's "pure eForth" an idiomatic combination of FOR .. NEXT, WHILE and ELSE .. THEN:

: test-for-while ( limit n -- )
  FOR
    DUP ( limit ) I = NOT WHILE
    I .       \ limit not reached
  NEXT
    ."  end"
  ELSE
    ."  limit"
    R> DROP   \ remove the FOR loop counter - the NEXT runtime couldn't do it
  THEN
  DROP        \ drop limit
;

In this example, 5 10 test-for-while prints 10 9 8 5 6 limit and 5 4 test-for-while prints 4 3 2 1 0 end.

WHILE puts the address of its conditional branch target on the stack (above the loop address from FOR which is used when compiling NEXT). While compilingELSE uses this address to write the "exit address" to the branch target of WHILE. The ELSE clause is responsible for removing the loop counter from the return stack (i.e. it has to be done in the program code). Failing to do that will lead to a crash. A less hacky implementation of this structure would at least require a word for ELSE R> DROP (but it's maybe better to just implement DO .. LEAVE .. +LOOP - that's what STM8 eForth does).

Note that the FOR .. WHILE .. NEXT .. ELSE .. THEN idiom can't be mixed with the FOR .. AFT .. THEN .. NEXT idiom.

Conclusion

Due to the implicit input range checking FOR .. AFT .. THEN .. NEXT is useful for creating minimal Forth systems, even if the code is difficult to understand at first. For improving readability it's maybe better to use the STM8 eForth DO .. LEAVE .. LOOP/+LOOP structure instead of FOR .. NEXT and its variants.

Clone this wiki locally