Jim, I knew you'd be the guy here to figure out how eForth's AFT works. Now all I have to do is figure out how you figured it out, then figure out a proper use for it ...
I definitely needed the examples for that. It looks like all duplicate spaces were deleted from the original control flow diagrams.
Here are two words using WHILE to branch from between AFT and AFT's matching THEN to between that THEN and NEXT .
v-------------------------------------------<<
FOR A0 ... AFT A1 ... WHILE A2 ... THEN A3... THEN A4... NEXT A5...
>>------------------------^
>>--------------------------^
Multiple occurrences of AFT in a FOR NEXT loop will not work. The first AFT drops the backward branch data from FOR and replaces it with its own and a forward branch. A second AFT would drop the forward branch data from the first AFT , leaving its backward branch data. The matching THEN will not resolve the backward branch and abort.
This will not compile on my system.
It is possible to tighten the compiler security so this doesn't happen. The control flow data consists of an address and a number.
One for >MARK and >RESOLVE .
Two for <MARK and <RESOLVE .
// FOR NEXT LOOP -- EXTRA SECURITY
: FOR ( -- )
( N -- )
COMPILE >R
<MARK 2+ 2+ ; IMMEDIATE
: NEXT ( -- )
COMPILE (NEXT)
2- 2- <RESOLVE ; IMMEDIATE
: AFT ( -- )
6 ?PAIRS DROP
COMPILE BRANCH >MARK
<MARK 2+ 2+
[COMPILE] CS-SWAP ; IMMEDIATE
Sixteen extra bytes to make sure: FOR is only resolved with AFT or NEXT . AFT is only resolved with THEN and NEXT in that order.
By the way, the version of Gforth on my desktop has FOR and NEXT but not AFT . I suspect it is because this version of Gforth does not have CS-DROP , just CS-PICK and CS-ROLL in accordance with the ANSI standard.
Since this version of Gforth uses the data stack for the control flow stack, I was able to add AFT to it.
Anyone who notices that DO-DEST and thinks this version of Gforth's FOR NEXT loops are compatible with its DO LOOP's would be correct. Gforth's DO LOOP's only have two parameters pushed to the return stack, the limit and index. Gforth's FOR pushes a zero as a limit and the number on the stack as the index. Here is an example of a mixed FOR and DO loop in Gforth. Gforth's I is an alias for R@ .
It would help me if I had some idea of what "AFT" stands for. "a forward transfer" is all I can think of, but that doesn't totally make sense. Some of this stuff is just too cryptic. I'm also going with IF_0, UNITL_0, etc..
I think of the most basic FOR A0 AFT A1 THEN A3 NEXT loop like this.
The first time through the loop, and only the first time, A0 gets executed.
If the loop executes more than once, all the other times A1 gets executed, but not the first time through. A3 always gets executed.
I also suspect the name AFT was a play on words, but I could be wrong. Fore and aft referring to the front and back of a ship.
When scotws started the topic Looping in a world without a BREAK statement, a few ideas were presented on how to proceed. Looking back, I think the best solution presented was to factor the loop out as a separate word. BREAK is just EXIT and CONTINUE is easy to implement. This version of CONTINUE goes back to the beginning of the word where it is used, not the beginning of a loop. As with EXIT , the level of control flow nesting in the word to be continued does not matter. CONTINUE can even be used to restart a word from within a DO LOOP or in a DO LOOP nested within another, but it must be preceded with an UNLOOP for each level of DO LOOP nesting.
For example:
Where the word LAST is replaced by the phrase LATEST NAME> . ?COMP may or may not be present. It aborts if state is not compiling. CONTINUE can be defined like this
?0CONTINUE continues if the top of stack is false. ?CONTINUE continues if the top of stack is true.
Since CONTINUE branches to the beginning of a word, whether a loop is present or not, it can be used to eliminate tail recursion.
IP 1+ STA
<falls through to NEXT, the address interpreter>
With only one trip through the address interpreter, NEXT , this CO is about five times faster for only ten more bytes.
My Forth has a word WITH-WORDS which is used like this:
: <SOMEWORD>
<do this once> WITH-WORDS <do this once for each word in the CONTEXT vocabulary> ;
For each word in the CONTEXT vocabulary, WITH-WORDS places that word's name field address on the stack. Here is a simple word to count all the words in the CONTEXT vocabulary.
When WITH-WORDS is defined with the primitive version of CO , #WORDS takes about 81% the time to run as it did before.
A longer word using WITH-WORDS is IN-NAME . This word takes about 96% the time to run.
It's certainly not what I would go with. Although I'm considering renaming 0EXIT to ?0EXIT (I'm still not sure about that), I will not rename it to something like ?!EXIT .
As for !IF , it kind of reminds me of C.
Speaking of C, chitselb has a word in pettil which is like the ternary operator from Ruby Java and C so he named it ?: . It consumes a flag from the data stack and executes one of the following two words before program flow resumes.
CODE (?:)
0 ,X LDA 1 ,X ORA
0= IF 2 # LDY THEN
IP )Y LDA W STA INY
IP )Y LDA W 1+ STA
CLC
IP LDA 4 # ADC IP STA
CS IF IP 1+ INC THEN
INX INX
0 # LDY W 1- JMP
END-CODE
: ?: ( -- )
?COMP COMPILE (?:)
BL WORD FIND 2/ ?HUH ,
BL WORD FIND 2/ ?HUH , ;
IMMEDIATE
is to insure that an immediate name is not compiled.
I'm not sure the high level protection is needed. The programmer just needs to remember that two words (other than the final exit) need to be compiled after ?: .
Although ?: is normally used like this:
...
(?:) TRUE.WORD
BEGIN
FALSE.WORD
<OTHER LOOP STUFF>
UNTIL
...
A flag is consumed on the first pass through the loop and either TRUE.WORD or FALSE.WORD will be executed. FALSE.WORD will be executed on each successive loop.
Here is another example. TESTTWO could take some time, so rather than AND the results from both tests, the quicker test is run first.
...
TESTONE
IF
TESTTWO
(?:) TRUE.WORD
THEN
FALSE.WORD
<ALWAYS DO THIS PART>
...
This works because THEN does not compile anything, it just resolves the forward branch from IF . FALSE.WORD is executed if either test returns a false flag. TRUE.WORD is only executed if both tests return a true flag.
So, should both ?: and (?:) exist, or should (?:) be renamed ?: ?
So, should both ?: and (?:) exist, or should (?:) be renamed ?: ?
I guess I can't really think of any reason to have the immediate compile-only word that compiles the other. I have a few words that others have built the protection into which I didn't think was really necessary, like 2LEAVE, 2?LEAVE, 2BOUNDS, 2I, and 2UNLOOP in my 32-bit DO...LOOP word set.
!if is probably what I'd go with, regardless of the clash with regular !
We used ! to mean NOT at university 30+ years ago, I use it in code, variables, comments...
I would still read it "Store If" in a Forth context, just like I might use /Select to mean an active low SPI select in a hardware context but understand /CELL to mean "divide by the Cell size" in a Forth context.
!if is probably what I'd go with, regardless of the clash with regular !
We used ! to mean NOT at university 30+ years ago, I use it in code, variables, comments...
I would still read it "Store If" in a Forth context, just like I might use /Select to mean an active low SPI select in a hardware context but understand /CELL to mean "divide by the Cell size" in a Forth context.
I suppose you could use a trailing backslash instead which I think OrCAD did (although I haven't used OrCAD in many years). We also have /MOD and other such words in Forth, where the / means "divide." ! in at least some BASICs means the rest of the line is comments.
I think of the most basic FOR A0 AFT A1 THEN A3 NEXT loop like this.
The first time through the loop, and only the first time, A0 gets executed.
If the loop executes more than once, all the other times A1 gets executed, but not the first time through. A3 always gets executed.
I also suspect the name AFT was a play on words, but I could be wrong. Fore and aft referring to the front and back of a ship.
I do think that the "Fore and Aft" was deliberate, but the verbose reading of AFT might be "once-then-afterward"
N @ FOR init AFT iterator THEN act NEXT
is "N fetch FOR init once-then-afterwards iterator THEN act NEXT"
So, should both ?: and (?:) exist, or should (?:) be renamed ?: ?
I guess I can't really think of any reason to have the immediate compile-only word that compiles the other.
Neither can I. Naming the primitive ?: sounds good to me.
GARTHWILSON wrote:
I have a few words that others have built the protection into which I didn't think was really necessary, like 2LEAVE, 2?LEAVE, 2BOUNDS, 2I, and 2UNLOOP in my 32-bit DO...LOOP word set.
My opinion is that protection should be just enough to prevent things which obviously will not work, but not enough to get in the programmer's way.
Since a >MARK can not be resolved by a <RESOLVE and a <MARK can not be resolved by a >RESOLVE , compiler security to make sure >MARK is resolved by >RESOLVE and <MARK is resolved by <RESOLVE makes sense; however, in a Forth where LEAVE and ?LEAVE do not have an inline branch address, but use a third loop parameter, immediate compiler words are unnecessary.
This version of CONTINUE goes back to the beginning of the word where it is used, not the beginning of a loop. As with EXIT , the level of control flow nesting in the word to be continued does not matter.
Other programming languages use the name CONTINUE to continue a loop at the beginning, not to restart a function. The word EXIT can exit a word at any point, but it is not called BREAK . The word I presented does not even need to be in a loop. It restarts the word it is in. Perhaps it should have a more suitable name. Maybe something like RESTART or RERUN ?
In my last post I indicated my dissatisfaction with the name CONTINUE as it implies continuing with the next iteration of a loop. It seems CONTINUE is a better name for a word which compiles a branch back to the beginning of a BEGIN loop without discarding the control flow information. Something like the following:
BEGIN
<SOMETHING0>
0=
CS-DUP UNTIL
<SOMETHING1>
UNTIL
The versions which branch back to the beginning of a word can be nested deeply in other control flow structures because the branch address they compile is not from control flow data. I'd also like to initially have just two names. Consider the Forth-83 Standard's two branching primitives, BRANCH and ?BRANCH . BRANCH always branches and ?BRANCH only branches if the top stack value is false. BEGIN loops have the word AGAIN to compile a branch always back to BEGIN and the word UNTIL to compile a branch back to BEGIN if the top of stack is false. The Standard does not specify an immediate word to compile the word 0= followed by ?BRANCH .
I'd like to follow that example and specify two new words rather than CONTINUE ?CONTINUE and ?0CONTINUE .
For the first word, I'd like the name to mean something like AGAIN , but to begin the word at the start. For the second, I'd like something like UNTIL , but to branch back to the start of the word.
BACK ( -- ) \ Branch back to beginning of definition.
?PASS ( flag -- ) \ Branch back if the flag is false.
BACK always branches back to the beginning of the word. ?PASS only branches back if the flag is false. The implied meaning is if the flag is true then pass onto the rest of the word. If the flag is false "You shall not pass!"