Thank you for the info on zero page but I was already aware of it. Fleet Forth uses most of that range for its data stack, some for the scratch pad area N, and some for some of the Forth virtual machine registers. Since Fleet Forth supports multitasking ( background tasks anyway ) the area of zero page used for the data stack gets divided among the foreground task and the background tasks. The reason I'm reluctant to put NEXT in zero page is that it is a routine that is around two dozen bytes long.
Fleet Forth design considerations
Re: Fleet Forth design considerations
BigDumbDinosaur wrote:
If you are not using the BASIC interpreter you can take up all zero page from $02 to $8F inclusive without interference.
Thank you for the info on zero page but I was already aware of it. Fleet Forth uses most of that range for its data stack, some for the scratch pad area N, and some for some of the Forth virtual machine registers. Since Fleet Forth supports multitasking ( background tasks anyway ) the area of zero page used for the data stack gets divided among the foreground task and the background tasks. The reason I'm reluctant to put NEXT in zero page is that it is a routine that is around two dozen bytes long.
Quote:
Upon exit from the Forth kernel you will have to jump to the BASIC cold start entry point at $A000 to initialize BASIC's ZP.
Re: Fleet Forth design considerations
I've just rewritten the code for : (colon) and ; (semicolon) in the Fleet Forth kernel.
SMUDGE , CSP , and ?CSP have been removed. There is a new word, TSB ( toggle smudge bit ) which takes an address and toggles the 5th bit ( value $20 ).
the old versions were.
The new versions are as follows.
?PAIRS takes two numbers and aborts with the message "STRUCTURE MISMATCH" if they are not the same.
This modification also made it necessary to change DOES> , ;CODE , CODE , and END-CODE .
I feel this is more in keeping with the Forth-83 standard.
And
It also makes it possible to implement :NONAME without explicitly using [ SMUDE ] in the source and without accidently smudging the latest name and then unsmudging it, as it may be needed by the headless word being compiled by :NONAME .
SMUDGE , CSP , and ?CSP have been removed. There is a new word, TSB ( toggle smudge bit ) which takes an address and toggles the 5th bit ( value $20 ).
the old versions were.
Code: Select all
: ; ( -- )
?CSP COMPILE EXIT
SMUDGE [COMPILE] [ ; IMMEDIATE
: : ( -- )
] SP@ CSP !
CURRENT @ CONTEXT !
CREATE SMUDGE
;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
Code: Select all
: ; ( ADR TRUE -- )
COMPILE EXIT
[COMPILE] [
TRUE ?PAIRS TSB ; IMMEDIATE
: : ( -- ADR TRUE )
CURRENT @ CONTEXT !
CREATE LATEST TRUE OVER TSB ]
;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
This modification also made it necessary to change DOES> , ;CODE , CODE , and END-CODE .
I feel this is more in keeping with the Forth-83 standard.
Code: Select all
: -- sys M,79 "colon"
... sys is balanced with its corresponding ; .
See: "compilation" "9.4 Compilation"
Code: Select all
; -- C,I,79 "semi-colon"
sys -- (compiling)
... sys is balanced with its corresponding : .
See: EXIT : "stack, return" "9.4 Compilation"
Re: Fleet Forth design considerations
I haven't implemented :NONAME but I was looking at Leo Brodie's DOER/MAKE from Thinking Forth. I got it working with Fleet Forth but it started me thinking that I might want to change semicolon's ( ; ) sys just a little.
Or possibly
To keep both versions of semicolon's sys the same size ( same number of stack entries ).
Since TRUE and FALSE are not used as compiler security numbers for any of the control flow words, this shouldn't be a problem.
Any comments?
P.S. Or possibly
Code: Select all
( ADDR TRUE -- ) There is a dictionary entry which needs revealed.
( FALSE -- ) There is not.
Code: Select all
( ADDR TRUE -- ) There is a dictionary entry which needs revealed.
( FALSE FALSE -- ) There is not.
Since TRUE and FALSE are not used as compiler security numbers for any of the control flow words, this shouldn't be a problem.
Any comments?
P.S. Or possibly
Code: Select all
( ADDR TRUE -- ) There is a dictionary entry which needs revealed.
( CFA FALSE -- CFA ) There is not, but there is the CFA of the headless word which gets left on the data stack
Re: Fleet Forth design considerations
JimBoyd wrote:
Fleet Forth has a word BYE that returns to basic by way of the processor's system reset hardware vector at $FFFC.
Code: Select all
: BYE ( -- )
SAVE-BUFFERS
ACLOSE
0FFFC EXECUTE ;
Re: Fleet Forth design considerations
I don't know why I didn't think of this sooner, but I can make BYE even smaller.
Code: Select all
: BYE ( -- )
SAVE-BUFFERS
ACLOSE
[ 0FFFC , ] ;
-2 ALLOT // RECLAIM MEMORY USED BY UNNEEDED EXIT
Re: Fleet Forth design considerations
I moved CO ( coroutine) and RB ( restore base) from the system loader to the kernel.
CO is a word that allows two words to be coroutines and RB used in a word causes the base to be restored, when the word exits, to what it was prior to the execution of RB .
My preference is that words, like DUMP and SEE , do not alter BASE. If I want to SEE in hexadecimal I'll say HEX SEE <SOME.WORD> . If I want a decimal dump I'll say DECIMAL <some address> <some count> DUMP . There are certain situations where this doesn't work. When communicating with a Commodore disk drive over the command channel, the command is a text string including the numbers for disk ( 0 or 1), track, and sector. In the Fleet Forth kernel, I use some of the pictured numeric words to build a text string, but BASE has to be decimal. By moving RB to the kernel, I can start the word with RB DECIMAL .
Having RB in the kernel necessitated the inclusion of CO . Here are their definitions.
A note about 2>R ( and 2R> ), When moving two cells ( four bytes in this case ) from one stack to the other, the byte order is preserved. When two cells are moved from the data stack, as in this case, the value that was on top of the data stack will be the one that is on top of the return stack. As an example, CO could have been defined as:
or
For the newcomers to Forth, I will attempt an explanation of how RB works.
Get the value of BASE.
Pull the return address off the return stack ( the return point in the calling word.)
Move both to the return stack with the value of BASE tucked under the return address of the calling word.
CO is executed which places IP on the stack.
CO switches the two addresses and returns to the word that called RB .
When that word EXITs, RB resumes execution at the point after CO.
The previous value of BASE is still on the return stack and is transfered to the data stack and stored in BASE.
I don't remember where I saw CO , but I knew it would be useful. The other place it is used is in the definition of WITH-WORDS .
Here is an example of its use in WORDS
For each word in the CONTEXT VOCABULARY , the code following WITH-WORDS through the EXIT compiled by semicolon is executed with the address of the word's name field on the data stack.
CO is a word that allows two words to be coroutines and RB used in a word causes the base to be restored, when the word exits, to what it was prior to the execution of RB .
My preference is that words, like DUMP and SEE , do not alter BASE. If I want to SEE in hexadecimal I'll say HEX SEE <SOME.WORD> . If I want a decimal dump I'll say DECIMAL <some address> <some count> DUMP . There are certain situations where this doesn't work. When communicating with a Commodore disk drive over the command channel, the command is a text string including the numbers for disk ( 0 or 1), track, and sector. In the Fleet Forth kernel, I use some of the pictured numeric words to build a text string, but BASE has to be decimal. By moving RB to the kernel, I can start the word with RB DECIMAL .
Having RB in the kernel necessitated the inclusion of CO . Here are their definitions.
Code: Select all
SCR# 9C
// CO RB
HEX
// COROUTINES
: CO 2R> SWAP 2>R ;
// RESTORE BASE AT END OF WORD
// CALLING RB
: RB ( -- )
BASE @ R> 2>R CO
R> BASE ! ;
Code: Select all
: CO 2R> >R >R ;
Code: Select all
: CO R> R> 2>R ;
Get the value of BASE.
Pull the return address off the return stack ( the return point in the calling word.)
Move both to the return stack with the value of BASE tucked under the return address of the calling word.
CO is executed which places IP on the stack.
CO switches the two addresses and returns to the word that called RB .
When that word EXITs, RB resumes execution at the point after CO.
The previous value of BASE is still on the return stack and is transfered to the data stack and stored in BASE.
I don't remember where I saw CO , but I knew it would be useful. The other place it is used is in the definition of WITH-WORDS .
Code: Select all
SCR# 38
// WITH-WORDS WORDS
HEX
: WITH-WORDS ( -- NFA )
COLS ?CR
R> CONTEXT @
BEGIN
@ DUP DONE? 0= AND
WHILE
2DUP 2>R L>NAME
SWAP >R CO 2R>
REPEAT
2DROP ;
Code: Select all
: WORDS ( -- )
WITH-WORDS DUP C@ 1F AND 1+ ?CR
ID. TAB ;
Re: Fleet Forth design considerations
I mentioned in another post that BLOCK and BUFFER are code definitions and BUFFER's code field points one byte into BLOCK's body. Here is how that came about. When a block is not the most recently used, BLOCK checks the buffer table. If it is in the table, the buffer number ( 1 - #BUF ) is returned. If it's not in the table, #BUF is placed on the data stack ( 0 is the phantom entry.) A flag is placed on the stack as well. If the flag is FALSE, the requested block does not need read from mass storage, it's already in a buffer. If the flag is TRUE then the block is read from mass storage. It occurred to me that when the flag is FALSE there is no difference between the behavior of BLOCK and BUFFER . A value, T/F , was added and the words used only by BLOCK and BUFFER were consolidated into BLOCK. The flag placed on the stack by that part of BLOCK is now FALSE to not read a block from mass storage and T/F to maybe read a block from mass storage.
Maybe read a block? That sounds iffy. It really is reliable.
Fleet Forth is an ITC Forth and NEXT always leaves the Y-register set to zero. The first thing BLOCK does is increment the Y-register and store it into both bytes of T/F's parameter field. BUFFER's code field points past the Y-increment so it stores a FALSE in T/F's parameter field. For BLOCK , T/F is always TRUE and for BUFFER , T/F is always FALSE .
Maybe read a block? That sounds iffy. It really is reliable.
Fleet Forth is an ITC Forth and NEXT always leaves the Y-register set to zero. The first thing BLOCK does is increment the Y-register and store it into both bytes of T/F's parameter field. BUFFER's code field points past the Y-increment so it stores a FALSE in T/F's parameter field. For BLOCK , T/F is always TRUE and for BUFFER , T/F is always FALSE .
Re: Fleet Forth design considerations
JimBoyd wrote:
CO is a word that allows two words to be coroutines and RB used in a word causes the base to be restored, when the word exits, to what it was prior to the execution of RB .
Re: Fleet Forth design considerations
JimBoyd wrote:
Here is an example of its use in WORDS
For each word in the CONTEXT VOCABULARY , the code following WITH-WORDS through the EXIT compiled by semicolon is executed with the address of the word's name field on the data stack.
Code: Select all
: WORDS ( -- )
WITH-WORDS DUP C@ 1F AND 1+ ?CR
ID. TAB ;
here is a log of a session.
Code: Select all
OK
VARIABLE WIDTH OK
: (NAME-LENGTH)
0 WITH-WORDS
C@ 1F AND WIDTH @ = - ; OK
1 WIDTH ! (NAME-LENGTH) .
1B OK
DECIMAL OK
: NAME-LENGTH 32 1 DO
I WIDTH ! (NAME-LENGTH) I 5 U.R 5 U.R
LOOP ; OK
0 0 JIFFY! NAME-LENGTH JIFFY@ CR D.
1 27
2 56
3 90
4 99
5 112
6 81
7 56
8 35
9 12
10 5
11 3
12 3
13 2
14 0
15 0
16 0
17 0
18 0
19 0
20 0
21 0
22 0
23 0
24 0
25 0
26 0
27 0
28 0
29 0
30 0
31 0
2676 OK
2676 60 /MOD . . 44 36 OK
CONSOLE OK
Another utility word which might be useful for new users of Fleet Forth ( When I finally finish making modifications and write the documentation so I can release it) is IN-NAME which uses WITH-WORDS and MATCH. As an example, a new user checking the glossary for the word OPEN will soon find that it doesn't exist and wonder how to open a disk for BLOCK access. Searching the entire glossary can be avoided by typing " OPEN" IN-NAME <carriage return or enter>. A list of all words in the context vocabulary with 'open' in their name will be displayed. The only two in the Forth vocabulary are DOPEN and (OPEN). A quick glossary check will reveal that DOPEN is the desired command. This example is just a bit contrived as I do plan to include a getting started guide, but you get the idea.
Re: Fleet Forth design considerations
Fleet Forth includes EVALUATE in the system loader and support for it in the kernel. This version of EVALUATE takes an address and a count on the data stack. The word QUERY EXPECT's a text string and stores it in TIB. In Fleet Forth, TIB is more like a VALUE than a CONSTANT and QUERY sets TIB to the proper address ( $2A7). EVALUATE saves the contents of BLK >IN #TIB and TIB to the return stack. It sets TIB to the address of the string to be evaluated, stores the length in #TIB , turns BLK and >IN off, then calls INTERPRET . Finally, all the saved values are restored.
Here is the code for QUERY in the kernel.
The DUP and the line with
have to do with output formatting and can be safely ignored.
The code for EVALUATE in the system loader.
I'm wondering if there is a better way to implement EVALUATE within the parameters of the Forth-83 standard?
Here is the code for QUERY in the kernel.
Code: Select all
: QUERY ( -- )
[ 'TIB ] LITERAL IS TIB
TIB 50 EXPECT SPAN @ DUP #TIB !
1+ #OUT +!
BLK OFF >IN OFF ;
Code: Select all
1+ #OUT +!
The code for EVALUATE in the system loader.
Code: Select all
SCR# 2C
// EVALUATE
HEX
// NESTABLE EVALUATE
: EVALUATE ( ADR N -- )
BLK @ >IN @ 2>R
TIB #TIB @ 2>R
#TIB ! IS TIB
BLK OFF >IN OFF
INTERPRET
2R> #TIB ! IS TIB
2R> >IN ! BLK ! ;
Re: Fleet Forth design considerations
I implemented part of the ANSForth test suite to test EVALUATE . I only implemented part of it because Fleet Forth is a Forth-83 standard Forth and because it does not have floating point. Here is my version of the test suite. In places where there is <[ or ]>, that's because the C64 lacks the curly braces { }.
<[ and ]> are used to count the number of items on the data stack between <[ and ]>, kind of like Garth's { and } .
-TEXT takes an address, a count and a second address as parameters and returns a flag ( addr1 cnt addr2. -- flag ). It performs a byte by byte comparison until it reaches the end of the count unless it finds a difference first. if both strings are identical, it returns a zero. The contents don't have to be text strings, -TEXT works just as well with strings of binary data.
here are the tests I ran.
I also ran a variation of these tests. EVALUATE is supposed to be nestable, isn't it?
Here are the results.
Cheers,
Jim
Code: Select all
SCR# 6C
// TEST WORDS
HEX
CREATE SB1 80 ALLOT
CREATE SB2 80 ALLOT
: T<[
SB1 80 ERASE SB2 80 ERASE
?EXEC <[ ; IMMEDIATE
: ->
?EXEC ]> 0
?DO I 2* SB1 + ! LOOP
<[ ; IMMEDIATE
: ]>T
?EXEC ]> 0
?DO I 2* SB2 + ! LOOP
SB1 80 SB2 -TEXT 0<> ABORT" FAIL"
CR ." PASS" ; IMMEDIATE
SCR# 6D
// S"
HEX
: S"
[COMPILE] "
STATE @ IF COMPILE THEN
COUNT ; IMMEDIATE
-TEXT takes an address, a count and a second address as parameters and returns a flag ( addr1 cnt addr2. -- flag ). It performs a byte by byte comparison until it reaches the end of the count unless it finds a difference first. if both strings are identical, it returns a zero. The contents don't have to be text strings, -TEXT works just as well with strings of binary data.
here are the tests I ran.
Code: Select all
SCR# 6E
// EVALUATE TEST
RB DECIMAL
: GE1 S" 123" ; IMMEDIATE
: GE2 S" 123 1+" ; IMMEDIATE
: GE3 S" : GE4 345 ;" ;
: GE5 EVALUATE ; IMMEDIATE
T<[ GE1 EVALUATE -> 123 ]>T
T<[ GE2 EVALUATE -> 124 ]>T
T<[ GE3 EVALUATE -> ]>T
T<[ GE4 -> 345 ]>T
T<[ : GE6 GE1 GE5 ; -> ]>T
T<[ GE6 -> 123 ]>T
T<[ : GE7 GE2 GE5 ; -> ]>T
T<[ GE7 -> 124 ]>T
Code: Select all
SCR# 6F
// EVALUATE TEST
RB DECIMAL
: GE1 S" 123" ; IMMEDIATE
: GE2 S" 123 1+" ; IMMEDIATE
: GE3 S" : GE4 345 ;" ;
: GE5 EVALUATE ; IMMEDIATE
S" T<[ GE1 EVALUATE ->" EVALUATE 123 ]>T
S" T<[ GE2 EVALUATE ->" EVALUATE 124 ]>T
S" T<[ GE3 EVALUATE ->" EVALUATE ]>T
S" T<[ GE4 ->" EVALUATE 345 ]>T
S" T<[ : GE6 GE1 GE5 ; ->" EVALUATE ]>T
S" T<[ GE6 ->" EVALUATE 123 ]>T
S" T<[ : GE7 GE2 GE5 ; ->" EVALUATE ]>T
S" T<[ GE7 ->" EVALUATE 124 ]>T
Code: Select all
OK
6E LOAD
PASS
PASS
PASS
PASS
PASS
PASS
PASS
PASS OK
FORGET GE1 OK
6F LOAD
PASS
PASS
PASS
PASS
PASS
PASS
PASS
PASS OK
CONSOLE
Jim
Re: Fleet Forth design considerations
Oops, I made one minor mistake with my test words. I didn't save the count returned by ]> .
Here is the corrected code. I'll have to wait till I get home to test it and retest EVALUATE .
By the way, the word ?EXEC aborts if compiling.
[Edit: corrected a typo]
Here is the corrected code. I'll have to wait till I get home to test it and retest EVALUATE .
Code: Select all
SCR# 6C
// TEST WORDS
HEX
CREATE SB1 80 ALLOT
CREATE SB2 80 ALLOT
: T<[
SB1 80 ERASE SB2 80 ERASE
?EXEC <[ ; IMMEDIATE
: ->
?EXEC ]> DUP SB1 C! 0
?DO I 2* SB1 + 1+ ! LOOP
<[ ; IMMEDIATE
: ]>T
?EXEC ]> DUP SB2 C! 0
?DO I 2* SB2 + 1+ ! LOOP
SB1 80 SB2 -TEXT 0<> ABORT" FAIL"
CR ." PASS" ; IMMEDIATE
[Edit: corrected a typo]
Last edited by JimBoyd on Mon Feb 25, 2019 9:29 pm, edited 1 time in total.
Re: Fleet Forth design considerations
In Fleet Forth, I treat some of the CONSTANTs more like VALUEs. I was wondering if it would be worth the overhead in memory to implement VALUEs and redefine IS and (IS) to only work on VALUEs and DEFERed words. After all, not everything in Fleet Forth that appears to be a CONSTANT is one. The words TRUE , FALSE , 0 , and 1 have no parameter field. Their code fields point to code elsewere. The C64 has 16 colors, but the color CONSTANT's in Fleet Forth were changed to code to save memory.
Although the parameter field for LT-GREY is 19 bytes, the other color words have no parameter field.
Code: Select all
SCR# 37
// COLORS
HEX
CODE LT-GREY ( -- 0F )
INY, INY, INY, INY, INY,
INY, INY, INY, INY, INY,
INY, INY, INY, INY, INY,
TYA, APUSH JMP, END-CODE
CREATE LT-BLUE -2 ALLOT
' LT-GREY @ 1+ ,
CREATE LT-GREEN -2 ALLOT
' LT-BLUE @ 1+ ,
CREATE MD-GREY -2 ALLOT
' LT-GREEN @ 1+ ,
CREATE DK-GREY -2 ALLOT
' MD-GREY @ 1+ ,
SCR# 38
// COLORS
HEX
CREATE LT-RED -2 ALLOT
' DK-GREY @ 1+ ,
CREATE BROWN -2 ALLOT
' LT-RED @ 1+ ,
CREATE ORANGE -2 ALLOT
' BROWN @ 1+ ,
CREATE YELLOW -2 ALLOT
' ORANGE @ 1+ ,
CREATE BLUE -2 ALLOT
' YELLOW @ 1+ ,
CREATE GREEN -2 ALLOT
' BLUE @ 1+ ,
CREATE PURPLE -2 ALLOT
' GREEN @ 1+ ,
SCR# 39
// COLORS
HEX
CREATE CYAN -2 ALLOT
' PURPLE @ 1+ ,
CREATE RED -2 ALLOT
' CYAN @ 1+ ,
CREATE WHITE -2 ALLOT
' RED @ 1+ ,
CREATE BLACK -2 ALLOT
' WHITE @ 1+ ,
Re: Fleet Forth design considerations
JimBoyd wrote:
Oops, I made one minor mistake with my test words. I didn't save the count returned by ]> .
Here is the corrected code. I'll have to wait till I get home to test it and retest EVALUATE .
Here is the corrected code. I'll have to wait till I get home to test it and retest EVALUATE .
Code: Select all
DECIMAL OK
: GE1 S" 123" ; IMMEDIATE OK
: GE2 S" 123 1+" ; IMMEDIATE OK
: GE3 S" : GE4 345 ;" ; OK
: GE5 EVALUATE ; IMMEDIATE OK
T<[ GE1 EVALUATE -> 123 ]>T
PASS OK
T<[ GE2 EVALUATE -> 124 ]>T
PASS OK
T<[ GE3 EVALUATE -> ]>T
PASS OK
T<[ GE4 -> 345 ]>T
PASS OK
T<[ : GE6 GE1 GE5 ; -> ]>T
PASS OK
T<[ GE6 -> 123 ]>T
PASS OK
T<[ : GE7 GE2 GE5 ; -> ]>T
PASS OK
T<[ GE7 -> 124 ]>T
PASS OK
CONSOLE
Re: Fleet Forth design considerations
JimBoyd wrote:
In Fleet Forth, I treat some of the CONSTANTs more like VALUEs. I was wondering if it would be worth the overhead in memory to implement VALUEs and redefine IS and (IS) to only work on VALUEs and DEFERed words.
Code: Select all
SCR# 90
// CONSTANT VALUE
HEX
: CONSTANT ( N -- )
CREATE , ;CODE ( -- N )
LABEL CONSTANT.CODE
2 # LDY,
W )Y LDA, PHA, INY,
W )Y LDA,
PUSH JMP, END-CODE
: VALUE ( N -- )
CONSTANT ;CODE ( -- N )
CONSTANT.CODE JMP,
END-CODE
Code: Select all
: IS ( -- )
' DUP @
DUP [ ' #BUF @ ] LITERAL <>
SWAP [ ' TYPE @ ] LITERAL <> AND
ABORT" CAN'T SET"
STATE @
IF COMPILE (IS) , EXIT THEN
>BODY ! ; IMMEDIATE