FORTH is an unusual programming environment invented by Charles Moore in the 1970's. It is interactive, interpreting commands entered at the keyboard, and displaying output on the screen, but can also receive input from mass storage and write to a printer. The FORTH I use is PC/FORTH (ver. 3.2) from LMI (see References), a 16-bit FORTH complying with the FORTH-83 standard. I do not know if it is still available. It runs well using the MS-DOS prompt in Windows 98, and should be run from its own directory. Compared to modern programming platforms, it is very compact, using only two 64K segments, and fitting easily on one floppy disk. FORTH is very convenient for laboratory control applications, running under DOS where the computer I/O resources are directly accessible. In Windows 98, it makes a fascinating game in which a lot can be learned about programming. If FORTH is still available, there is probably a Windows version that gives access to the I/O. The fundamental FORTH uses integer arithmetic only, but floating point packages are available for performing numerical calculations.
When running under the MS-DOS prompt in Windows, FORTH is very capable of collapsing when the user makes a mistake, and bringing down Windows with it. When experimenting with FORTH it is wise to run FORTH only, so that other applications are not ravaged. FORTH normally starts fresh each time it is entered, and all your mistakes are forgotten. Get the MS-DOS prompt from Windows Start, go to the root with cd\, then make a directory with md pcf. Change directory to pcf with cd pcf, and copy the distribution disk to this directory. Follow the instructions supplied with the progam for installation. When FORTH is installed, at the MS-DOS prompt (usually C:\WINDOWS>) execute cd \pcf. Then, at C:\pcf> type FORTH or forth and press return. When FORTH comes up, it will say Ready! Then press
Consult a reference, such as Mastering Forth (called Tracy and Anderson here), for the elements of using FORTH. This article will not cover basic information on the program, but additional explanations and material that may be helpful. The basic component of FORTH is the word, whose name is typed at the keyboard and then executed on pressing
The first feature to master in FORTH is the parameter stack. When numbers are typed from the keyboard (not names!) they are pushed on the stack in the order they are presented. They are removed when used as inputs to words that are executed. To see what is on the stack, execute the word .S. This command does not change the stack at all. For example, type 1 2 and then
When you type a line and press
Loading a screen means using a block as input to FORTH, just as if its text were typed at the keyboard. To load block 3 of MYSTUFF.SCR, first be sure you are USING MYSTUFF.SCR, then execute 3 LOAD. To load blocks 1 to 4, the command is 1 4 LOAD. Block 0 cannot be loaded, and is generally used for comments. The command 1 4 INDEX will show the first lines of blocks 1 to 4 on the screen, so you can get some idea of what they contain. QX will display the first 14 characters of each first line of all the blocks in a screen file. A "program" is usually saved in a .SCR file.
FORTH will accept and display numbers as DECIMAL, HEX or BINARY. Simply type the word for the desired radix and press
Every FORTH word is identified by a name, which is a string of up to 31 characters. Anything is allowed, even numbers and punctuation. In fact, 1 and -1 are both words defined in the basic vocabulary. Alphabetic characters are conventionally upper case. FORTH is case-sensitive; "ONE" and "one" are not the same name. FORTH words are stored in a linked list called the dictionary as they are defined, building towards higher addresses. The word HERE fetches the first address beyond the top of the dictionary.
The first byte of a word begins with a 1 bit. Then comes the precedence bit. If it is 1, the word is executed whenever read, even while compiling. The next bit is the smudge bit. If it is 1, the word is omitted in a dictionary search. These two bits are normally 0. The five remaining bits give the number of characters in the name, so it is clear why the limit is 31. A word with 4 characters in its name would have the first byte 10000100 (84H). The next 4 bytes would contain the ASCII characters of the name. The address of the first byte is called the name field address, or nfa, of the word.
Following the name field, the next two bytes point to the nfa of the last word defined previously. This link field address, or lfa, is the first link in a chain that extends down to the beginning of the dictionary. When a name is entered at the keyboard, the search for it begins at the top of the directory and proceeds down until the name is found. If a name is not found, it is converted to a number and pushed on the parameter stack. LAST always contains the nfa of the last word defined, to start the search chain. LIMIT contains the highest address available to the dictionary.
Every word does something. The address of this routine is stored after the lfa, at a location called the compilation field address or cfa. This is the usual address that is associated with the word. All the other addresses are very easily obtained from it if they are needed. The word "'" or "tick" is used to push the cfa of a word on the stack, as in ' TEST, which will push the cfa of TEST on the stack.
Following the cfa is the parameter field, of variable length. Its address is called the parameter field address, or pfa. It may contain data used by the word, or addresses of routines to be executed, called compilation addresses. The name itself pushes the address of the pfa of a word on the stack. Executing TEST pushes its pfa on the stack. Of course, this is just cfa + 2, as you can easily check.
The word BODY> converts a pfa to a cfa. Executing TEST BODY> will give you the cfa of TEST, just as ' TEST does. Once you have a cfa, then >LINK will give the lfa, >NAME will give the nfa, and >BODY will give the pfa. Additionally, NAME> converts the nfa to the cfa, LINK> converts the lfa to the cfa, N>LINK converts the nfa to the lfa, and L>NAME converts the lfa to the nfa. This is all much simpler than it looks, and means that if you know the name of a word, you can find all the addresses involved, and turn any address into any other.
Now let's look at some typical words. A VARIABLE is a defining word used to store a number that may change. The variable X would be defined by executing VARIABLE X. Its first byte would be 10000001 (81H), stored at its nfa, which would be the location pointed to by HERE. Then would come ASCII X, or 58H. The next two bytes would be the link to the preceding name, and the next two after that the vector to the routine that does what VARIABLE does, which is push its pfa on the stack. The next two bytes, at the pfa, would be the value of the variable, which at this point is undefined. If you execute 0 X !, you push 0 on the stack, then the pfa of X, and store the 0 there. The stack diagram for ! can be written ( n a -), meaning that n (the number) and a (the address) are on the stack (n below, a above) before execution, and are eaten by the !. To get the value back, execute X @, which fetches the value to the stack ( a - n ). To see the value, execute ".". This word pops the top number off the stack and displays it as a signed integer. To display it as an unsigned integer, use "U.". It is instructive to check what happens at each step in the process of defining this variable, and what is at all the addresses involved.
A slightly different sort of word is a CONSTANT. If we define a CONSTANT ATE by executing 8 CONSTANT ATE, we get a word exactly like that of a VARIABLE except that the action of the word is to push the value, not the address, on the stack. The defining word CONSTANT also eats a number off the stack to give the constant a value when it defines the word. Now, executing ATE gives us 8 on the stack. We could craftily execute 7 ' ATE >BODY ! to change the value of the constant from 8 to 7. We can't use ATE to get the pfa, because the run-time action would get in the way. The "'" consumes the ATE before the interpreter can execute it.
Many words are defined by a colon definition, which begins with a ":" and ends with a ";". A name follows the ":", and then a list of previously defined words or literal numbers. The name is put in the dictionary in the way we have just seen. The parameter field consists of the cfa's of each of the words in the definition, ending with the cfa of the action associated with the ";", which is to tidy up and return to the interpreter. The cfa of the word contains the action of the ":", which is to begin executing the cfa's in the parameter field, one by one. An internal execution pointer, called "I" gives the next cfa to be executed. If a literal number is encountered, the corresponding cfa is that of a routine that pushes the following number (the literal number involved) on the stack. The literal number is an "immediate" operand.
Several buffers are assigned above the dictionary, from LIMIT to CS:FFFF. Graphics parameters, a hash table, and the return and parameter stacks build down from the top of the stack segment, SS:FFFF. Locations R0 and S0 contain the starts of the return and parameter stacks. RP and SP are the corresponding stack pointers. The return stack is the usual one that handles subroutine return addresses, and also serves as a scratch pad for temporary variables. It is of limited size (R0 - S0, 1024 bytes in PC/FORTH), but the parameter stack can grown downwards to SS:0000. The terminal input buffer, TIB holds 81 characters, while the string buffer PAD holds 128. The block buffer, between LIMIT and PAD, holds 3200 bytes. PC/FORTH can never occupy more than 128KB of RAM, a very small amount in current terms.
The word CREATE and its assistant ALLOT can be used to define named areas of storage. CREATE BUF 80 ALLOT puts a header in the dictionary with the name BUF, and reserves 80 bytes of storage in its parameter field. The run-time action of BUF is to push its nfa on the stack, so the data in it can be accessed. The word VARIABLE could be defined as follows: : VARIABLE CREATE 2 ALLOT ;. When used as VARIABLE X, CREATE finds X as the following token, which becomes the name. ALLOT reserves uninitialized locations. If you actually store values in the pfa with, say "," that stores a 16-bit quantity, you should not ALLOT the space first, or you will be storing at the end of the ALLOTed locations. "C," stores a byte, incidentally. It is important to keep track of whether you are working with 16-bit quantities, the usual numerical values, or 8-bit quantities, that usually represent characters or the lengths of strings. The storage takes place at HERE, and increments it. CREATE is used to generate a header so that we can access the storage by name, instead of having to remember the address.
A CODE word is like a colon word except that its parameter field contains machine code, and its cfa contains just the address of the pfa. When the first address in the pfa is executed by the interpreter, control is passed to the processor. The final address, the NEXT macro, passes control back to the FORTH interpreter. In the final analysis, everything must be reduced to CODE words, but the ordinary programmer will find enough already available that no further CODE words must be written. However, CODE is always available to speed up critical sections of a program. This was once more necessary than it now is when running FORTH on a fast processor.
Defining words are words used to make other words. We have already investigated VARIABLE, CONSTANT, CREATE, CODE and :;. While we are defining words, the words within them are not executed, but compiled; that is, their cfa's are stored in the definition. This is the compiling state of FORTH, distinct from the interpreting state. The current state is specified in the variable STATE, which is 0 for interpreting, -1 for compiling. If the precedence bit in the header of a word is set, then the word is always executed when encountered and never compiled. To set the precedence bit, execute IMMEDIATE immediately after the definition of the word.
The way to give a word a certain run-time action is to use the word DOES> while compiling it. For example, : CONSTANT CREATE 2 ALLOT DOES> @ ; This adds the run-time action of fetching the contents of the address pushed on the stack by CREATE. A constant initialized to zero is defined by : CONSTANT CREATE 0 , DOES> @ ;. The "," stores a 16-bit number directly in the directory. Remember to leave a space on both sides of the comma.
The joint use of CREATE and DOES> makes possible some very useful words, such as one defining a matrix that pushes the address of a desired element on the stack, so that data can be stored or retrieved. Let the number of rows r and the number of columns c be pushed on the stack. We want to make an array that will hold r x c elements, or r x c x 2 bytes. This could be done with the defining word SMATRIX, defined by : SMATRIX CREATE 2DUP , , * 2* ALLOT ; If we create a matrix M by r c SMATRIX M, then executing M will push the address of the first parameter, c, on the stack. The next location will contain r, and the next again the first element, M(0,0).
The word defined can be made much more useful if executing r' c' M pushes the address of M(r',c') on the stack instead. Now, M puts the address of c on the stack. To get the address of M(r', c'), we must add 2 (to get to M(0,0)) and also [(r' x c) + c'] x 2. This can be done with DOES> DUP >R @ (makes the stack r' c' addr c addr c, saves addr c on the return stack, then fetches c, so the stack is now r' c' c) ROT (stack now c' c r') * + (stack now c' + c*r') 2+ 2* R> (retrieves address of M(0,0)) + . Now the address of M(r',c') is on the stack, as desired.
The defining word is now : SMATRIX CREATE 2DUP , , * 2* ALLOT DOES> DUP >R @ ROT * + 2+ 2* R> + ;. A 3x5 matrix M is created with 3 5 SMATRIX M. The 1,2 element is loaded with 100 by 100 1 2 M !, and the value of the 1,2 element is fetched with 1 2 M @. A final refinement is to limit the addresses to the scope of the matrix. This is not so important for fetches, when only garbage will be the result, but is important for loading, so that other addresses are not corrupted. This refinement is given in Tracy and Anderson, but since it contains an error, the correct word is given here. : SMATRIX CREATE 2DUP , , * 2* ALLOT DOES> DUP >R @ 1- MIN 0 MAX SWAP R@ 2+ @ 1- MIN 0 MAX SWAP R@ @ ROT * + 2+ 2* R> + ;. The mistake is not comparing the indices with r - 1 and c - 1 instead of with r and c. For a 3x5 matrix, the maximum r' is 2, and the maximum c' is 4. MAX and MIN select the maximum and minimum of two numbers on the top of the stack. The word 1- is a register decrement, much faster than 1 -.
The word : REJOICE ." HURRAH! " ; types the word HURRAH! on the screen when it is executed. ." is a compiling word that deposits a routine that types the string up to the separator " when the word containing it is executed. REJOICE requires no parameters, and leaves the stack unaffected. The cfa of REJOICE is pushed on the stack with ' REJOICE. The vector is retrieved from the cfa by @, and this vector can be executed with EXECUTE. Therefore, executing ' REJOICE @ EXECUTE will display HURRAH! on the screen. This is a general way of executing a word. From the keyboard, we would always use just REJOICE, of course, but we can now make a given word execute words of our choice.
Consider the word : DOIT CREATE 2 ALLOT DOES> PERFORM ;. PERFORM is just @ EXECUTE. CREATE makes DOIT push its pfa on the stack. This address contains garbage when newly created, but if we load it with a valid cfa from some word, then the run-time action is to execute the word. For example, ' REJOICE DOIT ! loads the cfa of REJOICE into DOIT. Executing DOIT now types HURRAH! on the screen. It should be clear that if DOIT were an array of cfa's we could arrange vectored execution. That is, 2 DOIT would execute one word, while 0 DOIT would execute another. Of course, at this point DOIT does not take an index.
DO-N can be defined as : DO-N CREATE 2 * ALLOT DOES> 2* + PERFORM ;. Executing 4 DO-N provides space for 4 vectors, that can be loaded by expressions like ' REJOICE 0 DO-N !. Then, 2 DO-N will execute vector 2. Tracy and Anderson show how to create an array of vectors and use them for vectored jumps.
If we define the word : THREE+ 3 + ; the numeric literal 3 is included. It has no cfa to compile, of course. What FORTH does is compile a routine that takes the following constant (which will be 3) and pushes it on the stack at run time. Another way to write this is : THREE+ [ 3 ] LITERAL + ; The [ means to go to interpret state. Then the 3 will be pushed on the stack. The ] then restores compile state, and LITERAL causes the compilation of the routine to push the next location, and stores the literal 3 in the pfa of THREE+ following this routine. In this case, the [ ] LITERAL is more long-winded than just the 3, but allows us to do any calculation between the [ and ] that leaves the result on the top of the stack and compile the result as a literal. Note that there is a space before and after [ and ].
The words ['] and [compile] do not include the words [ and ], since there are no blanks to delimit them. ['] compiles the cfa of the following word as a literal, leaving this address on the stack at run time (instead of executing it). [COMPILE] overrides the IMMEDIATE property of a word and causes it to be compiled, not executed. It will be executed at run time. Both of these compiling words cause a departure from the usual course of things.
My FORTH includes 2DUP, but if it did not, it would be simple to define : 2DUP OVER OVER ; This 2DUP can be used in a definition, but there is some extra overhead involved. It would be faster to replace 2DUP, wherever it was found, by OVER OVER, and let these words be compiled. This can be done with the word : 2DUP COMPILE OVER COMPILE OVER ; IMMEDIATE. Because it is IMMEDIATE, when 2DUP is encountered in a definition, it will be executed immediately, and its action will be to compile OVER twice. We have to use COMPILE explicitly here, since they are not found literally in the definition. If they were executed, they would just scramble the stack. If OVER were an IMMEDIATE word, we would have to use [COMPILE] instead. As far as compiling goes, this 2DUP just compiles the equivalent, OVER OVER. It is really a macro that expands into OVER OVER.
A string is an array of characters. The address of the string is the address of the first character. In C, the end of a string is marked with a null byte, '\0'. No character has a zero ASCII code. This 0 is just a flag marking the end of the string. In FORTH, the length of the string must also be known separately. If the length is n, the string extends from addr to addr + n. If the length is to be a single byte, then the maximum length of a string is 255, from 01 to FF. A length of 0 implies that the string is empty.
A counted string has its length stored at its address, and the first character of the string is at address + 1. Some functions require the address and length of a string on the stack as ( a n -), and others require a counted string identified by its address alone. The word COUNT is used to go from a counted string in the ( addr -) form to the (addr' n -) form, where addr' = addr + 1. Going the other way is more difficult, since we must make sure that room is available for the length. If BUF is a string buffer of adequate size, then the word : PLACE 2DUP >R >R 1+ SWAP CMOVE> R> R> C! ; will do the job when used as BUF
In this definition, the length and the address pushed on the stack by BUF are duplicated and saved on the return stack. 1 is added to the address of BUF to allow for the length to be stored. The buffer addresses are swapped to be in the correct order for the move, and then CMOVE> copies BUF (shifted right 1 byte) to BUF. Now we get the length and the address of the first location in BUF, which are in the right order to be stored with C!, which stores the byte
PAD is a 128-byte buffer available for temporary use. Alternatively, you can define your own buffer with CREATE BUF 81 ALLOT, choosing the name (BUF) and size (81 bytes) as you want. A line can be read from the keyboard with : READLN CR BUF 80 EXPECT SPAN @ LSTR ! ;. EXPECT issues no prompt, and terminates when you press
The word FIND can look up a name in the dictionary, and return its cfa if the name is found. In fact, (cfa -1) is returned on the stack if the name is found and is not IMMEDIATE. If it is IMMEDIATE, (cfa 1) is returned. If the name is not found, (addr 0) is returned. To use FIND, the address addr of a counted string is first pushed on the stack, then FIND is executed. The counted string can be obtained as explained in the preceding paragraph. Of course, this is an alternative to the use of '.
Boolean variables are those that take only two values. A single bit can take values of 0 and 1 only. 0 is often called TRUE, and 1 is FALSE. These are just arbitrary names, and can be interchanged. It is always the interpretation that is important. A 16-bit integer in FORTH contains 16 such quantities. It can also be interpreted as a single Boolean quantity if 0 (0000000000000000) represents FALSE and -1 (1111111111111111) represents TRUE. In these definitions, each bit has the same value.
Boolean expressions are made up of Boolean variables combined by the operators AND, OR and NOT. In FORTH, these operate on each bit in a 16-bit quantity in the same way. To see what happens, execute BINARY and then experiment with bit combinations. 0 0 AND gives 0, as do 1 0 AND and 0 1 AND, but 1 1 AND gives 1. FORTH, as usual, takes these in RPN, while they would normally be written 0 * 0 = 0 * 1 = 1 * 0 = 0, 1 * 1 = 1. AND is represented by multiplication, which it resembles. 0 0 OR gives 0, while 1 0 OR, 0 1 OR and 1 1 OR all give 1. That is, 0 + 0 = 0, 1 + 0 = 0 + 1 = 1 + 1 = 1. OR is represented by addition, but has a peculiarity as seen in the last equality. Finally, 0 NOT = 1 and 1 NOT = 0, which can be written /0 = 1 and /1 = 0.
These rules also hold for the 16-bit Boolean quantities TRUE = -1 and FALSE = 0, because each bit changes in the same way. In some cases, any nonzero number will be interpreted as TRUE. However, such general numbers cannot be used in Boolean expressions. For example, -2 NOT gives 1, not 0. If we took -2 as TRUE, then this would be NOT TRUE = TRUE, which is absurd. FORTH does not have separate Boolean connectives for zero and non-zero 16-bit quantities as FALSE and TRUE, but only the bit-by-bit connectives. It is a good idea, therefore, always to work only with TRUE = -1 and FALSE = 0. Then NOT TRUE = FALSE, as it should be.
Two numbers on the stack can be compared with each other, or a single number can be compared to zero. The comparison words are =, >, <, 0=, 0> and 0>. They consume the two numbers compared, and push TRUE or FALSE, which are called Boolean flags, on the stack in their place. These words always give 0 or -1. If you arrive at a flag in some other way, be sure to convert it to -1 if it is nonzero.
The compiling words IF ... ELSE ... THEN, when used in the definition of a word, pop the flag on top the stack at run time. If this flag is TRUE, or actually nonzero, the words between IF and ELSE are executed, and then execution passes to what is beyond THEN. If the flag is zero, the words between ELSE and THEN are executed instead. If there is no ELSE clause, the ELSE can be eliminated, and we have just IF ... THEN. These tests can be nested as much as desired, but a simple test is always preferred. To try this out, define and use the word : TRUTH IF ." TRUE" ELSE ." FALSE" THEN ;. Every IF must have a THEN.
Repeated execution of words is very often needed, especially for the boring and repetitive work computers are so good at. Control structures that depend on flags calculated within them are BEGIN ... UNTIL, BEGIN ... WHILE ... REPEAT, and BEGIN ... AGAIN. UNTIL pops and tests the flag on the stack when it executes at the bottom of the loop. If this flag is TRUE, then execution continues beyond UNTIL. If it is false, execution goes back to BEGIN. WHILE tests a flag computed by the words between BEGIN and WHILE, at the top of the loop. If it is TRUE, the words between WHILE and REPEAT are executed, and execution returns to BEGIN. If the flag is FALSE, execution is passed to words beyond REPEAT. BEGIN ... AGAIN makes no test at all. It simply repeats over and over, and the loop is exited only when EXIT in a conditional statement is executed. EXIT can be used to leave any of these loops early.
Loops that are to be executed a number of times known at compile time, analogous to the "for (i = n1; i <= n2; i++)" of C, are also available as DO ... LOOP or DO ... +LOOP. Like IF ... ELSE ... THEN, these words must be used inside a colon definition. : BANGS DO CR ." BANG!" LOOP ; will type BANG! on the screen multiple times. 6 0 BANGS will type a column of six BANG!'s. +LOOP increments by n instead of by 1, when n is the number on the stack when it is executed. The first number on the stack (6 here) is the upper limit of the counter variable I. The loop will exit when I is incremented across the boundary (limit - 1) and limit (here, between 5 and 6). The second number (0 here) is the starting value of I. I is a word that copies the loop index from the return stack to the parameter stack. If you commanded 0 0 BANGS (or 4 4 BANGS, etc.) you would get a lot of bangs, 65,535 to be exact, before I was incremented back around to the boundary FFFF = -1 and 0000 = 0 (or 4), not just one bang. The word ?DO will look out for equality of the limit and the initial index, and will not execute the loop if they are equal. ?DUP will copy the number on top of the stack only if it is nonzero, which can save some effort if a following ?DO loop will not be executed. I is incremented as an unsigned integer to 32767 then to -32768 and eventually up to -3, -2, -1 and 0. EXIT cannot be used to leave a DO loop (unless you explicitly remove the loop parameters from the return stack); use LEAVE instead, which cleans up the return stack for you.
The word I pushes the index of the current DO loop on the parameter stack. The word J pushes the index of the enclosing DO loop on the stack. If I is the index of one level of nested DO loops, then J is the index of the next enclosing DO loop, not necessarily the innermost loop and the next innermost loop. That is , I and J are relative to the currently executing loop. The return stack must be kept valid. It can be used for temporary storage, but anything pushed on it must be popped before the loop exits. Anything pushed after the loop begins hides the loop index, which is then unavailable. Anything pushed before the loop begins in unavailable inside the loop. >R and R> move items between the return and parameter stacks. R@ copies the top element of the return stack to the parameter stack. In a loop, this is related to I, but is not I; it seems to increment like I, however.
PC/FORTH, ver. 3.2. Laboratory Microsystems, Inc., P.O. Box 10430, Marina del Rey, CA 90295 (1987). The User's Manual is an essential reference, with an explanation of the basic words.
M. Tracy, A. Anderson, etc., Mastering FORTH (New York: Brady, 1989). Many of the examples in this article are suggested in this book.
Composed by J. B. Calvert
Created 26 August 2004