6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun Nov 10, 2024 7:58 pm

All times are UTC




Post new topic Reply to topic  [ 354 posts ]  Go to page Previous  1 ... 11, 12, 13, 14, 15, 16, 17 ... 24  Next
Author Message
PostPosted: Mon Nov 15, 2021 12:42 am 
Offline

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

I previously mentioned fixing Fleet Forth's multitasking support in the kernel. This allows PAUSE to execute while the user is typing at Forth's command line, or even just thinking about what to type. It's nice when your background tasks don't freeze while you're sitting at the keyboard thinking.
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.
Background tasks normally execute PAUSE to switch tasks. In that case PAUSE is vectored to an actual task switcher.
The following Fleet Forth words execute PAUSE directly or indirectly through another word.
Code:
TYPE
QTYPE
KEY
(EXPECT)
INTERPRET
QUIT
DJIFFIES
JIFFIES

As well as any word which displays text, with the exception of EMIT since it displays a single character.
The error handling words ( ABORT ABORT" WHERE ) also execute PAUSE by way of TYPE but, they should not be in a background task. ABORT and ABORT" switch off any kind of multitasking by executing SINGLE , setting PAUSE to the no-op NOOP .

The following will cycle the border through all sixteen colors.
Code:
: BORDER?  ( -- B )
   $D020 C@ ;
: CYCLE  ( -- )
   BORDER? 1+ BORDER ;
' CYCLE IS PAUSE

Something like the cyclic executive Garth mentions can be set up easily:
Code:
: BACKGROUND
   TASK1
   TASK2
   TASK3
   TASK4 ;
' BACKGROUND IS PAUSE

What is missing is a way for a 'task' to run to a certain stopping point and resume where it left off. Leo Brodie's DOER/MAKE could be used. I propose a little structure of my own, inspired by DOER/MAKE, halting colon definitions. One of these runs to the halting point, where it exits. It resumes where it left off.
Code:
: H:
   :  ( -- ADR TRUE )
      HERE 2+ ,
      [ HERE 2+ >A ]
   DOES>  ( -- )
      @ >R ;
: (HALT)  ( -- )
          ( R: ADR -- )
   R@ 2+  R> @ >BODY ! ;
: HALT
   LATEST NAME> @ [ A> ] LITERAL <>
   ABORT" NON HALTING WORD"
   COMPILE (HALT)  [COMPILE] RECURSE
   ; IMMEDIATE

Or if an auxiliary stack is not available, a pair of free zero page locations can be used.
Code:
: H:
   :  ( -- ADR TRUE )
      HERE 2+ ,
      [ HERE 2+ 2 ! ]
   DOES>  ( -- )
      @ >R ;
: (HALT)  ( -- )
          ( R: ADR -- )
   R@ 2+  R> @ >BODY ! ;
: HALT
   LATEST NAME> @ [ 2 @ ] LITERAL <>
   ABORT" NON HALTING WORD"
   COMPILE (HALT)  [COMPILE] RECURSE
   ; IMMEDIATE

Here is a test halting word.
Code:
H: HTEST  ( -- )
   BEGIN
      CR ." STARTING HTEST" HALT
      CR ." MIDDLE OF HTEST" HALT
      CR ." END OF HTEST" HALT
   AGAIN ;

Each time HTEST executes, it displays one of the messages and exits.
Code:
HTEST
STARTING HTEST OK
HTEST
MIDDLE OF HTEST OK
HTEST
END OF HTEST OK
HTEST
STARTING HTEST OK

Here is an example without a BEGIN AGAIN loop.
Code:
H: TEST2  ( -- )
   CR ." BEFORE HALTING." HALT
   CR ." AFTER HALTING." ;

The very first time it is executed, it will display the message "BEFORE HALTING" and every time after that it will, when executed, display "AFTER HALTING" .
Here is what gets compiled for TEST2 .
Code:
' TEST2 >BODY :DIS
 22937 22939
 22939  7024 CR
 22941  6724 (.") BEFORE HALTING.
 22959 22754 (HALT)
 22961 22935 TEST2
 22963  7024 CR
 22965  6724 (.") AFTER HALTING.
 22982  2930 EXIT
47
 OK

Halting colon definitions even support recursion.
Code:
H: TEST  ( N -- N )
   BEGIN
      CR DUP SPACES ." BEGIN"
      1+ DUP 4 <
      IF  RECURSE  THEN
      HALT
      CR DUP SPACES ." MIDDLE"
      1+ DUP 6 <
      IF  RECURSE  THEN
      HALT
      CR DUP SPACES ." END"
      1- DUP 0< 0=
      IF  RECURSE  THEN
      HALT
   AGAIN ;

Code:
0 TEST
BEGIN
 BEGIN
  BEGIN
   BEGIN OK
TEST
    MIDDLE
     MIDDLE OK
TEST
      END
     END
    END
   END
  END
 END
END OK
. 0  OK

If PAUSE is set to a halting colon definition, or a regular colon definition which executes more than one halting colon definition, the halting colon definition(s) will perform an action each time PAUSE is executed. This may be too frequent. Garth also mentioned alarms. An interval timer would be nice. How about the down-counter by Tim Hendtlass?
Here is DOWN-COUNTER for Fleet Forth.
Code:
: DOWN-COUNTER
   2VARIABLE  ( -- )
   DOES>  ( -- ADR )
      JIFFY@ DROP
      OVER 2+ @
      OVER -  0 MIN
      2PICK +!
      OVER 2+ ! ;

That '0 MIN' is to handle the case when the jiffy timer resets to zero when it reaches twenty four hours.
A down-counter, a child word of DOWN-COUNTER , has two cells of storage. The first cell holds a value. The second cell holds the low cell of the Commodore 64 jiffy clock from the last time the down-counter was executed. Each time a down-counter is executed the amount of time, in jiffies, which passed from the last time is subtracted from the value in the first cell.
Code:
DOWN-COUNTER DELAY1 OK
300 DELAY1 ! OK
DELAY1 ? 209  OK
DELAY1 ? 118  OK
DELAY1 ? 5  OK
DELAY1 ? -445  OK

Of course, the values returned each time I type "DELAY1 ?" depends on how long I wait.
Here is sample code to cycle the Commodore 64 border and cursor colors through predetermined values without an actual multitasker other than PAUSE built into certain kernel words. It cycles the colors while I'm still able to use Fleet Forth for other things.
Code:
DOWN-COUNTER DELAY1
H: CYCLE-INK  ( -- )
   BEGIN
      DELAY1 @ 0< 0EXIT
      GREEN INK
      60 DELAY1 !  HALT
      DELAY1 @ 0< 0EXIT
      BROWN INK
      120 DELAY1 !  HALT
      DELAY1 @ 0< 0EXIT
      BLACK INK
      180 DELAY1 !  HALT
   AGAIN ;

DOWN-COUNTER DELAY2
H: CYCLE-BORDER  ( -- )
   BEGIN
      DELAY2 @ 0< 0EXIT
      GREEN BORDER
      30 DELAY2 !  HALT
      DELAY2 @ 0< 0EXIT
      CYAN BORDER
      90 DELAY2 !  HALT
      DELAY2 @ 0< 0EXIT
      YELLOW BORDER
      60 DELAY2 !  HALT
   AGAIN ;

: CYCLE  ( -- )
   CYCLE-INK  CYCLE-BORDER ;

' CYCLE IS PAUSE

Maybe halting colon definitions, or halting words, will be useful outside the realm of simulated multitasking, I don't know. Which will be more useful, halting words or DOER/MAKE? I don't know that either.

Note: My first attempt to port halting words to Blazin' Forth did not work. Blazin' Forth's word ] doesn't just change to the compiling state, it is the colon compiler. As a result, when Blazin' Forth's ] executes, parsing of the text stream and compiling begin immediately.
The only workaround I could come up with was to incorporate the functionality of : into H: without compiling : directly.
Code:
: H:                       \ nothing placed on stack?
   CREATE                  \ create a new header
      SMUDGE               \ hide name
      CURRENT @ CONTEXT !  \ set CONTEXT equal to CURRENT
      !CSP                 \ Blazin' Forth checks for a change in stack depth
      HERE 2+ ,            \ allocate and initialize one cell of storage
      ]                    \ include this AFTER everything up to DOES>
   DOES>
      @ >R ;

The test to make sure HALT is not used in normal colon definitions was left out of the version ported to Blazin' Forth. Because of the way Blazin' Forth handles compilation, it is not easy to test if a new definition is a child of H: until after the first line of text is parsed from the keyboard or until the block it is defined in is finished.

CREATE is used in both Fleet Forth and Blazin' Forth by all the words which create a header. It is basically VARIABLE without storage allotted.
Code:
\ Fleet Forth's VARIABLE
: VARIABLE
   CREATE  0 , ;


In regards to round robin multitasking, the problem with dividing the stacks so each task gets a portion is that it limits the number of tasks, as the background task's portion of the return stack has to have room for the nesting of that task and also for interrupts. One possibility is to have round robin multitasking with fewer background tasks. One or more background tasks could be a cyclic executive. The cyclic executive would need to be in a loop and include PAUSE .
Something like the following should work, but I haven't tested this yet.
Code:
\ define some halting words.
H: BG1 ... ;
H: BG2 ... ;
H: BG3 ... ;
0 $20 $13F TASK BACKGROUND
: CYCLE
   BACKGROUND ACTIVATE
   BEGIN
      BG1  BG2  BG3  PAUSE
   AGAIN ;
BACKGROUND LINK-TASK
\ define and link other tasks
   ...
\ switch on multitasking
MULTI



Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 15, 2021 8:07 pm 
Offline

Joined: Sun May 13, 2018 5:49 pm
Posts: 255
JimBoyd wrote:
What is missing is a way for a 'task' to run to a certain stopping point and resume where it left off. Leo Brodie's DOER/MAKE could be used. I propose a little structure of my own, inspired by DOER/MAKE, halting colon definitions. One of these runs to the halting point, where it exits. It resumes where it left off.
Code:
: H:
   :  ( -- ADR TRUE )
      HERE 2+ ,
      [ HERE 2+ >A ]
   DOES>  ( -- )
      @ >R ;

You are breaking my brain (in a good way, I think). I was not aware you could put : in a colon definition (I would normally use CREATE, but I can see how they are different). Now that I think about it, of course you can do that. Forth lets you run with scissors by holding on to the sharp parts if you want.

Once I made it past that part, I see your DOES> pulls the address you left off (stored right at the beginning of the word's definition) at and puts it on the return stack just before the ; That immediately made me cringe because I've spent too many hours trying to balance my stores and fetches on the return stack, but of course you want that here. That's where it will "return" to and ; will bring you there. It's just like the co-routine code you showed earlier. It reminds me that >R and ; are just tools that can be used in any order if the resulting behavior is your desired behavior.

The rest was pretty easy to understand, once you understood what this part does. I haven't noodled my way all the way through your example yet, but I find it very interesting. Also, bonus points for showing off you auxiliary stack. I wrote one, but haven't really used it too much.


Top
 Profile  
Reply with quote  
PostPosted: Thu Nov 18, 2021 12:25 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 894
SamCoVT wrote:
You are breaking my brain (in a good way, I think). I was not aware you could put : in a colon definition (I would normally use CREATE, but I can see how they are different). Now that I think about it, of course you can do that. Forth lets you run with scissors by holding on to the sharp parts if you want.


The reason for including : in the definition of H: is to include the compile time functionality of colon ( : ). In Fleet Forth this means creating a name and smudging it, setting the CONTEXT vocabulary equal to the CURRENT vocabulary, placing on the control flow stack (data stack) the same parameters as colon, and setting the state to compiling. Since Fleet Forth is an ITC Forth, this is not a problem. All words in Fleet Forth have a two byte code field, no exceptions. Even the child words of a CREATE DOES> word have a two byte code field.
In Fleet Forth, colon executes CREATE , which lays down the code field for do-variable. Colon overwrites that code field with do-colon. H: overwrites the code field with do-does. Do-does uses the Forth virtual machine W register to find the address to leave on the data stack and the high level code to push in the Forth virtual machine's IP register.
As an example, here is the source for Fleet Forth's 2CONSTANT , it is a CREATE DOES> word rather than a CREATE ;CODE word because it isn't used much.
Code:
: 2CONSTANT
   CREATE , , DOES>
      2@ ;

And here is what gets compiled.
Code:
SEE 2CONSTANT
2CONSTANT
 11961  8866 CREATE
 11963  7932 ,
 11965  7932 ,
 11967  8025 DOES
 11969  9483    JMP ' DOES> >BODY 21 +
 11972  4762 2@
 11974  2930 EXIT
15
 OK

DOES> compiles DOES and a jump to do-does, which saves the contents of IP to the return stack and fetches the address from W with an offset of 3 (to skip over the JMP) and places it in IP .
Code:
9483 DIS
  9483   252    LDA IP 1+
  9485          PHA
  9486   251    LDA IP
  9488          PHA
  9489          CLC
  9490   254 )Y LDA  W
  9492     3  # ADC
  9494   251    STA IP
  9496          INY
  9497   254 )Y LDA  W
  9499     0  # ADC
  9501   252    STA IP 1+
  9503  9032    JMP ' CREATE >BODY 164 +
