6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Fri Nov 22, 2024 12:32 am

All times are UTC




Post new topic Reply to topic  [ 354 posts ]  Go to page Previous  1 ... 9, 10, 11, 12, 13, 14, 15 ... 24  Next
Author Message
PostPosted: Mon Apr 12, 2021 11:39 pm 
Offline

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

In addition to .S to display the contents of the data stack and .AS to display the contents of the auxiliary stack, Fleet Forth also has .RS to display the contents of the return stack. These three words can aid in tracking down errors.
Here is the source for Fleet Forth's ABORT , the word executed by (ABORT") .
Code:
: ABORT  ( -- )
   SINGLE ERR SP! AP!
   ['] LIT (IS) WHERE
   QUIT ; -2 ALLOT

SINGLE switches off multitasking, setting the deferred word PAUSE to execute the word NOOP , a no op.
SP! and AP! clear the data stack and the auxiliary stack.
The line
Code:
   ['] LIT (IS) WHERE

re enables WHERE .
-2 ALLOT reclaims the memory used by EXIT , as there is no returning from QUIT .
ERR is a deferred word normally set to the no op, NOOP . To help with troubleshooting, ERR can be set to a word like (ERR) .
Code:
: (ERR)
   CR ." DATA: " .S
   CR ."  AUX: " .AS
   CR ."  RET:"  .RS ;

This will display all three stacks when an error is encountered.

I recently redefined .RS . The old version worked well enough. Here is a test case:
Code:
: INNER
   1 0  DO  .RS  LOOP ;
: OUTER
   1 0  DO  INNER  LOOP ;
: WRAPPER
   OUTER ;

Executing WRAPPER produced something like the following:
Code:
WRAPPER
5807 .RS
7FFF
8001
580B
5823 INNER
7FFF
8001
5827
5839 OUTER
21A0 EXECUTE
21EB INTERPRET
 OK

This does show the nesting to the word .RS , but it is not accurate and I desired that accuracy for some of my Forth experiments.
The address 21EB is not in INTERPRET , but in QUIT . Do-colon places the address 21EB on the return stack when QUIT executes INTERPRET , just as the address 21A0 is placed on the return stack when WRAPPER is executed. The improved .RS uses the word AFIND to show which word contains a given address.
Code:
AFIND  ( ADDR -- ADDR LINK1 LINK2 )

ADDR is the address in question. LINK1 is the closest link address to ADDR from below and LINK2 is the closest link address to ADDR from above.
For each value on the return stack, .RS displays it as an unsigned number. If, as an address, it is within the dictionary, the name of the word containing that address is displayed.
Here is the result of using the new .RS with the above test case (I've added lowercase comments to the session log):
Code:
WRAPPER
5807 INNER
7FFF
8001
580B INNER      \ one of the DO LOOP parameters in INNER
5823 OUTER
7FFF
8001
5827 OUTER      \ one of the DO LOOP parameters in OUTER
5839 WRAPPER
21A0 INTERPRET
21EB QUIT
 OK

Although the address 580B is in the word INNER , it is not placed there by executing another word. It is the first DO LOOP parameter placed on the return stack. It is the branch address used by any LEAVE or ?LEAVE ( of which there are none in this test ) and used by LOOP and +LOOP when a DO LOOP terminates through LOOP or +LOOP.


Top
 Profile  
Reply with quote  
PostPosted: Fri Apr 16, 2021 11:11 pm 
Offline

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

I made a change to Fleet Forth's LINELOAD , which is used by LOAD . LINELOAD takes a line number and a screen number as parameters. It loads a given screen starting at the line specified.
I read somewhere (I don't remember where) a recommendation that LOAD should set BASE to decimal prior to loading a screen. I've given this some thought and can not really see a down side. As it is, I have been in the habit of specifying the number base at the start of a screen. With this modification, I will not have to specify the number base if I am using decimal for a particular screen.
Here is the source for Fleet Forth's LINELOAD and LOAD
Code:
: LINELOAD  ( LINE# SCR# -- )
   ?DUP 0=
   ABORT" CAN'T LOAD 0"
   RB DECIMAL
   BLK 2@ 2>R
   BLK !  C/L * >IN !
   INTERPRET  2R> BLK 2! ;
: LOAD  ( U -- )
   0 SWAP LINELOAD ;

This line:
Code:
   RB DECIMAL

Sets the base to decimal and also causes LINELOAD and LOAD to revert to the number base in use prior to loading a screen.


Top
 Profile  
Reply with quote  
PostPosted: Sat Apr 17, 2021 12:56 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8543
Location: Southern California
I think it's a bit short-sighted for someone to say decimal should be the default. In my own uses, it make sense to keep it in hex most of the time, and change to decimal or binary only temporarily and in limited places.

_________________
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: Sat Apr 17, 2021 2:12 am 
Offline

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

The idea isn't so much to have decimal as a default, but to avoid having no default. This change doesn't change anything for screens where I still set the base to hexadecimal on line one since having LOAD set a particular base doesn't preclude changing the base while loading the screen. I usually use line zero for a comment that the word INDEX displays and line one to set the base. On the screens that will compile the same in hexadecimal or decimal, it is tempting to not specify the base. Without a default of some kind, it may be necessary to specify the number base for each screen since they can be loaded individually for testing purposes.
With the other change, RB causes LOAD to restore base to the value it had prior to loading a screen. Base can be changed in a given screen (possibly multiple times). After a screen is loaded, base is restored to what it was before I loaded that screen.
As an example, suppose I'm working in binary to check out some information that makes more sense to view in binary and I have some bit manipulation words defined on screen 250. No base was set in the source because that screen will load just fine in hexadecimal or decimal. It just won't load correctly in binary. While working in binary, I can load these words by typing:
#250 LOAD ( or $FA LOAD ) without leaving binary and I'm right back in binary after the screen loads.
I suppose this change to LOAD has to do with my recent change to NUMBER? . Before that change, If I was working in binary and wanted to load screen number 250, I would have to change to decimal (or hexadecimal) to load it, since that would be more convenient and easier to remember than typing:
11111010 LOAD


There is one caveat: If there is an error while loading a screen, Fleet Forth will be left in whatever base was in use until I next change base. This is no different than before. RB can't do anything about that.


Top
 Profile  
Reply with quote  
PostPosted: Sat Apr 17, 2021 2:20 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8543
Location: Southern California
I guess it would be a little strange to have screens numbered in something other than decimal.

_________________
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: Sat Apr 17, 2021 2:36 am 
Offline

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

Funny you should say that. Part of the reason I chose decimal as the default is that I'd been thinking a little too much in hexadecimal when working on Fleet Forth. When I would print a range of screens to a print dump file, I was in the habit of printing them in hexadecimal.


Top
 Profile  
Reply with quote  
PostPosted: Sat Apr 17, 2021 5:55 am 
Offline

Joined: Sun Apr 26, 2020 3:08 am
Posts: 357
GARTHWILSON wrote:
I guess it would be a little strange to have screens numbered in something other than decimal.

I find it stranger to have screens numbered in decimal. But that's just because I wrote some words based on screens being multiples of 16 (0-$F). Also, being on a hard drive and not dealing with floppies really helps. Screen files can be created to be multiples of $50 (80), $100 (256), $200 (512), up to $8000 (32768) screens at a time, which is the Prodos max file size on the Apple II.

Personally, I just find it easier to count in hexadecimal.


Top
 Profile  
Reply with quote  
PostPosted: Thu Apr 22, 2021 2:32 am 
Offline

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

Whether listing in hexadecimal, decimal, or some other base, the base used to list is independent of the base used for loading.
The advantages of my modifications, for me, are:
1) I don't have to specify the base in the source of a screen. If I need a number in hexadecimal, I can prefix the number with the character $.
2) If I need to load some tools, whatever base I'm working in is automatically restored after the load.


Top
 Profile  
Reply with quote  
PostPosted: Thu Apr 22, 2021 2:58 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
barrym95838 wrote:
I'm not properly equipped to give any meaningful critiques, but I'm very happy to tag along and try to learn a thing or two.


You don't need to let that stop you. Although I eventually went with AYPUSH (which has the high byte in Y) as well as keeping PUSH , your suggestion about PUSHAY got me thinking in that direction.


Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 27, 2021 12:16 am 
Offline

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

Coroutines in Fleet Forth, an ITC Forth.
I discussed coroutines here. This is a more thorough and, I hope, more complete presentation of coroutines in Fleet Forth.
Coroutines are two routines which take turns executing. This is not the same as mutual recursion. Two (or more) routines which are mutually recursive execute one another from the beginning, causing the return stack to grow until the condition is met to not recurse. One place I've seen mutual recursion in Forth is turtle graphics. Mutual recursion in Forth can be achieved with deferred words.
Code:
DEFER LBRANCH
: RBRANCH   \ draw right branch and sub-branches of tree
   ... RECURSE ... ;
   ... LBRANCH ... ;
: (LBRANCH)   \ draw left branch and sub-branches of tree
   ... RECURSE ... ;
   ... RBRANCH ... ;
(LBRANCH) IS LBRANCH

In Fleet Forth, the word RECURSE causes the CFA of the word being defined to be compiled.
Note that comments to the end of line in Fleet Forth use the word // since the Commodore 64 lacks a backslash character. The use of the backslash indicates comments which were added to the listing or session log, although in this case, this isn't an actual listing as the ellipses indicate code not shown.

As coroutines switch back and forth, each coroutine resumes where it left off. If two coroutines are infinite loops, they behave like a primitive multitasking system with two tasks that share stacks and run forever. I have yet to think of a good use for such coroutines where a cooperative round robin multitasker wouldn't work better. If anyone knows of such a case, I would like to know about it. Coroutines which are not infinite loops are, in my opinion, far more interesting.

Fleet Forth's coroutine word is CO . CO can be written as a primitive (a code word) or in high level Forth; either way, it swaps the top value on the return stack with the contents of IP , Forth's interpretive pointer. When a high level Forth word executes, do-colon pushes the current contents of IP onto the return stack and places the address of the body of the high level word being executed in IP. EXIT , the word used to return from a high level Forth word to the word which executed it, pulls the top address off the return stack and stores it in IP. When CO is defined as a high level Forth word, do-colon and EXIT move the contents of IP to and from the return stack so all that is necessary, in the body of CO , is to swap the top two items on the return stack.
Code:
: CO   2R> SWAP 2>R ;

Here is a brief example.
Code:
: GREETINGS
   CR ." GREETINGS!"      CO
   CR ." HOW DO YOU DO?"  CO
   CR ." GOODBYE."        ;
: HELLO
   GREETINGS
   CR TAB ." HELLO THERE!"       CO
   CR TAB ." AS WELL AS I CAN."  CO
   CR TAB ." SO LONG." ;

Here is an excerpt of a session log showing the execution of the word HELLO
Code:
GREETINGS!
          HELLO THERE!
HOW DO YOU DO?
          AS WELL AS I CAN.
GOODBYE.
          SO LONG.

Notice that HELLO executes GREETINGS and when GREETINGS exits it exits to HELLO . HELLO exits to whatever executed it.

Here is a slightly different version.
Code:
: GREETINGS
   CR ." GREETINGS!"      CO
   CR ." HOW DO YOU DO?"  CO
   CR ." GOODBYE."        CO
   CR ." THANK YOU."      ;
: HELLO
   GREETINGS
   CR TAB ." HELLO THERE!"        CO
   CR TAB ." AS WELL AS I CAN."   CO
   CR TAB ." BYE AND TAKE CARE."  ;

and an excerpt from the session log where HELLO is executed.
Code:
GREETINGS!
          HELLO THERE!
HOW DO YOU DO?
          AS WELL AS I CAN.
GOODBYE.
          BYE AND TAKE CARE.
THANK YOU.

Once again, HELLO executes GREETINGS . This time, HELLO exits to GREETINGS which exits to INTERPRET .


One coroutine might be an infinite loop. here is an example.
Code:
// DECISIONS DECISIONS
: ANSWER
   BEGIN
      CR TAB ." MAYBE." CO
      CR TAB ." YES!"   CO
      CR TAB ." NO."    CO
   AGAIN ;
: QUESTION
   CR ." WILL THIS WORK?" ANSWER
   CR ." ARE YOU SURE?"       CO
   CR ." REALLY?"             CO
   CR ." CAN YOU BE SERIOUS?" CO
   CR ." FOR THIS PROJECT?"   CO
   CR ." ARE YOU QUALIFIED?"  CO
   CR ." KNOCK IT OFF!"       CO
   R> DROP ;

And the session log.
Code:
WILL THIS WORK?
          MAYBE.
ARE YOU SURE?
          YES!
REALLY?
          NO.
CAN YOU BE SERIOUS?
          MAYBE.
FOR THIS PROJECT?
          YES!
ARE YOU QUALIFIED?
          NO.
KNOCK IT OFF!
          MAYBE.

In this case, when the word QUESTION is finished, it must drop from the return stack the address of the other coroutine before QUESTION exits. What happens if it doesn't? When QUESTION exits, it will exit into ANSWER where ANSWER left off. ANSWER will, on the next execution of CO switch with INTERPRET . When interpret exits, it will exit into ANSWER . When ANSWER switches again, it will be with QUIT . QUIT is an infinite loop which clears the return stack at the start of the loop. That will be the end of the coroutine switching.

Although some words in Fleet Forth can start out as high level then transition to low level and vice versa, the high level portions of mixed level words can use coroutines and even be a coroutine to another word.

Looking back at the second example with GREETINGS and HELLO , the number of coroutine switching in the two words is different. This causes the word which executed the other to be the one which exits to the other. Here is a simplified example.
Code:
// COROUTINE DEMO 1 COROUTINE
: GREETINGS
   CR ." GREETINGS!"        CO
   CR ." AS WELL AS I CAN." ;
: HELLO
   GREETINGS
   CR TAB ." HI! HOW DO YOU DO?" ;

And the result of executing HELLO .
Code:
GREETINGS!
          HI! HOW DO YOU DO?
AS WELL AS I CAN.

Only the word GREETINGS uses the coroutine word, CO . HELLO executes GREETINGS but also exits to GREETINGS. The part of GREETINGS before CO is executed, then GREETINGS switches with HELLO . The part of GREETINGS after CO is executed when HELLO exits. This can be used to advantage. One example is the word RB , the word which causes the word executing it to cause the value of BASE to be restored to what it was prior to the execution of RB .
Code:
: RB  ( -- )
   BASE @ R> 2>R  CO
   R> BASE ! ;

The only tricky thing here is to tuck the value of BASE under the return address.

It is possible to craft even more complex words, but care must be exercised. In the following examples, the word (ERR) displays the contents of all of Fleet Forth's stacks.

For each iteration of its loop, the word PROTO will cause execution to pass to the Forth thread which is the body of the word TEST . TEST exits to PROTO which conditionally continues the loop.
Code:
: TEST
   CR ." TESTING." (ERR) ;
: PROTO
   BEGIN
      ['] TEST >BODY >R CO
      DONE?
   UNTIL
   (ERR)
   CR ." PROTO FINISHED." ;

And here is an excerpt of a session log showing the execution of PROTO .
Code:
TESTING.
DATA: EMPTY
 AUX: EMPTY
 RET:
5610 (ERR)
57AF TEST
57C7 PROTO
21A0 INTERPRET
21EB QUIT

TESTING.
DATA: EMPTY
 AUX: EMPTY
 RET:
5610 (ERR)
57AF TEST
57C7 PROTO
21A0 INTERPRET
21EB QUIT

DATA: EMPTY
 AUX: EMPTY
 RET:
5610 (ERR)
57CF PROTO
21A0 INTERPRET
21EB QUIT

PROTO FINISHED.

This variation of PROTO will cause execution to pass to the Forth thread which is the portion of TEST which occurs after PROTO .
Code:
: PROTO
   BEGIN
      R@ >R CO
      DONE?
   UNTIL
   (ERR)  R> DROP
   CR ." PROTO FINISHED." ;
: TEST
   PROTO  CR ." TESTING." (ERR) ;

Notice this time TEST is executed directly, rather than PROTO . When the loop in PROTO finishes, there is an address of a Forth thread on the return stack. It points into the body of TEST just past where PROTO occurs in the definition of TEST . It must be dropped or that portion of TEST will be executed again. This would not matter for this trivial example, however it will be important later.
Here is an excerpt of a session log where TEST is executed.
Code:
TESTING.
DATA: EMPTY
 AUX: EMPTY
 RET:
5610 (ERR)
5783 TEST
5745 PROTO
5774 TEST
21A0 INTERPRET
21EB QUIT

TESTING.
DATA: EMPTY
 AUX: EMPTY
 RET:
5610 (ERR)
5783 TEST
5745 PROTO
5774 TEST
21A0 INTERPRET
21EB QUIT

DATA: EMPTY
 AUX: EMPTY
 RET:
5610 (ERR)
574D PROTO
5774 TEST
21A0 INTERPRET
21EB QUIT

PROTO FINISHED.

The following version of PROTO has a value which is incremented each time through the loop. A copy is kept on the return stack while another copy is left on the data stack for the thread of Forth from TEST to use.
Code:
: PROTO  ( -- N )
   0
   BEGIN
      1+
      DUP R@ 2>R CO R>
      DONE?
   UNTIL
   (ERR)
   R> DROP
   CR ." OUT OF PROTO LOOP"
   CR ." LOOPED " . ." TIMES" CR ;
: TEST
   0  PROTO  + (ERR) ;

And here is an excerpt of a session log where TEST is executed.
Code:
DATA:    1
 AUX: EMPTY
 RET:
5610 (ERR)
579C TEST
574B PROTO
   1
5798 TEST
21A0 INTERPRET
21EB QUIT

DATA:    3
 AUX: EMPTY
 RET:
5610 (ERR)
579C TEST
574B PROTO
   2
5798 TEST
21A0 INTERPRET
21EB QUIT

DATA:    6
 AUX: EMPTY
 RET:
5610 (ERR)
579C TEST
574B PROTO
   3
5798 TEST
21A0 INTERPRET
21EB QUIT

DATA:    6    3
 AUX: EMPTY
 RET:
5610 (ERR)
5755 PROTO
5798 TEST
21A0 INTERPRET
21EB QUIT

OUT OF PROTO LOOP
LOOPED 3 TIMES

TEST , or any other word which has PROTO , can not use the coroutine word CO after PROTO . If TEST had CO after PROTO , it would be like the other examples with coroutines in that TEST would switch to PROTO without exiting to it. The thread address would be left on the stack and PROTO would pull it from the return stack and use it as the value which is being incremented. The actual value being incremented would be used as the thread of Forth to switch with PROTO . This would most likely cause a crash when PROTO executes CO , depending on the value.
There is nothing preventing TEST from using CO before PROTO or using another word which uses CO , so long as TEST exits to PROTO rather than switching with it.
Code:
: AFTER-.   CO  TAB U. (ERR) ;
: TEST2   PROTO  AFTER-. ;

Here is an excerpt of a session log where TEST2 is executed.
Code:
          1
DATA: EMPTY
 AUX: EMPTY
 RET:
5610 (ERR)
57B4 AFTER-.
574B PROTO
   1
57C4 TEST2
21A0 INTERPRET
21EB QUIT
          2
DATA: EMPTY
 AUX: EMPTY
 RET:
5610 (ERR)
57B4 AFTER-.
574B PROTO
   2
57C4 TEST2
21A0 INTERPRET
21EB QUIT
          3
DATA: EMPTY
 AUX: EMPTY
 RET:
5610 (ERR)
57B4 AFTER-.
574B PROTO
   3
57C4 TEST2
21A0 INTERPRET
21EB QUIT

DATA:    3
 AUX: EMPTY
 RET:
5610 (ERR)
5755 PROTO
57C4 TEST2
21A0 INTERPRET
21EB QUIT

OUT OF PROTO LOOP
LOOPED 3 TIMES

Here is another example:
Code:
: TEST3
   PROTO  CR U. HELLO ;

And the session log excerpt where TEST3 is executed.
Code:
1
GREETINGS!
          HELLO THERE!
HOW DO YOU DO?
          AS WELL AS I CAN.
GOODBYE.
          BYE AND TAKE CARE.
THANK YOU.
2
GREETINGS!
          HELLO THERE!
HOW DO YOU DO?
          AS WELL AS I CAN.
GOODBYE.
          BYE AND TAKE CARE.
THANK YOU.
3
GREETINGS!
          HELLO THERE!
HOW DO YOU DO?
          AS WELL AS I CAN.
GOODBYE.
          BYE AND TAKE CARE.
THANK YOU.
DATA:    3
 AUX: EMPTY
 RET:
5610 (ERR)
5755 PROTO
58AE TEST3
21A0 INTERPRET
21EB QUIT

OUT OF PROTO LOOP
LOOPED 3 TIMES

As these examples show, TEST can have other coroutine words in its body after PROTO as long as TEST does not switch with PROTO , it must exit to PROTO because of the way PROTO works.

A few modifications to PROTO will change it into a simpler version of WITH-WORDS .
1) Use the value of CONTEXT and follow the link fields rather than increment the value.
2) Test for the end of a vocabulary and exit the loop by way of WHILE .
3) convert the copy of the value left on the data stack from the LFA to the NFA before CO is executed.
Code:
: WITH-WORDS  ( -- NFA )
   CONTEXT @
   BEGIN
      @ ?DUP
   WHILE
      DUP R@ 2>R  L>NAME CO
      R>
   REPEAT
   R> DROP ;

Here is the prototype for WORDS .
Code:
: WORDS  ( -- )
   WITH-WORDS  ID. SPACE ;

Here is a variation on Albert van der Horst's FOR-WORDS and WORDS which have been modified to work better with Fleet Forth.
Code:
// ALBERT'S FOR-WORDS
CODE CO-EXIT  ( R: ADR -- )
   PLA  PLA  NEXT JMP  END-CODE
: FOR-WORDS
   BEGIN
      @ ?DUP
   WHILE
      DUP CO
   REPEAT
   CO-EXIT ;
: WORDS
   CONTEXT @ FOR-WORDS
   BEGIN
      L>NAME ID. SPACE CO
   AGAIN ;

CO-EXIT ( exit the other coroutine) is just another name for RDROP , but the name CO-EXIT is more descriptive of what's happening in this case.
If both WITH-WORDS and FOR-WORDS used CO-EXIT ( or if both used the phrase R> DROP ), the combined size with with each one's respective version of WORDS would be the same, excluding the difference in name sizes. Actually, Albert's version could be made slightly smaller by reclaiming the two bytes used by EXIT , since that version of WORDS is an infinite loop.
Nevertheless, WITH-WORDS and the version of WORDS which uses it is a cleaner implementation. This version of WORDS is not itself a coroutine word. The only parameters on the data stack for the part of WORDS after WITH-WORDS is the NFA of a word in the context vocabulary. Words can be defined to use WITH-WORDS to:
Count the words in a vocabulary:
Code:
: #WORDS  ( -- N )
   0 WITH-WORDS  DROP 1+ ;

Or find the size of all the headers in a vocabulary:
Code:
: HEADERS  ( -- N )
   0 WITH-WORDS
   C@ $1F AND   \ get the size
   1+           \ add 1 for count byte
   2+           \ add 2 for size of link field
   + ;

The general rule for using WITH-WORDS in a word:
1) The part of a word which is before WITH-WORDS is executed once.
2) The part of a word after WITH-WORDS is executed once for each word in the context vocabulary.
3) The net affect WITH-WORDS has on the data stack is to leave the address of a word's NFA for use by that portion of threaded code which follows WITH-WORDS in a definition.
Here is a sample of the output of the simple version of WORDS presented so far:
Code:
WORDS WITH-WORDS ELAPSED CLEAR.TIME TIME TIME! .TIME SEXTAL BCD>INT (ERR)
                         .
                         .
                         .
