Not your run of the mill control flow

Topics relating to various Forth models on the 6502, 65816, and related microprocessors and microcontrollers.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Not your run of the mill control flow

Post by JimBoyd »


Another nonstandard control flow word is CO , the coroutine word.
The high level definition is

Code: Select all

: CO   2R> SWAP 2>R ;

Here is a low level definition for my Forth, which is a Forth-83 Standard ITC Forth.

Code: Select all

CODE CO  (  R: ADR1 -- ADR2 )
         ( IP: ADR2 -- ADR1 )
   PLA  TAY  PLA  N STA
   IP 1+ LDA  PHA
   IP    LDA  PHA
   IP STY  N LDA
   ' EXIT @ 4 + JMP
   END-CODE

The code at ' EXIT @ 4 +

Code: Select all

   IP 1+ STA
  <falls through to NEXT, the address interpreter>

With only one trip through the address interpreter, NEXT , this CO is about five times faster for only ten more bytes.
My Forth has a word WITH-WORDS which is used like this:

Code: Select all

: <SOMEWORD>
   <do this once> WITH-WORDS <do this once for each word in the CONTEXT vocabulary> ;

For each word in the CONTEXT vocabulary, WITH-WORDS places that word's name field address on the stack. Here is a simple word to count all the words in the CONTEXT vocabulary.

Code: Select all

: #WORDS  ( -- N )
   0  WITH-WORDS  DROP 1+ ;

When WITH-WORDS is defined with the primitive version of CO , #WORDS takes about 81% the time to run as it did before.
A longer word using WITH-WORDS is IN-NAME . This word takes about 96% the time to run.
User avatar
pjeaton
Posts: 23
Joined: 23 Feb 2005
Location: Zurich, Switzerland

Re: Not your run of the mill control flow

Post by pjeaton »

!if is probably what I'd go with, regardless of the clash with regular !

We used ! to mean NOT at university 30+ years ago, I use it in code, variables, comments...
P*h*i*l*l*i*p EEaattoon in real life
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Not your run of the mill control flow

Post by JimBoyd »


It's certainly not what I would go with. Although I'm considering renaming 0EXIT to ?0EXIT (I'm still not sure about that), I will not rename it to something like ?!EXIT .
As for !IF , it kind of reminds me of C.
Speaking of C, chitselb has a word in pettil which is like the ternary operator from Ruby Java and C so he named it ?: . It consumes a flag from the data stack and executes one of the following two words before program flow resumes.

Code: Select all

: TEST  ( FLAG -- )
   ?: EXECUTE.IF.TRUE  EXECUTE.IF.FALSE  EXECUTE.ALWAYS ... ;

chitselb had this source for ?:

Code: Select all

: ?:   ( "name1" "name2" -- )
    ?comp compile (?:) ' , ' , ; immediate

There is the possibility of compiling an immediate word by accident.
Here is the source for an ITC Forth which avoids that problem.

Code: Select all

CODE (?:)
   0 ,X LDA  1 ,X ORA
   0= IF  2 # LDY  THEN
   IP )Y LDA  W STA  INY
   IP )Y LDA  W 1+ STA
   CLC
   IP LDA  4 # ADC  IP STA
   CS IF  IP 1+ INC  THEN
   INX  INX
   0 # LDY  W 1- JMP
   END-CODE

: ?:  ( -- )
   ?COMP  COMPILE (?:)
   BL WORD FIND 2/ ?HUH ,
   BL WORD FIND 2/ ?HUH , ;
   IMMEDIATE

The source for ?HUH

Code: Select all

: ?HUH  ( -F -- )
   0= ABORT" WHAT?" ;

?HUH aborts if the parsed name can't be found.
the phrase

Code: Select all

BL WORD FIND 2/ ?HUH

is to insure that an immediate name is not compiled.

I'm not sure the high level protection is needed. The programmer just needs to remember that two words (other than the final exit) need to be compiled after ?: .
Although ?: is normally used like this:

Code: Select all

   ... ?: TRUE.WORD FALSE.WORD ...

this will also work.

Code: Select all

   ... (?:) TRUE.WORD FALSE.WORD ...

(?:) can be used like the following because BEGIN doesn't compile anything:

Code: Select all

   ...
   (?:) TRUE.WORD
   BEGIN
      FALSE.WORD
      <OTHER LOOP STUFF>
   UNTIL
   ...