23
 OK

Then jumps to do-variable.
Code:
9032 DIS
  9032          CLC
  9033     2  # LDA
  9035   254    ADC  W
  9037          PHA
  9038     0  # LDA
  9040   255    ADC  W 1+
  9042  2632    JMP PUSH
13
 OK

As I showed in the listing for the version of H: for Blazin' Forth, because Blazin' Forth's ] is the compiler as well as changing state to compiling, it was necessary to use CREATE and include what Blazin' Forth's : does at compile time.
Since Tali Forth is an STC Forth, I ported H: and HALT to Durex Forth version 1.6.3 which is, from what I've seen, a subroutine threaded Forth. Colon words have no code field, but CREATE words have a five byte code field. The first three bytes are a JSR to its equivalent of do-does. The next two bytes are a pointer to the high level forth to execute.
This is the source for the port to Durex Forth.
Code:
\ halting words
create dummy
: h:
   :
      ['] dummy dup c@ c, 1+ @ , 0 ,
      here 1+ ,
   does>
      @ >r ;
: (halt)
   r@ 3 + r> 2+ @ 5 + ! ;
: halt
   postpone (halt) postpone recurse ;
   immediate

\ for demos
: spaces  ( +n -- )
   0 max spaces ;

I was able to include colon to do at compile time, whatever Durex Forth's colon does. I first had to define a dummy CREATE word so I could build up a duplicate of it's code field. I duplicated the JSR and padded the last two bytes with zero. That gets patched by whatever DOES> compiles in Durex Forth.
Notice how (halt) has become more complicated.
I left out the error checking which would abort if HALT were used in a regular colon definition. This was to keep the code to a bare minimum for clarity.
I don't know how Tali Forth's CREATE words differ from Durex Forth's, so caution is advised.
I would also like to mention that Durex Forth's version of SEE was practically useless for investigating H: words. However, Durex Forth does have DUMP , which was indispensable.

Quote:
Once I made it past that part, I see your DOES> pulls the address you left off (stored right at the beginning of the word's definition) at and puts it on the return stack just before the ; That immediately made me cringe because I've spent too many hours trying to balance my stores and fetches on the return stack, but of course you want that here. That's where it will "return" to and ; will bring you there. It's just like the co-routine code you showed earlier. It reminds me that >R and ; are just tools that can be used in any order if the resulting behavior is your desired behavior.


Wait till you see Dynamically Structured Codes by M. L. Gassanenko.


Top
 Profile  
Reply with quote  
PostPosted: Thu Dec 02, 2021 3:49 am 
Offline

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

I noticed a problem with my modification to Fleet Forth' (EXPECT) when I tried to use a feature of VICE, the Commodore 64 'mulator I'm using.
The feature in question is the ability to copy text from a file and paste it into VICE. It's supposed to work as if all that text (each line terminated with a carriage return) is typed in.
The modification to (EXPECT) was so I could have the benefit of both the Commodore 64's screen editor and reliable cooperative multitasking without writing my own screen editor. The problem is caused because (EXPECT) is pushing a carriage return back into the keyboard buffer, but it is also emptying it of everything else. This hasn't been a problem for me because I don't keep typing after hitting the return key without seeing the result first. This slight delay on my part keeps the keyboard buffer from having any key presses to lose when the carriage return is stuffed into the buffer. Apparently, VICE keeps placing characters in the buffer as long as there is room.
I've corrected the way the carriage return is added to the buffer and text pasting in VICE works in Fleet Forth.
Code:
// (EXPECT) -- MULTITASKER SUPPORT
CODE (EXPECT)  ( ADR CNT -- )
   $99 LDA
   0= IF
      >FORTH
      COLS ?CR
      BEGIN
         KEY DUP 13 <>
      WHILE
         DEMIT
      REPEAT
      >ASSEM
      $D3 STY  SEI  $C6 LDY
      0= NOT IF
         BEGIN
            $276 ,Y LDA  $277 ,Y STA  DEY
         0= UNTIL
      THEN
      0 ,X LDA  $277 STA  $C6 INC
      CLI  0 # LDY  INX  INX
   THEN
   2 # LDA  SETUP JSR
   XSAVE STX  SPAN 1+ STY
   BEGIN
         N CPY
      0= WHILE
         N 1+ DEC
      0< NOT WHILE  CS-ROT
      THEN
         $FFCF JSR  // CHRIN
         13 # CMP
      0= NOT WHILE
         N 2+ )Y STA  INY
      CS-DUP 0= UNTIL
      N 3 + INC  SPAN 1+ INC
   AGAIN  CS-SWAP
   THEN
      $99 LDA
      0= IF
         BEGIN
            $FFCF JSR  13 # CMP
         0= UNTIL
      THEN
   THEN
   SPAN STY
   XSAVE.NEXT JMP  END-CODE

This version pushes the carriage return in the keyboard buffer without emptying it.
I also came up with another way to solve this problem.
I could factor a new word out of KEY , the headerless word WKEY . WKEY waits till there is a key in the keyboard buffer without removing it. It blinks the cursor and runs a loop with PAUSE while waiting.
Code:
// WKEY KEY
CODE WKEY  ( --   )
   $CC STY
   >FORTH
   BEGIN
      PAUSE $C6 C@
   UNTIL
   >ASSEM
   INY  $CD STY
   BEGIN
      $CF LDA
   0= UNTIL
   $CC STY
   NEXT JMP  END-CODE
: KEY  ( -- C )
   WKEY  ?KEY ;

This version of (EXPECT) only echoes a key to the screen if that key is not a carriage return. It leaves the first carriage return it finds in the keyboard buffer for the Commodore 64 Kernal routine CHRIN , the screen editor.
Code:
// (EXPECT) -- MULTITASKER SUPPORT
CODE (EXPECT)2 ( ADR CNT -- )
   $99 LDA
   0= IF
      >FORTH
      COLS ?CR
      BEGIN
         WKEY $277 C@ 13 <>
      WHILE
         ?KEY DEMIT
      REPEAT
      $D3 OFF
      >ASSEM
   THEN
   2 # LDA  SETUP JSR
   XSAVE STX  SPAN 1+ STY
   BEGIN
         N CPY
      0= WHILE
         N 1+ DEC
      0< NOT WHILE  CS-ROT
      THEN
         $FFCF JSR  // CHRIN
         13 # CMP
      0= NOT WHILE
         N 2+ )Y STA  INY
      CS-DUP 0= UNTIL
      N 3 + INC  SPAN 1+ INC
   AGAIN
   CS-SWAP
   THEN
      $99 LDA
      0= IF
         BEGIN
            $FFCF JSR  // CHRIN
            13 # CMP
         0= UNTIL
      THEN
   THEN
   SPAN STY
   XSAVE.NEXT JMP  END-CODE

