The basic counted loop is:
limit start ?DO body LOOP
This performs one iteration for every integer, starting from start
and up to, but excluding limit. The counter, or index, can be
accessed with i
. For example, the loop:
10 0 ?DO i . LOOP
prints 0 1 2 3 4 5 6 7 8 9
The index of the innermost loop can be accessed with i
, the index
of the next loop with j
, and the index of the third loop with
k
.
You can access the limit of the innermost loop with i'
and
i'
-i
with delta-i
. E.g., running
: foo 7 5 ?do cr i . i' . delta-i . loop ;
prints
5 7 2 6 7 1
The loop control data are kept on the return stack, so there are some restrictions on mixing return stack accesses and counted loop words. In particuler, if you put values on the return stack outside the loop, you cannot read them inside the loop14. If you put values on the return stack within a loop, you have to remove them before the end of the loop and before accessing the index of the loop.
There are several variations on the counted loop:
LEAVE
leaves the innermost counted loop immediately; execution
continues after the associated LOOP
or NEXT
. For example:
10 0 ?DO i DUP . 3 = IF LEAVE THEN LOOP
prints 0 1 2 3
UNLOOP
prepares for an abnormal loop exit, e.g., via
EXIT
. UNLOOP
removes the loop control parameters from the
return stack so EXIT
can get to its return address. For example:
: demo 10 0 ?DO i DUP . 3 = IF UNLOOP EXIT THEN LOOP ." Done" ;
prints 0 1 2 3
?DO
loop is entered
(and LOOP
iterates until they become equal by wrap-around
arithmetic). This behaviour is usually not what you want. Therefore,
Gforth offers +DO
and U+DO
(as replacements for
?DO
), which do not enter the loop if start is greater than
limit; +DO
is for signed loop parameters, U+DO
for
unsigned loop parameters.
?DO
can be replaced by DO
. DO
always enters
the loop, independent of the loop parameters. Do not use DO
, even
if you know that the loop is entered in any case. Such knowledge tends
to become invalid during maintenance of a program, and then the
DO
will make trouble.
LOOP
can be replaced with n +LOOP
; this updates the
index by n instead of by 1. The loop is terminated when the border
between limit-1 and limit is crossed. E.g.:
4 0 +DO i . 2 +LOOP
prints 0 2
4 1 +DO i . 2 +LOOP
prints 1 3
n +LOOP
is peculiar when n is negative:
-1 0 ?DO i . -1 +LOOP
prints 0 -1
0 0 ?DO i . -1 +LOOP
prints nothing.
We recommend not combining ?DO
with +LOOP
. Gforth offers
several alternatives:
If you want -1 +LOOP
’s behaviour of including an iteration
where I
=limit, start the loop with -[DO
or U-[DO
(where the [
is inspired by the mathematical notation for
inclusive ranges, e.g., [1,n]):
-1 0 -[DO i . -1 +LOOP
prints 0 -1
.
0 0 -[DO i . -1 +LOOP
prints 0
.
0 -1 -[DO i . -1 +LOOP
prints nothing.
If you want to exclude the limit, you instead use 1 -LOOP
(or
generally u -LOOP
) and start the loop with ?DO
,
-DO
or U-DO
. -LOOP
terminates the loop when the
border between limit+1 and limit is crossed. E.g.:
-2 0 -DO i . 1 -LOOP
prints 0 -1
-1 0 -DO i . 1 -LOOP
prints 0
0 0 -DO i . 1 -LOOP
prints nothing.
Unfortunately, +DO
, U+DO
, -DO
, U-DO
and
-LOOP
are not defined in Standard Forth. However, an implementation
for these words that uses only standard words is provided in
compat/loops.fs.
bounds
for getting there from the
addr ubytes representation, so you can write a forward loop
through a cell array v
as:
create v 1 , 3 , 7 , : foo v 3 cells bounds U+DO i . cell +LOOP ; foo
which prints 1 3 7
. Preprocessing the inputs for walking
backwards is more involved, so Gforth provide a loop construct of the
form MEM-DO
...LOOP
that does it for you: It takes an
array in addr ubytes representation and the element size, and
iterates over the addresses of the elements in backwards order:
create v 1 , 3 , 7 , : foo1 v 3 cell array>mem MEM-DO i . LOOP ; foo1
This prints 7 3 1
. ARRAY>MEM
converts the addr
uelems uelemsize representation into the addr ubytes uelemsize
representation expected by MEM-DO
. This loop is finished with
LOOP
which decrements by uelemsize when it finishes a
MEM-DO
.
Gforth also adds MEM+DO
for completeness. It takes the same
parameters as MEM-DO
, but walks forwards through the array:
create v 1 , 3 , 7 , : foo2 v 3 cell array>mem MEM+DO i . LOOP ; foo2
prints 1 3 7
.
n FOR body NEXT
This is the preferred loop of native code compiler writers who are too
lazy to optimize ?DO
loops properly. This loop structure is not
defined in Standard Forth. In Gforth, this loop iterates n+1 times;
i
produces values starting with n and ending with 0. Other
Forth systems may behave differently, even if they support FOR
loops. To avoid problems, don’t use FOR
loops.
The counted-loop words are:
?DO
( compilation – do-sys ; run-time w1 w2 – | loop-sys ) core-ext “question-do”
See Counted Loops.
+DO
( compilation – do-sys ; run-time n1 n2 – | loop-sys ) gforth-0.2 “plus-do”
See Counted Loops.
U+DO
( compilation – do-sys ; run-time u1 u2 – | loop-sys ) gforth-0.2 “u-plus-do”
See Counted Loops.
bounds
( u1 u2 – u3 u1 ) gforth-0.2 “bounds”
Given a memory block represented by starting address addr and length u in aus, produce the end address addr+u and the start address in the right order for u+do
or ?do
.
-[do
( compilation – do-sys ; run-time n1 n2 – | loop-sys ) gforth-experimental “minus-bracket-do”
Start of a counted loop with negative stride; Skips the loop if
n2<n1; such a counted loop ends with +loop
where
the increment is negative; it runs as long as I
>=n1.
u-[do
( compilation – do-sys ; run-time u1 u2 – | loop-sys ) gforth-experimental “u-minus-bracket-do”
Start of a counted loop with negative stride; Skips the loop if
u2<u1; such a counted loop ends with +loop
where
the increment is negative; it runs as long as I
>=u1.
-DO
( compilation – do-sys ; run-time n1 n2 – | loop-sys ) gforth-0.2 “minus-do”
See Counted Loops.
U-DO
( compilation – do-sys ; run-time u1 u2 – | loop-sys ) gforth-0.2 “u-minus-do”
See Counted Loops.
array>mem
( uelements uelemsize – ubytes uelemsize ) gforth-experimental “array>mem”
ubytes=uelements*uelemsize
mem+do
( compilation – w xt do-sys; run-time addr ubytes +nstride – ) gforth-experimental “mem-plus-do”
Starts a counted loop that starts with I
as addr and
then steps upwards through memory with nstride wide steps
as long as I
<addr+ubytes. Must be finished with
loop.
mem-do
( compilation – w xt do-sys; run-time addr ubytes +nstride – ) gforth-experimental “mem-minus-do”
Starts a counted loop that starts with I
as
addr+ubytes-ustride and then steps backwards
through memory with -nstride wide steps as long as
I
>=addr. Must be finished with loop.
DO
( compilation – do-sys ; run-time w1 w2 – loop-sys ) core “DO”
See Counted Loops.
FOR
( compilation – do-sys ; run-time u – loop-sys ) gforth-0.2 “FOR”
See Counted Loops.
LOOP
( compilation do-sys – ; run-time loop-sys1 – | loop-sys2 ) core “LOOP”
See Counted Loops.
+LOOP
( compilation do-sys – ; run-time loop-sys1 n – | loop-sys2 ) core “plus-loop”
See Counted Loops.
-LOOP
( compilation do-sys – ; run-time loop-sys1 u – | loop-sys2 ) gforth-0.2 “minus-loop”
See Counted Loops.
NEXT
( compilation do-sys – ; run-time loop-sys1 – | loop-sys2 ) gforth-0.2 “NEXT”
See Counted Loops.
i
( R:n – R:n n ) core “i”
n is the index of the innermost counted loop.
j
( R:n R:w1 R:w2 – n R:n R:w1 R:w2 ) core “j”
n is the index of the next-to-innermost counted loop.
k
( R:n R:w1 R:w2 R:w3 R:w4 – n R:n R:w1 R:w2 R:w3 R:w4 ) gforth-0.3 “k”
n is the index of the third-innermost counted loop.
i'
( R:w R:w2 – R:w R:w2 w ) gforth-0.2 “i-tick”
The limit of the innermost counted loop
delta-i
( r:ulimit r:u – r:ulimit r:u u2 ) gforth-1.0 “delta-i”
u2=I'
-I
(difference between limit and index).
LEAVE
( compilation – ; run-time loop-sys – ) core “LEAVE”
See Counted Loops.
?LEAVE
( compilation – ; run-time f | f loop-sys – ) gforth-0.2 “question-leave”
See Counted Loops.
unloop
( R:w1 R:w2 – ) core “unloop”
DONE
( compilation do-sys – ; run-time – ) gforth-0.2 “DONE”
resolves all LEAVEs up to the do-sys
The standard does not allow using CS-PICK
and CS-ROLL
on
do-sys. Gforth allows it, except for the do-sys produced by
MEM+DO
and MEM-DO
, but it’s your job to ensure that for
every ?DO
etc. there is exactly one UNLOOP
on any path
through the definition (LOOP
etc. compile an UNLOOP
on
the fall-through path). Also, you have to ensure that all
LEAVE
s are resolved (by using one of the loop-ending words or
DONE
).