Fleet Forth design considerations
- barrym95838
- Posts: 2056
- Joined: 30 Jun 2013
- Location: Sacramento, CA, USA
Re: Fleet Forth design considerations
Is this your most recent .zip?
download/file.php?id=9542
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.
download/file.php?id=9542
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.
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!
Mike B. (about me) (learning how to github)
Mike B. (about me) (learning how to github)
Re: Fleet Forth design considerations
In this post I would like to talk about Fleet Forth's DO LOOPs. Just a reminder, Fleet Forth is an ITC Forth for the C64.
Based on an idea from Garth Wilson, Fleet Forth's DO LOOP's place three cells on the return stack. There is no primitive (LEAVE) or (?LEAVE) . LEAVE and ?LEAVE are the primitives. LEAVE and ?LEAVE do not have an inline branch address. They use one of the DO LOOP parameters.
The three parameters placed on the return stack by (DO) and (?DO) are:
The address LEAVE uses to branch out of the loop is placed there first. This address is copied from an inline cell following (DO) or (?DO) .
The loop's limit.
The index.
LEAVE removes the index and limit from the return stack and discards them, it removes the address saved as one of the parameters and stores it in IP, effecting a branch out of the loop. In the event the loop runs to completion, (LOOP) ( or (+LOOP) ) exits the same way LEAVE does.
This short word:
Code: Select all
: TEST
DO
CR I .
LEAVE
LOOP
;
compiles to this:
Code: Select all
1000 (DO)
1002 1010 \ Address saved on return stack used to branch out of loop
1004 CR
1006 I
1008 .
100A LEAVE
100C (LOOP)
100E 1004 \ Address used to branch back to beginning of loop
1010 EXIT
Note that there is no branch address after LEAVE .
Why have (LOOP) behave so much like LEAVE instead of stepping IP over the branch address? With this version of DO LOOP's there are three reasons:
1. It is faster. Since all three DO LOOP parameters have to be pulled from the return stack anyway, storing the final parameter in IP is faster than adding the value 2 to IP.
2. It is smaller. LEAVE (LOOP) and (+LOOP) use the same code sequence to exit a DO LOOP. (+LOOP) branches into (LOOP) . LEAVE has no body, it's code field points into the appropriate location in the body of (LOOP) .
But wait! there is even more memory to be saved. EXIT also has no body and a code field that points at a later point in (LOOP) since EXIT pulls one cell from the return stack and stores it in IP.
3. Consistency. LEAVE ( and ?LEAVE ) always branch to the same address that (LOOP) and (+LOOP) go to when the loop terminates.
This behavior of LEAVE ( using an address saved as one of the DO LOOP parameters) allows some interesting possibilities. LEAVE normally can only exit the DO LOOP it is a part of. For example.
Code: Select all
DO
<high level stuff>
LEAVE
<high level stuff>
LOOP
<LEAVE branches to here>
In this code fragment, LEAVE branches to the code right after LOOP's branch address as expected.
In this fragment:
Code: Select all
DO
<high level stuff>
DO
<high level stuff>
LEAVE
<high level stuff>
LOOP
<LEAVE branches to here>
<high level stuff>
LOOP
LEAVE once again branches to the code right after the first LOOP's branch address, also as expected.
However, in this code fragment I introduce UNLOOP , a primitive that discards the loop parameters. It only discards the parameters. This makes it possible to leave nested loops by having an UNLOOP for each level of DO LOOP nesting, then the word EXIT to exit that word from within one or more DO LOOPS.
Code: Select all
DO
<high level stuff>
DO
<high level stuff>
UNLOOP UNLOOP EXIT \ Exit this word from within a nested DO LOOP.
<high level stuff>
LOOP
<high level stuff>
LOOP
In this next example, UNLOOP is not used to allow EXITing a word with a DO LOOP, but to make it possible for LEAVE in the inner loop to branch out of the outer loop.
Code: Select all
DO
<high level stuff>
DO
<high level stuff>
UNLOOP LEAVE
<high level stuff>
LOOP
<high level stuff>
LOOP
<LEAVE branches to here>
UNLOOP discards the inner DO LOOP's parameters and LEAVE uses the address from the outer DO LOOP's parameters to branch to right after the second LOOP's branch address.
Re: Fleet Forth design considerations
?LEAVE is not in the Forth-83 Standard. I was just checking the website for Forth 2012 and couldn't find it there either. I'm surprised it is not even an extension word ( unless I missed it somehow) given how useful it is.
In Fleet Forth it, like LEAVE , is a primitive, but it is semantically equivalent to:
Code: Select all
IF LEAVE THEN
but smaller and faster.
Fleet Forth's LEAVE is semantically equivalent to:
Code: Select all
2R> 2DROP EXIT
Interestingly, this version of LEAVE ( and ?LEAVE ) will work outside a DO LOOP. If used in a word, it will exit not just that word, but the word that called that word and the word that called that one. I haven't found a use for it yet, but anywhere this is used:
Code: Select all
... 2R> 2DROP EXIT ...
Fleet Forth's version of LEAVE can be used:
Code: Select all
... LEAVE ...
The same for ?LEAVE . This:
Code: Select all
... IF 2R> 2DROP EXIT THEN ...
can be replaced with:
Code: Select all
... ?LEAVE ...
I realize it's not portable to any Forth system which uses an inline branch address for LEAVE and ?LEAVE , but the equivalent code can be placed in a comment to show what is happening and provide portable alternate source while the faster and smaller LEAVE or ?LEAVE is used.
P.S. Given my enjoyment with Forth experimentation, I wish circumstances had been different and I had a chance to participate in the FORML conferences.
Re: Fleet Forth design considerations
In another thread,
I haven't written a word to implement this, but I implemented the idea in my metacompiler. I make use of >FORTH since it is already in Fleet Forth.
In my metacompiler, the word LITERAL needs to behave differently. The word MLITERAL is for metacompiling literals. With the older version of the metacompiler, I redefined each word that used LITERAL. This is somewhat error prone as I could miss one or miss a word that is dependent on one of the redefinitions.
MLITERAL was a colon definition ( a secondary) . It is now a code word that transitions to high level. Here is it's definition:
Since Fleet Forth is an ITC Forth, all words have a code field.
LITERAL is redirected to perform the function of MLITERAL by altering its code field.
Since this is from the source for my metacompiler, there will be some non-standard words. For this discussion, the important thing to note is this phrase in META.ON
patches LITERAL to perform the function of MLITERAL.
This phrase in META.OFF
fetches the value from colon's code field ( though it could have been any colon definition) and stores it in the code field of LITERAL, resetting LITERAL to it's former functionality.
GARTHWILSON wrote:
Jim, you have quite a few words above that are neither part of any standard I know of, nor defined above. One I'll ask about however is REDEFINE:. It appears to edit the old word to redirect execution to the new one, for secondaries that are already compiled using it, so those secondaries don't need to be recompiled.. Is that what's happening? I've had a way to do that but I like yours more.
I haven't written a word to implement this, but I implemented the idea in my metacompiler. I make use of >FORTH since it is already in Fleet Forth.
In my metacompiler, the word LITERAL needs to behave differently. The word MLITERAL is for metacompiling literals. With the older version of the metacompiler, I redefined each word that used LITERAL. This is somewhat error prone as I could miss one or miss a word that is dependent on one of the redefinitions.
MLITERAL was a colon definition ( a secondary) . It is now a code word that transitions to high level. Here is it's definition:
Code: Select all
HEX
CODE MLITERAL ( N -- )
>FORTH
DUP 100 U<
IF
MCOMPILE CLIT C,-T
EXIT
THEN
MCOMPILE LIT ,-T ;
Since Fleet Forth is an ITC Forth, all words have a code field.
LITERAL is redirected to perform the function of MLITERAL by altering its code field.
Code: Select all
: META.ON
['] M,
['] INTERPRET I.OFFSET + !
['] MLITERAL @ ['] LITERAL ! ;
: META.OFF
['] ,
['] INTERPRET I.OFFSET + !
['] : @ ['] LITERAL ! ;
Since this is from the source for my metacompiler, there will be some non-standard words. For this discussion, the important thing to note is this phrase in META.ON
Code: Select all
['] MLITERAL @ ['] LITERAL !
patches LITERAL to perform the function of MLITERAL.
This phrase in META.OFF
Code: Select all
['] : @ ['] LITERAL !
fetches the value from colon's code field ( though it could have been any colon definition) and stores it in the code field of LITERAL, resetting LITERAL to it's former functionality.
Re: Fleet Forth design considerations
JimBoyd wrote:
barrym95838 wrote:
Have you considered changing your PUSH to PUSHAY?
I've looked over the source for my kernel and I though I saw where PUSHAY would make some things smaller and faster. When I looked on the forum, I found out that PUSHAY pushes the accumulator to the high byte and the Y index register to the low byte. I had it the other way around! I was thinking low byte high byte order and the A for accumulator is before the Y for Y index register. I also called it AYPUSH ( I'd forgotten it was called PUSHAY).
There are some words that need PUSH, or at least need to save the accumulator and the X and Y registers are not available ( Y indexing and data stack access). The version of my kernel I wrote with AYPUSH falling through to NEXT was larger, so I went back to the version with PUSH falling through to NEXT .
I also have AYPUSH and AYPUT ( as well as APUSH ). They are in (FIND) .
Code: Select all
LABEL PUSH.ONE // 1
1 # LDA,
LABEL APUSH
0 # LDY,
LABEL AYPUSH
DEX, DEX,
LABEL AYPUT
1 ,X STY, PUT 3 + JMP,
and the location in PUT
Code: Select all
LABEL PUT
1 ,X STA, PLA, 0 ,X STA,
LABEL NEXT
<code for NEXT>
Here are the constants my Forth assembler supports:
Code: Select all
85 CONSTANT N 8D CONSTANT XSAVE
8E CONSTANT UP FB CONSTANT IP
FE CONSTANT W
83C CONSTANT PUSH
843 CONSTANT NEXT
86E CONSTANT SETUP
889 CONSTANT POPTWO
PUSH 2+ CONSTANT PUT
POPTWO 2+ CONSTANT POP
' 1 @ 2+ CONSTANT APUSH
APUSH 2+ CONSTANT AYPUSH
AYPUSH 2+ CONSTANT AYPUT
Re: Fleet Forth design considerations
I've made a slight change to Fleet Forth's word -NUMBER . I've added about 44 bytes of code to check the beginning of a numeric string for one of the following three characters: # $ % .
For # base is temporarily set to decimal.
For $ base is temporarily set to hexadecimal.
For % base is temporarily set to binary.
After conversion, the value of base is restored to what it was prior to the execution of -NUMBER .
If one of these three characters is not the first character of the numeric string to be converted, the number base is unaltered.
I just build this version of Fleet Forth yesterday, so I don't know how useful this change will be. Does anyone have any experience with this?
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Fleet Forth design considerations
I like the easy base specification. I don't think I've seen it done in Forth. Doing [ DECIMAL ] and [ HEX ] and [ BINARY ] was so common in my code and taking so much space in the source code that I made IMMEDIATE words [D] and [H] and . The I/O in my kind of work is mostly not human-oriented, so I normally keep the base at hex, not decimal; and after going to a non-hex base, I normally try to keep the return-to-hex word either directly to the right or directly below the other word, to make it easy to find. For example:
Here the [H] is directly below the . I try to take maximum advantage of visual factoring (like your column of CONSTANTs above). I do not however put the ; on the margin, because ; and : look too much alike at first glance. I don't put anything on the margin that will be encountered when COMPILER is on. In a sequence of definitions, I try to align the ; for example:
or
I see Jim mostly does this, but others don't; so I'm just trying to encourage everyone else in the visual-factoring department.
Code: Select all
: SYNCSERSETUP ( -- )
[B] VIA1ACR C@
00010100 OR \ Enable SR to shift out under T2 ctrl w/
11010111 AND VIA1ACR C! \ µP resetting T2, and decr at the φ2 rate.
00100100 VIA1IER C! \ Disable SR-empty and T2 IRQs.
00001000 VIA1T2CL C! \ Set T2 for about 100kHz shift rate, low
[H] VIA1T2CH C_OFF ; \ byte 1st. T2 gets reset automatically;
\ so it's not really 1-shot in SR mode.Code: Select all
: ESC ( -- ) 1B EMIT ;
: OUT-2 ( -- ) 2 OUT -! ;
: BOLDON ESC ." E" OUT-2 ; \ Put printer in bold mode.
: BOLDOFF ESC ." F" OUT-2 ; \ Take printer out of bold.
: TINYON 0F EMIT OUT DECR ; \ Put printer in compressed.
: TINYOFF 12 EMIT OUT DECR ; \ Take printer out of compressed.
: ITALON ESC ." 4" OUT-2 ; \ Put printer in italics mode.
: ITALOFF ESC ." 5" OUT-2 ; \ Cancel printer italics mode.Code: Select all
: TYPE ( addr len -- )
DUP OUT +! type ;
: CR ( -- )
0D emit OUT OFF
EOLSEQ C@ ?DUP
IF emit THEN ;
: SPACE ( -- )
BL EMIT ;
: SPACES ( n -- )
0 MAX 0
?DO SPACE LOOP ;I see Jim mostly does this, but others don't; so I'm just trying to encourage everyone else in the visual-factoring department.
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?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
Re: Fleet Forth design considerations
JimBoyd wrote:
For # base is temporarily set to decimal.
For $ base is temporarily set to hexadecimal.
For % base is temporarily set to binary.
After conversion, the value of base is restored to what it was prior to the execution of -NUMBER .
If one of these three characters is not the first character of the numeric string to be converted, the number base is unaltered.
For $ base is temporarily set to hexadecimal.
For % base is temporarily set to binary.
After conversion, the value of base is restored to what it was prior to the execution of -NUMBER .
If one of these three characters is not the first character of the numeric string to be converted, the number base is unaltered.
Seeing as I might be the one to write it, I'm curious where you ended up temporarily putting the BASE value (return stack?) and how you signaled that BASE needed to be restored at the end?
Re: Fleet Forth design considerations
SamCoVT wrote:
This is part of the ANS-2012 standard (I checked the 94 and 83 versions and didn't see it mentioned there) in section 3.4.1.3. ANS-2012 also specifies the form 'c' which is for constant characters (rather than using CHAR or [CHAR]).
Fleet Forth conforms to the Forth-83 Standard, which seems more appropriate considering when the C64 came out, and I like the Forth-83 Standard better. Since Fleet Forth conforms to the Forth-83 Standard, I didn't include the form 'c'. I didn't want to add that much extra code. I use ASCII , the immediate state smart word replaced by CHAR and [CHAR] in the new standard.
Since Fleet Forth conforms to the Forth-83 Standard, there are some differences in how numeric strings are handled. The leading count is ignored and the numeric string must have a trailing space.
Quote:
Seeing as I might be the one to write it, I'm curious where you ended up temporarily putting the BASE value (return stack?) and how you signaled that BASE needed to be restored at the end?
Yes, I put the value of BASE on the return stack, or rather, I used a word that did. Fleet Forth has the word RB (restore base). When RB is used in a word, whatever the base was prior to executing RB is restored when that word exits, unless it aborts. -NUMBER never aborts. It passes a flag, FALSE for successful conversion, TRUE if it failed.
RB uses the word CO (coroutine).
Code: Select all
// COROUTINES
: CO 2R> SWAP 2>R ;
// RESTORE BASE AT END OF WORD
// CALLING RB
: RB ( -- )
BASE @ R> 2>R CO
R> BASE ! ;
Here is the original version of -NUMBER
Code: Select all
: -NUMBER ( ADR -- D FLAG )
DPL ON 0 0 ROT
DUP 1+ C@ ASCII - =
DUP>R - DUP>R
BEGIN
CONVERT DUP C@ VALID?
WHILE
DUP 1+ C@ VALID? 0=
WHILE
DPL OFF
REPEAT
THEN
DUP C@ BL <> SWAP
2- DPL @ 0< - R> = OR
R> 0EXIT >R DNEGATE R> ;
Here is the modified version of -NUMBER
Code: Select all
HEX
NH
CREATE BASE.TABLE
0A C, 10 C, 2 C,
: -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
DUP 1+ C@ ASCII - =
DUP>R - DUP>R
BEGIN
CONVERT DUP C@ VALID?
WHILE
DUP 1+ C@ VALID? 0=
WHILE
DPL OFF
REPEAT
THEN
DUP C@ BL <> SWAP
2- DPL @ 0< - R> = OR
R> 0EXIT >R DNEGATE R> ;
NH is a metacompiler word that causes the following word to be compiled without a header.
VALID? is a word that takes a character and returns a true flag if it is valid punctuation for a number. It is a deferred word and the default is to only accept a period as valid.
For completeness, here is the source for CONVERT
Code: Select all
: CONVERT ( D1 ADR1 -- D2 ADR2 )
1+
BEGIN
COUNT BASE @ DIGIT
WHILE
2SWAP BASE @ *
SWAP BASE @ UM* D+
ROT
DPL @ 0< 1+ DPL +!
REPEAT
1- ;
DIGIT takes the character to be converted and the number base to use. It returns the digit as a single cell number and a true flag or just a false flag if the character is not a digit in the specified base.
( CHR #BASE -- N TF )
( CHR #BASE -- FF )
Re: Fleet Forth design considerations
JimBoyd wrote:
Fleet Forth conforms to the Forth-83 Standard, which seems more appropriate considering when the C64 came out, and I like the Forth-83 Standard better. Since Fleet Forth conforms to the Forth-83 Standard, I didn't include the form 'c'.
JimBoyd wrote:
Yes, I put the value of BASE on the return stack, or rather, I used a word that did. Fleet Forth has the word RB (restore base). When RB is used in a word, whatever the base was prior to executing RB is restored when that word exits, unless it aborts. -NUMBER never aborts. It passes a flag, FALSE for successful conversion, TRUE if it failed.
RB uses the word CO (coroutine).
RB uses the word CO (coroutine).
Code: Select all
// COROUTINES
: CO 2R> SWAP 2>R ;
// RESTORE BASE AT END OF WORD
// CALLING RB
: RB ( -- )
BASE @ R> 2>R CO
R> BASE ! ;
I will probably just toss the BASE on the return stack because the number processing is written in assembly in Tali2. I think your method of just always tossing the original BASE on the return stack and restoring it regardless of what happened is the way to go. That way I won't need a flag to remember if I need to restore it later or not.
Your trick with the BASE.TABLE is pretty slick. I did not notice before that #, $, and % are all right next to each other in the ASCII table. I was looking through your code trying to see where you compared to those characters and I only saw #. Then I saw you looking up in the table with an index and I had to go pull up an ASCII table. Very nice.
Thanks for the detailed response. It's neat to see how other people tackle a problem. The other neat thing (for me) is that I was able to read/understand your Forth code. When I first starting playing around with Forth, reading other people's code was quite difficult for me. I'll also give a shout out to Garth's suggestions for making things visually clear with indentation and spacing.
Re: Fleet Forth design considerations
SamCoVT wrote:
Are the # $ and % forms in the 83 standard?
No. I guess you could say I added them as a non standard extension.
Quote:
It does seem useful to me, though, to be able to specify constants in regular Forth code without having to switch the base. I've definitely been bit several times when my 10 was 16 and #10 could have been useful!
Exactly. It's a convenience that shouldn't have much impact on portability with, in my case, other Forth-83 systems.
Quote:
Your trick with the BASE.TABLE is pretty slick. I did not notice before that #, $, and % are all right next to each other in the ASCII table. I was looking through your code trying to see where you compared to those characters and I only saw #. Then I saw you looking up in the table with an index and I had to go pull up an ASCII table. Very nice.
Thank you. I didn't notice that #, $, and % are next to each other either, until I looked at their values in binary. I was trying to find a way to manipulate the numbers that would yield a small solution before settling on using the table.
Quote:
Thanks for the detailed response. It's neat to see how other people tackle a problem. The other neat thing (for me) is that I was able to read/understand your Forth code. When I first starting playing around with Forth, reading other people's code was quite difficult for me. I'll also give a shout out to Garth's suggestions for making things visually clear with indentation and spacing.
Poorly formatted source only contributes to Forth's write only reputation. The following is a sample of source code for another Forth for the C64. It's not for the multitasker (this Forth didn't have one), it does the same thing as Fleet Forth's DONE? .
Code: Select all
: PAUSE ?KEY ?DUP IF 3 <> IF KEY 3 = ELSE TRUE THEN
ELSE FALSE THEN ;
?KEY returns a zero if no key was pressed or the value of the key that was pressed.
KEY waits for a keypress and returns the value of that press. It calls ?KEY in a loop.
I don't care that this was from source that was in Forth blocks, it's not worth saving blocks if the control flow is difficult to follow.
Code: Select all
: PAUSE ( -- F )
?KEY ?DUP
IF
3 <>
IF KEY 3 = ELSE TRUE THEN
ELSE
FALSE
THEN ;
This version of formatting is easier to follow and, I think, easier to improve (hint: TRUE , FALSE , and both ELSEs are not needed in the improved version).
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Fleet Forth design considerations
JimBoyd wrote:
No. I guess you could say I added them as a non standard extension.
As long as things don't conflict, it's nice when you can accommodate multiple standards. The only drawback is that it takes more memory.
Quote:
Your trick with the BASE.TABLE is pretty slick. I did not notice before that #, $, and % are all right next to each other in the ASCII table.
I hadn't noticed that either.
Quote:
The other neat thing (for me) is that I was able to read/understand your Forth code. When I first starting playing around with Forth, reading other people's code was quite difficult for me. I'll also give a shout out to Garth's suggestions for making things visually clear with indentation and spacing.
Forth has been called a write-only language, ie, that it's unreadable. I blame that on the programmer though, not the language. I had an article in Forth dimensions in the early 90's on making Forth more readable. Hopefully my own programming has further improved since then. My article-writing definitely has. To be frank, most people's assembly language on 6502.org is also very difficult to read, and I often have to copy it to my text editor and massage it some to figure out what they're trying to do.
Quote:
The following is a sample <snip>
The first sample follows what so many Forthers are dogmatic about, that definitions should be limited to two lines. This kind of dogma hurts readability. It tends to result in over-factoring too, taking more memory for all the unnecessary headers for factors that are only used once, more time to run because of the extra nesting, and it can be hard sometimes to come up with a descriptive name for the factor that is any shorter than the code it replaces! There was a regular contributor to Forth Dimensions magazine who defended this strongly, and said screens should be viewed as 3x5 cards. He kept pushing this as a plus, yet I just saw it as inadequate room to make things clear and add adequate comments. Then in a private mail exchange (which today we would do by email, but back then it was on paper), he sent me his code for something we were discussing, and he had a definition that was over 100 lines long! I didn't call out his hypocrisy.
Quote:
(hint: TRUE , FALSE , and both ELSEs are not needed in the improved version).
In the two-line version it's particularly easy to miss the fact that those could be eliminated to improve both readability and efficiency.
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?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
- barrym95838
- Posts: 2056
- Joined: 30 Jun 2013
- Location: Sacramento, CA, USA
Re: Fleet Forth design considerations
Here's my clumsy stab at it:Am I in the right ballpark?
[Edited to try to balance the stack, probably while Garth was reading my first attempt ...]
P.S. eFORTH has the stack comment for ?KEY as ( -- c T | F ) , so in that case I would get rid of my second DUP ... at least I think that's what I'd try ... eh, I'm all messed up! I'll try again when my head's screwed on the right way!
P.P.S. I think I would have to DROP the T, like so: ... but that doesn't seem any better than the original! Back to the drawing board!
Code: Select all
: PAUSE ( -- F )
?KEY DUP IF
DUP 3 <> IF
DROP KEY THEN
3 = THEN
;[Edited to try to balance the stack, probably while Garth was reading my first attempt ...]
P.S. eFORTH has the stack comment for ?KEY as ( -- c T | F ) , so in that case I would get rid of my second DUP ... at least I think that's what I'd try ... eh, I'm all messed up! I'll try again when my head's screwed on the right way!
P.P.S. I think I would have to DROP the T, like so:
Code: Select all
: PAUSE ( -- F )
?KEY DUP IF
DROP DUP 3 <> IF
DROP KEY THEN
3 = THEN
;Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!
Mike B. (about me) (learning how to github)
Mike B. (about me) (learning how to github)
Re: Fleet Forth design considerations
barrym95838 wrote:
Here's my clumsy stab at it:Am I in the right ballpark?
Code: Select all
: PAUSE ( -- F )
?KEY DUP IF
DUP 3 <> IF
DROP KEY THEN
3 = THEN
;Yes, but I like the factoring where both THEN's are one after the other followed by the semicolon. I'll show why.
Quote:
P.S. eFORTH has the stack comment for ?KEY as ( -- c T | F )
In Fleet Forth it's ( -- c | 0 ) because that is what the C64 kernal supports.
PAUSE is the name of Fleet Forth's task switcher (which is set to a no-op when not multitasking), so I'm just going to call this word DONE? , which is what it is called in Fleet Forth.
Code: Select all
: DONE? ( -- F )
?KEY ?DUP
IF
3 <>
IF KEY 3 = ELSE TRUE THEN
ELSE
FALSE
THEN ;
Notice the ELSE clause at the end of the word?
Code: Select all
ELSE
FALSE
THEN ;
If ?DUP is replaced with DUP , that clause is no longer needed.
Code: Select all
: DONE? ( -- F )
?KEY DUP
IF
3 <>
IF KEY 3 = ELSE TRUE THEN
THEN ;
In my ITC Forth, this saves 3 cells.
The next improvement is a little harder to see.
3 <> is equivalent to 3 = 0=
At first glance, this doesn't seem to save memory. if ?DUP is inserted between = and 0= the final ELSE clause is no longer needed.
Code: Select all
: DONE? ( -- F )
?KEY DUP
IF
3 = ?DUP 0=
IF KEY 3 = THEN
THEN ;
A savings of another cell.
Fleet Forth also has the primitives ?EXIT and 0EXIT .
Both consume the top stack item.
?EXIT exits the word if the top stack item is TRUE (any non-zero value).
0EXIT exits the word if the top stack item is zero.
Using these two words, some more memory savings can be had, as well as a performance boost. Both IF's branch to the exit at the end of the word. They can both be replaced with 0EXIT and the THEN's removed.
Code: Select all
: DONE? ( -- F )
?KEY DUP
0EXIT
3 = ?DUP 0=
0EXIT KEY 3 = ;
A little reformatting.
Code: Select all
: DONE? ( -- F )
?KEY DUP 0EXIT
3 = ?DUP 0= 0EXIT
KEY 3 = ;
Finally, 0= 0EXIT can be replaced with ?EXIT
Code: Select all
: DONE? ( -- F )
?KEY DUP 0EXIT
3 = ?DUP ?EXIT
KEY 3 = ;