As for which solution I'll go for, I'll have to see which one results in a smaller Fleet Forth kernal.


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 07, 2021 2:08 am 
Offline

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

The feature in question is the ability to copy text from a file and paste it into VICE. It's supposed to work as if all that text (each line terminated with a carriage return) is typed in.
The modification to (EXPECT) was so I could have the benefit of both the Commodore 64's screen editor and reliable cooperative multitasking without writing my own screen editor. The problem is caused because (EXPECT) is pushing a carriage return back into the keyboard buffer, but it is also emptying it of everything else. This hasn't been a problem for me because I don't keep typing after hitting the return key without seeing the result first. This slight delay on my part keeps the keyboard buffer from having any key presses to lose when the carriage return is stuffed into the buffer. Apparently, VICE keeps placing characters in the buffer as long as there is room.
I've corrected the way the carriage return is added to the buffer and text pasting in VICE works in Fleet Forth.


I think I have a slight involuntary pause after hitting the return key, then again, it could be that I'm typing to slowly to add a key to the buffer before the carriage return is processed. Either way, this wasn't a problem until using the text pasting feature of VICE, but I suppose it could be a problem for a really fast typist.
I also think I have a better idea of what VICE is doing during the paste operation, but this is just speculation. I think it fills the keyboard buffer, then waits until the buffer is empty before refilling it with the next ten characters to paste.
As for which version of (EXPECT) I used, the second version which doesn't push anything back into the keyboard buffer resulted in a smaller Fleet Forth kernel.
I modified the editor's screen editor (as in screens in blocks) to properly push the right shift character into the keyboard buffer.
Code:
// XED
EDITOR DEFINITIONS
: XED  ( N -- )
   CREATE  ( N -- )
      C,
   DOES>  ( -- )
      C@ C/L * R# !
      0 TEXT C/L PAD C!
      IBUF BUFMOVE (R)
      >ASSEM
      SEI  $C6 LDY
      0= NOT IF
         BEGIN
            $276 ,Y LDA  $279 ,Y STA
            DEY
         0= UNTIL
      THEN
      ASCII" ]" # LDA  3 # LDY
      BEGIN
         $276 ,Y STA  $C6 INC  DEY
      0= UNTIL
      CLI
      >FORTH QUIT ; -2 ALLOT
// XED WORDS 0: - F:  LQ
HEX
 0 XED 0:    1 XED 1:    2 XED 2:
 3 XED 3:    4 XED 4:    5 XED 5:
 6 XED 6:    7 XED 7:    8 XED 8:
 9 XED 9:   0A XED A:   0B XED B:
0C XED C:   0D XED D:   0E XED E:
0F XED F:
FORTH DEFINITIONS

This worked well for copying source from a text file and pasting it into VICE. I could paste it into a Forth screen by listing the relevant screen and positioning the cursor where I wanted the text to start.
The downside is that the keyboard buffer would sometimes spill over into adjacent memory. It affected the pointer for bottom of memory for the C64 kernal. This isn't really a problem since that pointer isn't used in Forth. The top of memory pointer for the C64 kernal is just above the pointer for the bottom of memory and the one which concerned me. The C64 top of memory pointer normally points to the first address past the usable RAM. If a file is opened on device 2 on the C64, it will be for RS-232 and the C64 will create two 256 byte buffers which end at the top of memory. Fleet Forth's CONFIGURE sets the top of memory pointer to point to the start of the block buffer table so if these two buffers are created, they will not overwrite part of the topmost block buffer. If a keyboard buffer overflow occurs due to XED stuffing three right shift characters into the keyboard buffer and the top of memory pointer is altered, opening a file on device 2 could corrupt the block buffer table or some blocks. There is an address the C64 uses to determine the size of the keyboard buffer, which is used by the ISR to keep from placing too many characters in the buffer. The version of VICE I'm using seems to ignore that value and uses the default size of ten.
I decided to alter (EXPECT) once again to work better with my Forth screen editor. I added the variable 3R to the Fleet Forth kernel source and added the following to (EXPECT) after 'COLS ?CR' .
Code:
                3R C@
      IF  " ]]]" COUNT DTYPE  THEN

Note: those are not three ] characters in the string above. they are three right shift key characters. On the C64 screen they display in reverse video. In the print dump file, they display as shown above.
I also changed this:
Code:
      $D3 OFF
      >ASSEM

to this:
Code:
      >ASSEM
      $D3 STY  $D4 STY  3R STY

which does the same thing as before while also switching 3R off.
Here is the full source for the new (EXPECT)
Code:
// (EXPECT) -- MULTITASKER SUPPORT
CODE (EXPECT)  ( ADR CNT -- )
   $99 LDA
   0= IF
      >FORTH
      COLS ?CR  3R C@
      IF  " ]]]" COUNT DTYPE  THEN
      BEGIN
         WKEY $277 C@ 13 <>
      WHILE
         ?KEY DEMIT
      REPEAT
      >ASSEM
      $D3 STY  $D4 STY  3R STY
   THEN
   2 # LDA  SETUP JSR
   XSAVE STX  SPAN 1+ STY
   BEGIN
         N CPY
      0= WHILE
         N 1+ DEC
      0< NOT WHILE  CS-ROT
      THEN
         $FFCF JSR  // CHRIN
         13 # CMP
      0= NOT WHILE
         N 2+ )Y STA  INY
      CS-DUP 0= UNTIL
      N 3 + INC  SPAN 1+ INC
   AGAIN
   CS-SWAP
   THEN
      $99 LDA
      0= IF
         BEGIN
            $FFCF JSR  // CHRIN
            13 # CMP
         0= UNTIL
      THEN
   THEN
   SPAN STY
   XSAVE.NEXT JMP  END-CODE

and here is the new source for XED
Code:
// XED
EDITOR DEFINITIONS
: XED  ( N -- )
   CREATE  ( N -- )
      C,
   DOES>  ( -- )
      C@ C/L * R# !
      0 TEXT C/L PAD C!
      IBUF BUFMOVE (R)
      3R ON  QUIT ; -2 ALLOT

This arrangement works well without the need to stuff any characters into the keyboard buffer and the Fleet Forth kernel is still smaller than the Blazin' Forth kernel.


Top
 Profile  
Reply with quote  
PostPosted: Sat Dec 11, 2021 1:47 am 
Offline

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

I've decided to rename 3R to RC for 'right cursor'. I've also changed this:
Code:
              3R C@
      IF  " ]]]" COUNT DTYPE  THEN

to this:
Code:
              RC C@ $D3 C!

$D3 is the location in zero page the C64 Kernal uses for the cursor's horizontal position, or column. $D6 is for the vertical position. Normally, these locations should be changed by using the C64 Kernal's PLOT routine, which has nothing to do with graphics (in spite of the name), just plotting the position of the text cursor. However, directly altering $D3 to alter the cursor's column position shouldn't be a problem as long as it stays on the same logical line.
Those familiar with the C64 will know what I mean by 'logical line'. The C64 has a screen which is 40 columns wide by 25 lines. These 25 lines are the 'physical lines'. Two adjacent physical lines can be joined together to form an 80 column logical line.
Here is the source for the latest version of (EXPECT) .
Code:
// (EXPECT) -- MULTITASKER SUPPORT
CODE (EXPECT)  ( ADR CNT -- )
   $99 LDA                     // Which device?
   0= IF                       // if keyboard
      INY  $292 STY            // enable auto down scrolling
      >FORTH
      COLS ?CR
      RC C@ $D3 C!             // bump cursor RC places
      BEGIN
         WKEY $277 C@ 13 <>    // wait till keypress in buffer
      WHILE                    // while not a CR
         ?KEY DEMIT            // echo to screen
      REPEAT
      >ASSEM
      $D3 STY  $D4 STY  RC STY // bump cursor to left margin &
   THEN                        // switch off quote mode and RC
   2 # LDA  SETUP JSR
   XSAVE STX  SPAN 1+ STY
   BEGIN
         N CPY
      0= WHILE
         N 1+ DEC
      0< NOT WHILE  CS-ROT
      THEN
         $FFCF JSR  // CHRIN
         13 # CMP
      0= NOT WHILE
         N 2+ )Y STA  INY
      CS-DUP 0= UNTIL
      N 3 + INC  SPAN 1+ INC
   AGAIN
   CS-SWAP
   THEN                        // did not receive CR
      $99 LDA                  // if from keyboard
      0= IF                    // read rest of line from screen
         BEGIN                 // and discard
            $FFCF JSR  // CHRIN
            13 # CMP
         0= UNTIL
      THEN
   THEN
   SPAN STY
   XSAVE.NEXT JMP  END-CODE
' (EXPECT) IS EXPECT

Here is the new source for XED .
Code:
// XED
EDITOR DEFINITIONS
: XED  ( N -- )
   CREATE  ( N -- )
      C,
   DOES>  ( -- )
      C@ C/L * R# !
      0 TEXT C/L PAD C!
      IBUF BUFMOVE (R)
      3 RC C!  QUIT ; -2 ALLOT



Top
 Profile  
Reply with quote  
PostPosted: Sun Dec 12, 2021 10:38 pm 
Offline

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

I've moved the Fleet Forth download to the head post.
When I'm ready to upload the new and improved Fleet Forth, I'll upload it to the head post as well. Before I do that, I would like to simplify the source for Fleet Forth's metacompiler.
I'm thinking about starting a new topic 'Fleet Forth Metacompiler Design Considerations'.
Extending the metacompiler to target compilation is also something I've been thinking about.


Top
 Profile  
Reply with quote  
PostPosted: Thu Dec 16, 2021 2:47 am 
Offline

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

I've considered adding :NONAME to Fleet Forth's system loader so I could define headerless words; words having no use outside of the limited scope where they are used. In this context I am referring to words defined in the process of working in a typical Forth environment, not metacompiling. The Fleet Forth metacompiler maintains a target Forth vocabulary on the host, so even headerless words in the target system have a header on the host.
It would be useful if a headerless word could also be recursive; however, the definition of RECURSE in Fleet Forth is:
Code:
: RECURSE
   LATEST NAME> , ; IMMEDIATE

