6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Thu May 09, 2024 12:11 pm

All times are UTC




Post new topic Reply to topic  [ 338 posts ]  Go to page Previous  1 ... 17, 18, 19, 20, 21, 22, 23  Next
Author Message
PostPosted: Mon May 08, 2023 12:37 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 858
I once mentioned that my Forth would not have a permanent stack display and it will not; however, there is a way to add a stack display to the top of the screen on an as needed temporary basis. This could be handy when hand tracing yet to be compiled (as in, not finished) forth words.
I was trying to think of a simple background task to demonstrate the multitasker when I thought of this.
Fleet Forth's interpret has the word PAUSE as the first word in the interpreter loop.
Code:
: INTERPRET
   BEGIN
      PAUSE NAME C@ 0EXIT
      HERE I/C
   AGAIN -;

Fleet Forth's IS only works with DEFERred words and its TO only works with VALUEs. When compiling, they both compile (IS) . (IS) will alter the first cell of the parameter field of any word.
It is used to define words to 'patch' the interpreter.
Code:
: TOP.S
   $400 #160 BLANK
   XY 2>R  CHARS @ >R
   0 1 AT-XY CHARS OFF .S
   R> CHARS !  2R> AT-XY ;
: TOP.S.ON  ( -- )
   ['] TOP.S (IS) INTERPRET ;
: TOP.S.OFF  ( -- )
   ['] PAUSE (IS) INTERPRET ;
: PAGE
    PAGE  CR CR CR CR ;

TOP.S saves the current screen coordinates as well as the number of characters emitted since the last page or carriage return. It sets the screen coordinates to the beginning of the second line and displays the stack contents before restoring the screen coordinates and the number of characters emitted.
TOP.S.ON and TOP.S.OFF switch on and off this capability.
The new PAGE is to leave room for the stack display.
Because task switching can occur in .S , multitasking still works while interpreting/compiling.


Top
 Profile  
Reply with quote  
PostPosted: Sun May 28, 2023 9:44 pm 
Offline

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

I've been reviewing Fleet Forth's implementation of virtual memory and noticed there is room for improvement.
Code:
0 VALUE BANK
: >VIRTUAL  ( VADR -- ADR )
   U/BBUF RAM BANK 6 LSHIFT + BLOCK + ;

Virtual memory normally started at the beginning of memory in the REU , the ram expansion unit. higher 64 kilobytes banks could be selected by setting the value BANK to the desired bank; however, this seems a bit clumsy.
The new version uses the value VOFFSET for a block offset. The following sets the start of virtual memory at the start of the ram expansion unit.
Code:
0 RAM TO VOFFSET

The start of virtual memory can also be set to start at a higher address in the ram expansion unit; therefore BANK is no longer needed.
The following causes virtual memory to start 128 kilobytes into the ram expansion unit.
Code:
128 RAM TO VOFFSET

The following causes virtual memory to start 20 kilobytes into the ram expansion unit.
Code:
20 RAM TO VOFFSET

I feel that setting VOFFSET is a much nicer solution that setting a bank number.

I've been rereading Dick Pountain's "Object Oriented Forth". By pointing the start of virtual memory at one of the disk drives, the virtual memory words can be used to store records or make a persistent copy of virtual memory.
The following sets the start of virtual memory to point to the third disk drive. In my case, the 1581 with a capacity for 799 blocks.
Code:
0 2 DR+ TO VOFFSET

I've also defined >VIRTUAL to call a long address version >LVIRTUAL to allow for an optional long address set of virtual memory words.
Large virtual memory addresses used with >LVIRTUAL , and >VIRTUAL with a high value in VOFFSET , could be larger than what can be accessed with the highest block; therefore, >LVIRTUAL has range checking to make sure the block number doesn't wrap around.
Code:
// VOFFSET V >LVIRTUAL >VIRTUAL
0 RAM VALUE VOFFSET
CODE V  ( UD -- OFS BLK# F )
   3 ,X LDA  TAY
   3 # AND  3 ,X STA  TYA
   1 ,X LSR  0 ,X ROR  .A ROR
   1 ,X LSR  0 ,X ROR  .A ROR
   0 ,X LDY  0 ,X STA
   1 ,X LDA  1 ,X STY
   APUSH JMP  END-CODE
: >LVIRTUAL  ( VLADR -- ADR )
   V  VOFFSET 0 D+
   ABORT" BLOCK LIMIT"
   BLOCK + ;
: >VIRTUAL ( VADR -- ADR )
   0 >LVIRTUAL ;

CMOVE>V and CMOVEV> were rewritten for greater speed and the BEGIN loop in VMSAVE was cleaned up a little.
Code:
: #LIM  ( VADR CNT1 -- CNT2 )
   B/BUF ROT $3FF AND - UMIN ;

: CMOVE>V  ( ADR VADR CNT -- )
   BEGIN
      2DUP #LIM >R
      2PICK 2PICK >VIRTUAL R@
      CMOVE UPDATE
      ROT R@ + -ROT
      R> /STRING
      ?DUP 0=
   UNTIL
   2DROP ;

: CMOVEV>  ( VADR ADR CNT -- )
   BEGIN
      2PICK OVER #LIM >R
      2PICK >VIRTUAL 2PICK R@
      CMOVE
      ROT R@ + -ROT R> /STRING
      ?DUP 0=
   UNTIL
   DROP ;

: VMSAVE  ( AS AE+1 ANAME CT -- )
   1 CLOSE
   1 DR# 1 OPEN IOERR ?DISK
   OVER -
   OVER  SPLIT SWAP
   1 CHKOUT ?IO  DEMIT DEMIT CLRCHN
   BEGIN
      2DUP #LIM >R
      OVER >VIRTUAL R@
      1 CHKOUT ?IO DTYPE CLRCHN
      R> /STRING ?DUP 0=
   UNTIL
   DROP 1 CLOSE ;

#LIM is a helper word which takes a virtual address and a count. It returns the unsigned minimum of the count and the maximum size for a move which will not overflow the block buffer.
CMOVE>V and CMOVEV> are considerably faster when moving a large amount of data. When moving a few bytes, no so much.


Top
 Profile  
Reply with quote  
PostPosted: Sun Jun 11, 2023 9:54 pm 
Offline

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

Last year I mentioned the changes I made to Fleet Forth's (ABORT") (.") and (") , the primitives compiled by ABORT" ." and " .
I've made another change.
The source presented for (") .
Code:
: (")  ( -- ADR )
   R@
   >ASSEM
   IP )Y LDA  SEC
   IP ADC  IP STA
   CS IF  IP 1+ INC  THEN
   NEXT JMP  END-CODE

Nine bytes were shaved off this by modifying ?BRANCH .
The original ?BRANCH .
Code:
CODE ?BRANCH  ( F -- )
   INX  INX
   $FE ,X LDA  $FF ,X ORA
   0= NOT IF
      LABEL 2.IP.+!
      CLC
      IP LDA  2 # ADC  IP STA
      CS IF  IP 1+ INC  THEN
      NEXT JMP
      A>CS THEN  A>CS THEN  \ ?BRANCH skips
      PHA                   \ this.
      A>CS THEN             \ It's support code
      TYA  PHA  0 # LDY     \ for (LOOP) and (+LOOP).
   THEN
   LABEL BRANCH.BODY        \ The CFA of BRANCH points here.
   IP )Y LDA  PHA  INY
   IP )Y LDA  IP 1+ STA
   PLA  IP STA
   NEXT 2+ JMP  END-CODE

And the modification.
Code:
CODE ?BRANCH  ( F -- )
   INX  INX
   $FE ,X LDA  $FF ,X ORA
   0= NOT IF
      LABEL 2.IP.+!
      1 # LDA
      LABEL X.IP.+!
      SEC  IP ADC  IP STA
      CS IF  IP 1+ INC  THEN
      NEXT JMP
      A>CS THEN  A>CS THEN  \ ?BRANCH skips
      PHA                   \ this.
      A>CS THEN             \ It's support code
      TYA  PHA  0 # LDY     \ for (LOOP) and (+LOOP).
   THEN
   LABEL BRANCH.BODY        \ The CFA of BRANCH points here.
   IP )Y LDA  PHA  INY
   IP )Y LDA  IP 1+ STA
   PLA  IP STA
   NEXT 2+ JMP  END-CODE