CURRENT CONTEXT SPAN STATE >IN BLK FENCE

Here is Fleet Forth's WITH-WORDS and WORDS with all enhancements:
Code:
// WITH-WORDS WORDS
: WITH-WORDS  ( -- NFA )
   CONTEXT @
   AHEAD
   BEGIN
      DUP R@ 2>R L>NAME CO R>
   CS-SWAP THEN
      @ DUP 0= DONE? OR
   UNTIL
   R> 2DROP ;
: WORDS  ( -- )
   COLS ?CR
   WITH-WORDS DUP C@ $1F AND 1+ ?CR
   ID. TAB ;

And a sample of the output when COLS , a VALUE , is set to 70.
Code:
WORDS     WITH-WORDS          ELAPSED   CLEAR.TIME          TIME
TIME!     .TIME     SEXTAL    BCD>INT   (ERR)     FILES     IN-NAME
                         .
                         .
                         .
SPAN      STATE     >IN       BLK       FENCE     

Albert van der Horst also introduced the word AFTER-DROP , which drops an item from the data stack after the word in which it is used exits. In the case of a word which uses WITH-WORDS , If AFTER-DROP occurs before WITH-WORDS then an item is dropped from the data stack after the word which uses WITH-WORDS exits.
Code:
: AFTER-DROP
   CO DROP ;

