6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Apr 27, 2024 3:45 pm

All times are UTC




Post new topic Reply to topic  [ 63 posts ]  Go to page Previous  1, 2, 3, 4, 5  Next
Author Message
PostPosted: Wed Oct 12, 2022 9:16 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 851

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



Top
 Profile  
Reply with quote  
PostPosted: Wed Oct 12, 2022 11:06 pm 
Online
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1927
Location: Sacramento, CA, USA
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)


Top
 Profile  
Reply with quote  
PostPosted: Wed Oct 12, 2022 11:28 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8428
Location: Southern California
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?


Top
 Profile  
Reply with quote  
PostPosted: Thu Oct 13, 2022 3:17 am 
Online
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1927
Location: Sacramento, CA, USA
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)


Top
 Profile  
Reply with quote  
PostPosted: Thu Oct 13, 2022 4:03 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8428
Location: Southern California
From my '816 Forth:
Code:
: ;             ( -- )                                        \      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?


Top
 Profile  
Reply with quote  
PostPosted: Thu Oct 13, 2022 8:36 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 851

Yes, -2 ALLOT trims two bytes off the end because the EXIT will never be reached.
Blazin' Forth had ?CSP . It works by checking that the stack depth is the same as that saved by colon.
My Forth works a little differently. Colon, and CODE push the address to unsmudge and TRUE on the control flow stack (in my case, the data stack) .
Semicolon, and END-CODE check to see if the top stack item is true and use the address to unsmudge the name. I went with this approach because I was already thinking of :NONAME or something like it. In the case where there is no name, semicolon is passed a dummy address which can be harmlessly manipulated.
Fleet Forth's semicolon.
Code:
: ;  ( ADR TRUE -- )
   COMPILE EXIT
   [COMPILE] [
   TRUE ?PAIRS  TSB ; IMMEDIATE

There is a name I've seen elsewhere I could add to my kernel, -; (dash-semicolon). It would do everything semicolon does except compile EXIT , the Forth-83 Standard name for unnest. Semicolon would compile EXIT and call dash-semicolon.
Code:
: -;  ( ADR TRUE -- )
   ?COMP  [COMPILE] [
   TRUE ?PAIRS TSB ; IMMEDIATE

: ;  ( ADR TRUE -- )
   COMPILE EXIT [COMPILE] -; ; IMMEDIATE

Dash-semicolon doesn't compile anything so ?COMP is there to make sure state is compiling.
I had thought about adding dash-semicolon to clarify things and make this technique portable, but I thought I would just be giving back the bytes I saved. The kernel size would increase by thirteen bytes; however, I just checked and I use this technique twenty five times just in the kernel.
[Edit: corrected a typo.]


Last edited by JimBoyd on Wed Oct 19, 2022 11:21 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 16, 2022 7:25 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 851

The following may not be run of the mill, yet, so I'll mention it. Rather than have nested IF ELSE THEN clauses at the end of a word:
Code:
   <TEST0>
   IF
      <ACTION0>
   ELSE
      <TEST1>
      IF
         <ACTION1>
      ELSE
         <TEST2>
         IF
            <ACTION2>
         ELSE
            <TEST3>
            IF
               <ACTION3>
            THEN
         THEN
      THEN
   THEN ;

I prefer this:
Code:
   <TEST0>
   IF   <ACTION0> EXIT  THEN
   <TEST1>
   IF   <ACTION1> EXIT  THEN
   <TEST2>
   IF   <ACTION2> EXIT  THEN
   <TEST3>
   IF   <ACTION3> THEN ;

EXIT is the Forth-83 Standard word to unnest a high level word.
If the series of tests and actions are in the middle of a word, I prefer to factor the test out as a separate word.


Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 16, 2022 8:25 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8428
Location: Southern California
In some situations, a CASE structure is appropriate; and with of as a primitive, this structure becomes a lot more efficient than a series of IF...ELSE...IF...ELSEs.

Otherwise, I often like to arrange the nested IFs as:
Code:
   CONDITIONS           IF
   ACTIONS ACTIONS
   ACTIONS ACTIONS      ELSE

   CONDITIONS           IF
   ACTIONS ACTIONS
   ACTIONS ACTIONS      ELSE

   CONDITIONS           IF
   ACTIONS ACTIONS
   ACTIONS ACTIONS      ELSE

   ACTIONS    THEN THEN THEN

(and what's right before one or more of the ELSEs might be an EXIT). Of course what's most readable will depend on the situation.

_________________
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?


Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 16, 2022 8:51 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 851

I agree that the appropriate method depends on the situation. A trade off between size and efficiency is also a factor.
EXIT and the conditional exits, ?EXIT and 0EXIT are also useful outside the context of multiple nested IF ELSE THEN structures. Here is a headerless word used by Fleet Forth's T&S41 , a word to map blocks to the initial track and sector for the 1541 and 1571 drives as well as their dual disk counterparts.
Code:
NH
: (T&S41)  ( BLK# -- BLK# S/T )
   21 OVER  88 < ?EXIT
   2- OVER 120 < ?EXIT
   1- OVER 146 < ?EXIT
   1- ;

These drives do not have the same number of sectors for each track. This small word takes a block number and returns the block number as well as the number of sectors per track for the starting sector of that block.
Writing this fragment without ?EXIT and including it as part of the word T&S41 would not have been as clean.


Top
 Profile  
Reply with quote  
PostPosted: Sat Oct 29, 2022 1:07 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 851

This post concerns a technique used on Forth systems with indirect threaded code (ITC). It may work with other threading models.
Although the example from M. L. Gassanenko's paper "Dynamically Structured Codes" wasn't really self modifying code, it certainly is not your run of the mill control flow.
Just as EXECUTE takes the address of a word's Code Field and launches that word, ENTER takes the address of a fragment of threaded code and 'launches' that fragment. The address must align on what Gassanenko calls an active 'threaded code element', or a TCE. An active TCE is an address which holds the CFA of a Forth word. The 'threaded code fragment' must also exit (or branch somewhere which exits or aborts). Consider the word ?COMP . In my Forth it is this:
Code:
: ?COMP  ( -- )
   STATE @ 0=
   ABORT" FOR COMPILING" ;

It is used in some immediate words. It aborts when interpreting.
The dis-assembly of ?COMP
Code:
?COMP
  7999  2130 STATE
  8001  3561 @
  8003  4602 0=
  8005  7822 (ABORT") FOR COMPILING
  8021  2970 EXIT

The body of this word, or some portion thereof, is a threaded code fragment. All the addresses on the left are an address in the threaded code fragment with an active TCE. Another way to obtain the address of a threaded code fragment which points to an active TCE is with R@ .
Here is an example from Gassanenko's paper. The example requires the data stack to initially be empty.
Code:
: ENTER >R ;
: EL   R@ ENTER DROP ;
: .<[]>
   CR ." <[ " DEPTH 0
   ?DO
      I PICK COUNT TYPE SPACE
   LOOP
   ." ]> " ;
: SUBSETS
   " FIRST" EL " SECOND" EL
   " THIRD" EL .<[]> ;

This is a log of the test run.
Code:
SUBSETS
<[ THIRD SECOND FIRST ]>
<[ SECOND FIRST ]>
<[ THIRD FIRST ]>
<[ FIRST ]>
<[ THIRD SECOND ]>
<[ SECOND ]>
<[ THIRD ]>
<[ ]>  OK

Here is a modification of that example.
Code:
: ENTER >R ;
0 VALUE ODEPTH
: EL   R@ ENTER DROP ;
: .<[]>
   CR ." <[ " DEPTH ODEPTH - 0
   ?DO
      I PICK 4 U.R SPACE
      RP@ $17F < ABORT" RS LIMIT"
   LOOP
   ." ]> " ;
: SUBSETS  ( -- )  DEPTH TO ODEPTH
   1 EL 2 EL 3 EL 4 EL 5 EL 6 EL
   7 EL
   .<[]> ;

The value ODEPTH is used so the data stack does not need to be empty. Here is the test run.
Code:
SUBSETS
<[    7    6    5    4    3    2    1 ]>
<[    6    5    4    3    2    1 ]>
<[    7    5    4    3    2    1 ]>
<[    5    4    3    2    1 ]>
<[    7    6    4    3    2    1 ]>
<[    6    4    3    2    1 ]>
<[    7    4    3    2    1 ]>
<[    4    3    2    1 ]>
<[    7    6    5    3    2    1 ]>
<[    6    5    3    2    1 ]>
<[    7    5    3    2    1 ]>
<[    5    3    2    1 ]>
<[    7    6    3    2    1 ]>
<[    6    3    2    1 ]>
<[    7    3    2    1 ]>
<[    3    2    1 ]>
<[    7    6    5    4    2    1 ]>
<[    6    5    4    2    1 ]>
<[    7    5    4    2    1 ]>
<[    5    4    2    1 ]>
<[    7    6    4    2    1 ]>
<[    6    4    2    1 ]>
<[    7    4    2    1 ]>
<[    4    2    1 ]>
<[    7    6    5    2    1 ]>
<[    6    5    2    1 ]>
<[    7    5    2    1 ]>
<[    5    2    1 ]>
<[    7    6    2    1 ]>
<[    6    2    1 ]>
<[    7    2    1 ]>
<[    2    1 ]>
<[    7    6    5    4    3    1 ]>
<[    6    5    4    3    1 ]>
<[    7    5    4    3    1 ]>
<[    5    4    3    1 ]>
<[    7    6    4    3    1 ]>
<[    6    4    3    1 ]>
<[    7    4    3    1 ]>
<[    4    3    1 ]>
<[    7    6    5    3    1 ]>
<[    6    5    3    1 ]>
<[    7    5    3    1 ]>
<[    5    3    1 ]>
<[    7    6    3    1 ]>
<[    6    3    1 ]>
<[    7    3    1 ]>
<[    3    1 ]>
<[    7    6    5    4    1 ]>
<[    6    5    4    1 ]>
<[    7    5    4    1 ]>
<[    5    4    1 ]>
<[    7    6    4    1 ]>
<[    6    4    1 ]>
<[    7    4    1 ]>
<[    4    1 ]>
<[    7    6    5    1 ]>
<[    6    5    1 ]>
<[    7    5    1 ]>
<[    5    1 ]>
<[    7    6    1 ]>
<[    6    1 ]>
<[    7    1 ]>
<[    1 ]>
<[    7    6    5    4    3    2 ]>
<[    6    5    4    3    2 ]>
<[    7    5    4    3    2 ]>
<[    5    4    3    2 ]>
<[    7    6    4    3    2 ]>
<[    6    4    3    2 ]>
<[    7    4    3    2 ]>
<[    4    3    2 ]>
<[    7    6    5    3    2 ]>
<[    6    5    3    2 ]>
<[    7    5    3    2 ]>
<[    5    3    2 ]>
<[    7    6    3    2 ]>
<[    6    3    2 ]>
<[    7    3    2 ]>
<[    3    2 ]>
<[    7    6    5    4    2 ]>
<[    6    5    4    2 ]>
<[    7    5    4    2 ]>
<[    5    4    2 ]>
<[    7    6    4    2 ]>
<[    6    4    2 ]>
<[    7    4    2 ]>
<[    4    2 ]>
<[    7    6    5    2 ]>
<[    6    5    2 ]>
<[    7    5    2 ]>
<[    5    2 ]>
<[    7    6    2 ]>
<[    6    2 ]>
<[    7    2 ]>
<[    2 ]>
<[    7    6    5    4    3 ]>
<[    6    5    4    3 ]>
<[    7    5    4    3 ]>
<[    5    4    3 ]>
<[    7    6    4    3 ]>
<[    6    4    3 ]>
<[    7    4    3 ]>
<[    4    3 ]>
<[    7    6    5    3 ]>
<[    6    5    3 ]>
<[    7    5    3 ]>
<[    5    3 ]>
<[    7    6    3 ]>
<[    6    3 ]>
<[    7    3 ]>
<[    3 ]>
<[    7    6    5    4 ]>
<[    6    5    4 ]>
<[    7    5    4 ]>
<[    5    4 ]>
<[    7    6    4 ]>
<[    6    4 ]>
<[    7    4 ]>
<[    4 ]>
<[    7    6    5 ]>
<[    6    5 ]>
<[    7    5 ]>
<[    5 ]>
<[    7    6 ]>
<[    6 ]>
<[    7 ]>
<[ ]>  OK

Here is an example of mine to show what is happening. FORK leaves a flag on the data stack, TRUE if this is the first time this fragment runs or FALSE if it is the second.
Code:
: ENTER  ( T-ADR -- )  >R ;
: FORK  ( -- FLAG )
   TRUE  R@ ENTER  FALSE ;
: TEST
   FORK CR ." FORK 1       PATH " U.
   FORK CR ."   FORK 2     PATH " U.
   FORK CR ."     FORK 3   PATH " U.
   FORK CR ."       FORK 4 PATH " U. ;

Here is the test run.
Code:
TEST
FORK 1       PATH 65535
  FORK 2     PATH 65535
    FORK 3   PATH 65535
      FORK 4 PATH 65535
      FORK 4 PATH 0
    FORK 3   PATH 0
      FORK 4 PATH 65535
      FORK 4 PATH 0
  FORK 2     PATH 0
    FORK 3   PATH 65535
      FORK 4 PATH 65535
      FORK 4 PATH 0
    FORK 3   PATH 0
      FORK 4 PATH 65535
      FORK 4 PATH 0
FORK 1       PATH 0
  FORK 2     PATH 65535
    FORK 3   PATH 65535
      FORK 4 PATH 65535
      FORK 4 PATH 0
    FORK 3   PATH 0
      FORK 4 PATH 65535
      FORK 4 PATH 0
  FORK 2     PATH 0
    FORK 3   PATH 65535
      FORK 4 PATH 65535
      FORK 4 PATH 0
    FORK 3   PATH 0
      FORK 4 PATH 65535
      FORK 4 PATH 0  OK

Here is another example of mine. It uses the word FORK from the previous example. The word GATE1 represents a logic gate, or circuit, with six inputs and one output. The first two inputs are exclusively ORed together, as are the third and forth, as well as the fifth and sixth. The three results are ORed together to derive the output.
GATE1 takes either one parameter or three. If the top of stack is true, GATE1 leaves its output on the stack. If the top of stack is false, the next two parameters are used to set one of the inputs.
Code:
: GATE
   CREATE  ( -- )  6 ALLOT
   DOES>  ( FLAG1 GATE# FALSE -- )
          ( TRUE -- FLAG2 )
      SWAP
      IF
         >R
         R@ C@     R@ 1+ C@  XOR
         R@ 2+ C@  R@ 3 + C@ XOR
         R@ 4 + C@ R> 5 + C@ XOR
         OR OR  EXIT
      THEN
      + C! ;
GATE GATE1
: SHOW
   ['] GATE1 >BODY 6 BOUNDS  CR
   DO  I C@ .  LOOP
   ." >> " TRUE GATE1 . ;
: GTT
   FORK ABS 0 0 GATE1
   FORK ABS 1 0 GATE1
   FORK ABS 2 0 GATE1
   FORK ABS 3 0 GATE1
   FORK ABS 4 0 GATE1
   FORK ABS 5 0 GATE1
   SHOW ;

GTT shows the truth table for GATE1 .
Code:
GTT
1 1 1 1 1 1 >> 0
1 1 1 1 1 0 >> 1
1 1 1 1 0 1 >> 1
1 1 1 1 0 0 >> 0
1 1 1 0 1 1 >> 1
1 1 1 0 1 0 >> 1
1 1 1 0 0 1 >> 1
1 1 1 0 0 0 >> 1
1 1 0 1 1 1 >> 1
1 1 0 1 1 0 >> 1
1 1 0 1 0 1 >> 1
1 1 0 1 0 0 >> 1
1 1 0 0 1 1 >> 0
1 1 0 0 1 0 >> 1
1 1 0 0 0 1 >> 1
1 1 0 0 0 0 >> 0
1 0 1 1 1 1 >> 1
1 0 1 1 1 0 >> 1
1 0 1 1 0 1 >> 1
1 0 1 1 0 0 >> 1
1 0 1 0 1 1 >> 1
1 0 1 0 1 0 >> 1
1 0 1 0 0 1 >> 1
1 0 1 0 0 0 >> 1
1 0 0 1 1 1 >> 1
1 0 0 1 1 0 >> 1
1 0 0 1 0 1 >> 1
1 0 0 1 0 0 >> 1
1 0 0 0 1 1 >> 1
1 0 0 0 1 0 >> 1
1 0 0 0 0 1 >> 1
1 0 0 0 0 0 >> 1
0 1 1 1 1 1 >> 1
0 1 1 1 1 0 >> 1
0 1 1 1 0 1 >> 1
0 1 1 1 0 0 >> 1
0 1 1 0 1 1 >> 1
0 1 1 0 1 0 >> 1
0 1 1 0 0 1 >> 1
0 1 1 0 0 0 >> 1
0 1 0 1 1 1 >> 1
0 1 0 1 1 0 >> 1
0 1 0 1 0 1 >> 1
0 1 0 1 0 0 >> 1
0 1 0 0 1 1 >> 1
0 1 0 0 1 0 >> 1
0 1 0 0 0 1 >> 1
0 1 0 0 0 0 >> 1
0 0 1 1 1 1 >> 0
0 0 1 1 1 0 >> 1
0 0 1 1 0 1 >> 1
0 0 1 1 0 0 >> 0
0 0 1 0 1 1 >> 1
0 0 1 0 1 0 >> 1
0 0 1 0 0 1 >> 1
0 0 1 0 0 0 >> 1
0 0 0 1 1 1 >> 1
0 0 0 1 1 0 >> 1
0 0 0 1 0 1 >> 1
0 0 0 1 0 0 >> 1
0 0 0 0 1 1 >> 0
0 0 0 0 1 0 >> 1
0 0 0 0 0 1 >> 1
0 0 0 0 0 0 >> 0  OK

On a sixteen bit Forth, ENTER has a body of four bytes and a code field of two bytes. The header is eight bytes, with full length names, for a total size of fourteen bytes.
I think this word has potential. Given its small size I'm inclined to include it in my Forth's system loader, unless I see a clever way to use it in the Forth kernel.


Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 30, 2022 7:30 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 851

Here are more interesting uses of ENTER .
My Forth has a tree like vocabulary structure with the FORTH vocabulary as the root. A vocabulary, other than FORTH , can be forgotten, like most words. one of the words to help manage the vocabularies is VOCS . It shows the vocabulary tree structure. Here is what VOCS shows when the metacompiler is loaded:
Code:
VOCS
FORTH
  META
    SHADOW
      FORTH
        ASSEMBLER
  EDITOR
  ASSEMBLER OK

Yes, there are two vocabularies named FORTH and two named ASSEMBLER .
VOCS places the PFA of FORTH and a zero (for the initial indentation level) on the stack. It would then call a nameless recursive word.
Code:
:NONAME  ( PFA CNT -- )
   ?STACK
   DUP>R CR SPACES
   DUP BODY> >NAME ID.  VOC-LINK @
   BEGIN
      2DUP 2- @ =
      IF
         DUP 2- 2- R@ 2+ RECURSE
      THEN
      @ ?DUP 0=
   UNTIL
   R> 2DROP ;  >A
: VOCS
   [ ' FORTH >BODY ] LITERAL 0
   [ A> , ] ;

The following version doesn't need a nameless word, thanks to ENTER . It's also slightly smaller.
Code:
: VOCS  ( -- )
   [ ' FORTH >BODY ] LITERAL 0
   [ <MARK ]
   ?STACK
   DUP>R CR SPACES
   DUP BODY> >NAME ID.  VOC-LINK @
   BEGIN
      2DUP 2- @ =
      IF
         DUP 2- 2- R@ 2+
         LIT CS-ROT [ <RESOLVE ] ENTER
      THEN
      @ ?DUP 0=
   UNTIL
   R> 2DROP ;

LIT is the primitive compiled by LITERAL . Note that <MARK and <RESOLVE are used to compile an address for LIT which is used by ENTER .
Here is the dis-assembly.
Code:
SEE VOCS
VOCS
 31269  3050 LIT 9591
 31273  3402 0
 31275  8405 ?STACK
 31277  4758 DUP>R
 31279  6698 CR
 31281  6673 SPACES
 31283  5007 DUP
 31285  6556 BODY>
 31287  6588 >NAME
 31289  8754 ID.
 31291  2240 VOC-LINK
 31293  3561 @
 31295  5289 2DUP
 31297  4794 2-
 31299  3561 @
 31301  4419 =
 31303  2781 ?BRANCH 31323
 31307  5007 DUP
 31309  4794 2-
 31311  4794 2-
 31313  4731 R@
 31315  4765 2+
 31317  3050 LIT 31275
 31321 31192 ENTER
 31323  3561 @
 31325  4986 ?DUP
 31327  4602 0=
 31329  2781 ?BRANCH 31295
 31333  4663 R>
 31335  2758 2DROP
 31337  2970 EXIT
70
 OK

ENTER does not cause a branch back to the address 31275, where ?STACK is located. This use of ENTER is like recursion but without the need to start at the beginning of a word.
Here is another example. ORDER shows the CONTEXT vocabulary and its parents. This will be the search order. Since there can be multiple vocabularies with the same name (again, metacompiling) it also shows the CURRENT vocabulary and its parents. The following example is when the metacompiler is loaded. The top FORTH vocabulary is the target FORTH vocabulary.
Code:
CONTEXT: FORTH
         SHADOW
         META
         FORTH
         
CURRENT: FORTH
         SHADOW
         META
         FORTH

 OK

Here is the source.
Code:
: ORDER  ( -- )
   CR
   ." CONTEXT: "  CONTEXT
   LIT [ >MARK ] ENTER
   ." CURRENT: "  CURRENT
   [ >RESOLVE ]
   BEGIN
      @ ?DUP
   WHILE
      DUP BODY> >NAME ID.
      CR 9 SPACES 2+
   REPEAT CR ;

This use is not like recursion. Since ORDER performs the same action for two different items, ENTER causes a part of the threaded code to run twice.


Top
 Profile  
Reply with quote  
PostPosted: Wed Nov 02, 2022 11:05 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 851

Almost a year ago I mentioned halting colon definitions
I believe this also qualifies as 'not your run of the mill control flow'.


Top
 Profile  
Reply with quote  
PostPosted: Sat Mar 04, 2023 3:47 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 851

[BEGIN] and [UNTIL] revisited and a new look at loading multiple blocks.
The versions of [BEGIN] and [UNTIL] presented here will only work on a single screen of Forth source in a block based system. That single screen can have a LOAD or THRU between [BEGIN] and [UNTIL] ; however, that could be somewhat inconvenient.
Here are versions which can span multiple blocks.
Code:
: [BEGIN]  ( -- )
   BLK @ >IN @ 2>A ; IMMEDIATE
: [UNTIL]  ( F -- )
   IF  2A>  2DROP  EXIT  THEN
   2A>  2DUP 2>A
   >IN ! BLK ! ; IMMEDIATE

These words will not work properly with THRU .
Code:
SCR# 300
[BEGIN]
   CR .( NUMBER ON THE STACK: )
   DUP .

SCR# 301
   1-

SCR# 302
   ?DUP 0=
[UNTIL]
CR .( FINISHED)

Here is the test run. By the way, these blocks are in the C64's REU so the numbers are higher than usual.
Code:
300 RAM 302 RAM THRU 16684
NUMBER ON THE STACK: 10 16685 16686
NUMBER ON THE STACK: 9  OK

THRU displays the number of each block it is about to load: 16684, 16685 and 16686. Notice that the [BEGIN] [UNTIL] structure almost worked. When block 16686 (ram block 302) was loaded, [UNTIL] worked perfectly and caused ram block 300 to load just after [BEGIN] instead of the rest of ram block 302; however, THRU exits because ram block 302 was the last one to load. Even if ram block 302 wasn't the last one to load, THRU would have proceeded to load ram block 303 instead of ram block 301 as desired. This THRU is implemented with a DO LOOP and uses the loop index to determine the number of the next block to load.

Here is a word from the uncontrolled reference words in the Forth-83 Standard which will make a multi block spanning [BEGIN] [UNTIL] structure possible.
Code:
: -->  ( -- )
   1 BLK +!  >IN OFF ; IMMEDIATE

--> causes the next higher block to load, but it is incompatible with THRU , at least this THRU .
Modifying the source slightly by appending --> to the end of each block will allow [BEGIN] and [UNTIL] to work across multiple blocks, but the first block must be LOADed. THRU can not be used!
Code:
SCR# 200
[BEGIN]
   CR .( NUMBER ON THE STACK: )
   DUP .
-->

SCR# 201
   1-
-->

SCR# 202
   ?DUP 0=
[UNTIL]
CR .( FINISHED)

Here is the session log.
Code:
200 RAM LOAD
NUMBER ON THE STACK: 10
NUMBER ON THE STACK: 9
NUMBER ON THE STACK: 8
NUMBER ON THE STACK: 7
NUMBER ON THE STACK: 6
NUMBER ON THE STACK: 5
NUMBER ON THE STACK: 4
NUMBER ON THE STACK: 3
NUMBER ON THE STACK: 2
NUMBER ON THE STACK: 1
FINISHED OK

I normally don't use --> and didn't define it until just moments ago. I'm not fond of the need to be extra cautious to not mix --> and THRU .
I can use a feature of my system to rewrite THRU .
My Forth's WORD saves the history of the text stream (the values of BLK and >IN) for use with WHERE , one of the error handling words.
Code:
2VARIABLE HISTORY
: WORD  ( C -- HERE )
   'STREAM
   BLK 2@ HISTORY 2!
   DUP >IN +!
   2PICK SKIP
   ROT 2PICK -ROT SCAN
   1- 0 MAX NEGATE >IN +!
   OVER - >HERE ;

This version of THRU does not use a DO LOOP.
Code:
: THRU  ( LO HI -- )
   >R  1- HISTORY !
   BEGIN
      5 ?CR
      HISTORY @ 1+
      DUP U. LOAD
      HISTORY @ R@ U< 0=
      DONE? OR
   UNTIL
   R> DROP ;

Under normal circumstances this THRU does exactly what the original does.
The last block to load is pushed to the return stack. The number of the first block is decremented and stored in the first cell of HISTORY . The line with ?CR is just for 'pretty printing' and can be ignored in this explanation. The value of HISTORY is fetched and incremented by one. This will be the first block to load the first time through the loop. Once the block loads, the first cell of HISTORY contains the number of the block which just finished loading, even if that block is the last in a chain of blocks linked by --> . This block number is compared to the number saved on the return stack. The loop keeps going as long as the block just loaded is less than the last one to be loaded.
This version of THRU can safely be used with --> . Indeed, they use a similar mechanism to advance to the next block. BLK contains the number of a block while it's loading. The first cell of HISTORY contains the number of the block which has just finished loading. More accurately, HISTORY holds the location of the last text string parsed by WORD wherever that string was.
Used in a block like this:
Code:
SCR# 157
HISTORY @ CR U. CR
0 RAM LOAD

The number of the block currently loading will be displayed.
Here are some blocks of source where --> is only used in one block and THRU is used to load the range of blocks.
Code:
SCR# 200
[BEGIN]
   CR .( NUMBER ON THE STACK: )
   DUP .
   .(  BLK# R200) CR
-->

SCR# 201
   1-
   CR .( BLK# R201) CR

SCR# 202
   CR .( BLK# R202) CR
   ?DUP 0=
[UNTIL]
CR .( FINISHED)

--> chains the loading of ram blocks 200 and 201. Notice that there is no chaining of blocks 201 and 202. I will load these blocks with the new THRU .
Code:
200 RAM LIST
SCR# 16584  8 200
0:
1:
2:
3:
4:
5:
6:
7: [BEGIN]
8:    CR .( NUMBER ON THE STACK: )
9:    DUP .
A:
B:    .(  BLK# R200) CR
C:
D:
E:
F: -->
 OK
0 FH 2 FH THRU 16584
NUMBER ON THE STACK: 9  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 8  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 7  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 6  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 5  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 4  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 3  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 2  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 1  BLK# R200

BLK# R201
16586
BLK# R202

FINISHED OK

And success! --> was successfully used within a range of blocks loaded by THRU . All it did was advance the loading in place of the mechanism used by this new THRU . Since this new THRU gets the number of the latest block loaded from HISTORY , --> will not cause it to unintentionally load blocks multiple times.

One last test.
Code:
SCR# 200
[BEGIN]
   CR .( NUMBER ON THE STACK: )
   DUP .
   .(  BLK# R200) CR
-->

SCR# 201
   1-
   CR .( BLK# R201) CR

SCR# 202
   CR .( BLK# R202) CR
   ?DUP 0=
[UNTIL]
CR .( FINISHED)
-->

SCR# 203
CR .( LET'S PULL A FAST ONE ON THRU)
CR .( AND SEE IF IT RUNS WILD!!!)
CR .( RAM BLK # 203)
-->

SCR# 204
CR .( LET'S PULL A FAST ONE ON THRU)
CR .( AND SEE IF IT RUNS WILD!!!)
CR .( RAM BLK #204)

ram blocks 202, 203 and 204 have been chained together with --> .
Here is the session log.
Code:
200 RAM 202 RAM THRU 16584
NUMBER ON THE STACK: 10  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 9  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 8  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 7  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 6  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 5  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 4  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 3  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 2  BLK# R200

BLK# R201
16586
BLK# R202

NUMBER ON THE STACK: 1  BLK# R200

BLK# R201
16586
BLK# R202

FINISHED
LET'S PULL A FAST ONE ON THRU
AND SEE IF IT RUNS WILD!!!
RAM BLK # 203
LET'S PULL A FAST ONE ON THRU
AND SEE IF IT RUNS WILD!!!
RAM BLK #204 OK

The result is the same as if I had typed
Code:
200 RAM 204 RAM THRU

even if all instances of --> are removed from this last test typing
Code:
200 RAM 204 RAM THRU

has the same effect.
I honestly don't know if anyone has implemented THRU without a DO LOOP . This version of THRU is compatible with --> and the versions of [BEGIN] and [UNTIL] capable of spanning multiple blocks.


Top
 Profile  
Reply with quote  
PostPosted: Mon Mar 06, 2023 12:31 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 851

It is important when writing a new version of THRU that it not place anything on the data stack which could get in the way of parameters left from a previous screen of source. A definition spanning more than one block would need the data stack to be unaffected by THRU .
As an example, here is the source stored in 4 blocks in the C64's REU.
Code:
CR .S CR

With the numbers 1, 2 and 3 on the data stack the screens are loaded with THRU .
Here is the session log.
Code:
1 RAM 4 RAM THRU 16385
    1     2     3
16386
    1     2     3
16387
    1     2     3
16388
    1     2     3
 OK

As can bee seen, nothing gets in the way of these three parameters on the data stack.

Here is a smaller version of the new THRU .
Code:
: THRU  ( U1 U2 -- )
   >R
   BEGIN
      5 ?CR
      DUP U.  LOAD
      R@ HISTORY @ 1+ TUCK U<
      DONE? OR
   UNTIL
   R> 2DROP ;

This version of THRU is only eight bytes bigger than the original one which uses a DO LOOP .
Code:
: THRU  ( U1 U2 -- )
   1+ SWAP
   DO
      5 ?CR
      I DUP U. LOAD DONE? ?LEAVE
   LOOP ;

Although on my Forth, I took advantage of how DO LOOP's work to make the version with a DO LOOP two bytes smaller.
The original THRU uses a DO LOOP so the loop parameters are on the return stack when LOAD executes. The new version only places one parameter on the return stack which is good when THRU is nested multiple times.


Top
 Profile  
Reply with quote  
PostPosted: Thu Mar 09, 2023 2:37 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 851

Another advantage of this new implementation of THRU has to do with testing modifications to source. If a change is made to a word the following words can be compiled and tested one screen (Forth block with source) at a time. If the source for a word happens to span multiple screens then they should not be loaded one at a time. Such screens can be linked together by placing --> at the bottom of all but the last screen for a word with source spanning multiple screens.
Code:
   -->          --                            I,M,79   "next-block"
                --                            (compilation)       
        Continue interpretation on the next sequential block.  May
        be used within a colon definition that crosses a block
        boundary.

As I mentioned in my last post, the worst effect of mixing --> with the new implementation of THRU is causing the rest of the screens linked by --> to be loaded.

If it is not feasible or desirable to modify WORD to save the value of BLK , and optionally >IN , to a variable such as HISTORY , LOAD can be redefined to save BLK to a HISTORY variable just before the previous values of BLK and >IN are pulled from the return stack and restored.
A generic LOAD
Code:
: LOAD  ( BLK# -- )
   DUP 0= ABORT" CAN'T LOAD 0"
   BLK @ >IN @ 2>R
   >IN OFF BLK !
   INTERPRET
   
   2R> >IN ! BLK ! ;

and the modification.
Code:
VARIABLE HISTORY
: LOAD  ( BLK# -- )
   DUP 0= ABORT" CAN'T LOAD 0"
   BLK @ >IN @ 2>R
   >IN OFF BLK !
   INTERPRET
   BLK @ HISTORY !
   2R> >IN ! BLK ! ;

I suppose an implementation of THRU which is compatible with --> isn't exactly run of the mill. Whether it is or not, is that compatibility worthwhile?


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 63 posts ]  Go to page Previous  1, 2, 3, 4, 5  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 8 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: