-
Notifications
You must be signed in to change notification settings - Fork 66
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".
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, after decrementing, the loop counter is greater or equal zero. 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
puts the next code address on the data stack - the compiled pops the loop counter from the data stack and pushes it 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 and exits the loop.
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.
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.
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.