6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun Jun 16, 2024 1:22 am

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: Sat Mar 04, 2023 3:47 am 
Offline

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

[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: 865

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

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  
PostPosted: Thu Mar 09, 2023 3:55 am 
Offline

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

I mentioned elsewhere that [BEGIN] and [UNTIL] may not be needed on a system with EVALUATE .


Top
 Profile  
Reply with quote  
PostPosted: Wed Apr 19, 2023 2:46 pm 
Offline

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

These are reasonably generic versions of the new LOAD and THRU .
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 ! ;
: THRU  ( U1 U2 -- )
   >R
   BEGIN
      5 ?CR
      DUP U.  LOAD
      R@ HISTORY @ 1+ TUCK U<
      DONE? OR
   UNTIL
   R> 2DROP ;

Only words from the Forth-83 Standard are used in the versions presented below. INTERPRET is from the controlled reference words and 2DROP is from the double number extension word set. All other words are from the required wordset.
Code:
VARIABLE HISTORY
: LOAD  ( BLK# -- )
   DUP 0= ABORT" CAN'T LOAD 0"
   BLK @ >IN @ >R >R
   0 >IN ! BLK !
   INTERPRET
   BLK @ HISTORY !
   R> R> >IN ! BLK ! ;
: THRU  ( U1 U2 -- )
   >R
   BEGIN
      DUP U.  LOAD
      HISTORY @ 1+ R@ OVER U<
   UNTIL
   R> 2DROP ;

In the definition of LOAD it may be tempting to store the value of BLK in HISTORY before INTERPRET when it is stored in BLK . Don't do it! With the version of LOAD which saves BLK in HISTORY it must be done AFTER INTERPRET because INTERPRET could change the block number if a screen has --> or the fancier version of [UNTIL] .
I mentioned in a previous post that a version of THRU which is compatible with --> makes it possible to chain together all the screens for a word spanning multiple screens without causing problems if those same screens are also in a range loaded with THRU. It is my hope that this will encourage Forth programmers keeping source in screens to use a structured layout rather than that unreadable horizontal format for source. I'm definitely in support of tools which encourage readable source.
This:
Code:
SCR# 125
// NUMBER?
: NUMBER?  ( ADR -- D FLAG )
   RB
   1+ DUP C@ ASCII # - DUP 3 U<
   IF
      BASE.TABLE + C@ BASE !
      COUNT
   THEN
   DROP
   DPL ON
   COUNT ASCII - <> DUP>R +
   COUNT DIGIT DUP>R  AND 0
   ROT
   -->

SCR# 126
// NUMBER?
   BEGIN
      1- CONVERT COUNT VALID?
   WHILE
      DPL OFF
      DUP C@ VALID?
   UNTIL
   THEN
   1- C@ BL =  R> AND
   R>  ?EXIT
   >R DNEGATE R> ;

vs this:
Code:
SCR# 125
// NUMBER?
: NUMBER?  ( ADR -- D FLAG )  RB 1+ DUP C@ ASCII # - DUP 3 U<
   IF   BASE.TABLE + C@ BASE ! COUNT   THEN   DROP DPL ON COUNT
   ASCII - <> DUP>R + COUNT DIGIT DUP>R  AND 0 ROT   BEGIN   1-
   CONVERT COUNT VALID?   WHILE   DPL OFF DUP C@ VALID?   UNTIL
   THEN   1- C@ BL =  R> AND R>  ?EXIT >R DNEGATE R> ;



Top
 Profile  
Reply with quote  
PostPosted: Sun Jun 25, 2023 9:52 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 865
JimBoyd wrote:
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).

I've noticed that some Forth programmers are uneasy about leaving a value on the return stack without removing it, and with good reason. I must admit that the definition of ENTER seems a little odd.
Code:
: ENTER  ( T-ADR -- )  >R ;

An address is placed on the return stack followed by EXIT or its equivalent, which is compiled by semicolon.
Here is a functionally equivalent definition of ENTER .
Code:
: ENTER  ( T-ADR -- )
   [ ASSEMBLER ] IP [ FORTH ] ! -;

Since -; does not compile EXIT , this version of ENTER is the same size.
Although do-colon changes the value of IP , what I wish to point out is that it first pushes the current value in IP onto the return stack. The address on the data stack is stored in IP . This version only works because ! (store) is a primitive. It stores the address in IP , drops the two values from the data stack and goes to NEXT . NEXT resumes address interpretation starting at the address T-ADR . When an EXIT is encountered, execution resumes in the word which called ENTER .

High level Forth words can branch using ?BRANCH and BRANCH . ENTER is similar to a subroutine call for high level Forth, with one major difference. The destination of the "subroutine call" is specified at run time which is like a computed GOSUB from BASIC .
I have the following in a few places in my Forth's system loader.
Code:
XXXX ENTER

where XXXX is a literal number known, or computed, at compile time. This is like a high level subroutine call, or a non computed GOSUB.

The following uses of ENTER are like a computed GOSUB .
Code:
   R@ ENTER
   <SOME.VARIABLE> @ ENTER
   <SOME.VALUE> ENTER

or even just ENTER by itself if the word using it requires an address to be on the data stack.
Code:
: TEXECUTE  ( T-ADR -- )  \ Execute trailing part of a high level word.
   ENTER ;


An STC version of ENTER would not just compile a subroutine call to an address known at compile time. It would not be this:
Code:
: ENTER  ( T-ADR -- )
   $20 C,  , ; IMMEDIATE

An STC version of ENTER for the 6502 would look like this:
Code:
: ENTER  ( T-ADR -- )
   1- >R ;

In an STC Forth for the 6502, any time an address is copied from the return stack for use with ENTER it would need adjusted.
For example, FORK would change from this:
Code:
: FORK  ( -- FLAG )
   TRUE  R@ ENTER  FALSE ;

to this:
Code:
: FORK  ( -- FLAG )
   TRUE  R@ 1+ ENTER  FALSE ;

Which is why I initially stated that it is a technique for an ITC Forth.


Top
 Profile  
Reply with quote  
PostPosted: Sun Jun 25, 2023 11:50 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8458
Location: Southern California
Thanks.  This kind of thing has crossed my mind before, but I never did it.  I don't remember what I might have wanted it for (and ended up taking a different approach).  Do you have real-life example usages?

_________________
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 Jun 29, 2023 2:47 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 865
GARTHWILSON wrote:
Thanks. This kind of thing has crossed my mind before, but I never did it. I don't remember what I might have wanted it for (and ended up taking a different approach). Do you have real-life example usages?
I do have some real-life examples. They may not be what M.L.Gassanenko envisioned in his paper Dynamically Structured Codes , but it's still early.

I've mentioned before that my Forth has EVALUATE . Since WORD parses from a BLOCK or the text input buffer, my Forth's EVALUATE saves the current values of TIB and #TIB as well as BLK and >IN .
Code:
: EVALUATE  ( ADR N -- )
   TIB #TIB @ 2>R
   #TIB ! (IS) TIB
   BLK 2@ 2>R
   BLK OFF >IN OFF
   INTERPRET
   2R> BLK 2!
   2R> #TIB ! (IS) TIB ;

(IS) gets compiled by IS and TO . Although IS will only write to the PFA of a DEFERred word and TO will only write to the body of a VALUE , (IS) will write to the body of anything.
This version of EVALUATE has a section which is similar to LOAD or LINELOAD . I can't have EVALUATE call LOAD or LINELOAD directly because both abort if trying to "load" block zero. I can use ENTER to enter the body of LINELOAD , which is used by LOAD , and return. The following is used twice in EVALUATE
Code:
#TIB ! (IS) TIB

I can reuse this section without factoring out a word useless for anything else.
Code:
: EVALUATE  ( ADR CNT -- )
   TIB #TIB @ 2>R
   LIT [ >MARK ] ENTER
   0 0 LIT
   [ ' LINELOAD >BODY DUP TRUE
     " LOAD 0" COUNT MATCH ?HUH
     + , ]
   ENTER
   2R>
   [ >RESOLVE ]
   #TIB ! (IS) TIB ;

The following line:
Code:
     " LOAD 0" COUNT MATCH ?HUH

searches for the string in LINELOAD and returns the offset to the end.
Code:
: LINELOAD  ( LINE# BLK# -- )
   DUP 0=
   ABORT" CAN'T LOAD 0"
   BLK 2@ 2>R
   BLK !  C/L * >IN !
   INTERPRET  2R>
   BRANCH [ BLK.2! , ] -;

Here is the decompilation of EVALUATE
Code:
SEE EVALUATE
EVALUATE
 16390  2761 TIB
 16392  2623 #TIB
 16394  3593 @
 16396  4768 2>R
 16398  3286 LIT 16416
 16402 13953 ENTER
 16404  3325 0
 16406  3325 0
 16408  3286 LIT 11109
 16412 13953 ENTER
 16414  4791 2R>
 16416  2623 #TIB
 16418  2230 !
 16420  7834 (IS)
 16422  2761 TIB
 16424  2480 EXIT
36
 OK

This version of EVALUATE is 14 bytes smaller. ENTER has a body size of 4 bytes, a 2 byte code field, 6 byte name field and a 2 byte link field for a total of 14 bytes.
I also saved a couple of bytes with two other words: VOCS and ORDER . VOCS shows the VOCABULARY tree structure and is recursive, but not in the sense that the entire word is recursively called.
Code:
: VOCS  ( -- )
   [ ' FORTH >BODY ] LITERAL 0  CR
   [ <MARK ]
   ?STACK  DUP>R SPACES
   DUP BODY> >NAME ID. CR
   VOC-LINK @
   BEGIN
      2DUP 2- @ =
      IF
         DUP 2- 2- R@ 2+ LIT
         CS-ROT  [ <RESOLVE ] ENTER
      THEN
      @ ?DUP 0=
   UNTIL
   R> 2DROP ;

The first line does not take part in the recursion; however, everything that follows does take part. The original version of VOCS factored out the recursive part as a :NONAME word. Although this version is only two bytes smaller, it's much easier to use with TRACE . Tracing by hand is fine when initially working out the logic for a word. Once a word is defined, the TRACE word is much more convenient. With the original version, I would have to either manually enter the addresses for <IP and IP> , the start and end limits for the trace range, or give the headerless word a name.
It's also possible to have a word with a recursive part within a larger recursive part.

ORDER is not recursive. It reuses a section of code.
Code:
: ORDER  ( -- )
   CONTEXT
   LIT [ >MARK ] ENTER
   CURRENT
   [ >RESOLVE ]
   CR DUP BODY> >NAME ID. ." : "
   @
   BEGIN
      DUP BODY> >NAME ID. CR
      2+ @ ?DUP  0EXIT
      9 SPACES
   AGAIN  -;


ENTER can also be used with other tools for diagnostics.
Tracing my Forth's new FORGET can be problematic. The new FORGET has NEXT> . NEXT> restores NEXT which switches off tracing. The word which was in the process of being traced resumes normal execution without tracing. Since The trace word I use, a modification of Blazin' Forth's trace word, single steps and waits for a key-press to continue, ENTER can help.
Code:
TRACE FORGET  OK
.S EMPTY  OK
FORGET BAD.WORD
NAME      EMPTY
CURRENT   29282
@         29282  2604
DUP       29282  9637
CONTEXT   29282  9637  9637
!         29282  9637  9637  2590
          29282  9637
?HUH      29278 65535
>LINK     29278
NEXT>     29267 TRACING OFF

Trace prints the name of the word which is about to be called and displays the data stack contents. The last line is where I pressed the RUN/STOP key to stop tracing before NEXT> was called. This not only stops the trace, it calls quit.
The following shows where I resume tracing FORGET . Instead of calling FORGET directly, I use ENTER to "GOSUB" into the body of FORGET . Including the headerless word for FORGET's new (FIND) , there are ten words to skip.
Code:
TRACE FORGET  OK
' FORGET >BODY 20 + ENTER
UNLINK    29267
SINGLE    29267
DUP>R     29267
FENCE     29267
@         29267  2531
LIT       29267 29218
UMAX      29267 29218 12054
U<        29267 29218
(ABORT")      0
VOC-LINK  EMPTY
R@         2675
TRIM       2675 29267
VOC-LINK  EMPTY
@          2675
DUP       26741
2-        26741 26741
2-        26741 26739
R@        26741 26737
TRIM      26741 26737 29267
@         26741
?DUP      24043
0=        24043 24043
?BRANCH   24043     0
DUP       24043
2-        24043 24043
2-        24043 24041
R@        24043 24039
TRIM      24043 24039 29267
@         24043
?DUP      24027
0=        24027 24027
?BRANCH   24027     0
DUP       24027
2-        24027 24027
2-        24027 24025
R@        24027 24023
TRIM      24027 24023 29267
@         24027
?DUP      24010
0=        24010 24010
?BRANCH   24010     0
DUP       24010
2-        24010 24010
2-        24010 24008
R@        24010 24006
TRIM      24010 24006 29267
@         24010
?DUP      14303
0=        14303 14303
?BRANCH   14303     0
DUP       14303
2-        14303 14303
2-        14303 14301
R@        14303 14299
TRIM      14303 14299 29267
@         14303
?DUP      12165
0=        12165 12165
?BRANCH   12165     0
DUP       12165
2-        12165 12165
2-        12165 12163
R@        12165 12161
TRIM      12165 12161 29267
@         12165
?DUP       9641
0=         9641  9641
?BRANCH    9641     0
DUP        9641
2-         9641  9641
2-         9641  9639
R@         9641  9637
TRIM       9641  9637 29267
@          9641
?DUP          0
0=            0
?BRANCH   65535
R>        EMPTY
DUP       29267
DP        29267 29267
!         29267 29267   872
LIT       29267
@         29267  2077
UMIN      29267 29218
LIT       29218
!         29218  2077
HERE      EMPTY
LIT       29267
BRANCH    29267  3053  OK

ENTER can be used to enter into the body of a high level word, with or without tracing, for diagnostic purposes. It can not be used to enter into the middle of a DO LOOP without causing problems, nor can it be used to enter into the middle of code which temporarily places items on the return stack; however, a word could be written which places the appropriate parameters on the return stack and calls ENTER .

In summary, not only can ENTER save a few bytes, it is also a handy diagnostic tool.
Regarding what M.L.Gassanenko mentions in his papers, I'll need to study that further.
[Edit: Corrected spelling: I just noticed that I misspelled M.L.Gassanenko's last name.]


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 28, 2023 7:40 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 865
GARTHWILSON wrote:
JimBoyd wrote:
GARTHWILSON wrote:
Dr Jefyll wrote:
One of the words I added to my version of FIG Forth is NIF (short for "not if").

Wow, looking at my own code, there are enough occurrences of 0= IF that I think that would pay for itself in memory, so there's no penalty for the faster execution. I think I would call it something else though unless "NIF" is already in common use, because it looks like a short form of "nifty" and definitely did not make me think "NOT IF." NOT in Forth XOR's the value with -1, rather than doing 0=.

I think I saw it called -IF but it has been a while and I can't remember where.

IF0 or IF_0 would be pretty intuitive.

What about these?
IFF -- IF FALSE
WHILEF -- WHILE FALSE
UNTILF -- UNTIL FALSE


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 28, 2023 8:24 pm 
Offline

Joined: Sun May 13, 2018 5:49 pm
Posts: 249
JimBoyd wrote:
Code:
: FORK  ( -- FLAG )
   TRUE  R@ 1+ ENTER  FALSE ;

Ooooooo... this one is fun. I wanted to print out some binary numbers so I tried:
Code:
: enter 1- >r ;  ok
: fork true R@ 1+ enter false ;  ok
fork . 0  ok
: test fork . ;  ok
test -1 0  ok
: print01 if 1 else 0 then 1 u.r ;  ok
5 print01 1 ok
0 print01 0 ok
: testmany fork print01 fork print01 fork print01 cr ;  ok
testmany 111
0
01
0
011
0
01
0
 ok

print01 prints a single 0 or 1 (using u.r so as to not print a space - I forget who showed me that, but it was someone here so thanks!). testmany is supposed to print all combinations of 3-digit binary values. What I actually got was not what I was expecting, but after thinking about it I realized it was exactly what I asked for. The issue is that it has to do all of the right hand forks before it will re-evaluate the left hand fork. I have the correct number of CRs, but not all of the digits for each line. Here is my "fix":
Code:
variable dig1  ok
variable dig2  ok
variable dig3  ok
: printdigs dig1 @ print01 dig2 @ print01 dig3 @ print01 cr ;  ok
printdigs 000
 ok
: testmany fork dig1 ! fork dig2 ! fork dig3 ! printdigs ; redefined testmany  ok
testmany 111
110
101
100
011
010
001
000
 ok
I never would have thought to write a FORK word for a non-multitasking system, but I can see where it might be useful.


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 28, 2023 9:03 pm 
Offline

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

You could define print01 as:
Code:
: PRINT01   1 AND  0 .R ;



Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 28, 2023 9:12 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 865
JimBoyd wrote:

You could define print01 as:
Code:
: PRINT01   1 AND  0 .R ;


This works on my system. A negative argument to SPACES (used by .R) prints nothing. Other systems might need print01 defined as this:
Code:
: PRINT01   1 AND  1 .R ;

Although it seems reasonable to me that a negative argument to SPACES should result in zero spaces without an error because SPACES takes a signed value.


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 28, 2023 10:56 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 865
SamCoVT wrote:
JimBoyd wrote:
Code:
: FORK  ( -- FLAG )
   TRUE  R@ 1+ ENTER  FALSE ;

Ooooooo... this one is fun. I wanted to print out some binary numbers so I tried:
Code:
: enter 1- >r ;  ok
: fork true R@ 1+ enter false ;  ok
fork . 0  ok
: test fork . ;  ok
test -1 0  ok
: print01 if 1 else 0 then 1 u.r ;  ok
5 print01 1 ok
0 print01 0 ok
: testmany fork print01 fork print01 fork print01 cr ;  ok
testmany 111
0
01
0
011
0
01
0
 ok

print01 prints a single 0 or 1 (using u.r so as to not print a space - I forget who showed me that, but it was someone here so thanks!). testmany is supposed to print all combinations of 3-digit binary values. What I actually got was not what I was expecting, but after thinking about it I realized it was exactly what I asked for. The issue is that it has to do all of the right hand forks before it will re-evaluate the left hand fork. I have the correct number of CRs, but not all of the digits for each line. Here is my "fix":
Code:
variable dig1  ok
variable dig2  ok
variable dig3  ok
: printdigs dig1 @ print01 dig2 @ print01 dig3 @ print01 cr ;  ok
printdigs 000
 ok
: testmany fork dig1 ! fork dig2 ! fork dig3 ! printdigs ; redefined testmany  ok
testmany 111
110
101
100
011
010
001
000
 ok
I never would have thought to write a FORK word for a non-multitasking system, but I can see where it might be useful.

Code:
: TESTMANY
   FORK  1 AND $30 +  PAD C!
   FORK  1 AND $30 +  PAD 1+ C!
   FORK  1 AND $30 +  PAD 2+ C!
   PAD 3 TYPE CR ;

Using ASCII for clarity:
Code:
: TESTMANY
   FORK  1 AND ASCII 0 +  PAD C!
   FORK  1 AND ASCII 0 +  PAD 1+ C!
   FORK  1 AND ASCII 0 +  PAD 2+ C!
   PAD 3 TYPE CR ;

ANSI Forth:
Code:
: TESTMANY
   FORK  1 AND [CHAR] 0 +  PAD C!
   FORK  1 AND [CHAR] 0 +  PAD 1+ C!
   FORK  1 AND [CHAR] 0 +  PAD 2+ C!
   PAD 3 TYPE CR ;



Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 29, 2023 12:54 pm 
Offline

Joined: Sun May 13, 2018 5:49 pm
Posts: 249
JimBoyd wrote:
... it seems reasonable to me that a negative argument to SPACES should result in zero spaces without an error because SPACES takes a signed value.
And so it does. The 2012 standard specifically denotes this behavior:
ANS 2012 wrote:
SPACES ( n -- ) If n is greater than zero, display n spaces.
I see that Tali2 assumes it is unsigned, so negative numbers give you LOTS of spaces, but passing it 0 does give you 0 spaces. I'll have to fix (add) the negative number handling.
JimBoyd wrote:
ANSI Forth:
Code:
: TESTMANY
   FORK  1 AND [CHAR] 0 +  PAD C!
   FORK  1 AND [CHAR] 0 +  PAD 1+ C!
   FORK  1 AND [CHAR] 0 +  PAD 2+ C!
   PAD 3 TYPE CR ;
2+ isn't in the 2012 ANS standard. Once I wrote it, this compiled and worked great. I totally forgot about PAD and that this is exactly what it was intended to be used for.


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 29, 2023 7:12 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 865
SamCoVT wrote:
2+ isn't in the 2012 ANS standard. Once I wrote it, this compiled and worked great. I totally forgot about PAD and that this is exactly what it was intended to be used for.

Code:
: TESTMANY
   FORK  1 AND [CHAR] 0 +  PAD C!
   FORK  1 AND [CHAR] 0 +  PAD 1+ C!
   FORK  1 AND [CHAR] 0 +  PAD 2 + C!
   PAD 3 TYPE CR ;

The nice thing about words like 2+ and DUP>R , it's easy to see where to split the name in the source to make it compatible.


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