The label X.IP.+! has nothing to do with the X-register. It is X in the sense of a variable quantity, specifically the value in the accumulator plus one.
These two different versions of ?BRANCH do the same thing by slightly different means. The modification allowed me to shorten (") by nine bytes.
The new code for (") .
Code:
: (")  ( -- ADR )
   R@
   >ASSEM
   IP )Y LDA
   X.IP.+! JMP
   END-CODE


This change to ?BRANCH necessitated a change to NEXT .
I have a modified version of Blazin' Forth's TRACE utility. Blazin' Forth's trace utility works by patching NEXT . On at least one occasion I neglected to turn tracing off with NOTRACE before forgetting a word which resulted in forgetting the trace words. Since NEXT was still patched, disaster struck when the dictionary grew to where the destination address in the patch to NEXT was overwritten.
NOTRACE calls NEXT> to restore NEXT so I decided to include it in Fleet Forth's kernel. I wrote a slightly smaller version of NEXT> for Fleet Forth's kernel.
Code:
CODE NEXT>  ( -- )  // RESTORE NEXT
   2 # LDY
   BEGIN
      2.IP.+! ,Y LDA
      NEXT1 ,Y STA  DEY
   0< UNTIL
   NEXT JMP  END-CODE

This works by copying three bytes from ?BRANCH to replace the JMP placed in NEXT by the trace word >NEXT .
Fleet Forth's NEXT was changed from this:
Code:
   LABEL NEXT
   1 # LDY
   IP )Y LDA  W 1+ STA  DEY
   IP )Y LDA  W    STA  CLC
   IP LDA  2 # ADC  IP STA
   CS NOT IF               \ The idea for this
      W 1- JMP             \ was from
   THEN                    \ Garth Wilson.
   IP 1+ INC
   W 1- JMP  END-CODE

To this:
Code:
   LABEL NEXT
   -->

SCR# 10
// NEXT
   1 # LDY
   IP )Y LDA  W 1+ STA  DEY
   IP )Y LDA  W    STA
   LABEL NEXT1
   1 # LDA
   SEC  IP ADC  IP STA
   CS NOT IF               \ The idea for this
      W 1- JMP             \ was from
   THEN                    \ Garth Wilson.
   IP 1+ INC
   W 1- JMP  END-CODE

FORGET was modified to include NEXT> . To clarify, Blazin' Forth's trace function replaces the first three bytes at the label NEXT1 with a jump instruction. FORGET , and EMPTY , will now restore NEXT .


Top
 Profile  
Reply with quote  
PostPosted: Tue Jun 20, 2023 9:15 pm 
Offline

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

I took a look at an experimental word I wrote in response to some posts about three years ago. The word SFORGET was an experimental word which would only forget smudged words. I thought it would be a good idea to incorporate that ability directly into FORGET so it could forget a word even if it was smudged. Here is my first attempt.
The relevant section of code for Fleet Forth's original FORGET .
Code:
: FORGET  ( -- )
   NAME  CURRENT @ DUP CONTEXT !
   VFIND ?HUH  >LINK


VFIND (vocabulary find) is like the find primitive (FIND) with one exception. It will not search parent vocabularies. It has no body of its own. It's code field points one byte into the body of (FIND) and it takes the same parameters: the address of the search string and the address of a vocabulary. It does not take the address of the latest word in a vocabulary. Assuming the address of a counted string is already on the data stack, it is used like this:

CURRENT @ VFIND
or
CONTEXT @ VFIND
or even
' FORTH >BODY VFIND

It is NOT used like this:

CURRENT @ @ VFIND

The word to be forgotten will only be forgotten if it is defined in the CURRENT vocabulary. The CONTEXT vocabulary will be set equal to the CURRENT vocabulary so there is no possibility of CONTEXT pointing to a vocabulary which was forgotten.
If the name was not found, ?HUH aborts with the message "WHAT?"
If the name was found, its CFA is converted to its LFA (link field address) the word's first field. This address is the "forget point."
It will be the new HERE and the lesser of it and the "empty point" will be the new "empty point." From this point on, FORGET will trim the VOC-LINK , a chain of all vocabularies. Of the vocabularies remaining in the VOC-LINK (the others will be forgotten) each of them will be trimmed. FORGET resets all deferred words which have a "vector" above the "forget point."
This is the modification to FORGET .
Code:
: FORGET  ( -- )
   NAME  CURRENT @ DUP CONTEXT !
   VFIND 0=
   IF
      DUP TSB  CURRENT @ VFIND ?HUH
   THEN
   >LINK


The idea is if the word is not found, smudge the count of the search string and try once more.
This approach only adds sixteen bytes to the system but it has at least two flaws.
First, it only works on systems which set the high bit at the end of a word's name to signal the end of the word not just for traversing a word but also for the find primitive.
Second, if the smudged word is a redefinition of another word, FORGET will find the other word and either forget that word or abort with the message that the word is "PROTECTED." This effectively removes the advantage of the new FORGET when testing an idea for an improved system word. I suppose the "smudged" version of the search string could be used first and if it is not found then use the version which is not smudged; however, I said this approach has at least two flaws. The third flaw is more of a philosophical point, yet it may have unforeseen repercussions. When a word is sought in a dictionary search, the most recent definition with that name is found. According the Forth-83 standard, FORGET searches the compilation vocabulary so the latest word in the compilation vocabulary with the sought after name should be the one found.

Given these three flaws, FORGET really should have a version of (FIND) of it's own, FFIND , FORGET FIND or FORGET's FIND . Only a slight modification of Fleet Forth's (FIND) is required to accommodate this new word with a minimal size increase.

(FIND) masks off the highest two bits of a dictionary entry's count so that even immediate words will be found but smudged ones will not. Fleet Forth's smudge bit has the value $20. The value ANDed to the count byte of an entry is $3F, all the bits of the count and the smudge bit are used in the comparison. A version of (FIND) which ANDs the value $1F would find a matching word even if it's name was smudged. This is a detriment to the Forth interpreter and compiler, but quite useful for FORGET .
The modification to (FIND) is to logically AND the entry's count byte with the value from a zero page location. (FIND) and VFIND will store a value of $3F in this location; therefore, they will behave as they always have. The new find word FFIND will store the value $1F in this location by loading $1F into the accumulator and branch into the body of (FIND) . FFIND will find a matching name regardless of whether it is smudged, but only in the specified vocabulary. Like VFIND it will not search parent vocabularies, which is perfect for FORGET . Obviously, I feel strongly that FORGET should only operate on the compilation vocabulary, specified by CURRENT because that is where new definitions are added. Here are the relevant sections of Fleet Forth's (FIND) .
Code:
CODE (FIND)  ( ADR VOC -- ADR2 F )
   DEY  N 1- STY   \ N-1 is a flag
         .         \ $FF = check parent vocabularies
         .         \ $00 = do not check parent vocabularies
         .
         N )Y LDA
         SBIT $1F OR # AND   \ combine smudge bit with count bits
         N 2+ )Y EOR         \ for the mask
         .
         .
         .

And the modifications:
Code:
NH    \ make the following word headerless
CODE FFIND  ( ADR VOC -- ADR2 F )
   $1F # LDA
   0= IF  CS>A  END-CODE    \ branch out of FFIND
