[BEGIN] and [UNTIL] revisited and a new look at loading multiple blocks.
The versions of [BEGIN] and [UNTIL] presented here will only work on a single screen of Forth source in a block based system. That single screen can have a LOAD or THRU between [BEGIN] and [UNTIL] ; however, that could be somewhat inconvenient.
Here are versions which can span multiple blocks.
300 RAM 302 RAM THRU 16684
NUMBER ON THE STACK: 10 16685 16686
NUMBER ON THE STACK: 9 OK
THRU displays the number of each block it is about to load: 16684, 16685 and 16686. Notice that the [BEGIN][UNTIL] structure almost worked. When block 16686 (ram block 302) was loaded, [UNTIL] worked perfectly and caused ram block 300 to load just after [BEGIN] instead of the rest of ram block 302; however, THRU exits because ram block 302 was the last one to load. Even if ram block 302 wasn't the last one to load, THRU would have proceeded to load ram block 303 instead of ram block 301 as desired. This THRU is implemented with a DO LOOP and uses the loop index to determine the number of the next block to load.
Here is a word from the uncontrolled reference words in the Forth-83 Standard which will make a multi block spanning [BEGIN][UNTIL] structure possible.
--> causes the next higher block to load, but it is incompatible with THRU , at least this THRU .
Modifying the source slightly by appending --> to the end of each block will allow [BEGIN] and [UNTIL] to work across multiple blocks, but the first block must be LOADed. THRU can not be used!
200 RAM LOAD
NUMBER ON THE STACK: 10
NUMBER ON THE STACK: 9
NUMBER ON THE STACK: 8
NUMBER ON THE STACK: 7
NUMBER ON THE STACK: 6
NUMBER ON THE STACK: 5
NUMBER ON THE STACK: 4
NUMBER ON THE STACK: 3
NUMBER ON THE STACK: 2
NUMBER ON THE STACK: 1
FINISHED OK
I normally don't use --> and didn't define it until just moments ago. I'm not fond of the need to be extra cautious to not mix --> and THRU .
I can use a feature of my system to rewrite THRU .
My Forth's WORD saves the history of the text stream (the values of BLK and >IN) for use with WHERE , one of the error handling words.
: THRU ( LO HI -- )
>R 1- HISTORY !
BEGIN
5 ?CR
HISTORY @ 1+
DUP U. LOAD
HISTORY @ R@ U< 0=
DONE? OR
UNTIL
R> DROP ;
Under normal circumstances this THRU does exactly what the original does.
The last block to load is pushed to the return stack. The number of the first block is decremented and stored in the first cell of HISTORY . The line with ?CR is just for 'pretty printing' and can be ignored in this explanation. The value of HISTORY is fetched and incremented by one. This will be the first block to load the first time through the loop. Once the block loads, the first cell of HISTORY contains the number of the block which just finished loading, even if that block is the last in a chain of blocks linked by --> . This block number is compared to the number saved on the return stack. The loop keeps going as long as the block just loaded is less than the last one to be loaded.
This version of THRU can safely be used with --> . Indeed, they use a similar mechanism to advance to the next block. BLK contains the number of a block while it's loading. The first cell of HISTORY contains the number of the block which has just finished loading. More accurately, HISTORY holds the location of the last text string parsed by WORD wherever that string was.
Used in a block like this:
The number of the block currently loading will be displayed.
Here are some blocks of source where --> is only used in one block and THRU is used to load the range of blocks.
200 RAM LIST
SCR# 16584 8 200
0:
1:
2:
3:
4:
5:
6:
7: [BEGIN]
8: CR .( NUMBER ON THE STACK: )
9: DUP .
A:
B: .( BLK# R200) CR
C:
D:
E:
F: -->
OK
0 FH 2 FH THRU 16584
NUMBER ON THE STACK: 9 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 8 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 7 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 6 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 5 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 4 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 3 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 2 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 1 BLK# R200
BLK# R201
16586
BLK# R202
FINISHED OK
And success! --> was successfully used within a range of blocks loaded by THRU . All it did was advance the loading in place of the mechanism used by this new THRU . Since this new THRU gets the number of the latest block loaded from HISTORY , --> will not cause it to unintentionally load blocks multiple times.
SCR# 200
[BEGIN]
CR .( NUMBER ON THE STACK: )
DUP .
.( BLK# R200) CR
-->
SCR# 201
1-
CR .( BLK# R201) CR
SCR# 202
CR .( BLK# R202) CR
?DUP 0=
[UNTIL]
CR .( FINISHED)
-->
SCR# 203
CR .( LET'S PULL A FAST ONE ON THRU)
CR .( AND SEE IF IT RUNS WILD!!!)
CR .( RAM BLK # 203)
-->
SCR# 204
CR .( LET'S PULL A FAST ONE ON THRU)
CR .( AND SEE IF IT RUNS WILD!!!)
CR .( RAM BLK #204)
ram blocks 202, 203 and 204 have been chained together with --> .
Here is the session log.
200 RAM 202 RAM THRU 16584
NUMBER ON THE STACK: 10 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 9 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 8 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 7 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 6 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 5 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 4 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 3 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 2 BLK# R200
BLK# R201
16586
BLK# R202
NUMBER ON THE STACK: 1 BLK# R200
BLK# R201
16586
BLK# R202
FINISHED
LET'S PULL A FAST ONE ON THRU
AND SEE IF IT RUNS WILD!!!
RAM BLK # 203
LET'S PULL A FAST ONE ON THRU
AND SEE IF IT RUNS WILD!!!
RAM BLK #204 OK
has the same effect.
I honestly don't know if anyone has implemented THRU without a DO LOOP . This version of THRU is compatible with --> and the versions of [BEGIN] and [UNTIL] capable of spanning multiple blocks.
It is important when writing a new version of THRU that it not place anything on the data stack which could get in the way of parameters left from a previous screen of source. A definition spanning more than one block would need the data stack to be unaffected by THRU .
As an example, here is the source stored in 4 blocks in the C64's REU.
: THRU ( U1 U2 -- )
1+ SWAP
DO
5 ?CR
I DUP U. LOAD DONE? ?LEAVE
LOOP ;
Although on my Forth, I took advantage of how DO LOOP's work to make the version with a DO LOOP two bytes smaller.
The original THRU uses a DO LOOP so the loop parameters are on the return stack when LOAD executes. The new version only places one parameter on the return stack which is good when THRU is nested multiple times.
Another advantage of this new implementation of THRU has to do with testing modifications to source. If a change is made to a word the following words can be compiled and tested one screen (Forth block with source) at a time. If the source for a word happens to span multiple screens then they should not be loaded one at a time. Such screens can be linked together by placing --> at the bottom of all but the last screen for a word with source spanning multiple screens.
--> -- I,M,79 "next-block"
-- (compilation)
Continue interpretation on the next sequential block. May
be used within a colon definition that crosses a block
boundary.
As I mentioned in my last post, the worst effect of mixing --> with the new implementation of THRU is causing the rest of the screens linked by --> to be loaded.
If it is not feasible or desirable to modify WORD to save the value of BLK , and optionally >IN , to a variable such as HISTORY , LOAD can be redefined to save BLK to a HISTORY variable just before the previous values of BLK and >IN are pulled from the return stack and restored.
A generic LOAD
I suppose an implementation of THRU which is compatible with --> isn't exactly run of the mill. Whether it is or not, is that compatibility worthwhile?
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.