Fig-Forth v2 has two effective instruction pointers operating at any instant, the first being the CS:IP registers within the system hardware. As a second IP Fig-Forth uses the DS:SI register combination, which is used to step through the execution list of tokens and enclosed strings. With both of these pointers in operation the CPU switches rapidly between the code fragments, intermixing the double IP values upon the return stack. Modifying these registers should be handled with extreme care or incorrect operation may result, primarily when defining dictionary code fragments. (See Appendix C.)
Within the execution lists of tokens Fig-Forth v2 allows more latitude in changing the value of the DS:SI pointer, having three types of branch operations available to your program. These operations are based on a conditional or unconditional change of the program counter, causing the processor to move forward or back by the number of token bytes specified in the command. This action destroys the old program counter value with all forms of jump, and consumes the top parameter stack item in the conditional form whether a jump takes place or not. Using these jump tokens and the full word range of offset, program flow can be passed to any area within the dictionary space.
To make the construction of such jumps more friendly to the programmer, Fig-Forth v2 contains the compiler directive words of IF, ELSE, THEN, CASE:, :END, SWITCH, BEGIN, AGAIN, UNTIL, END, WHILE and REPEAT, which are outlined below;
Contrary to most other languages Forth uses a reversed-nested version of the IF statement, which requires that the programmer "pre-compile" the resulting branch jump instruction and it's matching condition. Since IF and ELSE are but variants of the compiler branch instructions made friendly, the condition to be tested must be performed before the IF itself;
2 1 = IF -- words following IF will be executed when 1=2.
The optional ELSE instruction modifies the branch compiled by IF to point to itself, while adding a branch to skip over the code to be executed when the condition is not met.
ELSE -- words following ELSE will be executed when 1<>2
Finally the THEN word completes the branch for both IF and ELSE, pointing to that code to be executed after the conditional sections.
THEN -- Words following THEN will be executed at the end of the test.
For beginners, the following statements summarize the action of the IF ELSE THEN operation;
"Test condition, IF true do these words, ELSE false do these, THEN do these words."
= IF ." The two are equal!" ELSE ." They are not equal." THEN ." All done."
All IF jumps must be closed by a subsequent THEN, and such a definition cannot be used across word boundaries. Any IF without a THEN or ELSE without an IF-THEN will be caught by the compiler.
Because IF THEN branches are often used for the conditional execution of token addresses following a comparison, Fig-Forth v2 contains a simplistic CASE statement for optimization. This word group replaces the following source of a typical IF THEN construction, performing the identical operations in a simplified manner;
: KEY-SCAN ( C -- ) |
45 OVER = IF ." NEGATIVE SIGN" THEN |
43 OVER = IF ." PLUS SIGN" THEN |
46 OVER = IF ." PERIOD" THEN |
.... |
The CASE: and :END words streamline this source to appear as follows, executing the OVER = IF operation with a single token;
: KEY-SCAN ( C -- ) |
45 CASE: ." NEGATIVE SIGN" :END |
43 CASE: ." PLUS SIGN" :END |
46 CASE: ." PERIOD" :END |
.... |
Either method may be applied at any time. CASE: statements can be nested to any level just as their IF-THEN equivalents, however programmers should note that the value tested for equality to the CASE: parameter is NOT dropped from the stack, allowing that case structures may span word lists or use the value in multiple statements.
Next in the Flow Control group is the SWITCH structure, a compiler sub-loop for making several tests as a collective operation. Similar in function to the CASE: and :END combination, the SWITCH statement builds an array of integer associated vectors within a program's processes. SWITCH is a "half automatic" operation of the compiler, adding a structure following the syntax below until the integer specified is -1 as the last parameter. As an example;
: AMINUS ." NEGATIVE SIGN" ; |
: APLUS ." PLUS SIGN" ; |
: APERIOD ." PERIOD MARK" ; |
SWITCH TEST 45 AMINUS 43 APLUS 46 APERIOD -1 |
: TRY-IT KEY TEST DROP ; |
This combination of definitions will associate the key codes of - + and . (or ASCII 45, 43 and 46) to the forth word lines of AMINUS, APLUS and APERIOD respectively through the array word of TEST. When TEST is executed the top value on the stack is compared to the numeric constants listed in the SWITCH statement, then the associated word definition is run when a match is declared. If the value given to the switch array does not equal any of the entries in the table no operation takes place, and the value given the switch array remains on the stack at all times. (So must be dropped explicitly.)
Switch arrays may be of any size, though only the first encountered match between parameters will be executed. Switch statements use integer values for storage and comparison, and must end with a -1 which exits the compiler loop. (Basically, I got tired of constructing this function over and over again in source.)
The SWITCH constructor also uses a "case ignorant" search process, first setting CASELOCK to 1 to make exact symbol matches and then to 0 for a search that is more broad for each symbol sought. As with all searches, only the first declared match is included in the switch table. Note that no other text or operations, including comments, may be incorporated into the list of word vectors, the definition must appear as <number> <word> for each entry except for a load arrow between vector entries, or the -1 used to terminate the table. e.g.;
SWITCH TEST 2 WORD1 3 WORD2 --> 4 WORD3 5 WORD4 ... -1 (valid)
SWITCH TEST 2 WORD1 3 WORD2 4 --> WORD3 5 WORD4 ... -1 (invalid)
When a load arrow is used in the switch definition, the file input pointer is advanced to the second screen line of the next file block, as similarly described for the wrap operation of the Editor program. (See Chapter 6, Using the Block Editor.)
Just as IF ELSE THEN CASE: :END and SWITCH combine the instructions to perform a conditional branch, (center diagram of figure 1 at right) Fig-Forth v2 contains the words of BEGIN, AGAIN, UNTIL, WHILE and REPEAT to build operational looping structures. Though not all words are used to define any single structure, these words create program loops which operate over a specified period; until a condition is met or becomes false, or an error occurs which causes the compiler to seize control. (Catching errors from such loops will be discussed in Advanced Procedures and Functions.)
This word serves to mark the start of any conditional loop, placing the location of the current dictionary pointer upon the parameter stack for later processing. In addition, Fig-Forth also saves a flag value with the address to detect any unclosed loops just as in IF THEN, so loops of this type cannot span word lists.
This word closes the loop of the last BEGIN using a unilateral branch instruction of the system kernel. This type of loop will never exit without the execution of a QUIT, WARM, COLD or ABORT token by the loop or its called components, or in the event of a detectable error which calls ABORT. Such a loop is similar to the Forth command interpreter.
This word closes the loop of the last BEGIN using a conditional instruction, such that the loop will be repeated until the specified condition becomes True. As with IF the condition test must be made before the UNTIL word, and the execution of QUIT, WARM, ABORT or an error will also exit the loop. Such a loop is diagrammed in the left side of Figure 1. This word is the same in Fig-Forth v2 as the older styled END word.
The WHILE word operates in much the same way as ELSE, however the word is required as part of the BEGIN-WHILE-REPEAT sequence. The WHILE word compiles a conditional branch just as above for the process of UNTIL, however the branch that is made goes around the token list following the test. Upon execution the word list following WHILE will be executed as long as the condition specified remains true, branching back to BEGIN at the point of REPEAT.
This word closes the loop of the last BEGIN-WHILE, compiling a branch command to the BEGIN location and closing the one from WHILE to the current location. Such a loop is diagrammed in the right most column of Figure 1.
BEGIN KEY DUP EMIT 13 = UNTIL | -- will echo keys until a return is entered. |
0 BEGIN KEY DUP 48 58 WITHIN WHILE
48 - SWAP 10 * + REPEAT |
-- will accept & convert keys to an integer. |
For the purposes of limited execution loops or indexed addressing, Fig-Forth v2 offers the counted loop consisting of four words. The central core to this operation is the (LOOP) kernel code, which increments an index value and then compares the result to an ending limit. When the index becomes mathematically equal to or larger than the limit, the loop is exited. The +LOOP word performs the same function as LOOP, but inserts a value to the increment count from the top stack item. Loops of this type conform to the Fig-Forth or Forth79 standard.
The source format of all counted loops is an ending value, a starting value, the DO or ?DO word, the words to be executed during the loop, and either a LOOP or +LOOP ending word. (See examples below.)
This word serves to mark the beginning of the counted loop, and transfers two values from the parameter stack to the return stack when executed. Word tokens between the DO and LOOP words will be executed in the sequence encountered for the number of times specified by the parameters.
This word also serves to mark the beginning of the counted loop, however the loop that results will not be executed if the Index and Limit given at run time are the same. Thus a sequence such as 0 0 ?DO will immediately jump to the words following LOOP or +LOOP, as though the loop was surrounded by a bounds checking IF THEN. In the event the loop is not executed, both the Index and Limit values are discarded as though the loop function was performed.
This word closes the counted loop, incrementing the index value and testing its equality to the ending limit. If the index is smaller than the limit, program control returns to that word token following the DO function.
This word also closes the counted loop, adding the current parameter stack's top value to the index and then testing its equality to the limit. Program control is transferred as in LOOP.
The I word returns the current index value of the DO-LOOP sequence in progress, placing it on the parameter stack as a signed single precision word. The J word returns the index of an outer DO-LOOP in progress, yet both loops must be within the same word;
: MYWORD 10 5 DO | -- the outer loop |
5 0 DO | -- the inner loop |
I | -- inner loop index, 0 to 4 range |
J | -- outer loop index, 5 to 9 range |
LOOP | -- close second inner loop |
I | -- outer loop index again. (Inner loop has terminated.) |
LOOP ; | -- close the loops |
Caution should be used in trying to use these words across word definitions, as in;
: MYWORD1 5 0 DO I J LOOP ; ( J will return an incorrect value. ) |
: MYWORD2 10 5 DO MYWORD1 LOOP ; |
The word REDO sets the index of the current loop to zero, or forces the top return stack item to zero outside of a loop definition. This word should not be used with non-zero starting loops, unless the desire is to restart the loop at the zero index. Caution should be employed in using the word outside of a DO-LOOP or operational errors may result. E.g., the top return stack item must be mutable data transferred to the return stack by the programmer.
LEAVE on the other hand sets the Index equal to the Limit, such that the next encountered LOOP word will cause an exit of the loop. Again, caution should be employed in calling this word outside of DO-LOOPs, for its effect is to execute an RDROP RDUP sequence. (A DROP DUP operation on the Return Stack.)
Note that in both cases the words in the loop after REDO or LEAVE will continue to be executed, using the modified Index as the current loop value.
Typical Examples
10 0 DO I . LOOP | -- will print the values of 0 to 9 |
15 5 DO I . 2 +LOOP | -- will print the odd values from 5 to 13 |
5 0 DO I . I 4 = IF REDO THEN LOOP | -- will loop forever from 0 to 4 |
10 5 DO MYWORD IF REDO THEN LOOP | -- will restart the loop at zero if MYWORD returns true. |
5 >R REDO R> . | -- will print a zero as REDO clears the value. |
Last in the Flow Control group is the compile only words of EXIT and ?EXIT, which conditionally or universally cease the operation of the current token list in favor of the one that called it. If the definition containing the EXIT word is the top most operation, program control returns to the Input Processor. Both EXIT and ?EXIT will scan for any loops compiled into the current definition upon creation, adding the necessary UNLOOP words to conform to proper operation. Any data placed on the Return Stack by the programmer must be pulled separately and before the EXIT words. EXIT will force an immediate un-nesting of the current token list in favor of its previous, while ?EXIT will do the un-nest if the flag given on the parameter stack is True. (Non-zero.)
: TEST 10 0 DO KEY 13 = IF EXIT THEN LOOP ; | -- exits upon a return key or loop end |
: TEST 10 0 DO KEY 13 = ?EXIT LOOP ; | -- does the same |
UNLOOP
Related to EXIT is the UNLOOP word, which discards the top two Return Stack items for the un-nesting of DO-LOOP operations. This word can be used by the programmer to discard any combination of two items from the Return Stack, though such data is expected to be mutable information placed on the stack by compiler or application programming. This word is equivalent to an RDROP RDROP sequence.
Return to Contents. Next Chapter. Previous Chapter.