CODE (FIND)  ( ADR VOC -- ADR2 F )
   DEY  SBIT $1F OR # LDA
   A>CS  THEN               \ branch from FFIND to this address
   N 1- STY  N 6 + STA
         .
         .
         .
         N )Y LDA   N 6 + AND
         N 2+ )Y EOR
         .
         .
         .

This works well enough. For a ram based system like Fleet Forth, it can be better with some Self Modifying Code.
Code:
NH                         \ headerless
CODE FFIND  ( ADR VOC -- ADR2 F )
   $1F # LDA
   0= IF  CS>A  END-CODE
CODE (FIND)  ( ADR VOC -- ADR2 F )
   DEY  SBIT $1F OR # LDA
   A>CS  THEN               \ FFIND branches to this address
   N 1- STY
   HERE 1+ >A  $BAD STA     \ Store the value in the accumulator
         .
         .
         .
         N )Y LDA   $1F # AND   \ into the operand for this AND immediate
         HERE 1- A> !           \ opcode
         N 2+ )Y EOR
         .
         .
         .

This version of (FIND) is only five bytes bigger. The headerless FFIND is only six bytes. The original FORGET gets one slight modification.
Code:
: FORGET  ( -- )
   NAME  CURRENT @ DUP CONTEXT !
   FFIND ?HUH
   >LINK


VFIND gets replaced with FFIND . This is a version of FORGET which will forget the most recent word in the compilation vocabulary which matches the search string regardless of whether it is smudged. Adding this ability only increases the size of Fleet Forth's kernel by eleven bytes.

Here is the full source for Fleet Forth's FORGET as it was before adding the ability to forget smudged words. As I said, the only difference is VFIND gets replaced with FFIND .
Code:
SCR# 14
// FORGET
: FORGET  ( -- )
   NAME  CURRENT @ DUP CONTEXT !
   VFIND ?HUH  >LINK
   LABEL (FORGET)
   NEXT> UNLINK SINGLE
   DUP>R FENCE @
   LIT
   LABEL KERNEL-FENCE
   [ 0 , ]  // WILL BE PATCHED
   UMAX U<
   ABORT" PROTECTED"
   VOC-LINK R@ TRIM
   VOC-LINK @
   -->

