The Forth language is commonly used for the same type of programs that people might choose to use Assembler. Typically they are embedded programs for electronic devices, measurement instruments, drum machines, satellites and even electric toothbrushes. Forth works close to the hardware but gives you ways to make your own simple language so in short order, you are using your own high level functions to get the job done. This can make it faster to get a program ready and out the door in Forth versus doing it all in Assembler.
This tutorial illustrates how Forth manages memory, starting with nothing more than the raw memory addresses, just like you would see in Assembler programming. The difference is that with a few quick definitions, you create a simple memory management system. Using that memory management system you can build named variables, constants buffers and arrays.
To begin we have to assume we already have a Forth compiler somewhere that lets us add new routines, or as Forth calls them WORDs to the Forth Dictionary. The dictionary is actually just the memory space that we will be managing in this demonstration.
The Forth compiler is nothing more than the ‘:’ and ‘;’ WORDS. To compile a new word that does nothing in Forth you would type:
: MY_NEW_WORD ;
This would create a word in the Forth dictionary but since there is no other code after the name it does nothing.
We also need our compiler to have the Forth word ‘VARIABLE’. With these things in place we can create our simple memory management system.
We will create an empty memory location that will hold the next available memory address that we can use. In Forth this is called the dictionary pointer or DP for short we declare it like this:
VARIABLE DP \ holds the address of the next free memory location
Next we will create a function that returns the address that is held in DP. Forth calls that function ‘HERE’ as in “Where is the next available memory location?” Forth says “HERE”.
HERE uses the ‘FETCH’ operator which is the ampersand, ‘@’ in Forth.
: HERE ( -- addr) DP @ ; \ fetch the address in DP
Another thing that we will need to do is move the “dictionary pointer” by changing the value in DP so Forth creates a function that takes a number from the stack and adds it to DP using the function ‘+!” (Pronounced “plus-store”) Plus-store is very much like ‘+=’ for those familiar with ‘C’.
Forth calls this DP altering function ‘ALLOT’ and it is defined like this:
: ALLOT ( n --) DP +! ; \ add n to value in variable DP.
\ in other words allocate dictionary space
(Pretty simple huh)
So with these three definitions we have the beginnings of a simple memory manager. We can now say things in our program like:
HEX 2000 HERE 20 CMOVE \ move $20 bytes from HEX 2000 to HERE
\ Now move DP forward to “allocate” the space we just wrote to:
By using “20 ALLOT”. HERE is moved past our little 20 byte space so we won’t tromp on it later. So we have allocated 20 bytes of memory for our own use. To make it really practical we should have recorded the address of HERE somewhere because HERE is now pointing to a new address.Getting Fancy
We can combine HERE and ALLOT and the STORE word ‘!’ in Forth to make an operator that ‘compiles” a number into memory. Forth uses the comma ‘,’ for this function and the definition is simple now.
: , ( n -- ) HERE ! \ store n at HERE,
2 ALLOT ; \ allocate 2 bytes (for a 16 bit computer)
To use the comma we simply type:
99 , 100 , 101 , 4096 ,
And the numbers go into memory like magic!
And of course we have a similar word that is used for bytes or characters called ‘C,’.
(pronounced “c-comma”) It works the same way a comma.
: C, ( c --) HERE C! 1 ALLOT ;
There is a Forth word called CREATE that lets us add a new word to the dictionary. Words made with ‘CREATE’ simply return the dictionary memory address after the name. So with our new system making a variable called ‘X’ is as simple as:
CREATE X 2 ALLOT
In fact using the colon compiler we can take it to a higher level still:
: VARIABLE CREATE 0 , ; \ create a name and compile 0 in memory
Notice how we used the comma to automate the memory allocation of one unit of memory and initialize it to zero.
Now our programs can say:
And if we type:
CREATE ABUFFER 50 ALLOT
We have created a named memory space that we can use to hold data. Invoking the name ‘ABUFFER’ in our program will give us the beginning address of that buffer.
But why stop there? Use the compiler to make it better:
: BUFFER: CREATE ALLOT ;
Now it’s all done with one word!
50 BUFFER: ABUFFER
We could also ‘CREATE’ an array of numbers in memory like this:
CREATE MYNUMS 0 , 11 , 22 , 33 , 44 , 55 , 66 , 77 , 88 , 99 ,
There are fancier ways to access this array but for this tutorial we will keep it simple. To get at these numbers in the array, we simply need to compute the address of the number we want. Since each number has been given 2 bytes of memory or 1 CELL as Forth calls it, the math is easy.
Given an index number, we multiply the index by the memory size, in bytes, of the CPU and add the result to the base address.
Let’s do it with the colon compiler:
: ]MYNUMS ( index –- addr) CELLS MYNUMS + ;
· CELLS is a forth function that multiplies a number by the memory address size of the CPU.
o As in x2 for a 16 bit CPU, x4 for 32 bit CPUs or x8 for a 64 bit computer.
o In this case it will multiply the index value on the stack
· MYNUMS returns the base address of the array
· ‘+’ simply adds the two numbers together giving us the address we need.
We can now fetch and see the value of any number in the array like this:
3 ]MYNUMS @ . \ the ‘.’ word prints the number on the top of the stack
The screen capture shows all of this entered at Forth console.
So this tutorial gives you examples of how Forth builds itself up from almost nothing to higher levels of programming. This approach is used to create the memory management words that you can use but it’s that the Forth compiler uses these same WORDs internally to compile words and numbers into memory and even to ASSEMBLE op-codes into memory in the Forth Assembler.
I know you are asking “Where did the compiler come from in the first place?” Well that’s a bit more black-belt level programming but the principals are the very same. You can start in Assembler or C and make a few primitive routines. Then you use those routines to make higher level WORDs. People have even built Forth compilers in Java and LISP. The method however is always the same. You begin with simple pieces and combine them to build something better and eventually you have built the compiler. It’s not for the faint-of-heart but the cool thing is that it is possible to understand every aspect of the system.