VARIABLE HISTORY
: LOAD ( BLK# -- )
DUP 0= ABORT" CAN'T LOAD 0"
BLK @ >IN @ 2>R
>IN OFF BLK !
INTERPRET
BLK @ HISTORY !
2R> >IN ! BLK ! ;
: THRU ( U1 U2 -- )
>R
BEGIN
5 ?CR
DUP U. LOAD
R@ HISTORY @ 1+ TUCK U<
DONE? OR
UNTIL
R> 2DROP ;
Only words from the Forth-83 Standard are used in the versions presented below. INTERPRET is from the controlled reference words and 2DROP is from the double number extension word set. All other words are from the required wordset.
VARIABLE HISTORY
: LOAD ( BLK# -- )
DUP 0= ABORT" CAN'T LOAD 0"
BLK @ >IN @ >R >R
0 >IN ! BLK !
INTERPRET
BLK @ HISTORY !
R> R> >IN ! BLK ! ;
: THRU ( U1 U2 -- )
>R
BEGIN
DUP U. LOAD
HISTORY @ 1+ R@ OVER U<
UNTIL
R> 2DROP ;
In the definition of LOAD it may be tempting to store the value of BLK in HISTORY before INTERPRET when it is stored in BLK . Don't do it! With the version of LOAD which saves BLK in HISTORY it must be done AFTER INTERPRET because INTERPRET could change the block number if a screen has --> or the fancier version of [UNTIL] .
I mentioned in a previous post that a version of THRU which is compatible with --> makes it possible to chain together all the screens for a word spanning multiple screens without causing problems if those same screens are also in a range loaded with THRU. It is my hope that this will encourage Forth programmers keeping source in screens to use a structured layout rather than that unreadable horizontal format for source. I'm definitely in support of tools which encourage readable source.
This:
SCR# 125
// NUMBER?
: NUMBER? ( ADR -- D FLAG ) RB 1+ DUP C@ ASCII # - DUP 3 U<
IF BASE.TABLE + C@ BASE ! COUNT THEN DROP DPL ON COUNT
ASCII - <> DUP>R + COUNT DIGIT DUP>R AND 0 ROT BEGIN 1-
CONVERT COUNT VALID? WHILE DPL OFF DUP C@ VALID? UNTIL
THEN 1- C@ BL = R> AND R> ?EXIT >R DNEGATE R> ;
This post concerns a technique used on Forth systems with indirect threaded code (ITC). It may work with other threading models.
Although the example from M. L. Gassanenko's paper "Dynamically Structured Codes" wasn't really self modifying code, it certainly is not your run of the mill control flow.
Just as EXECUTE takes the address of a word's Code Field and launches that word, ENTER takes the address of a fragment of threaded code and 'launches' that fragment. The address must align on what Gassanenko calls an active 'threaded code element', or a TCE. An active TCE is an address which holds the CFA of a Forth word. The 'threaded code fragment' must also exit (or branch somewhere which exits or aborts).
I've noticed that some Forth programmers are uneasy about leaving a value on the return stack without removing it, and with good reason. I must admit that the definition of ENTER seems a little odd.
An address is placed on the return stack followed by EXIT or its equivalent, which is compiled by semicolon.
Here is a functionally equivalent definition of ENTER .
: ENTER ( T-ADR -- )
[ ASSEMBLER ] IP [ FORTH ] ! -;
Since -; does not compile EXIT , this version of ENTER is the same size.
Although do-colon changes the value of IP , what I wish to point out is that it first pushes the current value in IP onto the return stack. The address on the data stack is stored in IP . This version only works because ! (store) is a primitive. It stores the address in IP , drops the two values from the data stack and goes to NEXT . NEXT resumes address interpretation starting at the address T-ADR . When an EXIT is encountered, execution resumes in the word which called ENTER .
High level Forth words can branch using ?BRANCH and BRANCH . ENTER is similar to a subroutine call for high level Forth, with one major difference. The destination of the "subroutine call" is specified at run time which is like a computed GOSUB from BASIC .
I have the following in a few places in my Forth's system loader.
In an STC Forth for the 6502, any time an address is copied from the return stack for use with ENTER it would need adjusted.
For example, FORK would change from this:
Thanks. This kind of thing has crossed my mind before, but I never did it. I don't remember what I might have wanted it for (and ended up taking a different approach). Do you have real-life example usages?
Thanks. This kind of thing has crossed my mind before, but I never did it. I don't remember what I might have wanted it for (and ended up taking a different approach). Do you have real-life example usages?
I do have some real-life examples. They may not be what M.L.Gassanenko envisioned in his paper Dynamically Structured Codes , but it's still early.
I've mentioned before that my Forth has EVALUATE . Since WORD parses from a BLOCK or the text input buffer, my Forth's EVALUATE saves the current values of TIB and #TIB as well as BLK and >IN .
(IS) gets compiled by IS and TO . Although IS will only write to the PFA of a DEFERred word and TO will only write to the body of a VALUE , (IS) will write to the body of anything.
This version of EVALUATE has a section which is similar to LOAD or LINELOAD . I can't have EVALUATE call LOAD or LINELOAD directly because both abort if trying to "load" block zero. I can use ENTER to enter the body of LINELOAD , which is used by LOAD , and return. The following is used twice in EVALUATE
This version of EVALUATE is 14 bytes smaller. ENTER has a body size of 4 bytes, a 2 byte code field, 6 byte name field and a 2 byte link field for a total of 14 bytes.
I also saved a couple of bytes with two other words: VOCS and ORDER . VOCS shows the VOCABULARY tree structure and is recursive, but not in the sense that the entire word is recursively called.
The first line does not take part in the recursion; however, everything that follows does take part. The original version of VOCS factored out the recursive part as a :NONAME word. Although this version is only two bytes smaller, it's much easier to use with TRACE . Tracing by hand is fine when initially working out the logic for a word. Once a word is defined, the TRACE word is much more convenient. With the original version, I would have to either manually enter the addresses for <IP and IP> , the start and end limits for the trace range, or give the headerless word a name.
It's also possible to have a word with a recursive part within a larger recursive part.
ORDER is not recursive. It reuses a section of code.
: ORDER ( -- )
CONTEXT
LIT [ >MARK ] ENTER
CURRENT
[ >RESOLVE ]
CR DUP BODY> >NAME ID. ." : "
@
BEGIN
DUP BODY> >NAME ID. CR
2+ @ ?DUP 0EXIT
9 SPACES
AGAIN -;
ENTER can also be used with other tools for diagnostics.
Tracing my Forth's new FORGET can be problematic. The new FORGET has NEXT> . NEXT> restores NEXT which switches off tracing. The word which was in the process of being traced resumes normal execution without tracing. Since The trace word I use, a modification of Blazin' Forth's trace word, single steps and waits for a key-press to continue, ENTER can help.
TRACE FORGET OK
.S EMPTY OK
FORGET BAD.WORD
NAME EMPTY
CURRENT 29282
@ 29282 2604
DUP 29282 9637
CONTEXT 29282 9637 9637
! 29282 9637 9637 2590
29282 9637
?HUH 29278 65535
>LINK 29278
NEXT> 29267 TRACING OFF
Trace prints the name of the word which is about to be called and displays the data stack contents. The last line is where I pressed the RUN/STOP key to stop tracing before NEXT> was called. This not only stops the trace, it calls quit.
The following shows where I resume tracing FORGET . Instead of calling FORGET directly, I use ENTER to "GOSUB" into the body of FORGET . Including the headerless word for FORGET's new (FIND) , there are ten words to skip.
ENTER can be used to enter into the body of a high level word, with or without tracing, for diagnostic purposes. It can not be used to enter into the middle of a DO LOOP without causing problems, nor can it be used to enter into the middle of code which temporarily places items on the return stack; however, a word could be written which places the appropriate parameters on the return stack and calls ENTER .
In summary, not only can ENTER save a few bytes, it is also a handy diagnostic tool.
Regarding what M.L.Gassanenko mentions in his papers, I'll need to study that further.
[Edit: Corrected spelling: I just noticed that I misspelled M.L.Gassanenko's last name.]
One of the words I added to my version of FIG Forth is NIF (short for "not if").
Wow, looking at my own code, there are enough occurrences of 0= IF that I think that would pay for itself in memory, so there's no penalty for the faster execution. I think I would call it something else though unless "NIF" is already in common use, because it looks like a short form of "nifty" and definitely did not make me think "NOT IF." NOT in Forth XOR's the value with -1, rather than doing 0=.
I think I saw it called -IF but it has been a while and I can't remember where.
IF0 or IF_0 would be pretty intuitive.
What about these? IFF -- IF FALSE WHILEF -- WHILE FALSE UNTILF -- UNTIL FALSE
: enter 1- >r ; ok
: fork true R@ 1+ enter false ; ok
fork . 0 ok
: test fork . ; ok
test -1 0 ok
: print01 if 1 else 0 then 1 u.r ; ok
5 print01 1 ok
0 print01 0 ok
: testmany fork print01 fork print01 fork print01 cr ; ok
testmany 111
0
01
0
011
0
01
0
ok
print01 prints a single 0 or 1 (using u.r so as to not print a space - I forget who showed me that, but it was someone here so thanks!). testmany is supposed to print all combinations of 3-digit binary values. What I actually got was not what I was expecting, but after thinking about it I realized it was exactly what I asked for. The issue is that it has to do all of the right hand forks before it will re-evaluate the left hand fork. I have the correct number of CRs, but not all of the digits for each line. Here is my "fix":
Although it seems reasonable to me that a negative argument to SPACES should result in zero spaces without an error because SPACES takes a signed value.
: enter 1- >r ; ok
: fork true R@ 1+ enter false ; ok
fork . 0 ok
: test fork . ; ok
test -1 0 ok
: print01 if 1 else 0 then 1 u.r ; ok
5 print01 1 ok
0 print01 0 ok
: testmany fork print01 fork print01 fork print01 cr ; ok
testmany 111
0
01
0
011
0
01
0
ok
print01 prints a single 0 or 1 (using u.r so as to not print a space - I forget who showed me that, but it was someone here so thanks!). testmany is supposed to print all combinations of 3-digit binary values. What I actually got was not what I was expecting, but after thinking about it I realized it was exactly what I asked for. The issue is that it has to do all of the right hand forks before it will re-evaluate the left hand fork. I have the correct number of CRs, but not all of the digits for each line. Here is my "fix":
... it seems reasonable to me that a negative argument to SPACES should result in zero spaces without an error because SPACES takes a signed value.
And so it does. The 2012 standard specifically denotes this behavior:
ANS 2012 wrote:
SPACES ( n -- ) If n is greater than zero, display n spaces.
I see that Tali2 assumes it is unsigned, so negative numbers give you LOTS of spaces, but passing it 0 does give you 0 spaces. I'll have to fix (add) the negative number handling.
: TESTMANY
FORK 1 AND [CHAR] 0 + PAD C!
FORK 1 AND [CHAR] 0 + PAD 1+ C!
FORK 1 AND [CHAR] 0 + PAD 2+ C!
PAD 3 TYPE CR ;
2+ isn't in the 2012 ANS standard. Once I wrote it, this compiled and worked great. I totally forgot about PAD and that this is exactly what it was intended to be used for.
2+ isn't in the 2012 ANS standard. Once I wrote it, this compiled and worked great. I totally forgot about PAD and that this is exactly what it was intended to be used for.
I believe FOR...NEXT is in the newest standards, probably as a concession to reduce newcomers' resistance to Forth, but I do like DO...LOOP and friends. I have 32-bit equivalents too, at http://wilsonminesco.com/Forth/32DOLOOP.FTH .
FOR and NEXT are not on the Forth 2012 Standard site.
I think the FOR NEXT loop may have been a low memory alternative to DO LOOPs for memory constrained systems.
Gforth has FOR and NEXT but not AFT , and it's not the only Forth system to have the FOR NEXT loop without AFT , not that anyone would claim Gforth is a minimal system.
I found some interesting control flow words in the Forth literature. The word GEN , for generator, uses these control flow words. Here is the source for GENTEST , a word to test GEN .
I believe FOR...NEXT is in the newest standards, probably as a concession to reduce newcomers' resistance to Forth, but I do like DO...LOOP and friends. I have 32-bit equivalents too, at http://wilsonminesco.com/Forth/32DOLOOP.FTH .
FOR and NEXT are not on the Forth 2012 Standard site.
I think the FOR NEXT loop may have been a low memory alternative to DO LOOPs for memory constrained systems.
Gforth has FOR and NEXT but not AFT , and it's not the only Forth system to have the FOR NEXT loop without AFT , not that anyone would claim Gforth is a minimal system.
As promised, here is what GEN does.
I already said GEN was a generator. Given an input of N, it will return the numbers from 0 to N-1; however, the way GEN does this is by placing a number on the data stack and executing the Forth threaded code fragment following GEN in the word where it appears. It runs this threaded code fragment once for each number it returns.
Two more words from M. L. Gassanenko are used in the definition of GEN , the pair PRO and CONT .
: PRO ( -- )
R> R> >L ENTER L> DROP ;
: CONT ( -- )
L> >R R@ ENTER R> >L ;
M. L. Gassanenko refers to PRO as a prolog-like procedure prologue.
I've previously shown ENTER in this thread; nonetheless, here is the source for ENTER .
ENTER takes the address of a fragment of threaded code and runs the threaded code fragment at that address. When that threaded code fragment exits, control is returned to the fragment which executed ENTER . >L and L> are words to move a number to and from what M. L. Gassanenko refers to as a continuation stack.
Here is the source for GEN .
PRO pulls two addresses off the return stack. The second address, in this case an address in the body of TEST, is placed on the continuation stack. The first address pulled from the return stack, an address in the body of GEN , is used as a parameter for ENTER to transfer control back to GEN . Control is returned back to PRO when GEN exits. PRO removes and drops the address it previously placed on the continuation stack then exits, transferring control back to the word which called TEST .
In the word GEN , CONT pulls the address off the continuation stack. This address is in TEST just past GEN . It saves a copy of this address on the return stack and transfers control to that portion of TEST which is after GEN . Control is returned to CONT when TEST exits. CONT moves that address from the return stack back to the continuation stack. The threaded code after GEN in the word TEST will run for each number returned by GEN .
That last fragment runs a total of 24 times each time GENTEST is run.
Words which manipulate return addresses are notoriously difficult to use in a definite loop, where the parameters are kept on the return stack. The pair of words PRO and CONT get around this. PRO gets to the addresses it needs because it is used before any words which place data on the return stack. CONT can get to the address it needs because it is no longer on the return stack.
M. L. Gassanenko gives the example of the word STACK , which non-destructively returns the numbers on the data stack similar to how GEN returns a range of numbers. Many Forth systems have the word .S to non-destructively display the contents of the data stack; however, the stack contents could be displayed as signed or unsigned numbers. This version shows the stack contents as signed numbers: