The coolest words
The coolest words
One of the words basically created a string constant using a colon definition.
S" has got to be the coolest way of storing and retrieving a string constant I have seen. Sure beats the heck out of what I was doing before.
I have seen examples where a word definition used .", but then to get to the start of the string, a value was added to skip past the rest of the header. ." already showed how to prevent a string from being compiled, all I had to do was expand upon that idea. Works great.
Any other cool words or routines anyone wants to share? Just a general description of what it does and how it benefits over regular ways is all that is needed. It doesn't have to be a part of any standard as it can be converted on this end.
Already have these words:
DOER/MAKE
IS
RECURSE
WHERES?
CASE - compact version that does not use OF, ENDOF, ENDCASE
.PHRASE - prints a paragraph of text using word wrap and delimited by a return mark.
SEE/DECODE/DECOMPILE
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: The coolest words
You can make it more efficient with of being a primitive. This is from my '816 Forth assembly-language source code:
Code: Select all
HEADER "of", NOT_IMMEDIATE ; ( n1 n2 -- ) if n1 = n2
_of: PRIMITIVE ; ( n1 n2 -- n1 ) if n1 <> n2
LDA 0,X ; Take top stack item,
CMP 2,X ; compare to next stack item, & if
BNE ofend ; not equal, get ready to try next OF.
INX4 ; Otherwise, drop the top two stack items,
; skip the branch address, and execute what's
JMP bump ; inside the OF, until you get to ENDOF.
ofend: INX2 ; If there wasn't a match, drop the top stack
JMP branch+2 ; item and go on to check the next OF.
;-------------------The following is from the "shadow screen" file (which is really just another text file, but instead of having the Forth assembly-language source code, it has more explanations and what the Forth source would look like if I were compiling a Forth from a metacompiler.
Code: Select all
The structure:
CASE \ the word CASE compiles nothing
n1 OF actions1 ENDOF
n2 OF actions2 ENDOF
n3 OF actions3 ENDOF
(default actions)
ENDCASE \ ENDCASE only compiles a DROP and the
\ address needed at each ENDOF
can be compiled without the internal " of ", but it becomes far less efficient,
both memory- and speedwise. It would be as if you wrote:
n1 OVER = IF DROP actions1 ELSE
n2 OVER = IF DROP actions2 ELSE
n3 OVER = IF DROP actions3 ELSE
(default actions)
THEN THEN THEN
Paranthetical note: Default actions don't have to be all at the end. For
example, you could have thee comparisons, and if none of those are met, do
some other processing before continuing on to the fourth comparison. This can
be done at various places along the CASE structure, as many as you want.
Using the " of " internal, each line saves three cells (six bytes), in addition
to running faster. Clearly, it doesn't take much to justify having the " of "
primitive. OF and IF both compile an internal ( of and 0branch ) plus a
branch address. Both ENDOF and ELSE compile the internal " branch ", plus the
branch address. The OVER , = , and DROP on every line are extra. The book
Thinking Forth, on page 239, says the CASE statement offers no efficiency
improvement in either execution speed or memory used. This is clearly false
when you have the internal " of ". The book is not very favorable to the CASE
structure, but the X3J14 commitee thought highly enough of it to put it in the
ANS standard Forth!
\ CASE below just puts the structure-check number (6) on the stack at compile
\ time so OF and ENDCASE could check for illegal constructs, and an initialized
\ number (0) of how many branch addresses ENDCASE will have to fill in at the
\ ENDOFs. CASE does not actually compile anything.
Not shown here:
of (internal)
: CASE ?COMP \ (compile only) TF239-240 ANS_CORE_EXT
6 0 ; IMMEDIATE
\ OF below compiles the internal of , which compares the two numbers on the top
\ of the stack. If the two are equal, it drops them, and increments the IP to
\ the first cell after the two-cell OF instruction. The second cell of this OF
\ instruction is the address of the next OF statement , which the IP would be
\ set to if the match failed. If the match does fail, of would only drop the
\ very top stack cell so the next of could continue with the next value check.
: OF ?COMP \ (compile only) ANS_CORE_EXT
DUP >R PICK 6 ?PAIRS COMPILE of \ Related: SET_OF RANGE_OF
HERE 0 , R> 1+ 7 ; IMMEDIATE
\ After making sure it's in the right kind of structure (by using ?PAIRS), ENDOF
\ below compiles the branch to the first cell after the ENDCASE. Actually, it
\ only compiles the branch internal, then leaves room for the address to be
\ compiled by ENDCASE later on, when that address is known. So that ENDCASE
\ will know where to put the address of the cell following it, the address
\ following the ENDOF is left on the stack, and n, the number of addresses left
\ by ENDOFs in the structure, is incremented by each "of" so ENDCASE will know
\ how many cells on the stack at compile time are addresses that it needs to
\ fill in. ENDOF also stores the address of the following cell in the last
\ previous of (compiled by OF ) so that if the test fails at run time, that of
\ will send the IP to the right place to check the next possibility.
( 6 [...] of's_branch_operand_adr n 7 -- 6 [...] ENDOF's_branch_adr n )
: ENDOF ?COMP \ (compile only) ANS_CORE_EXT
7 ?PAIRS \ OF's compiler security # is 7. CASE's is 6.
>R \ Hold n, the number of previous ENDOFs.
COMPILE branch HERE 0 , \ Get branch's operand adr, & allot its field.
>R \ Hold ENDOF's branch operand adr.
HERE SWAP ! \ Fill in of's operand to point to next inst.
R>
R> ; IMMEDIATE
\ I would like to call them END_OF and END_CASE, but ANS and earlier Forths call
\ them ENDOF and ENDCASE without the _ character. Since these are so common, I
\ won't change them.
<snip>
\ ENDCASE below first reaches back in the stack for the case structure number,
\ which is 6 in this case. The ROLL reaches back behind all the addresses put
\ on the stack by all the ENDOFs that preceded the ENDCASE, and ?PAIRS makes
\ sure ENDCASE goes in the right kind of structure. DROP is compiled to drop
\ the number that would have failed all the OF comparisons (at run time) if it
\ actually got to the ENDCASE instead of being routed around it by an ENDOF at
\ the end of one of the normal statements. (Remember that if encountered,
\ ENDCASE wants to drop a cell off the stack even if it's after some "else"-type
\ code after the last ENDOF. For this reason, you might need to put an
\ otherwise-unnecessary DUP in that part of the code. Sometimes DUMMYCELL,
\ which just puts a 0 on the stack, is the more readable way to show what's
\ happening in that situation.)
\ Next in the compilation process, the ?DO...LOOP puts the address of the cell
\ following the ENDCASE after each of the ENDOFs in the structure so the IP can
\ skip the rest of the CASE statement after executing an ENDOF.
: ENDCASE ?COMP \ (compile only) ( casecode adr...adr n -- )
DUP 1+ ROLL 6 ?PAIRS \ ANS_CORE_EXT
COMPILE DROP
0 ?DO HERE SWAP ! LOOP ; IMMEDIATEThen I also have SET_OF and RANGE_OF. The internal set_of (compiled by SET_OF) is a primitive, but I have not written range_of (compiled by RANGE_OF) as a primitive.
Code: Select all
HEADER "set_of", NOT_IMMEDIATE
_set_of: PRIMITIVE
LDA 0,X
STA N+2 ; Store in N+2 the number of cells to compare, and
INX2 ; remove that number from the stack.
STZ N ; Zero N & N+1. Two bytes get zeroed, which is ok.
; Non-0 will get put in N later if we find a match.
TXY ; We can't do LDA (N+2),X, so get ready to use Y.
LDA (N+2),Y ; Get the number for SET_OF to compare to.
LSR N+2 ; Number of comparisons = number of bytes divided by 2.
LDY N+2 ; Set up to loop enough times to compare all the numbrs.
s_o1: CMP 0,X ; Compare the next number on the stack.
BNE s_o2 ; If there's no match, skip putting something in N.
STX N ; (N got zeroed before this loop.)
s_o2: INX2 ; Drop the top of stack to be ready for next comparison.
DEY ; Decrement the number of comparisons left to be done.
BNE s_o1 ; If there's one or more left, loop back again.
LDY N
BEQlong branch+2 ; If there was no match, skip past ENDOF for next OF .
INX2 ; If match was found, drop the input number, and
JMP bump ; bump the IP past the address cell following set_of.
;-------------------The "shadow screen" file portion for it is as follows:
Code: Select all
\ { and } below are primarily for use with SET_OF . You can start a series of
\ stack entries with {, and end it with }. The final } puts a number on TOS
\ telling how many items got added to the stack, not including itself, since
\ the {. This way, code can appropriately handle an arbitrary number of stack
\ entries. { and } have been valuable for other uses besides with SET_OF too.
\ The idea for SET_OF came from FD May/Jun '97, but I modified it (including the
\ SET[ to { and ]SET-OF to } SET_OF ) to be of wider usefulness, making the {
\ and } useful anywhere you want to correctly handle an undertermined number of
\ inputs on the stack, whether compiling, interpreting, or executing.
: { ( -- ) R> DEPTH >R >R ; \ R>...>R preserve the return
: } ( -- n ) R> DEPTH R> - SWAP >R ; \ adr's position in return stk.
\ Actually, it looks like { and } would only be one byte longer as primitives!
\ The idea for SET_OF below is from FD, May/Jun '97. It's nice for situations
\ where you want something like
DUP 14 =
OVER 15 = OR
OVER 18 = OR
OVER 20 = OR
OVER 25 = OR
OVER 65 = OR
IF bla bla bla ELSE ...
\ but in a CASE statement. Normally in a case statement, you'd have to test for
\ each of these numbers, and then have the same code between the OF and ENDOF
\ that follows each. You can't give OF more than one number to compare to, so
\ SET_OF takes care of that. The situation above now all fits on the first OF
\ line of the example
CASE
{ 14 15 18 20 25 65 } SET_OF bla bla bla ENDOF
OF ENDOF
OF ENDOF
if-no-match-then-do-this-other-stuff
ENDCASE
\ SET_OF allows a type of OF that can compare to several possible values that
\ can result in the same action. The values can be literals, or they can be
\ fetched or calculated between the { and the } . set_of is the internal
\ compiled by SET_OF.
\ Notice that { and } are not limited to have only literals between them. You
\ can just as easily have some of the values calculated on the fly, as long as
\ you keep in mind that the number of comparisons that will be made by SET_OF
\ will be the difference between the stack depth immediately before } minus the
\ stack depth at { . You cannot, for example, remove a previously existing cell
\ from the stack to calculate one of the comparison values unless you
\ specifically take action to correct the resulting stack-depth discrepancy.
: set_of \ Syn: { 13 16 20 24 } SET_OF ... ENDOF (internal)
DUP 1+ PICK \ Copy the argument that went into CASE
\ ^ arg T1 T2 ... Tn n arg
FALSE ROT 0 \ ^ arg T1 T2 ... Tn arg FALSE n 0
DO \ 1st time into loop: ^ arg T1 T2 ... Tn arg FALSE
OVER 3ROLL = OR
LOOP \ After last loop: ^ arg arg flag
?EXIT NOT ; \ If flag was false, change one copy
\ of arg so they don't match.
\ SET_OF works very much like OF . It does not make sure you preceded it with
\ { ... } . I may decide to add that to the compiler security in the future.
: SET_OF ( CASECODE n -- CASECODE ADR n+1 OFCODE ) \ (FD May/Jun '97)
?COMP \ Related: RANGE_OF
>R R@ PICK 6 ?PAIRS
COMPILE set_of COMPILE of
HERE 0 ,
R> 1+ 7 ; IMMEDIATE
\ Then in FD Jan/Feb '98, someone suggested RANGE_OF to add to SET_OF above. It
\ lets you compare to a range of values. Like BETWEEN in ANS Forth, the range
\ includes the limit numbers. range_of is the internal compiled by RANGE_OF .
\ Similar to SET_OF , it doesn't matter where the input limits for RANGE_OF come
\ from. OF , SET_OF , and RANGE_OF can be mixed in the same CASE statement.
: range_of ( Syn: 14 20 RANGE_OF ... ENDOF ) \ (internal)
2PICK -ROT RANGE
IF DUP EXIT
THEN DUP NOT ;
: RANGE_OF ( CASECODE n -- CASECODE ADR n+1 OFCODE ) \ (FD Jan/Feb '98)
?COMP \ Related: SET_OF
>R R@ PICK 6 ?PAIRS
COMPILE range_of
COMPILE of
HERE 0 ,
R> 1+ 7 ; IMMEDIATEI've used SET_OF more than RANGE_OF, but neither of them very much. You'll definitely want of and the other CASE-statement words though.
I might add more good stuff later.
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
Re: The coolest words
One of the words basically created a string constant using a colon definition.
S" has got to be the coolest way of storing and retrieving a string constant I have seen. Sure beats the heck out of what I was doing before.
I have seen examples where a word definition used .", but then to get to the start of the string, a value was added to skip past the rest of the header. ." already showed how to prevent a string from being compiled, all I had to do was expand upon that idea. Works great.
Any other cool words or routines anyone wants to share? Just a general description of what it does and how it benefits over regular ways is all that is needed. It doesn't have to be a part of any standard as it can be converted on this end.
Fleet forth doesn't have S" . It has the word " to compile a counted string. It can be used to compile an inline string like so:
Code: Select all
: HEXAPUMA " HMS HEXAPUMA ROYAL MANTICORAN NAVY." ; OK
CR HEXAPUMA COUNT TYPE
HMS HEXAPUMA ROYAL MANTICORAN NAVY. OK
or save a string in the PAD when interpreting.
Code: Select all
" NOW IS THE WINTER OF OUR DISCONTENT" OK
.S 22484 OK
CR COUNT TYPE
NOW IS THE WINTER OF OUR DISCONTENT OK
PAD COUNT CR TYPE
NOW IS THE WINTER OF OUR DISCONTENT OK
CONSOLE
Speaking of cool words, I think COMPILE and [COMPILE] are better than POSTPONE .
Fleet Forth has a word, DOS that takes a counted string and sends it to the currently selected disk drive as a disk command. The word DOS" sends an inline string as a command or it can compile an inline disk command.
Code: Select all
: DOS"
[COMPILE] "
STATE @
IF
COMPILE
THEN
DOS ; IMMEDIATE
I don't see how this could be done as simply with POSTPONE .
How about coroutines?
Code: Select all
// COROUTINES
: CO 2R> SWAP 2>R ;
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: The coolest words
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
Re: The coolest words
Fleet Forth has a word, DOS that takes a counted string and sends it to the currently selected disk drive as a disk command. The word DOS" sends an inline string as a command or it can compile an inline disk command.
Code: Select all
: DOS"
[COMPILE] "
STATE @
IF
COMPILE
THEN
DOS ; IMMEDIATE
But I read somewhere that POSTPONE can do both. The only difference is the COMPILE works on non-IMMEDIATE words and [COMPILE] works on IMMEDIATE words. THEN follows your COMPILE example above, which gets compiled at run-time. I don't follow this reasoning.
Code: Select all
// COROUTINES
: CO 2R> SWAP 2>R ;
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: The coolest words
Code: Select all
// COROUTINES
: CO 2R> SWAP 2>R ;
Code: Select all
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
;-------------------Edit: BarryM pointed out that I could make it four bytes shorter and four cycles faster the following way. I don't know why I didn't do it that way to start. In the '02 Forth, Y has a special use; but it wouldn't matter on the '816.
Code: Select all
HEADER "RSWAP", NOT_IMMEDIATE ; Swaps the two top return-stack cells.
RSWAP: PRIMITIVE
INDEX_16
PLA
PLY
PHA
PHY
INDEX_8
GO_NEXT
;-------------------A lot of others were even more trivial to implement, like merging I and @ (where the loop index is intentionally an address) into I@ like this:
Code: Select all
HEADER "I@", NOT_IMMEDIATE ; Same as I @
Ifetch: PRIMITIVE
LDY #<0
LDA (1,S),Y
JMP PUSH
;-------------------Code: Select all
HEADER "I+", NOT_IMMEDIATE ; Same as I +
I_add: PRIMITIVE
LDA 1,S
CLC
ADC 0,X
JMP PUT
;-------------------(These are written for the 65816 which is much shorter than for the 6502, and I realize most readers are using the '02 instead; but this makes it easier to see what's going on, since there aren't a lot of lines for handling 16-bit quantities 8 bits at a time.)
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
Re: The coolest words
Someone had re-defined the semi-colon word and added a TRACE function. The .S function was introduced and as each word exits, it prints to the screen either: (The depth of the stack plus any values left on the stack), " Stack is Empty" or "Stack Underflow".
All automatic so one doesn't have to type the word to execute, followed by .S every time.
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: The coolest words
Someone had re-defined the semi-colon word and added a TRACE function. The .S function was introduced and as each word exits, it prints to the screen either: (The depth of the stack plus any values left on the stack), " Stack is Empty" or "Stack Underflow".
All automatic so one doesn't have to type the word to execute, followed by .S every time.
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
Re: The coolest words
Speaking of cool words, I think COMPILE and [COMPILE] are better than POSTPONE .
Fleet Forth has a word, DOS that takes a counted string and sends it to the currently selected disk drive as a disk command. The word DOS" sends an inline string as a command or it can compile an inline disk command.
Code: Select all
: DOS"
[COMPILE] "
STATE @
IF
COMPILE
THEN
DOS ; IMMEDIATE
I don't see how this could be done as simply with POSTPONE .
My definition of POSTPONE is just [COMPILE] [COMPILE] ; IMMEDIATE which basically just takes the place of [COMPILE].
But I read somewhere that POSTPONE can do both. The only difference is the COMPILE works on non-IMMEDIATE words and [COMPILE] works on IMMEDIATE words. THEN follows your COMPILE example above, which gets compiled at run-time. I don't follow this reasoning.
Actually, [COMPILE] will compile anything, immediate or not. Here is the source in my Forth:
Code: Select all
: [COMPILE] ( -- )
?COMP ' , ; IMMEDIATE
?COMP aborts with an error message if STATE is not compiling. [COMPILE] parses the following word in the text stream ( with ' ) and compiles the parsed word. It does not matter if the parsed word is immediate or not.
COMPILE , on the other hand, is not even an immediate word. Here is the definition in my Forth:
Code: Select all
: COMPILE
?COMP R> DUP 2+ >R @ , ;
Here is the definition of IF
Code: Select all
: IF ( -- >SYS )
COMPILE ?BRANCH
>MARK ; IMMEDIATE
And this is what gets compiled:
Code: Select all
SEE IF
IF IMMEDIATE
2D23 1F68 COMPILE
2D25 CE6 ?BRANCH
2D27 2CC4 >MARK
2D29 B6F EXIT
8
OK
COMPILE does not do anything when IF is compiled. When IF is executed ( while defining a new word ), it compiles the address of ?BRANCH into the word being defined. Lets call this word the 'new definition'.
Here is the source for WHILE in my Forth:
Code: Select all
: WHILE ( ?SYS -- >SYS ?SYS )
[COMPILE] IF 2SWAP ; IMMEDIATE
and what it looks like:
Code: Select all
SEE WHILE
WHILE IMMEDIATE
2D55 2D21 IF IMMEDIATE
2D57 1739 2SWAP
2D59 B6F EXIT
6
OK
Now let's take a closer look at my tricky use of COMPILE and branching.
Code: Select all
: DOS"
[COMPILE] "
STATE @
IF
COMPILE
THEN
DOS ; IMMEDIATE
Since the word " is immediate, [COMPILE] is used to compile it. IF and THEN compile a conditional branch around the word COMPILE . This is what DOS" looks like:
Code: Select all
SEE DOS"
DOS" IMMEDIATE
429E 35D7 " IMMEDIATE
42A0 84D STATE
42A2 12BF @
42A4 CE6 ?BRANCH 42AA
42A8 1F68 COMPILE
42AA 2919 DOS
42AC B6F EXIT
10
OK
If interpreting, " parses the string in the text stream up to a delimiting " character and stores it as a counted string at PAD then leaves the address of PAD on the data stack.
STATE @ leaves a zero on the stack so ?BRANCH branches around COMPILE and DOS is executed.
If compiling, " compiles (") into the new definition and parses the string as before. This time the string is compiled into the new definition as an inline string.
STATE @ leaves a -1 ( TRUE ) on the data stack so ?BRANCH does not branch over COMPILE . COMPILE is executed and compiles DOS into the new definition and advances IP past DOS . Suppose I wanted a word to reset the current disk drive so I would not have to type this:
Code: Select all
DOS" UJ"
It doesn't save much typing, but it's just an example. Here is how it would be defined:
Code: Select all
: DRESET
DOS" UJ" ;
And here is what it would look like to my decompiler:
Code: Select all
SEE DRESET
DRESET
5760 1A8C (") UJ
5765 2919 DOS
5767 B6F EXIT
9
OK
As I understand it, POSTPONE tries to encompase the behavior of both COMPILE and [COMPILE] . It tests to see if the following word , the parsed word, is immediate. If it is then the parsed word is compiled into the new definition. If the following word is not immediate then POSTPONE compiles something like COMPILE and the parsed word.
Here is a definition for POSTPONE that I threw together for demonstration purposes:
Code: Select all
: POSTPONE
' DUP >NAME C@ $40 AND 0=
IF
COMPILE COMPILE
THEN
, ; IMMEDIATE
and some sample useage:
Code: Select all
: IF2
POSTPONE IF ; IMMEDIATE
Code: Select all
SEE IF2
IF2 IMMEDIATE
5773 2D21 IF IMMEDIATE
5775 B6F EXIT
4
OK
Code: Select all
: IF3
POSTPONE ?BRANCH >MARK ; IMMEDIATE
Code: Select all
SEE IF3
IF3 IMMEDIATE
5781 1F68 COMPILE
5783 CE6 ?BRANCH
5785 2CC4 >MARK
5787 B6F EXIT
8
OK
There is another reason I don't like POSTPONE . My assembler's control flow words are immediate, but they don't have to be since assembly occurs during interpretation. Here is the source for the assembler's WHILE
Code: Select all
: WHILE ( A1 C1 -- A2 C2 A1 C1 )
[COMPILE] IF 2SWAP ; IMMEDIATE
That [COMPILE] in the definition will have the same effect independent of the immediacy of the assembler's IF . The assembler's IF gets compiled either way.
Suppose I had POSTPONE in my Forth permanently and used it in place of [COMPILE] and COMPILE.
The source for the assembler's WHILE would be:
Code: Select all
: WHILE ( A1 C1 -- A2 C2 A1 C1 )
POSTPONE IF 2SWAP ; IMMEDIATE
If the assembler's control flow words were not immediate, the assembler's WHILE would have to be rewritten to remove POSTPONE and it isn't the only word that would need rewritten.
Re: The coolest words
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:
Code: Select all
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
;-------------------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:
Code: Select all
: CO
RSWAP ;
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:
Code: Select all
: COROUTINE1
BEGIN
CR ." ROUTINE ONE HERE!"
CO DONE?
UNTIL ;
: COROUTINE2
BEGIN
CR ." SECOND COROUTINE!"
CO DONE?
UNTIL ;
And a portion of the system log showing their execution:
Code: Select all
' 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!
or they can be launched another way:
Code: Select all
: ROUTINES
COROUTINE1 COROUTINE2 ;
And a sample run:
Code: Select all
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? .
Code: Select all
: RB ( -- )
BASE @ R> 2>R CO
R> BASE ! ;
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:
Code: Select all
BASE @ R> 2>R
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.
Code: Select all
: CO 2R> SWAP 2>R ;
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 .
Code: Select all
2R> SWAP 2>R ;
CO swaps the two return addresses. When CO exits, it exits into RB's caller at the point just after RB .
When RB's caller finishes and exits, it exits into RB just after CO .
Code: Select all
R> BASE ! ;
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:
Code: Select all
: 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.
- barrym95838
- Posts: 2056
- Joined: 30 Jun 2013
- Location: Sacramento, CA, USA
Re: The coolest words
Code: Select all
: RB ( -- )
BASE @ R> 2>R CO
R> BASE ! ;
Mike B. (about me) (learning how to github)
Re: The coolest 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.
Re: The coolest words
I cover coroutines in greater depth here.
Re: The coolest words
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> .
Code: Select all
: CO R> R> SWAP >R >R ;
: RB R> BASE @ >R >R CO R> BASE ! ;
These and the GREET HELLO examples worked as expected.
Re: The coolest words
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: Select all
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: Select all
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.