The word LATEST returns the address of the name field of the latest word in the CURRENT vocabulary.
Code:
: LATEST  ( -- NFA )
   CURRENT @ @ L>NAME ;

This does not work for a headerless word. Without a header, it is not in any VOCABULARY .
I added a 'system value' to Fleet Forth. The value LAST . CREATE sets this value to the CFA of the latest word created. RECURSE now uses LAST .
Code:
: RECURSE
   LAST , ; IMMEDIATE

LAST can be invalidated by FORGET and EMPTY , but that doesn't matter. LAST is only needed during the creation of a colon definition, and then only when recursion is used. Since LAST does not depend on the ability to find a name, it can be set by :NONAME which now allows RECURSE to be used in headerless definitions.
LATEST is still used in words like IMMEDIATE to only affect words with a header.
Oddly enough, with this addition, Fleet Forth's kernel is now the same size as Blazin' Forth's kernel.


Top
 Profile  
Reply with quote  
PostPosted: Thu Dec 16, 2021 4:27 am 
Offline

Joined: Sun Apr 26, 2020 3:08 am
Posts: 357
In stead of using :NONAME, can one not just use HERE, then write some code, leave HERE on the stack so that when some future word definition needs it, the address to return to is already there. You wouldn't even need to create a colon definition or use :NONAME.

It would just be:

Code:
HERE 0 , CODE
.
.
.
ENDCODE


The 0 , would be needed since CODE does a -2 allot, which stores the CFA which points to the PFA (or body) of a regular word definition. The address for HERE gets left on the stack and is used, when needed, in the assembler calculation. Which I believe needs to be HERE+2 to skip past the CFA pointer.


Top
 Profile  
Reply with quote  
PostPosted: Thu Dec 16, 2021 5:35 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8539
Location: Southern California
My :NONAME is defined as:
Code:
: :NONAME       ( -- addr )                                     \   ANS_CORE_EXT
   ?EXEC
   ALIGN  HERE
   DEPTH  CSP !
   ['] nest ,
   ]            ;

but I never thought of making it work with RECURSE. Anyway, all it compiles is the CFA of nest.

_________________
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: Fri Dec 17, 2021 10:11 pm 
Offline

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

Here is the version of :NONAME I'm considering adding to Fleet Forth's system loader.
Code:
: ALIGN  ( ADR1 -- ADR2 )
   HERE 1+ SPLIT DROP 0= ABS ALLOT ;
// DUMMY TARGET TO UNSMUDGE
HERE >A  0 C,
: :NONAME  ( -- ADR )
   ALIGN  HERE  DUP (IS) LAST
   CURRENT @ CONTEXT !
   [ A> ] LITERAL TRUE
   [ ' : @ ] LITERAL , ]       ;

This version of align doesn't align to an even address. It will allot one byte if the address is of the form $xxFF. The C64 uses the 6510 processor which has the infamous indirect jump bug.
Fleet Forth doesn't save the stack depth for colon or code words. The address to unsmudge and the value of true are pushed on the stack for compiler security. The one byte allotted prior to the definition of :NONAME provides a safe address for semicolon to unsmudge. Here are Fleet Forth's : and ; .
Code:
: :  ( -- ADR TRUE )
   ] CREATE  LATEST
   TRUE OVER TSB
   CURRENT @  CONTEXT !
   ;CODE
   IP 1+ LDA  PHA
   IP    LDA  PHA
   CLC
   2 # LDA  W    ADC  IP    STA
       TYA  W 1+ ADC  IP 1+ STA
   NEXT JMP  END-CODE

: ?PAIRS  ( N1 N2 -- )
   <>
   ABORT" STRUCTURE MISMATCH" ;
