As promised, here is what GEN does.
I already said GEN was a generator. Given an input of N, it will return the numbers from 0 to N-1; however, the way GEN does this is by placing a number on the data stack and executing the Forth threaded code fragment following GEN in the word where it appears. It runs this threaded code fragment once for each number it returns.
Two more words from M. L. Gassanenko are used in the definition of GEN , the pair PRO and CONT .
Code:
: PRO ( -- )
R> R> >L ENTER L> DROP ;
: CONT ( -- )
L> >R R@ ENTER R> >L ;
M. L. Gassanenko refers to PRO as a prolog-like procedure prologue.
I've previously shown ENTER in this thread; nonetheless, here is the source for ENTER .
Code:
: ENTER ( T-ADR -- ) >R ;
ENTER takes the address of a fragment of threaded code and runs the threaded code fragment at that address. When that threaded code fragment exits, control is returned to the fragment which executed ENTER .
>L and L> are words to move a number to and from what M. L. Gassanenko refers to as a continuation stack.
Here is the source for GEN .
Code:
: GEN ( U1 -- U2 )
PRO 0 ?DO I CONT LOOP ;
And the source for TEST .
Code:
: TEST
5 GEN . ;
PRO pulls two addresses off the return stack. The second address, in this case an address in the body of TEST, is placed on the continuation stack. The first address pulled from the return stack, an address in the body of GEN , is used as a parameter for ENTER to transfer control back to GEN . Control is returned back to PRO when GEN exits. PRO removes and drops the address it previously placed on the continuation stack then exits, transferring control back to the word which called TEST .
In the word GEN , CONT pulls the address off the continuation stack. This address is in TEST just past GEN . It saves a copy of this address on the return stack and transfers control to that portion of TEST which is after GEN . Control is returned to CONT when TEST exits. CONT moves that address from the return stack back to the continuation stack. The threaded code after GEN in the word TEST will run for each number returned by GEN .
Back to the brain teaser.
Code:
: GENTEST ( -- )
2 GEN CR .
4 GEN CR 2 .R
3 GEN CR 3 .R ;
The first occurrence of GEN will return two numbers. It will cause this fragment of Forth to run for each of them.
Code:
CR .
4 GEN CR 2 .R
3 GEN CR 3 .R ;
Each time it runs the next occurrence of GEN will cause this fragment of Forth to run four times.
Code:
CR 2 .R
3 GEN CR 3 .R ;
Each time this fragment runs the last occurrence of GEN will cause this final fragment of Forth from the word GENTEST to run three times.
Code:
CR 3 .R ;
That last fragment runs a total of 24 times each time GENTEST is run.
Words which manipulate return addresses are notoriously difficult to use in a definite loop, where the parameters are kept on the return stack. The pair of words PRO and CONT get around this. PRO gets to the addresses it needs because it is used before any words which place data on the return stack. CONT can get to the address it needs because it is no longer on the return stack.
M. L. Gassanenko gives the example of the word STACK , which non-destructively returns the numbers on the data stack similar to how GEN returns a range of numbers. Many Forth systems have the word .S to non-destructively display the contents of the data stack; however, the stack contents could be displayed as signed or unsigned numbers. This version shows the stack contents as signed numbers:
Code:
: .S STACK . ;
while this version shows the stack contents as unsigned numbers:
Code:
: U.S STACK U. ;
My Forth has an auxiliary stack. I defined the continuation stack words >L and L> as aliases for the auxiliary stack words.
Code:
: >L >A ;
: L> A> ;
In the word CONT
Code:
L> >R R@
is functionally equivalent to
Code:
L> DUP >R
or on my system
Code:
L> DUP>R
so I redefined CONT
Code:
: CONT
L> DUP>R ENTER R> >L ;
I wondered why CONT was defined this way and not simply:
Code:
: CONT
L@ ENTER ;
At first I thought CONT removed the address from the continuation stack to permit something like the word GEN2 .
Code:
: GEN ( U -- )
PRO 0 ?DO I CONT LOOP ;
: GEN2 ( U -- )
PRO GEN 0 ?DO I CONT LOOP ;
However, a word like GEN2 could be defined like this:
Code:
: GEN2 ( U -- )
GEN PRO 0 ?DO I CONT LOOP ;
A word like GEN2 is not even necessary.
Code:
: TESTA ( U -- )
GEN GEN . ;
: TESTB ( U -- )
GEN2 . ;
So, why the lengthier definition for CONT ? Any thoughts on this?