Page 14 of 25
Re: Fleet Forth design considerations
Posted: Wed Oct 20, 2021 11:28 pm
by JimBoyd
I have previously mentioned the word VALID? . This word is used to test if a non-digit character is valid punctuation for a number. It is a deferred word and by default is set to (VALID?) , which only recognizes the period ( . ) as valid punctuation in a double number. Since VALID? is a deferred word, what is accepted as valid punctuation for a number can be changed. Starting Forth mentions these as valid punctuation for a double number:
Some of these, as well as other punctuation, could be added by using a new version of (VALID?) . Taking this into consideration, I have decided to use the version of NUMBER? which requires valid non-digit punctuation in a number be separated by at least one digit. I believe a typo would be less likely slip by as a valid number with the tighter restriction and that it is well worth the extra six bytes.
My fellow Forthwrites, am I being a bit too cautious? Is it worth the extra six bytes?
Re: Fleet Forth design considerations
Posted: Wed Oct 20, 2021 11:47 pm
by barrym95838
I say it's worth it, but you should just follow your gut ... any decision you make can be easily revised later.
Re: Fleet Forth design considerations
Posted: Thu Oct 21, 2021 12:40 am
by JimBoyd
I say it's worth it, but you should just follow your gut
Thanks.
This was one of those decisions where I could have gone either way, but leaned toward the more restrictive, and slightly larger, version of NUMBER? .
Re: Fleet Forth design considerations
Posted: Thu Oct 21, 2021 1:58 am
by JimBoyd
I have previously made brief mention of Fleet Forth's word WHERE . It is used to display the location of an error when (ABORT") aborts with an error message. WHERE can be useful apart from (ABORT") to display an error message which is not known beforehand.
-SET is one example:
Code: Select all
: -SET ( -- )
WHERE
R@ 2- @ >NAME CR RON ID. ROFF
." NOT SET" ABORT ; -2 ALLOT
-SET is the vector for a new deferred word until a new vector is set with IS :
Code: Select all
' NOOP IS PAUSE
' (PAUSE) IS PAUSE
' (EMIT) IS EMIT
If the new deferred word, prior to being set to a new vector, is executed from the command line, -SET displays the odd message that EXECUTE is not set. When executed from within a high level Forth word, the message shows the name of the deferred word which is not set.
Another example is ?D , the word which checks the disk drive for an error condition after block read/write.
Code: Select all
: ?D ( -- )
$0F >SLF# (?D) 0EXIT
WHERE
CR ." DISK ERROR:" CR
DEB S? ABORT ; -2 ALLOT
WHERE displays where the error occurred. The message 'DISK ERROR:' is displayed, followed my the message in the Disk Error Buffer or DEB , which comes from the disk drive as a text string.
To get a better feel of what Fleet Forth error messages look like, here are some examples.
This one is an example of using PAD as a buffer for sector 10 of track 37 on disk 0 of the current drive to read (r/w flag value is 1) 256 bytes with the sector read/write word. Device 8 is a 1541 disk drive and does not have 37 tracks. I've added comments after the fact.
Code: Select all
8 DRIVE OK \ select device 8 as the current drive
DOPEN OK \ open current drive for block access
PAD 10 37 0 1 256 SR/W \ try to read sector 10 of track 37
PAD 10 37 0 1 256 SR/W \ this line shown by Fleet Forth in reverse video
^^^^
DISK ERROR:
66,ILLEGAL TRACK OR SECTOR,37,10
When WHERE shows the line in a screen or the text input buffer or an evaluated string, it shows that line in reverse video so it stands out. The carets ( ^ ) identify the text which caused the error.
Here are some errors due to (intentional) typos.
From the console:
Code: Select all
DECIMAL OK
CR 1 2 + . 123OOPS 4 5 * .
3
CR 1 2 + . 123OOPS 4 5 * .
^^^^^^^
WHAT?
and from a screen:
Code: Select all
1 RAM LOAD 7
SCR# 32769 LINE# 7
2 5 + . 321OOPS 7 9 * .
^^^^^^^
WHAT?
Missing delimiter for a string:
Code: Select all
: TEST2
CR ." START TEST2 TEST1
CR ." START TEST2 TEST1
^^
" MISSING
and using a deferred word which has not been set:
Code: Select all
DEFER PWM OK
: TEST 50 PWM ; OK
TEST
TEST
^^^^
PWM NOT SET
Here is a screen shot of trying to forget a word protected by FORGET's internal 'fence'.
Re: Fleet Forth design considerations
Posted: Sun Oct 31, 2021 8:41 pm
by JimBoyd
I've decided to change Fleet Forth's word DIGIT to have the third stack effect mentioned in my previous post.
Code: Select all
DIGIT ( CHAR -- N TRUE )
( CHAR -- CHAR FALSE )
Code: Select all
CODE DIGIT ( CHAR -- N TRUE )
( CHAR -- CHAR FALSE )
SEC 0 ,X LDA $30 # SBC
' FALSE @ CS NOT BRAN
$A # CMP
CS IF
7 # SBC $A # CMP
' FALSE @ CS NOT BRAN
THEN
' BASE >BODY C@ # LDY
UP )Y CMP
' FALSE @ CS BRAN
0 ,X STA
' TRUE @ JMP END-CODE
I should have seen this before. I changed the jump at the end of DIGIT to a branch.
Code: Select all
CODE DIGIT ( CHAR -- N TRUE )
( CHAR -- CHAR FALSE )
SEC 0 ,X LDA $30 # SBC
PUSH.FALSE CS NOT BRAN
$A # CMP
CS IF
7 # SBC $A # CMP
PUSH.FALSE CS NOT BRAN
THEN
' BASE >BODY C@ # LDY
UP )Y CMP
PUSH.FALSE CS BRAN
0 ,X STA
PUSH.TRUE CS NOT BRAN
END-CODE
Re: Fleet Forth design considerations
Posted: Sun Oct 31, 2021 8:58 pm
by JimBoyd
My fellow Forthwrites, am I being a bit too cautious? Is it worth the extra six bytes?
It's actually eight bytes. Here is the source for both versions of NUMBER? . Just a reminder that DIGIT takes a character, it accesses the value of BASE internally, and returns either:
1) The numerical value of the digit and a true flag.
2) The character if it is not a valid digit and a false flag.
NUMBER? takes the address of a counted string which is followed by a trailing blank and returns a double number and a flag. The flag is true for a successful conversion. NUMBER? also sets the user variable DPL to the count of how many digit characters occur after the last valid punctuation character, or TRUE (-1) if there is no punctuation in the numeric string.
The larger stricter version:
Code: Select all
: NUMBER? ( ADR -- D FLAG )
RB
DUP 1+ C@ ASCII # - DUP 3 U<
IF
BASE.TABLE + C@ BASE !
1+ DUP
THEN
DROP
DPL ON 0 0 ROT
1+
COUNT ASCII - <> DUP>R +
COUNT DIGIT NIP >R
2-
BEGIN
CONVERT DUP C@ VALID?
WHILE
DPL OFF
DUP 1+ C@ VALID?
UNTIL
THEN
C@ BL = R> AND
R> ?EXIT
>R DNEGATE R> ;
The smaller and less strict version:
Code: Select all
: NUMBER? ( ADR -- D FLAG )
RB
DUP 1+ C@ ASCII # - DUP 3 U<
IF
BASE.TABLE + C@ BASE !
1+ DUP
THEN
DROP
DPL ON 0 0 ROT
1+
COUNT ASCII - <> DUP>R +
COUNT DIGIT NIP >R
2-
BEGIN
CONVERT DUP C@ VALID?
WHILE
DPL OFF
REPEAT
C@ BL = R> AND
R> ?EXIT
>R DNEGATE R> ;
Re: Fleet Forth design considerations
Posted: Mon Nov 15, 2021 12:42 am
by JimBoyd
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: Select all
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: Select all
: BORDER? ( -- B )
$D020 C@ ;
: CYCLE ( -- )
BORDER? 1+ BORDER ;
' CYCLE IS PAUSE
Something like the cyclic executive Garth mentions can be set up easily:
Code: Select all
: 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: Select all
: 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: Select all
: 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: Select all
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: Select all
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: Select all
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: Select all
' 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: Select all
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: Select all
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: Select all
: 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: Select all
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: Select all
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: Select all
: 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: Select all
\ 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: Select all
\ 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
Re: Fleet Forth design considerations
Posted: Mon Nov 15, 2021 8:07 pm
by SamCoVT
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: Select all
: 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.
Re: Fleet Forth design considerations
Posted: Thu Nov 18, 2021 12:25 am
by JimBoyd
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.
And here is what gets compiled.
Code: Select all
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: Select all
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: Select all
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: Select all
\ 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.
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.
Re: Fleet Forth design considerations
Posted: Thu Dec 02, 2021 3:49 am
by JimBoyd
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: Select all
// (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: Select all
// 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: Select all
// (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.
Re: Fleet Forth design considerations
Posted: Tue Dec 07, 2021 2:08 am
by JimBoyd
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: Select all
// 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' .
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:
to this:
which does the same thing as before while also switching 3R off.
Here is the full source for the new (EXPECT)
Code: Select all
// (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: Select all
// 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.
Re: Fleet Forth design considerations
Posted: Sat Dec 11, 2021 1:47 am
by JimBoyd
I've decided to rename 3R to RC for 'right cursor'. I've also changed this:
to this:
$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: Select all
// (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: Select all
// 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
Re: Fleet Forth design considerations
Posted: Sun Dec 12, 2021 10:38 pm
by JimBoyd
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.
Re: Fleet Forth design considerations
Posted: Thu Dec 16, 2021 2:47 am
by JimBoyd
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: Select all
: RECURSE
LATEST NAME> , ; IMMEDIATE
The word LATEST returns the address of the name field of the latest word in the CURRENT vocabulary.
Code: Select all
: 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 .
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.
Re: Fleet Forth design considerations
Posted: Thu Dec 16, 2021 4:27 am
by IamRob
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:
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.