SCR# 15
// FORGET
   BEGIN
      DUP 2- 2- R@ TRIM
      @ ?DUP 0=
   UNTIL
   R> DUP DP ! [ 'THERE ] LITERAL @
   UMIN [ 'THERE ] LITERAL !
// RESET ANY SYSTEM DEFERRED WORD
// WITH CFA ABOVE HERE
   HERE [ END.FORGET ] LITERAL
// BRANCH INTO IORESET
   BRANCH [ (IORESET) , ] -;

Edit: I used the word any when I meant a. The word FFIND will find the most recent word in the specified vocabulary which matches the search string. Unlike (FIND) or VFIND it will find that word even if it is smudged. I also failed to mention that the modified (FIND) is not noticeably slower than the original because the extra work is performed before the first loop is entered.


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 22, 2023 1:09 pm 
Offline

Joined: Sun May 13, 2018 5:49 pm
Posts: 247
Is the idea here to be able to "clean up" after you've messed up a word a few times? Does the programmer keep running this version of forget until it can't find the word anymore in order to clear it all out?

When I make a mistake while defining a word, it's always in the back of my mind that I used up a bit of dictionary space, but It's never really been an issue because 32K of RAM (on my SBC) is so much RAM for Forth. The only exception is running Tali Forth 2's test suite. That DOES use more than 32K of RAM and I had to use MARKERs.

Tali Forth has MARKER instead of FORGET (we've discussed the pros and cons of each briefly elsewhere in this forum) which rewinds all of the wordlists back to a previous point in time. It looks like you are doing this for vocabularies so that you end up with almost identical effect.

How do you handle resetting the system deferred words? Do you save their previous values somewhere (although I think you'd need to save ALL of their previous values if that were the case) or just reset them back to their defaults? That seems like a good idea and I might add that to my list of improvements for Tali2. I currently don't handle that particular situation, but I envision that it would be more user friendly for users that don't understand all of the internals.


Top
 Profile  
Reply with quote  
PostPosted: Fri Jun 23, 2023 12:41 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 858
SamCoVT wrote:
Is the idea here to be able to "clean up" after you've messed up a word a few times? Does the programmer keep running this version of forget until it can't find the word anymore in order to clear it all out?

Sometimes I'll write experimental words to flesh out an idea. Each iteration helps work out the details to go from idea to something functional. There may be a few words with the same name after this prototyping. Some may be smudged or none may be smudged. This version of FORGET doesn't care. The latest name in the compilation vocabulary which matches the parsed string gets forgotten, as well as everything defined after that word.
As for forgetting multiple words with the same name, I do this:
FORGET <SOME.NAME> >IN OFF
When FORGET can no longer find <SOME.NAME> , it aborts.

Quote:
How do you handle resetting the system deferred words? Do you save their previous values somewhere (although I think you'd need to save ALL of their previous values if that were the case) or just reset them back to their defaults? That seems like a good idea and I might add that to my list of improvements for Tali2. I currently don't handle that particular situation, but I envision that it would be more user friendly for users that don't understand all of the internals.

Only a deferred word defined in the kernel is reset to its default value and only if its vector is above the new HERE .
This seemed like a prudent thing to do since some deferred words are used by the interpreter.
Care is required if a deferred word defined outside the kernel has its vector forgotten; however, this will not keep the Forth system from working.

Fleet Forth has a table of system deferred words and their default vectors.
Code:
LABEL START.I&F
' EMIT      >BODY ,  0 ,
' EXPECT    >BODY ,  0 ,
' KEY       >BODY ,  0 ,
' I/C       >BODY ,  0 ,
LABEL END.IORESET
' INITIAL   >BODY ,  0 ,
' ERR       >BODY ,  0 ,
' VALID?    >BODY ,  0 ,
' DR/W      >BODY ,  0 ,
' RR/W      >BODY ,  0 ,
' T&S       >BODY ,  0 ,
LABEL END.FORGET

The labels mark the start of the table and two different endpoints. IORESET also uses this table to reset EMIT EXPECT KEY and I/C . I/C isn't an I/O word. The cold and warm start routines call IORESET so this will also reset the interpreter's Interpret/Compile word if I type the Run/Stop and Restore keys.
Code:
: IORESET  ( -- )
   0
   [ END.IORESET ] LITERAL
   LABEL (IORESET)
   [ START.I&F ] LITERAL
   DO
      DUP  I @ @  U<
      IF
         I 2@ !
      THEN
   4 +LOOP
   DROP
   CLRCHN ;

For each of those four deferred words IORESET compares its vector with zero. FORGET places the new value of HERE and the address pointed to by the label END.FORGET on the data stack and branches into IORESET at the point with the label (IORESET) .

Here is where the default vectors are stored in the table.
Code:
START.I&F
" TARGET  DUP @ @ OVER 2+ ! 4 +
  END.FORGET OVER 2+ U<
  HOST >IN !"
COUNT EVALUATE  TARGET
DROP
COMMENT:
;S THIS SYSTEM HAS EVALUATE.
   THE STRING IS REINTERPRETED AS
LONG AS THE COMPARISON IS FALSE.
   IN THIS SYSTEM ANY VALUE OF >IN
GREATER OR EQUAL TO THE SIZE OF THE
TEXT STREAM ENDS INTERPRETATION.
;COMMENT

You may have noticed there is one system deferred word missing from this table. PAUSE is not included in the table because FORGET calls SINGLE to switch off multitasking.
Code:
: SINGLE  ( -- )
   ['] NOOP IS PAUSE ;

The cold start routine also calls EMPTY to reset the dictionary to its latest "empty point." EMPTY branches into the body of FORGET at the label (FORGET) .
Code:
: EMPTY  ( -- )
   FORTH DEFINITIONS
   [ 'THERE ] LITERAL @
   DUP FENCE !
// BRANCH INTO FORGET
   BRANCH [ (FORGET) , ] -;

Since the cold start routine calls both IORESET and EMPTY , it may seem that some of the deferred words are getting reset twice. EMPTY and FORGET will only reset a deferred word if its vector has been forgotten. IORESET will always reset the first four deferred words in the table.


Top
 Profile  
Reply with quote  
PostPosted: Sun Jul 09, 2023 10:42 pm 
Offline

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

Tracing by hand is useful when working out the logic of a yet to be written word. If a word is already defined, a TRACE word is useful to help find out where something is going wrong or verify that a word is working properly. A TRACE word is also useful when a hand trace is impractical, such as when testing coroutines.
I have on more than one occasion mentioned Blazin' Forth's TRACE word. I had a version of TRACE modified to work with Fleet Forth and have enhanced it to some degree. I finally got around to writing Fleet Forth's own TRACE word inspired by Blazin' Forth, but taking advantage of Fleet Forth's >FORTH and >ASSEM . Fleet Forth's TRACE functionality, while having a look and feel similar to Blazin' Forth's TRACE , is implemented differently.
>NEXT and NEXT> are used to patch and restore NEXT .
Blazin' Forth's word >NEXT patches NEXT roughly in the middle. Fleet Forth's >NEXT patches NEXT as close to the beginning as possible. There is a primitive, BRANCH , which jumps to NEXT 2+ to save two cycles because it leaves a one in the Y-register; therefore, the earliest point NEXT can be patched, without re-implementing BRANCH , is two bytes in.
Code:
CODE >NEXT ( ADR -- )
   // POINTS NEXT TO ADR
   $4C # LDA  NEXT 2+ STA
   0 ,X LDA  NEXT 3 + STA
   1 ,X LDA  NEXT 4 + STA
   POP JMP  END-CODE

This leaves the first instruction of NEXT intact and overwrites the next two. The other instructions in NEXT are intact.
NEXT> restores NEXT . It is included in the kernel so FORGET can restore NEXT .
Code:
   LABEL NEXT
   1 # LDY
   LABEL NEXT1
   IP )Y LDA  W 1+ STA  DEY
   LABEL NEXT2
   IP )Y LDA  W STA
   1 # LDA
   SEC  IP ADC  IP STA
   CS NOT IF
      W 1- JMP
   THEN
   IP 1+ INC
   W 1- JMP  END-CODE

CODE NEXT>  ( -- )  // RESTORE NEXT
   2 # LDY
   BEGIN
      NEXT2 ,Y LDA
      NEXT1 ,Y STA   DEY
   0< UNTIL
   NEXT JMP  END-CODE

Two variables, <IP and IP> , are used to check if IP is in the address range for a TRACE .
TRACE gets a name from the text stream and uses AFIND to find where that word ends.
AFIND takes an address, any address, and finds the nearest link fields below and above that address. It checks all vocabularies. It's reasonably fast because it uses a primitive to search a vocabulary for the nearest link fields above and below. Fleet Forth already uses this word for some components of SEE .
Code:
: TRACE  ( ++ )
   NEXT>
   ' AFIND NIP  IP> ! <IP !
   STEP >NEXT ;

STEP is a SUBR , a subroutine in Fleet Forth. It returns its PFA just like a VARIABLE . TRACE stores the beginning and end of a word in <IP and IP> respectively and patches NEXT to jump to the body of STEP .
Code:
SUBR STEP
   <IP LDA  IP CMP
   <IP 1+ LDA  IP 1+ SBC
   CS NOT IF
      IP> LDA  IP CMP
      IP> 1+ LDA  IP 1+ SBC
   CS NOT ELIF
   BEGIN  CS-SWAP
      IP )Y LDA  W 1+ STA
      NEXT 6 + JMP
   THEN
   >FORTH
   NEXT>  R@ HLD @ >R +PAD
   CR .STEP
   KEY DUP ASCII" {RUN/STOP}" =
   IF  DROP NOTRACE  THEN
   ASCII" {CONTROL-P}" =
   IF
      CON OFF
      BEGIN
         CR ." P? "  QUERY INTERPRET
         STATE @ 0= IF  ."  OK" THEN
         CON @
      UNTIL
   THEN
   -PAD  R> HLD !  RECURSE >NEXT
   >ASSEM
   INY
   0= UNTIL  END-CODE

The following tests if IP is in the range for a TRACE .
Code:
   <IP LDA  IP CMP
   <IP 1+ LDA  IP 1+ SBC
   CS NOT IF
      IP> LDA  IP CMP
      IP> 1+ LDA  IP 1+ SBC
   CS NOT ELIF

BEGIN CS-SWAP just marks the destination for a backward jump and places that information under the control flow data for the IF ELIF THEN structure.
When IP is not in range, NEXT needs to run to completion. Since the Y-register is not altered by the comparison, it still holds a value of one. The following does the work of the overwritten instructions in NEXT and jumps to the rest of NEXT .
Code:
      IP )Y LDA  W 1+ STA
      NEXT 6 + JMP

When IP is in range, the rest of STEP runs.
Code:
   >FORTH
   NEXT>  R@ HLD @ >R +PAD
   CR .STEP
   KEY DUP ASCII" {RUN/STOP}" =
   IF  DROP NOTRACE  THEN

STEP shifts to high level Forth. NEXT> switches off tracing. The top of the return stack is fetched. This will be the value of IP pushed there by the shift to high level Forth. The current value of HLD is pushed on the return stack and the gap between HERE and PAD is altered.
Code:
: +PAD
   $FF ?MEM
   [ ' PAD >BODY 4 + ] LITERAL C! ;

This allows tracing pictured numeric output words.
.STEP displays the information such as the name of the word about to be executed and the contents of the data stack, for this step. .STEP is a DEFERred word so it can be set to an appropriate display word for the given trace.
Code:
DEFER .STEP
: (.ST1)  ( IP -- )
   @ .NAME TAB .S ;
' (.ST1) IS .STEP
: (.ST2)  ( IP -- )
   (.ST1)  CR TAB .AS ;
: (.ST3)  ( IP -- )
   (.ST2)
   2R> .RS 2>R ;

(.ST2) would be useful when tracing a word which uses the auxiliary stack and (.ST3) would be useful when tracing coroutine words since it shows the contents of the return stack.
{RUN/STOP} and {CONTROL-P} are where the RUN/STOP and CONTROL-P keys are used on the Commodore 64. They do not display properly in a print dump.
If the RUN/STOP key is pressed, tracing stops. STEP calls NOTRACE which restores NEXT , restores the gap between HERE and PAD then QUITs. Before NOTRACE runs, NEXT is already restored, but NOTRACE can also be used at the command line.
Code:
: -PAD
   $55
   BRANCH [ ' +PAD >BODY 5 + , ] -;

: NOTRACE
   NEXT>  -PAD
   ." TRACING OFF"  QUIT -;

If the Control-P key combination is pressed, a mini quit loop runs. This is useful for checking other data or changing some of the items on the return stack.
Code:
   ASCII" {CONTROL-P}" =
   IF
      CON OFF
      BEGIN
         CR ." P? "  QUERY INTERPRET
         STATE @ 0= IF  ."  OK" THEN
         CON @
      UNTIL
   THEN

The mini quit loop runs until the word CONT is entered.
Code:
VARIABLE CON
: CONT   CON ON ;

If a word being traced is about to branch out to another word, the trace can be changed to that word.
An example is FORGET . It branches into IORESET . When the trace shows that the branch to IORESET is to be executed, I can press Control-P. While in the mini quit loop I can type the following to resume tracing where FORGET branches.
Code:
TRACE IORESET
CONT

Code:
   -PAD  R> HLD !  RECURSE >NEXT
   >ASSEM
   INY
   0= UNTIL  END-CODE

STEP restores the gap between HERE and PAD and restores the previous value of HLD .
Although RECURSE is used, STEP is not recursive. RECURSE compiles STEP which places its PFA on the data stack. This value is used to patch NEXT so tracing can continue. >ASSEM shifts the word to low level code. In the process of shifting to low level >ASSEM performs the function of EXIT by pulling the top value from the return stack and storing it in IP . At this point the Y-register has a value of zero. It is incremented followed by a jump to the address marked by BEGIN .
Code:
   BEGIN  CS-SWAP
      IP )Y LDA  W 1+ STA
      NEXT 6 + JMP


There is another tool I like from Blazin' Forth, WATCH . WATCH watches two consecutive memory locations (16 bits) such as a VARIABLE , VALUE or other location which is getting changed unexpectedly. Here is Fleet Forth's implementation.
Code:
: WATCH ( ADR -- )
   DUP @ EYE !
   // PATCH LOOK
   DUP LOOK 1+ !
   1+ LOOK 9 + !
   LOOK >NEXT ;

WATCH stores the original 16 bit value from the address on the stack in the variable EYE . It then patches the SUBRoutine LOOK with the address.
Code:
VARIABLE EYE
SUBR LOOK
   TRUE LDA EYE CMP
   0= IF
      TRUE LDA EYE 1+ CMP
      0= IF
         IP )Y LDA  W 1+ STA
         NEXT 6 + JMP
      THEN
   THEN
   INY
   BEGIN
      NEXT 7 + ,Y LDA
      NEXT 2+ ,Y STA  DEY
   0< UNTIL
   >FORTH
   .RS  TRUE
   ABORT" WATCHED MEMORY ALTERED" -;

The following tests if the memory locations have been altered.
Code:
   TRUE LDA EYE CMP
   0= IF
      TRUE LDA EYE 1+ CMP
      0= IF

At this point the Y-register has a value of one. If the memory under observation has not been changed, go back to NEXT the same as STEP
Code:
         IP )Y LDA  W 1+ STA
         NEXT 6 + JMP

If the memory under observation has been altered, NEXT must be restored before shifting to high level to avoid "infinite recursion."
Code:
   INY
   BEGIN
      NEXT 7 + ,Y LDA
      NEXT 2+ ,Y STA  DEY
   0< UNTIL

With NEXT restored, LOOK shifts to high level. The high level section displays the return stack with .RS and aborts with the message "WATCHED MEMORY ALTERED."
The session log of the test.
Code:
 OK
VARIABLE NONESUCH  OK
$1234 NONESUCH !  OK
NONESUCH WATCH  OK
: TEST
    1 NONESUCH +! ;  OK
: TEST2   TEST ;  OK
: TEST3   TEST2 ;  OK
TEST3
22464 LOOK
23515 TEST
23531 TEST2
23547 TEST3
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

TEST3
^^^^^
WATCHED MEMORY ALTERED



Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 18, 2023 8:12 pm 
Offline

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

I wish I had Fleet Forth's new TRACE back when I presented coroutines in Fleet Forth.
.RS was rewritten to support an improvement to (.ST3) which removes another artifact of the trace from the display of the return stack.
Code:
: .RS  ( -- )
   RP0 @ RP@
   SETWIDTH
   DO
      CR I 1+ @ DUP U.W
      2- AFIND 2PICK U< 0= OVER 0<>
      AND AND NIP ?DUP
      IF
         L>NAME ID.
      THEN
      2
   +LOOP -;
   ' CR >BODY  HERE 2- @ 2- !

.RS is one cell smaller and the first three words in .RS are now
Code:
RP0 @ RP@

which makes the improved (.ST3) slightly smaller.
Code:
: (.ST3)  ( IP -- )
   (.ST2)  RP0 @ RP@ 2+ 2+
   BRANCH [ ' .RS >BODY 6 + , ] -;

(.ST4) adds a few carriage returns for clarity.
Code:
: (.ST4)  ( IP -- )
   [ ' CR >BODY ] LITERAL >R
   CR (.ST2) CR  RP0 @ RP@ 6 +
   BRANCH [ ' .RS >BODY 6 + , ] -;

The following coroutine words were used as a test of TRACE using (.ST3) for the step display.
Code:
: CO1
   BEGIN  1+ DUP . CO  AGAIN ;
: CO2
   CR 0 CO1
   BEGIN
      1- DUP . CO DONE?
   UNTIL
   R> 2DROP ;

This is part of a session log from the trace.
Code:
' (.ST3) IS .STEP  OK
TRACE CO2  OK
' CO1 <IP !  OK
CO2
CR          EMPTY
            EMPTY
23511 CO2
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT


0           EMPTY
            EMPTY
23513 CO2
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

CO1             0
            EMPTY
23515 CO2
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

1+              0
            EMPTY
23489 CO1
23517 CO2
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

DUP             1
            EMPTY
23491 CO1
23517 CO2
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

.               1     1
            EMPTY
23493 CO1
23517 CO2
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT
1
CO              1
            EMPTY
23495 CO1
23517 CO2
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

1-              1
            EMPTY
23517 CO2
23497 CO1
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

DUP             0
            EMPTY
23519 CO2
23497 CO1
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

.               0     0
            EMPTY
23521 CO2
23497 CO1
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT
0
CO              0
            EMPTY
23523 CO2
23497 CO1
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

BRANCH          0
            EMPTY
23497 CO1
23525 CO2
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT
TRACING OFF
CONSOLE

Code:
TRACE CO2
sets <IP and IP> to the beginning and end of CO2 .
Code:
' CO1 <IP !
sets <IP to the beginning of CO1 .
CO1 is defined before CO2 ; therefore, both words will be traced. If any words had been defined between CO1 and CO2 they would also have been traced when executed.
CO1 and CO2 can be seen trading places in IP and on the top of the return stack every time CO is executed.

what the trace looks like using (.ST1) .
Code:
' (.ST1) IS .STEP  OK
TRACE CO2  OK
' CO1 <IP !  OK
CO2
CR          EMPTY

0           EMPTY
CO1             0
1+              0
DUP             1
.               1     1 1
CO              1
1-              1
DUP             0
.               0     0 0
CO              0
BRANCH          0 TRACING OFF
CONSOLE

An excerpt from the session log with comments added.
Code:
.               0     0   \ Word to be executed and data stack contents.
            EMPTY         \ Auxiliary stack contents.
23521 CO2                 \ Contents of IP.
23497 CO1                 \ These four lines show
 8557 (I/C)               \ the contents of
 8629 INTERPRET           \ the return stack.
 8724 QUIT                \
0                         \ Value displayed by . (dot)

Jut to be clear, that period is not an intrinsic part of the display when using (.ST3) . It is the Forth word dot.
Note that the word shown in the TRACE has not been executed yet. The display shows the contents of the stacks and IP prior to execution.
If CO2 were executed from the command line, the information shown during the trace is exactly what would be in IP and the stacks.
Unlike a hand trace, TRACE can trace a word which stores items on the return stack. Using (.ST3) for the step display word makes it easier to follow what is happening when tracing more complex coroutine words such as WITH-WORDS .
Code:
: WITH-WORDS  ( -- NFA )
   CONTEXT @
   AHEAD
   BEGIN
      DUP R@ 2>R L>NAME CO R>
   CS-SWAP THEN
      @ DUP 0= DONE? OR
   UNTIL
   R> 2DROP ;

A simple word using WITH-WORDS was defined to demonstrate tracing WITH-WORDS .
Code:
: #WORDS  ( -- N )
   0 WITH-WORDS  DROP 1+ ;

A testbed vocabulary was created and two words defined. This is an excerpt from the session log showing the trace of WITH-WORDS
Code:
TRACE WITH-WORDS  OK
#WORDS
CONTEXT       0
          EMPTY
16950 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

@             0  2590
          EMPTY
16952 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

BRANCH        0 23592
          EMPTY
16954 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

@             0 23592
          EMPTY
16970 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

DUP           0 23615
          EMPTY
16972 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

0=            0 23615 23615
          EMPTY
16974 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

DONE?         0 23615     0
          EMPTY
16976 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

OR            0 23615     0     0
          EMPTY
16978 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

?BRANCH       0 23615     0
          EMPTY
16980 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

DUP           0 23615
          EMPTY
16958 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

R@            0 23615 23615
          EMPTY
16960 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

2>R           0 23615 23615 23572
          EMPTY
16962 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

L>NAME        0 23615
          EMPTY
16964 WITH-WORDS
23572 #WORDS
23615 FOOBAR
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

CO            0 23617
          EMPTY
16966 WITH-WORDS
23572 #WORDS
23615 FOOBAR
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

R>            1
          EMPTY
16968 WITH-WORDS
23615 FOOBAR
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

@             1 23615
          EMPTY
16970 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

DUP           1 23600
          EMPTY
16972 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

0=            1 23600 23600
          EMPTY
16974 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

DONE?         1 23600     0
          EMPTY
16976 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

OR            1 23600     0     0
          EMPTY
16978 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

?BRANCH       1 23600     0
          EMPTY
16980 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

DUP           1 23600
          EMPTY
16958 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

R@            1 23600 23600
          EMPTY
16960 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

2>R           1 23600 23600 23572
          EMPTY
16962 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

L>NAME        1 23600
          EMPTY
16964 WITH-WORDS
23572 #WORDS
23600 TESTBED
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

CO            1 23602
          EMPTY
16966 WITH-WORDS
23572 #WORDS
23600 TESTBED
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

R>            2
          EMPTY
16968 WITH-WORDS
23600 TESTBED
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

@             2 23600
          EMPTY
16970 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

DUP           2     0
          EMPTY
16972 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

0=            2     0     0
          EMPTY
16974 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

DONE?         2     0 65535
          EMPTY
16976 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

OR            2     0 65535     0
          EMPTY
16978 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

?BRANCH       2     0 65535
          EMPTY
16980 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

R>            2     0
          EMPTY
16984 WITH-WORDS
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

2DROP         2     0 23572
          EMPTY
16986 WITH-WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

EXIT          2
          EMPTY
16988 WITH-WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT
 OK
. 2  OK

And an excerpt showing the trace of #WORDS
Code:
TRACE #WORDS  OK
.S EMPTY  OK
#WORDS
0         EMPTY
          EMPTY
23568 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

WITH-WORDS              0
          EMPTY
23570 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

DROP          0 23617
          EMPTY
23572 #WORDS
16968 WITH-WORDS
23615 FOOBAR
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

1+            0
          EMPTY
23574 #WORDS
16968 WITH-WORDS
23615 FOOBAR
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

EXIT          1
          EMPTY
23576 #WORDS
16968 WITH-WORDS
23615 FOOBAR
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

DROP          1 23602
          EMPTY
23572 #WORDS
16968 WITH-WORDS
23600 TESTBED
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

1+            1
          EMPTY
23574 #WORDS
16968 WITH-WORDS
23600 TESTBED
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

EXIT          2
          EMPTY
23576 #WORDS
16968 WITH-WORDS
23600 TESTBED
23572 #WORDS
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT
 OK
. 2  OK
CONSOLE


There is one caveat. Never trace STEP !
STEP will only run during a trace. Executing STEP returns its PFA ; therefore, the following is harmless.
Code:
TRACE STEP

However, addresses can be stored in <IP and IP> to trace a range of words. If STEP is within that range, when one of the words in that range is traced, STEP will try to trace itself recursively.

WITH-WORDS and RB are two coroutine words which don't strictly behave as coroutines. Rather than switching places with another coroutine, each of these is a creative use of the coroutine word CO . There is another Fleet Forth word, IN-NAME , which searches all the names of the CONTEXT VOCABULARY for a sub string. It uses the coroutine words AFTER-DROP and WITH-WORDS .
Code:
: AFTER-DROP  ( N -- )
   CO DROP ;

(.ST3) really shines when analyzing the execution of a word like IN-NAME or even Fleet Forth's version of EVALUATE .
The source.
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 de-compilation.
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

And the log of the test run.
Code:
 OK
: MESSAGE   ." NOTHING TO SEE HERE!" ;  OK
" CR MESSAGE CR TAB MESSAGE"  OK
.S 23609  OK
COUNT EVALUATE
NOTHING TO SEE HERE!
          NOTHING TO SEE HERE! OK
SEE .STEP
.STEP DEFERED TO (.ST3) 22251  OK
TRACE EVALUATE  OK
.S EMPTY  OK
PAD COUNT EVALUATE
TIB       23610    25
          EMPTY
16390 EVALUATE
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

#TIB      23610    25   679
          EMPTY
16392 EVALUATE
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

@         23610    25   679  2625
          EMPTY
16394 EVALUATE
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

2>R       23610    25   679    18
          EMPTY
16396 EVALUATE
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

LIT       23610    25
          EMPTY
16398 EVALUATE
   18
  679
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

ENTER     23610    25 16416
          EMPTY
16402 EVALUATE
   18
  679
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

#TIB      23610    25
          EMPTY
16416 EVALUATE
16404 EVALUATE
   18
  679
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

!         23610    25  2625
          EMPTY
16418 EVALUATE
16404 EVALUATE
   18
  679
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

(IS)      23610
          EMPTY
16420 EVALUATE
16404 EVALUATE
   18
  679
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

EXIT      EMPTY
          EMPTY
16424 EVALUATE
16404 EVALUATE
   18
  679
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

0         EMPTY
          EMPTY
16404 EVALUATE
   18
  679
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

0             0
          EMPTY
16406 EVALUATE
   18
  679
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

LIT           0     0
          EMPTY
16408 EVALUATE
   18
  679
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

ENTER         0     0 11109
          EMPTY
16412 EVALUATE
   18
  679
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

NOTHING TO SEE HERE!
          NOTHING TO SEE HERE!
2R>       EMPTY
          EMPTY
16414 EVALUATE
   18
  679
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

#TIB        679    18
          EMPTY
16416 EVALUATE
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

!           679    18  2625
          EMPTY
16418 EVALUATE
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

(IS)        679
          EMPTY
16420 EVALUATE
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT

EXIT      EMPTY
          EMPTY
16424 EVALUATE
 8557 (I/C)
 8629 INTERPRET
 8724 QUIT
 OK


In summary: Fleet Forth's TRACE is, in my opinion, a cleaner implementation of TRACE than the one from Blazin' Forth. The biggest improvement is also the simplest. The use of a DEFERred word to display the information for a step. A custom display word can be written to display the most relevant information for a given trace.


Top
 Profile  
Reply with quote  
PostPosted: Fri Jul 21, 2023 11:21 pm 
Offline

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

I have noticed the occasional programming challenge on this forum. Here is a programming challenge in Forth.
While improving some feature in Fleet Forth, I wrote a test word and the results were as expected. The word begins like this:
Code:
: TEST  ( LO HI -- )
   1+ SWAP
   DO

and end like this:
Code:
   LOOP ;

Here is a session log showing interactive use of the test word. I typed TEST (as well as its two parameters) and I typed RESET . Everything else is Fleet Forth's response.
Code:
0 5 TEST
HEAD:   0
TAIL:   1^
        2^^
        3^^^
        4^
        5^^ OK
0 5 TEST
        0^^^
        1^
        2^^
        3^^^
        4^
        5^^ OK
0 19 TEST
        0^^^
        1^
        2^^
        3^^^
        4^
        5^^
        6^^^
        7^
        8^^
        9^^^
       10^
       11^^
       12^^^
       13^
       14^^
       15^^^
       16^
       17^^
       18^^^
       19^ OK
20 39 TEST
       20^^
       21^^^
       22^
       23^^
       24^^^
       25^
       26^^
       27^^^
       28^
       29^^
       30^^^
       31^
       32^^
       33^^^
       34^
       35^^
       36^^^
       37^
       38^^
       39^^^ OK
-10 10 TEST
      -10^
       -9^^
       -8^^^
       -7^
       -6^^
       -5^^^
       -4^
       -3^^
       -2^^^
       -1^
        0^^
        1^^^
        2^
        3^^
        4^^^
        5^
        6^^
        7^^^
        8^
        9^^
       10^^^ OK
RESET  OK
-10 10 TEST
HEAD: -10
TAIL:  -9^
       -8^^
       -7^^^
       -6^
       -5^^
       -4^^^
       -3^
       -2^^
       -1^^^
        0^
        1^^
        2^^^
        3^
        4^^
        5^^^
        6^
        7^^
        8^^^
        9^
       10^^ OK
0 5 TEST
        0^^^
        1^
        2^^
        3^^^
        4^
        5^^ OK
RESET  OK
0 5 TEST
HEAD:   0
TAIL:   1^
        2^^
        3^^^
        4^
        5^^ OK

Can any of my fellow Forthwrites deduce a likely body for the DO LOOP in TEST ?


Top
 Profile  
Reply with quote  
PostPosted: Sat Jul 22, 2023 6:33 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1928
Location: Sacramento, CA, USA
Inside the DO loop you appear to have I . and a variable or other persistent cell that you're using for the caret count and resetting to 1 if it hits 4 after incrementing. RESET seems to return the variable to 0, which also triggers the HEAD: TAIL: response when TEST sees it. That's about as far as my feeble limits can carry me at present. Am I nipping at the wrong tire?

_________________
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: Sun Jul 23, 2023 8:49 pm 
Offline

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

First, thank you for your response.
I stated in my post that I wrote the test word while improving some feature in Fleet Forth; therefore, it's going to be more generic than just using a counter.
Not too long ago I read about how coroutines do not play well with DO LOOPs. Consider two coroutine words which trade places with each other as the currently running word, the norm for coroutines. If one of the coroutine words has a BEGIN loop, it can launch the other coroutine word before entering the BEGIN loop and can remove the other coroutine word after leaving the BEGIN loop. When one of the coroutine words has a DO LOOP , anything which alters the return stack within the body of the loop has to restore it before the next iteration or before the loop terminates. Parameters can not be passed into the loop on the return stack. Not portably. To launch the second coroutine word in a loop requires knowing if this is the first pass through the loop. A VARIABLE can be used for this, but the variable would need reset before entering the loop to insure the coroutine word is launched. The difficult problem is removing the other coroutine word from the return stack on the last pass through the loop.
I realized a halting colon definition does not have this problem. Neither does Leo Brodie's DOER/MAKE .
The source for halting colon definitions was improved to add resetting a halting colon definition.
Code:
: H:
   :  ( -- ADR TRUE )
      HERE 2+ ,  [ HERE 2+ >A ]
   DOES>  ( -- )
      @ >R ;
: (HALT)  ( R: ADR -- )
   R@ 2+  R> @ >BODY ! ;
: ?H: ( CFA -- )
   @ [ A> ] LITERAL <>
   ABORT" NON HALTING WORD" ;
: HALT   LATEST NAME> ?H:
   COMPILE (HALT) [COMPILE] RECURSE
   ; IMMEDIATE
: HRESET  ( CFA -- )
   DUP ?H: >BODY DUP 2+ SWAP! ;

Here is the source for TEST and RESET
Code:
: TEST  ( LO HI -- )
   1+ SWAP
   DO
     STREAM  I 3 .R  ARROWS
   LOOP ;
: RESET
   ['] ARROWS HRESET
   ['] STREAM HRESET ;

STREAM and ARROWS are both halting colon definitions.
Code:
H: STREAM  ( N -- )
   CR ." HEAD: " HALT
   CR ." TAIL: "
   BEGIN
      HALT
      CR 6 SPACES
   AGAIN -;
H: ARROWS  ( -- )
   BEGIN
      HALT  ." ^"
      HALT  ." ^^"
      HALT  ." ^^^"
   AGAIN -;

As I mentioned in the post on halting colon definitions, HALT is like an EXIT which also tells the halting colon definition where to resume the next time it is run.
A halting colon definition isn't as efficient as a coroutine word. One cell is required to compile CO. HALT is an immediate word which compiles two cells. A halting colon definition can be used in places where a coroutine word can't. On the other hand, coroutines can be used where a halting colon definition can't. One example is RB . A word which calls RB can, without problems, call another word which calls RB because RB saves its state on the return stack. A halting colon definition saves its state (where it should resume) in its own parameter field.

I also mentioned in that post:

JimBoyd wrote:

If round robin multitasking will not be used, to avoid splitting the stacks into smaller areas for each task, PAUSE can be vectored to a 'run to completion' word. There are two caveats. Any word PAUSE is vectored to must leave the stacks as they were when it began. It, and all the words it executes, must not execute PAUSE to avoid runaway recursion.


If this run to completion word will not be run from the command line, SINGLE can be used to set PAUSE to NOOP , a no-op, at the beginning of the word and PAUSE can be restored at the end.
Example run to completion word with halting colon definitions and a normal colon definition.
Code:
H: HCD1   <DO SOME STUFF> ;
H: HCD2   <DO OTHER STUFF> ;
H: HCD3   <DO DIFFERENT STUFF> ;
: NCD  <DO ONE THING> ;
: BACKGROUND
   SINGLE
   HCD1 HCD2 HCD3 NCD
   LIT RECURSE IS PAUSE ;
' BACKGROUND IS PAUSE

Actually, running BACKGROUND from the command line would run it and install it as the (simulated) multitasker.
NCD , the Normal Colon Definition, does the same thing each time BACKGROUND runs.


Top
 Profile  
Reply with quote  
PostPosted: Thu Jul 27, 2023 8:34 pm 
Offline

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

A few posts back I wrote about Fleet Forth's new TRACE .
I like TRACE and my STC version of Fleet Forth will support it and other tools, such as WATCH , which depend on patching NEXT .
The STC version of Fleet Forth is on a back burner for now. I don't have the uninterrupted spans of free time to work out some of the technical details. When I resume, there will be two versions of the STC version of Fleet Forth. One will have primitives which end with RTS . The other will have primitives which end with a jump to NEXT . This NEXT will just be RTS with two additional bytes. Other than that, both versions will be identical and built from the same source.
This will allow me to have a version with a NEXT which can be patched with a jump to a trace routine or other diagnostic tool and still have an STC version running at top speed. A word which is not working as it should, for whatever reason, can be compiled and traced on the slower STC Fleet Forth. This will also allow supporting WATCH to keep an eye on a memory cell which gets changed unexpectedly.


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 01, 2023 7:15 pm 
Offline

Joined: Sun May 13, 2018 5:49 pm
Posts: 247
JimBoyd wrote:

... there will be two versions of the STC version of Fleet Forth. One will have primitives which end with RTS . The other will have primitives which end with a jump to NEXT . This NEXT will just be RTS with two additional bytes. Other than that, both versions will be identical and built from the same source.
This will allow me to have a version with a NEXT which can be patched with a jump to a trace routine or other diagnostic tool and still have an STC version running at top speed. A word which is not working as it should, for whatever reason, can be compiled and traced on the slower STC Fleet Forth. This will also allow supporting WATCH to keep an eye on a memory cell which gets changed unexpectedly.
That's a neat idea. I like how that leaves the diagnostic routine up to the end user. Does the diagnostic routine, that would be patched into NEXT, have to be written in assembly so it doesn't try to diagnose itself? I normally end up running my STC routines in a simulator when I need to debug at a low level, but your method would work on real hardware as well, which I could see being useful for debugging "driver" words that access hardware.


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 01, 2023 9:46 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 858
SamCoVT wrote:
Does the diagnostic routine, that would be patched into NEXT, have to be written in assembly so it doesn't try to diagnose itself?

Yes. In an ITC Forth, when NEXT is patched, the jump has to go to low level code. This low level code can not shift to high level Forth for the test without causing infinite recursion.
The same is true for an STC Forth where words no longer end with an RTS , but end with a jump to a dummy NEXT , a routine with an RTS followed by two unused bytes.
The diagnostic routine will also start out as assembly for the speed. 64Forth by T. J. Zimmer had a TRACE which worked differently. It's TRACE would begin tracing the word to be traced immediately. With the version of TRACE I presented, the word to be traced doesn't get traced until it is executed. The word to be traced can be traced interactively or in actual use in another word. Until this word is executed, the low level portion of the diagnostic routine does nothing out of the ordinary, just test if a condition has been met and continue.
Only when the specified condition has been met, such as IP , or the return address for STC Forth, within range for a TRACE , will the high level portion run. The rest of the time the slowdown should be kept to a minimum.
Also notice when STEP shifts to high level, the value in IP changes from an address within the traced word to an address within the high level portion of STEP . The word being traced might be called when the high level portion of STEP runs, so the first thing it does is switch tracing off with NEXT> .
Here is STEP .
Code:
SUBR STEP
   <IP LDA  IP CMP
   <IP 1+ LDA  IP 1+ SBC
   CS NOT IF
      IP> LDA  IP CMP
      IP> 1+ LDA  IP 1+ SBC
   CS NOT ELIF
   BEGIN  CS-SWAP
      IP )Y LDA  W 1+ STA
      NEXT 6 + JMP
   THEN
   >FORTH
   NEXT>  R@ HLD @ >R +PAD
   CR .STEP
   KEY DUP ASCII" {RUN/STOP}" =
   IF  DROP NOTRACE  THEN
   ASCII" {CONTROL-P}" =
   IF
      CON OFF
      BEGIN
         CR ." P? "  QUERY INTERPRET
         STATE @ 0= IF  ."  OK" THEN
         CON @
      UNTIL
   THEN
   -PAD  R> HLD !  RECURSE >NEXT
   >ASSEM
   INY
   0= UNTIL  END-CODE

LOOK , used by WATCH , checks for a changed memory location, rather than an address in IP ; therefore, it restores NEXT before the shift to high level.
Code:
SUBR LOOK
   TRUE LDA EYE CMP
   0= IF
      TRUE LDA EYE 1+ CMP
      0= IF
         IP )Y LDA  W 1+ STA
         NEXT 6 + JMP
      THEN
   THEN
   INY
   BEGIN
      NEXT 7 + ,Y LDA
      NEXT 2+ ,Y STA  DEY
   0< UNTIL
   >FORTH
   .RS  TRUE
   ABORT" WATCHED MEMORY ALTERED" -;

The purpose of WATCH is to look for a pair of memory locations, such as a VARIABLE or VALUE , which is getting changed unexpectedly. It will display the contents of the return stack when the memory contents change and abort with the message "WATCHED MEMORY ALTERED." The pair of memory locations under observation may not get altered right away, so it's a good idea if the portion of LOOK which runs the test is as fast as possible.
The source for TRACE and WATCH.


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 01, 2023 10:43 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 858
JimBoyd wrote:
.STEP is a DEFERred word so it can be set to an appropriate display word for the given trace.
Code:
DEFER .STEP
: (.ST1)  ( IP -- )
   @ .NAME TAB .S ;
' (.ST1) IS .STEP
: (.ST2)  ( IP -- )
   (.ST1)  CR TAB .AS ;
: (.ST3)  ( IP -- )
   (.ST2)
   2R> .RS 2>R ;

(.ST2) would be useful when tracing a word which uses the auxiliary stack and (.ST3) would be useful when tracing coroutine words since it shows the contents of the return stack.
If the auxiliary and return stacks do not need to be shown but a halting colon definition or a word with branches is to be traced, (.ST5) could be used. It shows the value of IP with the step data shown by (.ST1) .
Code:
: (.ST5)  ( IP -- )
   DUP U. (.ST1) ;

Tracing TEST from the example with halting colon definitions.
Code:
RESET  OK
0 5 TEST
23744 1+      0     5
23746 SWAP              0     6
23748 (DO)              6     0
23752 STREAM        EMPTY
HEAD:
23754 I   EMPTY
23756 3       0
23758 .R      0     3   0
23760 ARROWS        EMPTY
23762 (LOOP)        EMPTY
23752 STREAM        EMPTY
TAIL:
23754 I   EMPTY
23756 3       1
23758 .R      1     3   1
23760 ARROWS        EMPTY ^
23762 (LOOP)        EMPTY
23752 STREAM        EMPTY
     
23754 I   EMPTY
23756 3       2
23758 .R      2     3   2
23760 ARROWS        EMPTY ^^
23762 (LOOP)        EMPTY
23752 STREAM        EMPTY
     
23754 I   EMPTY
23756 3       3
23758 .R      3     3   3
23760 ARROWS        EMPTY ^^^
23762 (LOOP)        EMPTY
23752 STREAM        EMPTY
     
23754 I   EMPTY
23756 3       4
23758 .R      4     3   4
23760 ARROWS        EMPTY ^
23762 (LOOP)        EMPTY
23752 STREAM        EMPTY
     
23754 I   EMPTY
23756 3       5
23758 .R      5     3   5
23760 ARROWS        EMPTY ^^
23762 (LOOP)        EMPTY
23766 EXIT          EMPTY  OK

The stack display looks a little ragged because (.ST5) calls (.ST1) which uses TAB . The tab size can be increased or something like the technique in (.ST6) can be used.
Code:
: (.ST6)  ( IP -- )
   DUP U. @ .NAME SPACE
   16 CHARS @ - SPACES .S ;

CHARS is a variable. It's value is the number of characters displayed since the last carriage return or page. In Fleet Forth, if the parameter for SPACES is zero or negative then SPACES will display zero spaces.
Code:
' (.ST6) IS .STEP  OK
RESET  OK
0 5 TEST
23744 1+            0     5
23746 SWAP          0     6
23748 (DO)          6     0
23752 STREAM    EMPTY
HEAD:
23754 I         EMPTY
23756 3             0
23758 .R            0     3   0
23760 ARROWS    EMPTY
23762 (LOOP)    EMPTY
23752 STREAM    EMPTY
TAIL:
23754 I         EMPTY
23756 3             1
23758 .R            1     3   1
23760 ARROWS    EMPTY ^
23762 (LOOP)    EMPTY
23752 STREAM    EMPTY
     
23754 I         EMPTY
23756 3             2
23758 .R            2     3   2
23760 ARROWS    EMPTY ^^
23762 (LOOP)    EMPTY
23752 STREAM    EMPTY
     
23754 I         EMPTY
23756 3             3
23758 .R            3     3   3
23760 ARROWS    EMPTY ^^^
23762 (LOOP)    EMPTY
23752 STREAM    EMPTY
     
23754 I         EMPTY
23756 3             4
23758 .R            4     3   4
23760 ARROWS    EMPTY ^
23762 (LOOP)    EMPTY
23752 STREAM    EMPTY
     
23754 I         EMPTY
23756 3             5
23758 .R            5     3   5
23760 ARROWS    EMPTY ^^
23762 (LOOP)    EMPTY
23766 EXIT      EMPTY  OK

The disassembly for TEST .
Code:
SEE TEST
TEST
 23744  4873 1+
 23746  5162 SWAP
 23748  2317 (DO) 23766
 23752 23642 STREAM
 23754  2425 I
 23756  2699 3
 23758  7570 .R
 23760 23698 ARROWS
 23762  2197 (LOOP) 23752
 23766  2480 EXIT
24
 OK



Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 338 posts ]  Go to page Previous  1 ... 17, 18, 19, 20, 21, 22, 23  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 1 guest


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: