The powerful facility of defining words allows the Forth programmer to define
application-specific data types, with associated execution procedures.
If you have programmed in Forth at all, you've used these defining words : (colon), VARIABLE and CONSTANT. These words are used to create specific instances of colondefinitions, variables and constants. In fig-Forth, we can create other defining words in the following way.
: name < BUILDS compiletime code
DOES > run-time code
Three points to be noted are:
1) name is whatever you want to call your defining word;
2) the compile-time code is any sequence of Forth words, which specifies the way an instance will be compiled into memory;
3) the run-time code specifies the execution procedure for instances, when they are used. The first operation to be done leaves the first storage address of the instance on the stack, for use by the rest of the DOES position of the defining word. Then, the run-time code executes.
As an example, we would create the following defining word
: ByteArray< builds allot does > + ;
To compile a specific instance, we could type
300 ByteArray TESTER
to create an array with 300 bytes alloted to it. (The locations are not set to any particular value.) To use our instance, we can type
so that the offset 53 is added to the first storage address, leaving the address of the 53rd byte in TESTER.
Atari ExamplesAs you may know, the ATARI Operating System supports communication with peripheral devices through data structures called 1/0 Control Blocks. The eight control blocks consist of 16-byte arrays in memory, with each location in the array serving a fixed function. Listing 1 shows some words I use to manipulate control blocks. In this example, all the words defined by SERVES.AS execute by leaving an address on the stack. This address is the storage address for the COMMAND byte, STATUS byte, etc., associated with a particular control block.
Now, how do we use those instances? First, we must decide which control block we are referencing by storing the control-block number into lOCB#. After that point, all references using COMMAND, AUX1, etc., will refer to that particular control block. The GETCHR definition shows one way to create a generalized routine, that can be applied to different control blocks by changing the value of IOCB#. Another situation where these instances have been useful is as macros for assembly language routines.
Listing 2 illustrates a similar technique, applied to a four-voice sound sequencer. Another area where this style has been applied is in the creation of attributes associated with Player/Missile graphic images.
PrinciplesThese examples point out three aspects of good program design.
1) Similarity: To save space and unnecessary typing, try to gather similar structures or execution activities into one definition only.
2) Locality: Once we start referencing one particular object in memory, it's likely that we will continue referring to it for the next few operations.
3) Generality: The routines you can define are independent of the particular control block, voice, player, that you are manipulating.