While coding DEFER and friends for Tali Forth, I realized that I had made a fundamental design error with DOES> so that it only worked with simple cases (as in, the ones I initially tested for). I ended up rewriting CREATE, DOVAR, and (DOES>) and DODOES. Since CREATE/DOES is one of the most complex, but also most powerful parts of Forth, here's the walkthrough I created for myself. The basis is Subroutine Threaded Code (STC) variant for the 65c02. You'll probably want to read Brad's backgrounder on CREATE/DOES at
http://www.bradrodriguez.com/papers/moving3.htm for the general idea and terminology.
So we start with the following standard learning example:
Code:
: CONSTANT CREATE , DOES> @ ;
We examine this in three phases or "sequences", based on Derick and Baker (see Brad for details):
SEQUENCE I: Compiling the word CONSTANT
CONSTANT is a "defining word", one that makes new words. In pseudocode, the above compiles to:
Code:
[Header "CONSTANT"]
jsr CREATE
jsr COMMA
jsr (DOES>) ; from DOES>
a: jsr DODOES ; from DOES>
b: jsr FETCH
rts
To make things easier to explain later, we've added the labels "a" and "b" in the listing. Note that DOES> is an immediate word that adds two subroutine jumps, one to (DOES>) and one to DODOES, which is a pre-defined system routine like DOVAR. We'll see what it does later.
SEQUENCE II: Executing the word CONSTANT / creating LIFE
Now, when we execute
Code:
42 CONSTANT LIFE
This pushes the RTS of the calling routine -- call it "main" -- to the 65c02's stack (the Return Stack, as Forth calls it). It now looks something like this:
Code:
[1] RTS to main routine
Without going into detail, the first two subroutine jumps of CONSTANT give us this word:
Code:
[Header "LIFE"]
jsr DOVAR ; in CFA, from LIFE's CREATE
4200 ; in PFA (little-endian)
Next, we JSR to (DOES>). This jump places the RTS address to CONSTANT on the 65c02's stack, the address we had labeled "a".
Code:
[2] RTS to CONSTANT ("a")
[1] RTS to main routine
Now the tricks start. (DOES>) takes this address off the stack and uses it to replace the DOVAR JSR target in the CFA of our freshly created LIFE word. We now have this:
Code:
[Header "LIFE"]
jsr a ; in CFA, modified by (DOES>)
c: 4200 ; in PFA (little-endian)
Note we added a label "c". Now when (DOES>) reaches its own RTS, it finds the RTS to the main routine on its stack. This is Good Thing (TM), because it aborts the execution of the rest of CONSTANT, and we don't want to do DODOES or FETCH now. We're back at the main routine.
SEQUENCE III: Executing LIFE
So now we have whatever main program we're running, and execute LIFE.
Code:
jsr LIFE
The first thing this call does is push the RTS to the main routine on the 65c02's stack:
Code:
[1] RTS to main
The CFA of LIFE executes a JSR to label "a" in CONSTANT. This pushes the RTS of LIFE on the 65c02' stack:
Code:
[2] RTS to LIFE ("c")
[1] RTS to main
This JSR to a lands us at the JSR to DODOES, so the return address to CONSTANT gets pushed on the stack as well. We had given this the label "b". After this, we have three addresses on the 65c02's stack:
Code:
[3] RTS to CONSTANT ("b")
[2] RTS to LIFE ("c")
[1] RTS to main
DODOES pops address b off the 65c02's stack and puts it in a nice safe place in Zero Page, which we'll call "z". More on that in a moment. First, DODOES pops the RTS to LIFE. This in fact is "c", the address of the PFA or LIFE, where we stored the payload of this constant. Basically, DODOES performs a DOVAR here, and pushes "c" on the Data Stack. Now all we have left on the 65c02's stack is the RTS to the main routine.
Code:
[1] RTS to main
This is where "z" comes in, the location in Zero Page where we stored address "b" of CONSTANT. Remember, this is where CONSTANT's own PFA begins, the FETCH command we had originally codes after DOES> in the very first definition. The really clever part: We perform an indirect JMP -- not a JSR! -- to this address.
Code:
jmp (z)
Now CONSTANT's little program is executed, the subroutine jump to FETCH. Since we just put the PFA ("c") on the Data Stack, FETCH replaces this by 42, which is what we were aiming for all along. And since CONSTANT ends with a RTS, we pull the last remaining address off the 65c02's stack, which is the return address to the main routine where we started.
And that is sort of it. Put together, this is what we have to code:
DOES>: Compiles a subroutine jump to (DOES>), compiles a subroutine jump to DODOES.
(DOES>): Pops the stack (address of subroutine jump to DODOES in CONSTANT), increase this by one, replace the original DOVAR jump target in LIFE.
DODOES: Pop stack (CONSTANT's PFA), increase address by one, store on Zero Page; pop stack (LIFE's PFA), increase by one, store on Data Stack; JMP to address we stored in Zero Page.
Remember we have to increase the addresses by one because of the way JSR stores the return address for RTS on the stack on the 65c02: It points to the third byte of the JSR instruction itself, not the actual return address.
Of course, DEFER still doesn't work, but I think that's a completely different problem
.