Skip to content

eForth FOR .. NEXT

Thomas edited this page Sep 16, 2023 · 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 properties, idioms and limitations of this "eForth-ism".

Basic properties of eForth FOR .. NEXT

The basic FOR .. NEXT loop accepts parameter n as the loop count:

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

NEXT decrements the loop counter on the return stack (accessible through I or R@) and loops as long as the loop counter is greater or equal zero after decrementing it. 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 with standard loop structures (e.g. DO .. LOOP)
  • the number of iterations is limited to 32768 (from the largest positive integer down to zero)

The implementation of the words FOR and NEXT is very simple:

  • at compile time FOR compiles >R and puts the next code address on the data stack
  • at run-time >R pops the loop counter from the data stack and pushes it on the return stack

An alternative way to implement the FOR .. NEXT loop in the first example 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 and exits the loop.

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 way the eForth core implementation uses AFT shows that it not only works around the "one-too-many" iterations in FOR .. NEXT, but it also provides loop count testing provided that the actual "loop body" 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 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. Alternatively, in STM8 eForth, the DO .. LEAVE .. LOOP/+LOOP structure can be used for improving readability.