The CO word above defined as a secondary would have very limited usefulness if any though, because your return address gets changed. I have RSWAP defined in my '816 Forth as:
HEADER "RSWAP", NOT_IMMEDIATE ; Swaps the two top return-stack cells.
RSWAP: PRIMITIVE
LDA 1,S
STA N
LDA 3,S
STA 1,S
LDA N
STA 3,S
GO_NEXT
;-------------------
which executes much, much faster and doesn't mess up where it comes back to for the next instruction.
The purpose of CO is to swap the return addresses. It's a co-routine word. If I had RSWAP in my system, I would define CO like this:
If I wanted to make CO a primitive, since Fleet Forth is an ITC Forth, I would have to write it to swap the top of the return stack with IP.
Here are two words used for a trivial example:
' COROUTINE1 >BODY >R COROUTINE2
SECOND COROUTINE!
SECOND COROUTINE!
ROUTINE ONE HERE!
SECOND COROUTINE!
ROUTINE ONE HERE!
SECOND COROUTINE!
ROUTINE ONE HERE!
SECOND COROUTINE!
ROUTINE ONE HERE!
ROUTINES
ROUTINE ONE HERE!
SECOND COROUTINE!
SECOND COROUTINE!
ROUTINE ONE HERE!
SECOND COROUTINE!
ROUTINE ONE HERE!
SECOND COROUTINE!
ROUTINE ONE HERE!
SECOND COROUTINE!
ROUTINE ONE HERE!
A trivial example to be sure.
Here is something useful. Fleet Forth has the word RB ( restore base ). Among other places, it is used in the definition of NUMBER? .
The behavior of a word like RB is fairly straight forward. perform the part before CO right now. Perform the part after CO when this word's caller exits.
Here is a walkthrough of how RB works:
The value in BASE is fetched then the return address of RB's caller is moved from the return stack to the data stack. Since 2>R preserves the order of the values moved to the return stack, after 2>R executes, the value of BASE will be tucked under the return address of RB's caller on the return stack.
When at the start of CO , there are three values of immediate interest on the return stack. The value of BASE , the return address of RB's caller, and the return address of RB .
The original value of BASE saved to the return stack by RB is now the top value of the return stack. It is pulled off the return stack and stored back in BASE . RB exits into the word that called RB's caller.
Another useful place for CO is in the definition of WITH-WORDS , a handy utility word in Fleet Forth. WITH-WORDS works like this:
: NAMEX <DO THIS ONCE> WITH-WORDS <DO THIS ONCE FOR EACH WORD IN THE CONTEXT VOCABULARY> ;
The thread of Forth after WITH-WORDS gets executed once for each word in the context vocabulary. The only values on the data stack when that thread executes are whatever was on the data stack prior to the execution of WITH-WORDS and the NFA of the word for which this thread is executed.
The behavior of a word like RB is fairly straight forward. perform the part before CO right now. Perform the part after CO when this word's caller exits.
That's too cool for school!
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!
I think RB and CO are my two new favorite words.
I've always saved and restored the base myself in words that needed to change it, but that solution is a lot more elegant. I especially enjoy how simple both words are. RB is also, by far, the most practical use of co-routines I have seen.
I tested CO and RB on Durex Forth for the C64. Durex Forth is an STC Forth. Both words needed a slight redefinition because Durex Forth didn't have 2>R and 2R> .
CO in my Forth has been a primitive for some time. Here is an excerpt from my Forth kernel's source. It was built with a metacompiler.
The three sections of code below are presented in the reverse order they appear in the kernel source. I felt this would make the source for CO easier to follow.
CODE CO ( R: ADR1 -- ADR2 )
( IP: ADR2 -- ADR1 )
PLA TAY PLA
(CO) JMP END-CODE
LABEL (CO) ( IN THE BODY OF >FORTH)
N STA
IP 1+ LDA PHA
IP LDA PHA
N LDA IP STY
' EXIT @ 4 + JMP ( THIS JMP)
( JUMPS HERE)
IP 1+ STA
( FALLS INTO NEXT)
The following hand translation is provided for those not familiar or comfortable with RPN assembly.
CODE CO ( R: ADR1 -- ADR2 )
( IP: ADR2 -- ADR1 )
PLA
TAY
PLA
JMP (CO)
END-CODE
LABEL (CO) ( IN THE BODY OF >FORTH)
STA N
LDA IP+1
PHA
LDA IP
PHA
LDA N
STY IP
JMP EXIT4 ( THIS JMP)
( JUMPS HERE)
LABEL EXIT4
STA IP+1
( FALLS INTO NEXT)
As I mentioned previously, CO swaps IP with the top of the return stack.
By taking advantage of what was already in another primitive, the SUBRoutine >FORTH , the primitive version of CO is smaller than the high level version.
: FH \ ( offset -- offset-block ) "from here"
BLK @ ?DUP 0= IF SCR @ THEN + ;
When used in a block which is loading, FH will add the block number in BLK to the number on the data stack. This allows relative loading.
Here is an example from my system loader.
This screen of source is on block 2. When the phrase 2 FH LOAD is interpreted, the screen on block 4 is loaded.
When not loading (BLK is zero), FH adds the screen number in SCR to the number on the stack. When editing this same screen on block 2, typing 3 FH LIST will list the screen with the source for THRU , --> , <-- and ;S on block 5.