A flag is consumed on the first pass through the loop and either TRUE.WORD or FALSE.WORD will be executed. FALSE.WORD will be executed on each successive loop.
Here is another example. TESTTWO could take some time, so rather than AND the results from both tests, the quicker test is run first.

Code: Select all

   ...
   TESTONE
   IF
      TESTTWO
      (?:) TRUE.WORD
   THEN
   FALSE.WORD
   <ALWAYS DO THIS PART>
   ...

This works because THEN does not compile anything, it just resolves the forward branch from IF .
FALSE.WORD is executed if either test returns a false flag. TRUE.WORD is only executed if both tests return a true flag.
So, should both ?: and (?:) exist, or should (?:) be renamed ?: ?
User avatar
GARTHWILSON
Forum Moderator
Posts: 8775
Joined: 30 Aug 2002
Location: Southern California
Contact:

Re: Not your run of the mill control flow

Post by GARTHWILSON »

JimBoyd wrote:
So, should both ?: and (?:) exist, or should (?:) be renamed ?: ?
I guess I can't really think of any reason to have the immediate compile-only word that compiles the other. I have a few words that others have built the protection into which I didn't think was really necessary, like 2LEAVE, 2?LEAVE, 2BOUNDS, 2I, and 2UNLOOP in my 32-bit DO...LOOP word set.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
BruceRMcF
Posts: 388
Joined: 21 Aug 2019

Re: Not your run of the mill control flow

Post by BruceRMcF »

pjeaton wrote:
!if is probably what I'd go with, regardless of the clash with regular !

We used ! to mean NOT at university 30+ years ago, I use it in code, variables, comments...
I would still read it "Store If" in a Forth context, just like I might use /Select to mean an active low SPI select in a hardware context but understand /CELL to mean "divide by the Cell size" in a Forth context.
User avatar
GARTHWILSON
Forum Moderator
Posts: 8775
Joined: 30 Aug 2002
Location: Southern California
Contact:

Re: Not your run of the mill control flow

Post by GARTHWILSON »

BruceRMcF wrote:
pjeaton wrote:
!if is probably what I'd go with, regardless of the clash with regular !