: ;  ( ADR TRUE -- )
   COMPILE EXIT
   [COMPILE] [
   TRUE ?PAIRS  TSB ; IMMEDIATE

TSB takes an address and toggles the smudge bit at that address.
Code:
// TOGGLE SMUDGE BIT
: TSB  ( ADR -- )
   SBIT TOGGLE ;

As for the ability to support recursion in a headerless word, Fleet Forth has the word VOCS to display the vocabulary tree. VOCS uses the recursive word .VOCS . .VOCS could be made headerless.
Code:
// .VOCS WITHOUT A NAME
:NONAME  ( PFA CNT -- )
   ?STACK
   DUP>R CR SPACES
   DUP BODY> >NAME ID.  VOC-LINK @
   BEGIN
      2DUP 2- @ =
      IF
         DUP 2- 2- R@ 2+ RECURSE
      THEN
      @ ?DUP 0=
   UNTIL
   R> 2DROP ;

and used like this.
Code:
>A
: VOCS
   [ ' FORTH >BODY ] LITERAL 0
   [ A> , ]  CR ;

Or if there is no aux stack.
Code:
: VOCS
   [ ' FORTH >BODY ] LITERAL 0
   [ ROT , ] CR ;

Here is a definition for CNONAME , a word which is used to create headerless code words.
Code:
: CNONAME  ( -- ADR ) // CODE DEF
   :NONAME  [COMPILE] [
   ASSEMBLER [ ASSEMBLER ] MEM
   LAST 2+ LAST ! ; // OVERWRITE CFA

Although I doubt I would need CNONAME .
LAST is handy if there is an error while compiling. The following will not compile successfully. There is no matching THEN .
Code:
:NONAME  ( N -- )
   2 MOD
   IF
      CR ." ODD"
   ELSE
      CR ." EVEN"
   ;

When the system aborts, the stacks will be cleared.
Code:
0 FH LOAD
SCR# 8489 LINE# 7
   ;
   ^
STRUCTURE MISMATCH
.S EMPTY  OK

If the definition was compiled from the command line, there will not even be source to examine; however, there is a LAST resort to (SEE) what went wrong.
Code:
LAST (SEE)

 23025  2262 2
 23027  5716 MOD
 23029  2749 ?BRANCH 23045
 23033  7052 CR
 23035  6741 (.") ODD
 23041  3043 BRANCH 0
 23045  7052 CR
 23047  6741 (.") EVEN
 23054  2949 EXIT
31
 OK

That BRANCH 0 shows that the ELSE was never resolved by a matching THEN .


Top
 Profile  
Reply with quote  
PostPosted: Fri Dec 17, 2021 10:23 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 894
IamRob wrote:
In stead of using :NONAME, can one not just use HERE, then write some code, leave HERE on the stack so that when some future word definition needs it, the address to return to is already there. You wouldn't even need to create a colon definition or use :NONAME.


:NONAME is used to create a headerless colon definition and some of the words in Fleet Forth's system loader are candidates. These colon definitions are complex enough that I would not want to rewrite them as code words, code words which would be much larger.

Quote:
It would just be:

Code:
HERE 0 , CODE
.
.
.
ENDCODE


The 0 , would be needed since CODE does a -2 allot, which stores the CFA which points to the PFA (or body) of a regular word definition. The address for HERE gets left on the stack and is used, when needed, in the assembler calculation. Which I believe needs to be HERE+2 to skip past the CFA pointer.


Using CODE to create a headerless code word doesn't work because CODE parses the text stream for a name and creates a header. 64Forth's CODE behaves the same way.
Code:
: CODE    ?EXEC   CREATE   [COMPILE] ASSEMBLER   MEM   !CSP   ;S IMMEDIATE

This is Fleet Forth's CODE
Code:
: SUBR  ( -- ADR TRUE )
   CREATE  LATEST TRUE
   OVER TSB
   [ ASSEMBLER ] ASSEMBLER MEM ;
: CODE  ( -- ADR TRUE )
   SUBR  HERE DUP 2- ! ;

Neither version has -2 ALLOT .
The word SUBR is for creating subroutines. Here is an example from Fleet Forth's kernel.
Code:
SUBR (>FORTH)  ( -- )
   CLC
   PLA  1 # ADC  N STA
   PLA  0 # ADC  TAY
   IP 1+ LDA  PHA
   IP    LDA  PHA
   N LDA
   IP    STA
   IP 1+ STY
   NEXT JMP  END-CODE

And its use in the assembler.
Code:
ASSEMBLER DEFINITIONS
: >FORTH  ( -- )
   ?EXEC
   (>FORTH) JSR
   CURRENT @ CONTEXT !
   ] ; IMMEDIATE
FORTH DEFINITIONS

Although (>FORTH) , and all SUBR words, return the address of the code, the code in (>FORTH) does not alter the data stack.

I've also looked at the fig-FORTH Installation Manual and it appears that version of CODE also creates a header.
Here is a test screen.
Code:
SCR# 8001
0: // TEST -- THIS IS GOING TO FAIL!
1: HERE 0 , CODE  ( N -- N+10 )
2:    CLC
3:    0 ,X LDA  10 # ADC  0 ,X STA
4:    TYA  1 ,X ADC  1 ,X STA
5:    NEXT JMP
6: END-CODE
7:
8:
9:
A:
B:
C:
D:
E:
F:

And the result of trying to load it.
Code:
0 FH LOAD
( EXISTS

SCR# 32769 LINE# 1
HERE 0 , CODE  ( N -- N+10 )
                   ^^
WHAT?

So CODE tries to redefine open parenthesis and the system aborts when double dash can't be found in the specified search order. Not surprising since double dash isn't defined in Fleet Forth.
One way to create a headerless code word in Fleet Forth would be the following.
Code:
ASSEMBLER MEM ALIGN  // NAMELESS CODE DEF -- INCONVENIENT
   HERE DUP 2+ ,
   $D020 INC
   NEXT JMP
   FORTH

I should mention that ALIGN does not align to an even address. If the address is of the form $xxFF, ALIGN allots one byte to push the address past the page boundary. This is done to avoid the indirect jump bug found in NMOS 6502s and the C64's 6510 processor.
Here is a way to create headerless subroutines in Fleet Forth. There is no CFA, just an address to to be used by JSR.
Code:
HERE ASSEMBLER MEM
   $D020 INC
   RTS
FORTH

Used like so.
Code:
>A                   \ push address to aux stack
CODE TEST
   A> JSR  NEXT JMP  \ pull it and use with JSR
   END-CODE



Top
 Profile  
Reply with quote  
PostPosted: Sat Dec 25, 2021 2:14 am 
Offline

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

I've been entertaining some ideas on changing the working of INTERPRET which breaks the way INTERPRET would exit the interpret loop.
I already mentioned that Fleet Forth's WORD parses the text stream and returns the address of a counted string padded with a trailing blank as per the Forth-83 Standard. The address it returns is HERE . If the text stream is exhausted as WORD is called, then a count of zero followed by a trailing blank will result.
The find primitive, when checking a header for a match, checks the count and each character, using exclusive or, until it finds a non-match. The high bit of the last comparison is masked off ( $7F and). If this results in a match, the word was found. If not, follow the link to the next header, unless the link field has a value of zero. This is the shortest and fastest way I could write the search with a traditional dictionary with headers next to their respective code fields. Because of this behavior, if there is a name in the dictionary with just a count of zero and a blank space (both having the high bit set), it will be found when WORD is called on an empty text stream and the returned address is passed to FIND .
This was the means used to exit the interpreter in Fleet Forth. The mystery word was an immediate alias for EXIT which had no parameter field, just a code field which pointed to the body of EXIT .
My latest changes to Fleet Forth did away with that. The mystery word is now gone and INTERPRET checks the size of the string returned by WORD . If the size is zero, INTERPRET exits.
Here is the new INTERPRET .
Code:
// INTERPRET
: INTERPRET  ( -- )
   BEGIN
      PAUSE  NAME C@ 0EXIT
      HERE FIND ?DUP
      IF
         STATE @ =
         IF
            ,
         ELSE
            EXECUTE ?STACK
         THEN
      ELSE
         NUMBER? ?HUH ?STACK
         DPL @ 0<
         IF
            DROP STATE @
            IF
               [COMPILE] LITERAL
            THEN
         ELSE
            STATE @
            IF
               SWAP
               [COMPILE] LITERAL
               [COMPILE] LITERAL
            THEN
         THEN
      THEN
   AGAIN ;   -2 ALLOT

This version of INTERPRET is six bytes larger; however, removing the mystery word saves six bytes. So far, everything works fine.
This is what I'm going to try next:
Code:
DEFER I/C
: INTERPRET
   BEGIN
      PAUSE NAME C@ 0EXIT
      HERE I/C
   AGAIN ; -2 ALLOT

which is just a streamlined version of this:
Code:
DEFER I/C
: INTERPRET
    BEGIN
      PAUSE NAME DUP C@
    WHILE
      I/C
    REPEAT
    DROP ;

with the default vector for I/C :
Code:
: (I/C)  ( ADR -- ? )
   FIND ?DUP
   IF
      STATE @ =
      IF  , EXIT  THEN
      EXECUTE ?STACK  EXIT
   THEN
   NUMBER? ?HUH ?STACK  DPL @ 0<
   IF
      DROP STATE @ 0EXIT
      [COMPILE] LITERAL  EXIT
   THEN
   STATE @ 0EXIT  SWAP
   [COMPILE] LITERAL
   [COMPILE] LITERAL ;

This latest modification will allow me to remove some of the 'tricky code' from the metacompiler. I wrestled with this for a while because the metacompiler is not a part of Fleet Forth for normal day to day use. It's more like an application written in Fleet Forth to build new versions of the kernel. The metacompiler is already large and reducing a little complexity in it at the expense of even a little bit larger Fleet Forth didn't seem worthwhile. I'm investigating this newest version of Fleet Forth's interpreter not just for the benefit of the metacompiler, but because it will open up other possibilities as well.
[Edit: Minor correction. It's the metacompiler's complexity which could be reduced, not necessarily its size.]


Last edited by JimBoyd on Sun Dec 26, 2021 7:33 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Sat Dec 25, 2021 2:26 am 
Offline

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

Here is the version of :NONAME I'm considering adding to Fleet Forth's system loader.
Code:
: ALIGN  ( ADR1 -- ADR2 )
   HERE 1+ SPLIT DROP 0= ABS ALLOT ;
// DUMMY TARGET TO UNSMUDGE
HERE >A  0 C,
: :NONAME  ( -- ADR )
   ALIGN  HERE  DUP (IS) LAST
   CURRENT @ CONTEXT !
   [ A> ] LITERAL TRUE
   [ ' : @ ] LITERAL , ]       ;



There is a slight error in the stack comment for :NONAME . The actual stack comment should be
( -- ADR ADR2 TRUE )
ADR2 and TRUE are consumed by semicolon . ADR2 is the address of an unused byte, a safe target semicolon can 'unsmudge'. After semicolon executes, ADR, the address of the nameless word's code field, is the only one of the three parameters left on the stack
For comparison, here is the stack effect for colon.
( -- LATEST TRUE )
[Edit: corrected an error. EXIT does not consume anything from the stack. It pulls an address off the return stack and places it in IP .
Code:
: ?PAIRS  ( N1 N2 -- )
   <>
   ABORT" STRUCTURE MISMATCH" ;
: ;  ( ADR TRUE -- )
   COMPILE EXIT
   [COMPILE] [
   TRUE ?PAIRS  TSB ; IMMEDIATE



Last edited by JimBoyd on Sun Jan 09, 2022 7:04 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Thu Dec 30, 2021 1:52 am 
Offline

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

This is what I'm going to try next:
Code:
DEFER I/C
: INTERPRET
   BEGIN
      PAUSE NAME C@ 0EXIT
      HERE I/C
   AGAIN ; -2 ALLOT

which is just a streamlined version of this:
Code:
DEFER I/C
: INTERPRET
    BEGIN
      PAUSE NAME DUP C@
    WHILE
      I/C
    REPEAT
    DROP ;

with the default vector for I/C :
Code:
: (I/C)  ( ADR -- ? )
   FIND ?DUP
   IF
      STATE @ =
      IF  , EXIT  THEN
      EXECUTE ?STACK  EXIT
   THEN
   NUMBER? ?HUH ?STACK  DPL @ 0<
   IF
      DROP STATE @ 0EXIT
      [COMPILE] LITERAL  EXIT
   THEN
   STATE @ 0EXIT  SWAP
   [COMPILE] LITERAL
   [COMPILE] LITERAL ;



I've made this change to Fleet Forth and it works great. Since FIND is in (I/C) rather than INTERPRET , (I/C) and any other vector used with the deferred word I/C takes the string address returned by BL WORD .
I mentioned multi line comments and the word COMMENT: in another post. The drawbacks to this version of COMMENT: are that it is case sensitive in a case insensitive Forth and it only works within the current text stream. This is more than sufficient if loading source from blocks; however, Fleet Forth also has the ability to include source from Commodore 64 sequential files (C64 version of text files).
Code:
" FILENAME" INCLUDE

The given file is included one line at a time. The original version of COMMENT: will not work with multi line comments in ordinary text files because the file is interpreted/compiled one line at a time, just like text typed in from the keyboard.
Fleet Forth's new INTERPRET allows an elegant solution.
Code:
// MULTI LINE COMMENTS
: MLC  ( ADR -- )
   9 " ;COMMENT" TEXT= 0EXIT
   ['] (I/C) IS I/C ;
: COMMENT:  ( -- )
   ['] MLC IS I/C ; IMMEDIATE

If the word COMMENT: is encountered, it sets I/C to MLC (Multi Line Comment). MLC checks if the string returned by BL WORD is ';COMMENT' . If it is, MLC sets I/C back to (I/C) .
This version of multi line comments will span multiple lines in a text file. It will even span multiple blocks loaded with THRU . I have also tested it by typing COMMENT: at the Forth's command line. Nothing else typed has any effect until I type ;COMMENT . It even displays the 'OK' prompt if in interpreting state.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 354 posts ]  Go to page Previous  1 ... 11, 12, 13, 14, 15, 16, 17 ... 24  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: