Page 9 of 25
Re: Fleet Forth design considerations
Posted: Mon Oct 12, 2020 11:49 pm
by JimBoyd
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:
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
Posted: Fri Oct 16, 2020 11:50 pm
by JimBoyd
?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:
but smaller and faster.
Fleet Forth's LEAVE is semantically equivalent to:
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:
Fleet Forth's version of LEAVE can be used:
The same for ?LEAVE . This:
can be replaced with:
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
Posted: Tue Nov 10, 2020 2:22 am
by JimBoyd
In another thread,
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
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.
Re: Fleet Forth design considerations
Posted: Tue Nov 10, 2020 2:48 am
by JimBoyd
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
Posted: Wed Nov 11, 2020 11:09 pm
by JimBoyd
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?
Re: Fleet Forth design considerations
Posted: Thu Nov 12, 2020 12:51 am
by GARTHWILSON
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:
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.
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:
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.
or
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.
Re: Fleet Forth design considerations
Posted: Fri Nov 13, 2020 7:24 pm
by SamCoVT
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.
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]). I actually just stumbled onto this last week and realized that Tali doesn't have support for this so I opened an issue on github (and will probably end up submitting a patch for it, eventually).
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
Posted: Fri Nov 13, 2020 10:56 pm
by JimBoyd
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.
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
Posted: Sat Nov 14, 2020 1:51 am
by SamCoVT
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'.
Are the # $ and % forms in the 83 standard? I couldn't find them. I guess what I meant to say is that I've only seen those forms mentioned in the 2012 standard. 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!
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 ! ;
... because of course your forth has coroutines! That's pretty nifty, and I may have to play around with that.
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
Posted: Sat Nov 14, 2020 3:31 am
by JimBoyd
Are the # $ and % forms in the 83 standard?
No. I guess you could say I added them as a non standard extension.
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.
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.
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).
Re: Fleet Forth design considerations
Posted: Sat Nov 14, 2020 5:00 am
by GARTHWILSON
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.
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.
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.
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.
(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.
Re: Fleet Forth design considerations
Posted: Sat Nov 14, 2020 5:37 am
by barrym95838
Here's my clumsy stab at it:
Code: Select all
: PAUSE ( -- F )
?KEY DUP IF
DUP 3 <> IF
DROP KEY THEN
3 = THEN
;
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:
Code: Select all
: PAUSE ( -- F )
?KEY DUP IF
DROP DUP 3 <> IF
DROP KEY THEN
3 = THEN
;
... but that doesn't seem any better than the original! Back to the drawing board!
Re: Fleet Forth design considerations
Posted: Sun Nov 15, 2020 8:29 pm
by JimBoyd
Here's my clumsy stab at it:
Code: Select all
: PAUSE ( -- F )
?KEY DUP IF
DUP 3 <> IF
DROP KEY THEN
3 = THEN
;
Am I in the right ballpark?
Yes, but I like the factoring where both THEN's are one after the other followed by the semicolon. I'll show why.
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?
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 = ;
Re: Fleet Forth design considerations
Posted: Mon Nov 16, 2020 4:31 am
by barrym95838
My way of thinking FORTH is brutally incomplete, but I did think of
?EXIT briefly. It's kind of like a conditionally executed
RTS in the middle of a subroutine, efficient but a bit lacking in what I perceive to be "structured" technique. Come to think of it, I often break those techniques when I'm doing my own thing, so ...
My (probably flawed) reasoning on the subject of where to place my
IFs and
ELSEs and
THENs is that I feel weird needlessly inserting line breaks between the data or code and the word immediately responsible for consuming or executing it. In my example, the
3 <> is immediately consumed by the following
IF, and the
DROP KEY is within the execution scope of the following
THEN ... kind of an "object verb verb" "noun noun verb" thing, like Yoda tends to use in his sentences. As my understanding hopefully improves, I may start to see things more the way you and Garth format your code, but
many miles to go still have I ...

Re: Fleet Forth design considerations
Posted: Mon Nov 16, 2020 5:16 am
by GARTHWILSON
Nothin' wrong with ?EXIT, as there's nothing pending on either stack like there would be with DO...LOOP (where you can remove the exit address, loop limit, and loop index with UNLOOP). Any of the structures will have stuff on the stack during compilation, but not necessarily at run time.
Putting the condition and flow-control words this way makes if very clear, and there's no visual searching:
Code: Select all
: FOOBAR ( n1 -- n2 f )
<do stuff>
<condition?>
IF ┌─────────────────┐
│ <do this stuff> │
└─────────────────┘
ELSE ┌─────────────────┐
│ <do this stuff> │
└─────────────────┘
THEN
<do more stuff> ;
But if you want the IF on the same line with the condition so the line is more stack-autonomous, you can re-arrange it this way:
Code: Select all
: FOOBAR ( n1 -- n2 f )
<do stuff>
<condition?> IF
┌─────────────────┐
│ <do this stuff> │
└─────────────────┘ ELSE
┌─────────────────┐
│ <do this stuff> │
└─────────────────┘ THEN
<do more stuff> ;
The latter works out really well if you have a lot of nested IF's, and no visual searching is necessary. (Color syntax highlighting is not only unnecessary when you do this, but totally undesirable, IMO.) Then you can put them one above the other instead of indenting and indenting and indenting way over to ridiculous extremes. You might end with a line of
Some may turn up their nose at this and say if you're having to do that, then you're taking the wrong approach; but then their "fix" usually ends up overfactoring or doing something else that's just as undesirable.