How is Forth LEAVE ... LOOP implemented, since number of LEAVEs is not known beforehand?
In SP-Forth the loop control parameters comprise the index, limit, and address after LOOP. So, no need to resolve LEAVE
at compile time, it knows the address from the loop control parameters at run-time.
Another approach is to store the control-flow stack depth on DO
, place the unresolved forward reference on the control-flow stack under all other already placed values (using the stored depth) on LEAVE
, and then resolve all the placed forward references on LOOP
.
See my high-level implementation of DO LOOP
on the basis of BEGIN UNTIL
and AHEAD THEN
(spoiler warning).
As another approach, you can thread a singly-linked list through the unresolved address "holes". I used this when I implemented counted loops in my FORTH:
( We can now define DO, ?DO, LOOP, +LOOP and LEAVE. It would be much easier
if LEAVE didn't exist, but oh well. Because storing LEAVE's data on the stack
would interfere with other control flow inside the loop, let's store it in a variable. )
VARIABLE LEAVE-PTR
( Let's consider the base case: only one LEAVE in the loop. This can be trivially
handled by storing the address we need to patch in the variable.
This would also work quite well with nested loops. All we need to do is store
the old value of the variable on the stack when opening a loop.
Finally, we can extend this to an arbitrary number of LEAVEs by threading
a singly-linked list through the branch target address holes. )
\ ...
: LEAVE, ( -- )
HERE
LEAVE-PTR @ ,
LEAVE-PTR !
;
: LEAVE POSTPONE BRANCH LEAVE, ; IMMEDIATE
: DO ( -- old-leave-ptr loop-beginning )
LEAVE-PTR @
0 LEAVE-PTR !
POSTPONE 2>R
HERE
; IMMEDIATE
\ SOME-LOOP is the common code between LOOP and +LOOP
: SOME-LOOP ( old-leave-ptr loop-beginning -- )
POSTPONE 0BRANCH ,
LEAVE-PTR @
BEGIN
?DUP
WHILE
DUP @ >R
HERE SWAP !
R>
REPEAT
POSTPONE UNLOOP
LEAVE-PTR !
;