Here is a trivial example to demonstrate.
Code:
: WORDS-3DROP
   AFTER-DROP AFTER-DROP AFTER-DROP
   WITH-WORDS
   ID. SPACE ;

This word will display all the words in the context vocabulary AND drop three items from the data stack.
Not a very useful word, I know. Here is one that is:
Code:
: IN-NAME  ( ADR -- )
   AFTER-DROP
   WITH-WORDS
   2DUP
   COUNT $1F AND >HERE CLIP COUNT
   2+ TRUE UNDER+
   BL HERE C!
   ROT COUNT MATCH NIP
   IF  CR ID. EXIT  THEN
   DROP ;

IN-NAME takes the address of a counted string ( the sub string) and displays the name of every word in the context vocabulary which contains that string in its name. The drop in AFTER-DROP drops the address of the sub string at the conclusion of IN-NAME .
Here is an excerpt from a session log where I've added comments.
Code:
" AD" IN-NAME  \ typed by me. substring: 'AD'
 .ADDR
 ADMODE
 ADDRESSES
 EAD
 SAD
 ADEPTH
 AHEAD
 LOAD
 LINELOAD
 ADD
 PAD  OK
"  AD" IN-NAME \ typed by me. substring: ' AD'
 ADMODE
 ADDRESSES
 ADEPTH
 ADD  OK
" AD " IN-NAME \ typed by me. substring: 'AD '
 EAD
 SAD
 AHEAD
 LOAD
 LINELOAD
 PAD  OK

The lines with the comment "typed by me. substring: 'xxx'" were entered at Forth's command line. Everything else in this excerpt is the computer's response.


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 03, 2021 2:40 am 
Offline

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

I've recently removed the virtual memory words from Fleet Forth's metacompiler's source, which it uses to build the target kernel, and placed them in the system loader but may move them to the optional utilities.
Since the kernel is less than 10k, Fleet Forth's virtual memory words are limited to 64k. Since a Ram Expansion Unit with 256 banks, the maximum possible with a C64 REU, can have 16 megabytes, I mention some ways around this limitation later in this post.
Fleet Forth uses blocks in the REU for virtual memory. Blocks in the REU start at block number 32768, or $8000. Fleet Forth includes a convenience word, RAM , which takes a number from the stack and adds $8000.
SPLIT , defined in the kernel, splits a number into its low and high bytes with the high byte on top.
JOIN takes a low byte and high byte and joins them into a sixteen byte number.
U/BBUF ( U divided by B/BUF ) divides an address by B/BUF and returns an offset into a block and a block number.
>VIRTUAL takes a virtual address and returns an address in a block buffer.
VC@ and VC! fetch and store a byte respectively in virtual memory.
V@ and V! are the virtual memory counterparts of @ and ! built on VC@ and VC! .
VDUMP takes an address and count. It does the same thing as DUMP , dump the contents of memory to the screen, or other output device, but it dumps the contents of virtual memory.
VMSAVE ( virtual memory save ) saves a section of virtual memory to a file on a Commodore 64 disk drive. VMSAVE takes the starting address, the address just past the end of the section of memory to be saved, the address of a name, and the length of the name. The name must end with ",P,W" to write the section of memory as a Commodore 64 program file.
There are a few display formatting words. 8BITS and 16BITS return the widths of the string representation of the largest 8 bit number and 16 bit number respectively. SETWIDTH sets these words according to the current number base.
Code:
CODE JOIN  ( LO HI -- U )
   0 ,X  LDA  3 ,X STA
   POP JMP  END-CODE
CODE U/BBUF  ( U -- OFFSET BLK# )
   1 ,X LDA  TAY
   3 # AND  1 ,X STA
   TYA  .A LSR  .A LSR
   APUSH JMP  END-CODE
: >VIRTUAL  ( VADR -- ADR )
   U/BBUF RAM BLOCK + ;
: VC@  ( VADR -- B )
   >VIRTUAL C@ ;
: VC!  ( B VADR -- )
   >VIRTUAL C! UPDATE ;
: V@  ( VADR -- N )
   DUP VC@  SWAP 1+ VC@  JOIN ;
: V!  ( N VADR -- )
   SWAP SPLIT
   ROT TUCK 1+ VC! VC! ;
: CMOVE>V  ( ADR VADR CNT -- )
   BOUNDS
   ?DO  COUNT I VC!  LOOP
   DROP ;
: (VDUMP)  ( ADR -- )  8 BOUNDS
   DO  I VC@ 8BITS .R SPACE  LOOP ;
: VDUMP  ( ADR CNT -- )
   SETWIDTH BOUNDS
   DO
      CR I DUP 16BITS U.R 2 SPACES
      DUP (VDUMP) SPACE
      DUP 8 + (VDUMP)
      2 SPACES 16 BOUNDS
      DO
         I VC@ QEMIT
      LOOP
      DONE? ?LEAVE
   16 +LOOP
   CR ;

The code for VMSAVE is somewhat convoluted just prior to and within the DO LOOP . When writing virtual memory to disk, I was trying to avoid opening and closing a channel for each byte written and it got a little messy. I plan to test a new version of VMSAVE to see how opening and closing the channel for each byte affects the time to save memory.
Code:
: VMSAVE  ( AS AE+1 ANAME CT -- )
   1 DR# 1 OPEN IOERR ?DISK
   1 CHKOUT IOERR
   SWAP DUP SPLIT SWAP
   (EMIT) (EMIT) CLRCHN
   DUP VC@ DROP 1 CHKOUT IOERR
   ?DO  I B/BUF MOD 0=
      IF  CLRCHN  THEN
      I VC@  I B/BUF MOD 0=
      IF  1 CHKOUT IOERR  THEN
      (EMIT)
   LOOP
   CLRCHN  1 CLOSE ;

These are the only general purpose virtual memory words needed by the metacompiler.
For completeness, here is CMOVEV> to move memory from virtual memory to the C64's memory.
Code:
: CMOVEV>  ( VADR ADR CNT -- )
   0
   DO
      OVER I + VC@  OVER I + C!
   LOOP
   2DROP ;

The metacompiler adds a few more virtual memory words specifically related to building a system in virtual memory. Here are just a few.
TDP target dictionary pointer.
THERE target HERE .
VALLOT virtual ALLOT .
VC, and V, the virtual memory counterparts of C, and , .
SALLOT string (at HERE ) allot. Takes size n on the stack and copies a string from HERE to THERE and allots n bytes of virtual memory.

Three possibilities come to mind to store more data than 64k in virtual memory.
64k at a time could be stored and the block moving words could be used to move those 64 blocks to higher numbered blocks in virtual memory. This is feasible because Fleet Forth has the word BMOVE to move blocks. The first 64 blocks in the REU could be copied to the next 64 with the following:
Code:
0 RAM 64 RAM 64 BMOVE

Another approach is modifying >VIRTUAL .
Code:
0 VALUE BANK
: >VIRTUAL  ( VADR -- ADR )
   U/BBUF RAM BANK 6 LSHIFT + BLOCK + ;

The REU can be though of as having a certain number of 64k banks (which is actually the case). The first 64k blocks can be selected for virtual memory with:
Code:
0 TO BANK

the next 64k blocks with:
Code:
1 TO BANK

and so on.

The third approach is to define a set of long virtual memory access words.
Code:
: >LVIRTUAL  ( LADDR -- ADDR )
   B/BUF UM/MOD RAM BLOCK + ;
: VLC@  ( LADDR -- B )
   >LVIRTUAL C@ ;
: VLC!  ( B LADDR -- )
   >LVIRTUAL C!  UPDATE ;
: VL@  ( LADDR -- N )
   2DUP VLC@  -ROT 1 0 D+
   VLC@ JOIN ;
: VL!  ( N LADDR -- )
   ROT SPLIT  2OVER 1 0 D+ VLC!
   -ROT VLC! ;

The LADDR in the stack comments is a double number virtual memory address. Here are the rest:
Code:
: VLCOUNT  ( LADDR -- LADDR+1 B )
   2DUP VLC@ -ROT 1 0 D+ ROT ;
: (VLDUMP)  ( LADDR -- LADDR+8 )
   8 0
   DO
      VLCOUNT 8BITS .R SPACE
   LOOP ;
: VLDUMP  ( LADDR CNT -- )
   SETWIDTH  0
   DO
      CR 2DUP 32BITS UD.R
      2 SPACES  2DUP
      (VLDUMP) SPACE (VLDUMP) 2DROP
      2 SPACES 16 0
      DO
         VLCOUNT QEMIT
      LOOP
      DONE? ?LEAVE
   16 +LOOP
   2DROP CR ;
: CMOVE>LV  ( ADR VLADR CNT -- )
   >R ROT R> 0
   DO
      COUNT 2OVER I 0 D+ VLC!
   LOOP
   2DROP DROP ;
: CMOVELV>  ( VLADR ADR CNT -- )
   0
   DO
      I 2OVER 0 -ROT D+ VLC@
      OVER I + C!
   LOOP
   DROP 2DROP ;

I realize that the REU could be accessed directly, rather than going through the block system. Part of the reason I do this on a simulated Commodore 64 is the nostalgia. Back in the day, not everyone had a Ram Expansion Unit. Some may have had more than one disk drive. Both versions of >VIRTUAL and >LVIRTUAL can be changed to access one of the disk drives for virtual memory by replacing RAM with the phrase
9 DR+ to access drive nine or
10 DR+ to access drive ten.


Top
 Profile  
Reply with quote  
PostPosted: Tue Jun 08, 2021 1:56 am 
Offline

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

The code for VMSAVE is somewhat convoluted just prior to and within the DO LOOP . When writing virtual memory to disk, I was trying to avoid opening and closing a channel for each byte written and it got a little messy. I plan to test a new version of VMSAVE to see how opening and closing the channel for each byte affects the time to save memory.


To deal with the Commodore 64 kernal using the same kernal routine to send a byte to the screen or any I/O device, I/O words in Fleet Forth, such as EMIT , TYPE , and the disk block read write words normally direct output to the desired device, transfer the data, and clear the channel. This version of VMSAVE did things a little differently to save time. I ran a test with a version of VMSAVE that did as other Fleet Forth I/O words. It directed output to the disk, emitted a byte, then cleared the channel. This version took about one and a half times as long to transfer 8k from virtual memory to a program file on one of the disk drives.
Since these listings are for a Commodore 64, just read each double slash // as a backslash \
Code:
// VMSAVE -- REALLY SLOW
: DDEMIT  ( C -- )
   1 CHKOUT ?IO DEMIT CLRCHN ;
: VMSAVE  ( AS AE+1 ANAME CT -- )
   1 DR# 1 OPEN IOERR ?DISK
   SWAP DUP SPLIT SWAP
   DDEMIT DDEMIT
   ?DO
      I VC@  DDEMIT
   LOOP
   1 CLOSE ;

I even used a very fast version of I/O error checking, ?IO .
Code:
// ERROR CODES FROM C64 KERNAL
// ARE ONE BYTE
CODE ?IO  ( EC -- )
   0 ,X LDA
   0= IF
      POP JMP
   THEN
   >FORTH
   IORESET IOERR ;


This next version of VMSAVE uses DTYPE to send up to 1k of data directly from the block buffer to the program file being created, rather than sending one byte at a time.
Code:
// VMSAVE
: VMSAVE  ( AS AE+1 ANAME CT -- )
   1 DR# 1 OPEN IOERR ?DISK
   OVER -
   OVER  SPLIT SWAP
   1 CHKOUT ?IO  DEMIT DEMIT CLRCHN
   BEGIN
      OVER $3FF AND B/BUF SWAP -
      OVER UMIN 2PICK >VIRTUAL OVER
      1 CHKOUT ?IO DTYPE CLRCHN
      /STRING ?DUP 0=
   UNTIL
   DROP 1 CLOSE ;

After opening a data channel to the disk, it converts the starting address and ending address plus one to the starting address and length. Since each kilobyte of virtual memory aligns on a block buffer boundary, the address is ANDed with $3FF and subtracted from B/BUF ( $400 ) . The unsigned minimum of this and the count is the amount to send. /STRING uses this unsigned minimum to adjust the starting address and the count. When the count reaches zero, all of the virtual memory from the starting address to the ending address has been sent.
Although the primary purpose of DTYPE is to send data to one of the disk drives, it does not redirect output. The only difference between DTYPE and TYPE are, DTYPE is a primitive and it does not update CHARS and LINES , the two variables used for output formatting.
In the line with DTYPE , output is directed to the disk drive before DTYPE and the channel cleared after, in keeping with how Fleet Forth I/O works.
This version of VMSAVE takes only forty four percent as much time as the original.

The same technique used by this version of VMSAVE , handling up to 1k at a time and directly accessing this data in the block buffer, could also be used to speed up CMOVE>V and CMOVEV> , as well as their long virtual address counterparts, if these words are used to move large amounts of data. In the metacompiler, they normally move a small amount, such as a name or small string, from HERE to THERE .


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 06, 2021 2:14 am 
Offline

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

I've been working with Fleet Forth's multitasker again. One of the difficulties with the multitasker is the word EXPECT . It is a primitive in Fleet Forth, but even if it was high level, it would still present difficulties. EXPECT , actually its vector, (EXPECT) , calls the C64 Kernal routine CHRIN at $FFCF. EXPECT is a deferred word.
Each call to CHRIN returns a single character, but CHRIN handles keyboard input differently than other sources. If the keyboard is the input source, CHRIN echoes the characters received to the screen until the return key is pressed. Once the return key is pressed, each call to CHRIN returns a character by reading it from the current logical line on the screen (the one with the cursor).
The advantage is that this is how the Commodore 64 screen editor works. It is possible to scroll to any line and type return. That line will be read in as if it had just been typed followed by a carriage return.
The disadvantage is that CHRIN will return nothing until the return key is pressed. It will not even return! It will keep running, echoing characters to the screen ( including characters generated by the scroll keys ), waiting for a carriage return.
This means that cooperative round robbin multitasking will not work.
My first work around was to write a multitasker version of (EXPECT) called (MEXPECT) . (MEXPECT) started as a primitive. If the input source was NOT the keyboard then (MEXPECT) jumped to the body of (EXPECT) . If input was from the keyboard, the jump to the body of (EXPECT) was branched over. (MEXPECT) transitioned to high level and performed the function of the screen editor in high level Forth.
This was not an elegant solution. (MEXPECT) was large enough that I didn't want to include it in the kernel as a better version of (EXPECT) . It was switched in by the word MULTI and switched back out by the word SINGLE . (MEXPECT) wasn't even a proper vector for EXPECT since I/O redirection could change which word EXPECT executed.
The multitasker word SINGLE , which is defined in Fleet Forth's kernel so it is always available, had two jobs:
1) set the deferred word PAUSE to NOOP , a no operation word.
2) set the code field of (EXPECT) to point to its own body.
The multitasker word MULTI had three jobs:
1) make sure the task executing MULTI is awake.
2) set the deferred word PAUSE to (PAUSE) , the task switcher.
3) set the code field of (EXPECT) to point to the body of (MEXPECT) . This worked since they both start out as code words.

I have since worked out a better solution: trick CHRIN.
The new version of (MEXPECT) still tests to see if input is from the keyboard and jumps to the body of (EXPECT) if it isn't. If input is from the keyboard, (MEXPECT) prints a carriage return, if necessary, to start a new line. It still echoes the characters to the screen until it receives a carriage return. It then sets location $D3 to zero to indicate the first column. It also sets $D4 to zero to clear quote mode. It then stuffs a carriage return into the keyboard buffer and executes (EXPECT) . CHRIN reads the carriage return and dutifully returns characters from the current logical line on the screen. The logical line could be 40 columns or 80 columns, the C64 Kernal maintains a table for that.
Code:
CODE (MEXPECT)  ( ADR CNT -- )
   $99 LDA
   0= NOT IF
      ' (EXPECT) >BODY JMP
   THEN
   >FORTH
   COLS ?CR
   BEGIN
      PAUSE  KEY DUP 13 <>
   WHILE
      DEMIT
   REPEAT
   $277 C!  1 $C6 C!  $D3 OFF
   (EXPECT) ;

This version of (MEXPECT) is small enough that I am considering making (EXPECT) headerless and including (MEXPECT) in the Fleet Forth kernel as (EXPECT) .
With the other changes to the Fleet Forth kernel to support it, such as simplifying SINGLE , it would only add about 45 bytes to Fleet Forth's kernel.

[Edit: fixed an off by one error in my count]


Top
 Profile  
Reply with quote  
PostPosted: Sun Jul 11, 2021 11:02 pm 
Offline

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

I've made the changes to my latest Fleet Forth kernel. The extra cost of 45 bytes would have been if I had removed the CR in QUIT and replaced the phrase COLS ?CR in the new (EXPECT) with CR .
I left those in place which should have made the new kernel 49 bytes bigger than it was before the new version of (EXPECT) .
Rather than make the old version of (EXPECT) a headerless word in the kernel, I incorporated it into the new version of (EXPECT) . The new Fleet Forth kernel is now only 42 bytes bigger with this new version of (EXPECT) .
Code:
CODE (EXPECT)  ( ADR CNT -- )
   $99 LDA
   0= IF
      >FORTH
      COLS ?CR
      BEGIN
         PAUSE  KEY DUP 13 <>
      WHILE
         DEMIT
      REPEAT
      // STUFF CR IN KBD BUFFER
      // AND SET CURSOR TO COLUMN 0
      $277 C!  1 $C6 C!  $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
' (EXPECT) IS EXPECT

There is an odd bug in the Commodore 64's CHRIN Kernal routine. The Commodore 64's I/O routines can be used with the keyboard and screen or I/O devices. The default is reading from the keyboard and writing to the screen. When using this routine to read the keyboard (actually, using the C64 screen editor), there is an edge case. Under certain conditions, If less than the number of characters on the logical line are read before writing to the screen, there will be odd cursor behavior. The following section of code in (EXPECT) consumes all characters after the requested amount until a carriage return is received. This eliminates the bug and is only done when using the C64 screen editor (when the input source is the keyboard) so will not affect using (EXPECT) to read from other sources, such as the disk drives.
Code:
      $99 LDA
      0= IF
         BEGIN
            $FFCF JSR  // CHRIN
            13 # CMP
         0= UNTIL
      THEN

With the new version of (EXPECT) , SINGLE is now just:
Code:
: SINGLE  ( -- )
   ['] NOOP IS PAUSE ;

That takes care of the multitasker support built into Fleet Forth's kernel. Now I need to clean up the rest of the multitasker source.


Top
 Profile  
Reply with quote  
PostPosted: Sun Jul 25, 2021 9:11 pm 
Offline

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

I've briefly mentioned Fleet Forth's cold start and warm start routines in previous posts. I'll try to explain them in this post.
When loading and running Fleet Forth, the cold start routine starts with Fleet Forth's one line of BASIC:
Code:
10 SYS XXXXX

where XXXXX is the address of the cold start routine. There is a subroutine used by both the cold start and warm start routines. It is headerless and has no code field since it is a subroutine, but it has the label (WARM) in the Fleet Forth source.
Here is the source for (WARM) :
Code:
HEX
HSUBR (WARM)
   SEI  CLD
   2F # LDA  0 STA
   36 # LDA  1 STA
   USER.DATA    LDA  UP    STA
   USER.DATA 1+ LDA  UP 1+ STA
   6C # LDA  W 1- STA
   'THERE USER.DATA - 1- # LDY
   BEGIN
      USER.DATA ,Y LDA  UP )Y STA
      DEY
   0< UNTIL
   CLI
   >FORTH
   SP! AP! [COMPILE] [
   IORESET SINGLE DECIMAL
   PAGE BOOTCOLORS
   >ASSEM
   RTS
END-CODE

The subroutine (WARM) has a mix of low and high level code.
It first disables maskable interrupts and clears the decimal mode. The memory map for Fleet Forth is set (switching out the BASIC rom) and the opcode for an indirect jump is stored at address W-1.
The label USER.DATA points to an area just after the BASIC fuse but before the first Forth word. This area holds the bootup, or startup, values for the first nine user variables. The first value is the startup value of the first user variable and the value for the Forth virtual machine register UP , the user pointer. It is copied to UP then the startup value for the first eight user variables are copied to the user area.
The first three are unnamed. They are reserved for the multitasker, which is not a part of Fleet Forth's kernel.
The next five are:
RP0 The address of the base of the return stack.
SP0 The address of the base of the data stack.
SPLIM The limit or "full mark" for the data stack. Since the data stack is only checked for overflow (and underflow) by the word ?STACK , which is normally only used by INTERRPET , there are fourteen cells available past this point.
AP0 The address of the base of the auxiliary stack.
APLIM The limit of the auxiliary stack. Since the words to move items to and from the auxiliary stack perform bounds checking on the auxiliary stack, this limit is a "hard" limit.

This subroutine then transitions to high level Forth and executes the following words:
SP! Clears the data stack. This word is a primitive that uses the value from the user variable SP0 to initialize the data stack.
AP! Clears the auxiliary stack using the value in AP0 .
[ Sets the Forth state to interpreting.
IORESET Resets the deferred words used for I/O to their default values.
SINGLE Switches off multitasking by setting PAUSE to a no-op.
DECIMAL Sets the number base to decimal.
PAGE Clears the screen.
BOOTCOLORS Sets the screen colors to their bootup values.
And finally >ASSEM causes a transition back to low level for an RTS back to the caller.

Here is the source for Fleet Forth's word COLD:
Code:
HEX // USER COLD START
: COLD  ( -- )
   SAVE-BUFFERS
   ACLOSE     // CLOSE ALL FILES
   >ASSEM
   // POWERUP COLD START
   // PATCH THE BASIC FUSE
   DECIMAL HERE 0 <# #S #>  HEX
   FUSE  OVER - SWAP CMOVE>V
   SEI  CLD  FF # LDX  TXS
   FF87 JSR  // RAMTAS
   FF8A JSR  // RESTOR
   FFE7 JSR  // CLALL
   FF84 JSR  // IOINIT
   FF81 JSR  // CINT
   WARM  SPLIT SWAP
      # LDA  $300 STA
      # LDA  $301 STA
   (WARM) JSR
   >FORTH
   EMPTY 0 DRIVE CONFIGURE
   #12 SPACES
   [ HERE #18 + >A ]
   ." C64 FLEET FORTH  COPYRIGHT (C) 1995-2021 BY JAMES BOYD "
   [ HERE 1- >A ]
   INITIAL QUIT ; -2 ALLOT
   $D A> C!  $D0D A> !

COLD is used from within Fleet Forth to perform a cold start.
It saves the contents of the block buffers with SAVE-BUFFERS and closes all open files with ACLOSE .
COLD then transitions to a code word and flows into the cold start routine.
This section of the source:
Code:
   DECIMAL HERE 0 <# #S #>  HEX
   FUSE  OVER - SWAP CMOVE>V

patches the one line of BASIC so the SYS instruction points to the cold start routine.
In the cold start routine interrupts are disabled, the processor stack is initialized and some Commodore 64 Kernal routines are called. I performed an experiment by replacing those five JSR instructions with fifteen NOP instructions and saving the modified Forth system. The test system loaded and ran just fine. This is why those five C64 kernal calls are there. Some of the C64 Kernal routines are vectored through page 3 so that the user can replace these routines or add functionality. This added functionality can be in code that is stored in the C64 tape buffer or in a range of RAM from $C000 TO $CFFF (49152 to 53247). Fleet Forth uses both of these memory areas so the C64 Kernal routines are called to reset RAM and reset these vectors to their default values.

$FF87 RAMTAS Clears pages 0 2 and 3 to zeros. It also performs a nondestructive test of memory to set the Kernal bottom and top of memory pointers and sets screen memory to $400.
$FF8A RESTOR As its name implies, restores the default C64 Kernal vectors in page 3.
$FFE7 CLALL Although this routine's name implies that it closes all, it merely sets the count of open files to zero at address $98 and falls through to the C64 kernal routine CLRCHN, which clears the channels and sets the keyboard as the input device and the screen as the output device.
$FF84 IOINIT This routine initializes the Complex Interface Adapter chips, sets CIA #1 to generate an interrupt via timer A every sixtieth of a second, and turns off the volume of the SID chip.
$FF81 CINT Initializes the screen editor and the VIC chip.

The cold start routine then stores the address of the warm start routine at addresses $300-$301 (768-769). The warm start routine will execute when the STOP and RESTORE keys are pressed. The RESTORE key is hardwired to generate a non-maskable interrupt. This is a convenient way to get out of an infinite loop without the need to reset the machine. A BRK instruction executed will also cause a Fleet Forth warm start.

The cold start routine calls (WARM) then transitions to high level Forth and executes the following:
EMPTY Empties the dictionary to its latest empty point. It accomplishes this by fetching the startup value for the ninth user variable with a value in the boot area, DP , the dictionary pointer. EMPTY stores a copy of this value in the variable FENCE before branching into FORGET . FORGET does the "heavy lifting" of removing all vocabularies defined after the "forget point" and pruning the remaining vocabularies.
0 DRIVE Sets the default drive to device 8.
CONFIGURE Initializes the block buffer system with all buffers empty.
The cold start routine prints the startup message before executing the word INITIAL and then QUIT .
INITIAL is a deferred word which is normally set to the no-op NOOP .

Here is the source for Fleet Forth's warm start routine:
Code:
HSUBR WARM
   $FF # LDX  TXS
   (WARM) JSR
   >FORTH
   FORTH DEFINITIONS
   ." RWARM START."
   ABORT ; -2 ALLOT

The processor stack is initialized and the subroutine (WARM) is called. The warm start routine then transitions to high level. It sets the CONTEXT and CURRENT search orders to FORTH , displays the warm start message in reverse video and aborts.

The biggest difference between the cold start and warm start routines for a programmer is this: The warm start routine will not empty the dictionary, alter the default disk drive or empty the block buffers.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 354 posts ]  Go to page Previous  1 ... 9, 10, 11, 12, 13, 14, 15 ... 24  Next

All times are UTC


Who is online

Users browsing this forum: Google [Bot] and 2 guests


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

Search for:
Jump to: