Page 1 of 2

Re: The coolest words

Posted: Tue Mar 23, 2021 7:51 pm
by JimBoyd
GARTHWILSON wrote:

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
 ;-------------------
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:

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.

Re: The coolest words

Posted: Thu Mar 25, 2021 5:51 am
by barrym95838
JimBoyd wrote:
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.
That's too cool for school!

Re: The coolest words

Posted: Sun Apr 11, 2021 6:23 pm
by SamCoVT
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.

Re: The coolest words

Posted: Sat May 01, 2021 1:22 am
by JimBoyd

I cover coroutines in greater depth here.

Re: The coolest words

Posted: Sat May 01, 2021 1:29 am
by JimBoyd

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

Posted: Wed Aug 28, 2024 11:40 pm
by JimBoyd
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: 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.

Re: The coolest words

Posted: Thu Aug 29, 2024 12:05 am
by JimBoyd
In my humble opinion, another cool word is FH , from here, in Leo Brodie's "Thinking Forth."

Code: Select all

: 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.

Code: Select all

SCR# 2 
// LOAD BLOCK
DECIMAL
CR .( DONE?      )         2 FH LOAD
CR .( THRU --> <-- ;S )    3 FH LOAD
CR .( ASSEMBLER  )   4 FH 16 FH THRU
CR .( UTILITIES  )  17 FH 25 FH THRU
CR .( SF EDITOR  )  26 FH 38 FH THRU
CR .( SCREEN ED  )        39 FH LOAD
CR .( AUX STACK  )  45 FH 48 FH THRU
CR .( MORE UTILITIES AND EXTENSIONS)
CR                  49 FH 80 FH THRU
CR .( VIRTUAL MEM ) 81 FH 86 FH THRU
CR .( CONDITIONAL COMP )  91 FH LOAD
CR .( AFIND       ) 92 FH 93 FH THRU
CR .( FILES (ERR)   94 FH 95 FH THRU
CR .( MULTITASKER) 96 FH 100 FH THRU

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.