We used ! to mean NOT at university 30+ years ago, I use it in code, variables, comments...
I would still read it "Store If" in a Forth context, just like I might use /Select to mean an active low SPI select in a hardware context but understand /CELL to mean "divide by the Cell size" in a Forth context.
I suppose you could use a trailing backslash instead which I think OrCAD did (although I haven't used OrCAD in many years). We also have /MOD and other such words in Forth, where the / means "divide." ! in at least some BASICs means the rest of the line is comments.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
BruceRMcF
Posts: 388
Joined: 21 Aug 2019

Re: Not your run of the mill control flow

Post by BruceRMcF »

JimBoyd wrote:

I think of the most basic FOR A0 AFT A1 THEN A3 NEXT loop like this.
The first time through the loop, and only the first time, A0 gets executed.
If the loop executes more than once, all the other times A1 gets executed, but not the first time through.
A3 always gets executed.

I also suspect the name AFT was a play on words, but I could be wrong. Fore and aft referring to the front and back of a ship.
I do think that the "Fore and Aft" was deliberate, but the verbose reading of AFT might be "once-then-afterward"

N @ FOR init AFT iterator THEN act NEXT

is "N fetch FOR init once-then-afterwards iterator THEN act NEXT"
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Not your run of the mill control flow

Post by JimBoyd »

GARTHWILSON wrote:
JimBoyd wrote:
So, should both ?: and (?:) exist, or should (?:) be renamed ?: ?
I guess I can't really think of any reason to have the immediate compile-only word that compiles the other.

Neither can I. Naming the primitive ?: sounds good to me.
GARTHWILSON wrote:

I have a few words that others have built the protection into which I didn't think was really necessary, like 2LEAVE, 2?LEAVE, 2BOUNDS, 2I, and 2UNLOOP in my 32-bit DO...LOOP word set.

My opinion is that protection should be just enough to prevent things which obviously will not work, but not enough to get in the programmer's way.
Since a >MARK can not be resolved by a <RESOLVE and a <MARK can not be resolved by a >RESOLVE , compiler security to make sure >MARK is resolved by >RESOLVE and <MARK is resolved by <RESOLVE makes sense; however, in a Forth where LEAVE and ?LEAVE do not have an inline branch address, but use a third loop parameter, immediate compiler words are unnecessary.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Not your run of the mill control flow

Post by JimBoyd »

JimBoyd wrote:

This version of CONTINUE goes back to the beginning of the word where it is used, not the beginning of a loop. As with EXIT , the level of control flow nesting in the word to be continued does not matter.

Other programming languages use the name CONTINUE to continue a loop at the beginning, not to restart a function. The word EXIT can exit a word at any point, but it is not called BREAK . The word I presented does not even need to be in a loop. It restarts the word it is in. Perhaps it should have a more suitable name. Maybe something like RESTART or RERUN ?
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Not your run of the mill control flow

Post by JimBoyd »


In my last post I indicated my dissatisfaction with the name CONTINUE as it implies continuing with the next iteration of a loop. It seems CONTINUE is a better name for a word which compiles a branch back to the beginning of a BEGIN loop without discarding the control flow information. Something like the following:

Code: Select all

: CONTINUE  ( -- )
   CS-DUP  [COMPILE] AGAIN ; IMMEDIATE

Which would be used like this:

Code: Select all

   BEGIN
      <SOMETHING0>
   CONTINUE
      <SOMETHING1>
   UNTIL 

This would be awkward to use in an IF THEN statement.

Code: Select all

   BEGIN
      <SOMETHING0>
      IF   CS-SWAP CONTINUE  CS-SWAP  THEN
      <SOMETHING1>
   UNTIL

Therefore I find CONTINUE , as it is used in BEGIN loops, to be not very useful. This fragment compiles the same thing:

Code: Select all

   BEGIN
      <SOMETHING0>
      CS-DUP  WHILE  AGAIN  THEN
      <SOMETHING1>
   UNTIL

but this is better.

Code: Select all

   BEGIN
      <SOMETHING0>
      0=
      CS-DUP UNTIL
      <SOMETHING1>
   UNTIL

The versions which branch back to the beginning of a word can be nested deeply in other control flow structures because the branch address they compile is not from control flow data. I'd also like to initially have just two names. Consider the Forth-83 Standard's two branching primitives, BRANCH and ?BRANCH . BRANCH always branches and ?BRANCH only branches if the top stack value is false. BEGIN loops have the word AGAIN to compile a branch always back to BEGIN and the word UNTIL to compile a branch back to BEGIN if the top of stack is false. The Standard does not specify an immediate word to compile the word 0= followed by ?BRANCH .
I'd like to follow that example and specify two new words rather than CONTINUE ?CONTINUE and ?0CONTINUE .
For the first word, I'd like the name to mean something like AGAIN , but to begin the word at the start. For the second, I'd like something like UNTIL , but to branch back to the start of the word.

Code: Select all

BACK  ( -- )  \ Branch back to beginning of definition.
?PASS  ( flag -- )  \ Branch back if the flag is false.

BACK always branches back to the beginning of the word.
?PASS only branches back if the flag is false. The implied meaning is if the flag is true then pass onto the rest of the word. If the flag is false "You shall not pass!"

Code: Select all

: BACK  ( -- )
   COMPILE BRANCH
   LAST >BODY , ; IMMEDIATE
: ?PASS  ( -- )  // COMPILING
         ( F -- )  // EXECUTING
   COMPILE ?BRANCH
   LAST >BODY , ; IMMEDIATE

Or

Code: Select all

: BACK  ( -- )
   COMPILE BRANCH
   LATEST NAME> >BODY , ; IMMEDIATE
: ?PASS  ( -- )  // COMPILING
         ( F -- )  // EXECUTING
   COMPILE ?BRANCH
   LATEST NAME> >BODY , ; IMMEDIATE

Does anyone have a suggestion for better names?
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Not your run of the mill control flow

Post by JimBoyd »


It slipped my mind that BACK is a turtle graphics command so it isn't a good choice for a name. For now, I'm going with RERUN and ?PASS .
The actions of these two words can, at times, be duplicated by other means. Sometimes, those means aren't pretty. Here is an example of how these words can make the source clearer.
Back before my Forth's current version of INTERPRET , I noticed in some cases INTERPRET had a BRANCH to other BRANCHes before finally branching to the beginning. I tried to rewrite the interpreter loop to eliminate the extra trips through NEXT . It looked something like this:

Code: Select all

: INTERPRET  ( -- )
   BEGIN
      BEGIN
         BEGIN
            BEGIN
               BEGIN
                  BEGIN
                     PAUSE
                     NAME FIND ?DUP
                  WHILE
                     STATE @ =
                  WHILE
                     ,
                  REPEAT CS-SWAP
                  EXECUTE ?STACK
               REPEAT
               NUMBER? ?HUH  ?STACK
               DPL @ 0<
            WHILE
               DROP STATE @
            UNTIL CS-SWAP
            [COMPILE] LITERAL
         REPEAT
         STATE @
      UNTIL
      SWAP
      [COMPILE] LITERAL
      [COMPILE] LITERAL
   AGAIN ; -2 ALLOT

This wasn't pretty and I didn't like it. If I'd thought of them, RERUN and ?PASS , would have made this easier. This is a cleaner version which compiles to exactly the same thing.

Code: Select all

: INTERPRET  ( -- )
   PAUSE
   NAME FIND ?DUP
   IF
      STATE @ =
      IF  ,  RERUN  THEN
      EXECUTE ?STACK  RERUN
   THEN
   NUMBER? ?HUH  ?STACK
   DPL @ 0<
   IF
      DROP STATE @ ?PASS
      [COMPILE] LITERAL  RERUN
   THEN
   STATE @ ?PASS
   SWAP
   [COMPILE]  LITERAL
   [COMPILE]  LITERAL
   RERUN ; -2 ALLOT

Or with a BEGIN AGAIN loop.

Code: Select all

: INTERPRET  ( -- )
   BEGIN
      PAUSE
      NAME FIND ?DUP
      IF
         STATE @ =
         IF  ,  RERUN  THEN
         EXECUTE ?STACK  RERUN
      THEN
      NUMBER? ?HUH  ?STACK
      DPL @ 0<
      IF
         DROP STATE @ ?PASS
         [COMPILE] LITERAL  RERUN
      THEN
      STATE @ ?PASS
      SWAP
      [COMPILE]  LITERAL
      [COMPILE]  LITERAL
   AGAIN ; -2 ALLOT

As can be seen in these examples, the results of RERUN and ?PASS can, at times, be difficult to replicate with other control flow words.
Here is a smaller version of RERUN and ?PASS .

Code: Select all

: RERUN  ( -- )
   COMPILE BRANCH
   LAST >BODY , ; IMMEDIATE
: ?PASS  ( -- )  // COMPILING
         ( F -- )  // EXECUTING
   COMPILE ?BRANCH
   BRANCH [ ' RERUN >BODY 4 + , ] ;
   -2 ALLOT  IMMEDIATE

User avatar
barrym95838
Posts: 2056
Joined: 30 Jun 2013
Location: Sacramento, CA, USA

Re: Not your run of the mill control flow

Post by barrym95838 »

Newb question, Jim. I have seen IMMEDIATE directly after a ; but not -2 ALLOT or anything else in between. Could you enlighten me with a brief description of how and why your compiler handles this situation, if it's not too much off-topic?
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)
User avatar
GARTHWILSON
Forum Moderator
Posts: 8775
Joined: 30 Aug 2002
Location: Southern California
Contact:

Re: Not your run of the mill control flow

Post by GARTHWILSON »

barrym95838 wrote:
Newb question, Jim. I have seen IMMEDIATE directly after a ; but not -2 ALLOT or anything else in between. Could you enlighten me with a brief description of how and why your compiler handles this situation, if it's not too much off-topic?
-2 ALLOT just removes the unnest (compiled by the semicolon) which is unnecessary if it'll never get executed because it is preceded by something like a branch. The semicolon does a couple of other things too at compile time though, which is why we don't just replace it with a [.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
User avatar
barrym95838
Posts: 2056
Joined: 30 Jun 2013
Location: Sacramento, CA, USA

Re: Not your run of the mill control flow

Post by barrym95838 »

Okay, I got the feeling he was trimming something off the end, but I wasn't sure why. Two bytes saved is two bytes earned! So, the ; does a bit more dictionary housekeeping than the [ (which just zeroes STATE )?
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)
User avatar
GARTHWILSON
Forum Moderator
Posts: 8775
Joined: 30 Aug 2002
Location: Southern California
Contact:

Re: Not your run of the mill control flow

Post by GARTHWILSON »

From my '816 Forth:

Code: Select all

: ;             ( -- )                                        \      SF8,16,26
   ?COMP
   COMPILE unnest
   UNSMUDGE  ?CSP
   [COMPILE] [  ;  IMMEDIATE

The ?CSP checks for incompleted structures, like IF without THEN, etc.. You probably know the others.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
Post Reply