I will discuss Fleet Forth's control flow words built around the branch primitives BRANCH and ?BRANCH which use the following words to mark and resolve an address.
Code:
<MARK ... <RESOLVE
>MARK ... >RESOLVE
BRANCH is followed by an inline address. It always branches to this address. ?BRANCH is also followed by an inline address. If the top element of the data stack is zero, It will branch to this address. If not, it will increment IP , Forth's instruction pointer, past this inline address.
Fleet Forth's control flow words are like building blocks. Other than the DO LOOPs, there are six fundamental control flow words.
Code:
BEGIN
AGAIN
UNTIL
IF
AHEAD
THEN
BEGIN marks the target of a backward branch which is resolved by AGAIN or UNTIL . AGAIN compiles BRANCH and UNTIL compiles ?BRANCH . AGAIN and UNTIL resolve the address placed on the stack by BEGIN or any other word which uses <MARK .
IF and AHEAD are for forward branches and leave an address to be patched by THEN. IF compiles ?BRANCH and AHEAD compiles BRANCH. THEN resolves the address from a single IF , a single AHEAD or any word which uses >MARK .
Here are the control flow structures possible with these six words.
Code:
BEGIN ... AGAIN
BEGIN ... UNTIL
IF ... THEN
AHEAD ... THEN
If a branch word which branches on true is added, there would be two more fundamental control flow words.
Code:
NIF
NUNTIL
or something similar. The prospective names are irrelevant for this post. There would be two more control flow structures.
Code:
BEGIN ... NUNTIL
NIF ... THEN
Fairly simple and seemingly limited; however, these six words plus immediate versions of DUP DROP SWAP and ROT for the control flow stack can be used to build all the other control flow words. These control flow stack manipulators are CS-DUP CS-DROP CS-SWAP and CS-ROT . Since there is compiler security (a single number on top of the address), each element of the control flow stack is a double number. Fleet Forth's control flow stack is the data stack, which means the control flow stack manipulators are aliases. They have no bodies of their own, each one's code field points to the body of the word it is aliasing.
CS-DUP is an alias for 2DUP
CS-DROP is an alias for 2DROP
CS-SWAP is an alias for 2SWAP
CS-ROT is an alias for 2ROT
The other control flow words can be built from these fundamental control flow words.
WHILE is just IF CS-SWAP
Code:
: WHILE ( CS1 -- CS2 CS1 )
[COMPILE] IF [COMPILE] CS-SWAP ; IMMEDIATE
In Fleet Forth it's defined like this.
Code:
: WHILE ( CS1 -- CS2 CS1 )
[COMPILE] IF 2SWAP ; IMMEDIATE
REPEAT is AGAIN THEN
Code:
: REPEAT ( CS2 CS1 -- )
[COMPILE] AGAIN [COMPILE] THEN ; IMMEDIATE
Code:
BEGIN ( begin.cs )
WHILE ( if.cs begin.cs )
AGAIN ( if.cs ) \ BEGIN was resolved
THEN ( -- ) \ IF (within WHILE) was resolved
REPEAT combines AGAIN and THEN in a single word. Multiple WHILEs can be in a BEGIN loop, but each one needs resolved by a THEN . It wasn't until after Fleet Forth's WHILE and REPEAT were implemented that I found I had inadvertently achieved Ansi compliance with their implementation. Sometimes conforming to multiple standards just happens.
ELSE is just AHEAD CS-SWAP THEN
Code:
: ELSE ( CS1 -- CS2 )
[COMPILE] AHEAD [COMPILE] CS-SWAP [COMPILE] THEN ; IMMEDIATE
Or as it's defined in Fleet Forth.
Code:
: ELSE ( CS1 -- CS2 )
[COMPILE] AHEAD 2SWAP [COMPILE] THEN ; IMMEDIATE
ELIF is IF CS-SWAP THEN
Code:
: ELIF ( CS1 -- CS2 )
[COMPILE] IF 2SWAP [COMPILE] THEN ; IMMEDIATE
In Fleet Forth two bytes are saved by defining ELIF as this.
Code:
: ELIF ( CS1 -- CS2 )
[COMPILE] WHILE [COMPILE] THEN ; IMMEDIATE
Code:
IF ( if.cs )
ELSE ( ahead.cs ) \ IF was resolved
THEN ( -- ) \ AHEAD (within ELSE) was resolved
When the auxiliary stack is loaded, there are two more control flow stack manipulation words.
CS>A moves control flow data to the auxiliary stack.
A>CS moves control flow data from the auxiliary stack.
I recently read an article about Forth control structures in the July 1986 issue of Dr Dobbs Journal. The article was "A Forth Standards Proposal: Extended Control Structures" by George W Shaw II. The author sites what he believes are five shortcomings in Forth's control structures. I find his proposed solution overly complex.
The first three shortcomings concern LEAVE . To paraphrase the author: The standard control structures can't handle multiple LEAVEs without retesting the exit condition, nor the loop termination separately from LEAVE , nor can LEAVE exit through multiple levels of DO LOOPs.
One of the author's proposed extensions.
Code:
DO IF LEAVES ... LOOP THEN
Apparently, LEAVES has a built in THEN and the THEN after LOOP resolves the branch from LEAVES .
The Forth-83 Standard LEAVE can't do this; however, the Standard IF along with UNLOOP AHEAD and an auxiliary stack can.
Code:
DO IF UNLOOP AHEAD CS>A THEN ... LOOP A>CS THEN
or without AHEAD
Code:
DO IF UNLOOP ELSE CS>A THEN ... LOOP A>CS THEN
Fleet Forth's BLOCK branches out of a ?DO LOOP .
Code:
CODE BLOCK ( BLK -- ADR )
DEY
BLK/BUF STY
' MRU >BODY LDA W STA
' MRU >BODY 1+ LDA W 1+ STA
6 # LDY
W )Y LDA 0 ,X CMP
0= IF
INY W )Y LDA 1 ,X CMP
0= IF
INY (W)PUT JMP
THEN
THEN
>FORTH
#BUF 1+ 2
?DO
DUP I >BT @ =
IF
DROP I UNLOOP
AHEAD CS>A
THEN
LOOP
LRU 2+ 2+ @
IF
LRU 2@ 0 B/BUF R/W
LRU 2+ 2+ OFF
THEN
BLK/BUF C@
IF
LRU ON
LRU 2+ @ OVER 1 B/BUF R/W
THEN
LRU ! #BUF
A>CS THEN
DUP >BT MRU 6 CMOVE
MRU 1 >BT ROT 6 * CMOVE>
MRU 2+ @ ;
Here is an explanation for what it is doing.
Code:
CODE BLOCK ( BLK -- ADR )
DEY \ store $FF in zero page location BLK/BUF.
\ BUFFER's cfa points to the following.
BLK/BUF STY \ BUFFER will store a zero at BLK/BUF.
if the requested block is the most recently used,
replace the block number with the buffer address for that block
ends with a jump to an address which jumps to NEXT.
if the requested block is not the most recently used
>FORTH \ transition to high level Forth.
#BUF 1+ 2
?DO
search buffer table for matching block number.
if it's found, drop the block number
and branch out of the loop with the index.
IF
DROP I UNLOOP
AHEAD CS>A \ save control flow data to the auxiliary stack.
THEN
LOOP
it's not in the buffer table.
save the least recently used block to mass storage.
BLK/BUF C@ \ is this BLOCK or BUFFER?
if this is block, read in the requested block.
LRU ! #BUF \ store block number in the table
\ at the entry for the least recently used block.
\ and place the address for this entry on the stack.
A>CS THEN \ resolve the branch from the loop.
the stack now has the number for the table entry
with the desired block.
move that entry to the top of the buffer table
and slide the others down.
place the buffer address of that block on the data stack.
;
As for simply exiting a word from within a DO LOOP nested within another DO LOOP
Code:
DO
<DO-SOMETHING>
DO
<DO-SOMETHING> <TEST>
IF
UNLOOP UNLOOP EXIT
THEN
<DO-SOMETHING>
LOOP
<DO-SOMETHING>
LOOP
As for branching out of a nested BEGIN loop
Code:
BEGIN
<DO-SOMETHING>
BEGIN
<DO-SOMETHING> <TEST0>
IF CS>A
<DO-SOMETHING> <TEST1>
UNTIL
<DO-SOMETHING> <TEST2>
UNTIL
<DO-SOMETHING>
A>CS THEN
<IF-BRANCHES-HERE>
The last two shortcomings sited are that the standard control structures can't handle multiple WHILE exits separately from each other or from UNTIL without retesting the exit condition.
This is not an issue with the WHILE from Fleet Forth and the Ansi Standard.
Code:
BEGIN
<DO-SOMETHING> <TEST0>
WHILE
<DO-SOMETHING> <TEST1>
WHILE
<DO-SOMETHING> <TEST2>
WHILE
<DO-SOMETHING>
UNTIL
<UNTIL-EXIT-FROM-LOOP> EXIT
THEN
<LAST-WHILE> EXIT
THEN
<SECOND-WHILE> EXIT
THEN
<FIRST-WHILE> ;
Although Fleet Forth's assembler uses reverse polish notation and control flow similar to Ragsdale's assembler, it is not based on his assembler.
The control flow words for Fleet Forth's assembler are modeled on the high level Forth control flow words presented here. The control flow data is also two items, an address and a security code. The security codes used for the assembler control flow words are different.
Fleet Forth's control flow words, like the Ansi Standard control flow words, are the blocks upon which other control flow words can be built. This is, in my opinion, a more elegant solution than the one presented by George W. Shaw II.