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!"
It slipped my mind that BACK is a turtle graphics command so it isn't a good choice for a name. For now, I'm going with RERUN and ?PASS .
The actions of these two words can, at times, be duplicated by other means. Sometimes, those means aren't pretty. Here is an example of how these words can make the source clearer.
Back before my Forth's current version of INTERPRET , I noticed in some cases INTERPRET had a BRANCH to other BRANCHes before finally branching to the beginning. I tried to rewrite the interpreter loop to eliminate the extra trips through NEXT . It looked something like this:
: INTERPRET ( -- )
BEGIN
BEGIN
BEGIN
BEGIN
BEGIN
BEGIN
PAUSE
NAME FIND ?DUP
WHILE
STATE @ =
WHILE
,
REPEAT CS-SWAP
EXECUTE ?STACK
REPEAT
NUMBER? ?HUH ?STACK
DPL @ 0<
WHILE
DROP STATE @
UNTIL CS-SWAP
[COMPILE] LITERAL
REPEAT
STATE @
UNTIL
SWAP
[COMPILE] LITERAL
[COMPILE] LITERAL
AGAIN ; -2 ALLOT
This wasn't pretty and I didn't like it. If I'd thought of them, RERUN and ?PASS , would have made this easier. This is a cleaner version which compiles to exactly the same thing.
: INTERPRET ( -- )
PAUSE
NAME FIND ?DUP
IF
STATE @ =
IF , RERUN THEN
EXECUTE ?STACK RERUN
THEN
NUMBER? ?HUH ?STACK
DPL @ 0<
IF
DROP STATE @ ?PASS
[COMPILE] LITERAL RERUN
THEN
STATE @ ?PASS
SWAP
[COMPILE] LITERAL
[COMPILE] LITERAL
RERUN ; -2 ALLOT
: INTERPRET ( -- )
BEGIN
PAUSE
NAME FIND ?DUP
IF
STATE @ =
IF , RERUN THEN
EXECUTE ?STACK RERUN
THEN
NUMBER? ?HUH ?STACK
DPL @ 0<
IF
DROP STATE @ ?PASS
[COMPILE] LITERAL RERUN
THEN
STATE @ ?PASS
SWAP
[COMPILE] LITERAL
[COMPILE] LITERAL
AGAIN ; -2 ALLOT
As can be seen in these examples, the results of RERUN and ?PASS can, at times, be difficult to replicate with other control flow words.
Here is a smaller version of RERUN and ?PASS .
Newb question, Jim. I have seen IMMEDIATE directly after a ; but not -2 ALLOT or anything else in between. Could you enlighten me with a brief description of how and why your compiler handles this situation, if it's not too much off-topic?
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!
Newb question, Jim. I have seen IMMEDIATE directly after a ; but not -2 ALLOT or anything else in between. Could you enlighten me with a brief description of how and why your compiler handles this situation, if it's not too much off-topic?
-2 ALLOT just removes the unnest (compiled by the semicolon) which is unnecessary if it'll never get executed because it is preceded by something like a branch. The semicolon does a couple of other things too at compile time though, which is why we don't just replace it with a [.
Okay, I got the feeling he was trimming something off the end, but I wasn't sure why. Two bytes saved is two bytes earned! So, the ; does a bit more dictionary housekeeping than the [ (which just zeroes STATE )?
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!