Main Menu

11. Extending the Compiler

In comparison with conventional languages, Forth’s compiler is completely backwards. Traditional compilers are huge programs designed to translate any foreseeable, legal combination of available operators into machine language. In Forth, however, most of the work of compilation is done by a single definition, only a few lines long. Special structures like conditionals and loops are not compiled by the compiler but by the words being compiled (IF, DO, etc.)

Lest you scoff at Forth’s simple ways, notice that Forth is unique among languages in the ease with which you can extend the compiler. Defining new, specialized compilers is as easy as defining any other word, as you will soon see.

When you’ve got an extensible compiler, you’ve got a very powerful language!

Just a Question of Time

Before we get fully into this chapter, let’s review one particular concept that can be a problem to beginning Forth programmers. It’s a question of time.

We have used the term “run time” when referring to things that occur when a word is executed and “compile time” when referring to things that happen when a word is compiled. So far so good. But things get a little confusing when a single word has both a run-time and a compile-time behavior.

In general there are two classes of words which behave in both ways. For purposes of this discussion, we’ll call these two classes “defining words” and “compiling words.”

A defining word is a a word which, when executed, compiles a new definition. A defining word specifies the compile-time and run-time behavior of each member of the “family” of words that it defines. Using the defining word CONSTANT as an example, when we say

   80 CONSTANT MARGIN

we are executing the compile-time behavior of CONSTANT; that is, CONSTANT is compiling a new constant-type dictionary entry called MARGIN and storing the value 80 into its parameter field. But when we say

   MARGIN

we are executing the run-time behavior of CONSTANT; that is, CONSTANT is pushing the value 80 onto the stack. We’ll pursue defining words further in the next few sections.

The other type of word which possesses dual behavior is the “compiling word.” A compiling word is a word that we use inside a colon definition and that actually does something during compilation of that definition.

One example is the word .”, which at compile time compiles a text string into the dictionary entry with the count in front, and at run time types it. Other examples are control-structure words like IF and LOOP, which also have compile-time behaviors distinct from their run-time behaviors. We’ll explore compiling words after we’ve discussed defining words.

How to Define a Defining Word

Here are the standard Forth defining words we’ve covered so far:

   :
   VARIABLE
   2VARIABLE
   CONSTANT
   2CONSTANT
   CREATE

What do they all have in common? Each of them is used to define a set of words with similar compile-time and run-time characteristics.

And how are all these defining words defined? First we’ll answer this question metaphorically.

Let’s say you’re in the ceramic salt-shaker business. If you plan to make enough salt shakers, you’ll find it’s easiest to make a mold first. A mold will guarantee that all your shakers will be of the same design, while allowing you to make each shaker a different color. In making the mold, you must consider two things:

  • How the mold will work. (E.g., how will you get the clay into and out of the mold without breaking the mold or letting the seams show?)
  • How the shaker will work. (E.g., how many holes should there be? How much salt should it hold? Etc.

To bring this analogy back to Forth, the definition of a defining word must specify two things: the compile-time behavior and the run-time behavior for that type of word.

Hold that thought a moment while we look at the most basic of the defining words in the above list: CREATE. At compile time, CREATE takes a name from the input stream and creates a dictionary heading for it.

Forth defining words: how CREATE makes a dictionary entry

At run time, CREATE pushes the body address of EXAMPLE onto the stack.

What happens if we use CREATE inside a definition? Consider this example, which is the definition for VARIABLE:

   : VARIABLE  CREATE  0 , ;

When we execute VARIABLE as in

   VARIABLE ORANGES

We are indirectly using CREATE to create a dictionary head with the name ORANGES and an xt that points to CREATE’s run-time code. Then we are allotting a cell for the variable itself (with “0 ,”).

Since the run-time behavior of a variable is identical to that of a word defined by CREATE, VARIABLE does not need to have run-time code of its own, it can use CREATE’s run-time code.

defining words - define a different run-time behavior with DOES

How do we specify a different run-time behavior in a defining word? By using the word DOES>, as shown here:

   : DEFINING-WORD  CREATE  (compile-time operations)
                    DOES>   (run-time operations) ;

To illustrate, the following could be a valid definition for CONSTANT (although in fact CONSTANT is usually defined in machine code):

   : CONSTANT  CREATE  ,  DOES> @ ;

To see how this definition works, imagine we’re using it to define a constant named TROMBONES, like this:

   76 CONSTANT TROMBONES

compile-time portion
CREATE Create a new dictionary entry (e.g., TROMBONES)
, Compiles the value (e.g., 76) for the constant from the stack into the constant's parameter field.
run-time portion
DOES> Marks the end of the compile-time behavior and the beginning of the run-time behavior. At run time, DOES> will leave the body address of the word being defined on the stack.
@ Fetches the contents of the constant, using the body address that will be on the stack at run time.

defining words - compile-time behaviors and run-time behaviors

The words that precede DOES> specify what the mold will do; the words that follow DOES> specify what the product of the mold will do.

DOES> run time: ( -- addr)
Used in creating a defining word; marks the end of its compile-time portion and the beginning of its run-time portion. The run-time operations are stated in higher-level Forth. At run time, the body address of the defined word will be on the stack.

Defining Words You Can Define Yourself

Here are some examples of defining words that you can create yourself.

Recall that in our discussion of “String Input Commands” in Chap. 10, we gave an example that employed character-string arrays called NAME, EYES, and ME. Every time we used one of these names, we followed it with a character count. In the input definition, we wrote

      ... PAD NAME 14 MOVE

and in the output definition we wrote

      ... NAME 14 -TRAILING TYPE ...

and so on.

Let’s eliminate the count by creating a defining word called CHARACTERS, whose product definitions will leave the address and count on the stack when executed.

We’ll use it like this: if we say

   20 CHARACTERS ME

we will create an array called ME, with twenty characters available for the character string.

When we execute ME, we’ll get the address of the array and the count on the stack. Now we can write

   PAD ME MOVE

instead of

   PAD ME 20 MOVE

or

   ME -TRAILING TYPE

instead of

   ME 20 -TRAILING TYPE

Here’s how we might define CHARACTERS:

: CHARACTERS
compile-time portion
CREATE Create a new dictionary entry (e.g., ME)
DUP , ALLOT Compiles the count (e.g., twenty) into the first cell of the array for future reference. Then allots an additional twenty bytes beyond the count for the string.
run-time portion
DOES> Marks the beginning of run-time code, leaving the body address of the product-word on the stack at run-time.
DUP Copies the body address.
CELL+ Advances the address to point past the count, to the start of the character string.
SWAP @ Swaps the string address with the count address and fetches the count. The stack now holds ( addr count -- ).
;

We have just extended our compiler! Our new word CHARACTERS is a defining word that creates a data structure and procedure that we find useful. CHARACTERS not only simplifies our input and output definitions, it also allows us to change the length of any string, should the need arise, in one place only (i.e., where we define it).

Our next example could be useful in an application where a large number of byte (not CHAR!) arrays are needed. Let’s create a defining word called STRING as follows:

   : STRING   CREATE ALLOT  DOES> + ;

to be used in the form

   30 STRING VALVE 

to create an array thirty bytes in length. To access any byte in this array, we merely say:

   6 VALVE C@

which would give us the current setting of hydraulic valve 6 at an oil-pumping station. At run time, VALVE will add the argument 6 to the body address left by DOES>, producing the correct byte address.

If our application requires a large number of arrays to be initialized to zero, we might include the initialization in an alternate defining word called 0STRING:

   : ERASED  HERE OVER  ERASE ALLOT ;
   : 0STRING  CREATE ERASED DOES> + ;

First we define ERASED to ERASE the given number of bytes, starting at HERE, before ALLOTing the given number of bytes.

Then we simply substitute ERASED for ALLOT in our new version.

By changing the definition of a defining word, you can change the characteristics of all the member words of that family. This ability makes program development much easier. For instance, you can incorporate certain kinds of error checking while you are developing the program, then eliminate them after you are sure that the program runs correctly.

Here is a version of STRING which, at run time, guarantees that the index into the array is valid:

   : STRING  CREATE  DUP , ALLOT
             DOES>   2DUP @ U< 0=
                     ABORT" Range error "  + CELL+ ;

which breaks down as follows:

DUP , ALLOT Compiles the count and allots the given number of bytes.
DOES> 2DUP @ At run time, given the argument on the stack, produces ( arg pfa arg count -- ).
U< 0= Tests that the argument is not less than the maximum, i.e., the stored count. Since U< is an unsigned compare, negative arguments will appear as very high numbers and thus will also fail the test.
ABORT" Range error " Check if the comparison test fails.
+ CELL+ Otherwise adds the argument to the body address, plus an additional cell to skip the count.

Here's another way that the use of defining words can help during development. Let's say you suddenly decide that all of the arrays you've defined with STRING are too large to be kept in computer memory and should be kept on disk instead. All you have to do is redefine the run-time portion of STRING. This new STRING will compute which record on the disk a given byte would be contained in, read the record into a buffer using INCLUDED, and return the address of the desired byte within the buffer. A string defined in this way could span many consecutive records (using the same technique as in Prob. 5, Chap. 10).

c0 c1 c2 c3
r0
r1
r2
r3

You can use defining words to create all kinds of data structures. Sometimes, for instance, it's useful to create multi-dimensional arrays. Here's an example of a defining word which creates two-dimensional byte arrays of given size:

   : ARRAY ( #rows #cols -- )
       CREATE DUP , * ALLOT
       DOES> ( member: row col -- addr )
            ROT OVER @ * + +  CELL+ ;

To create an array four bytes by four bytes, we would say

   4 4 ARRAY BOARD

To access, say, the byte in row 2, column 1, we could say

   2 1 BOARD C@

Here's how our ARRAY works in general terms. Since the computer only allows us to have one-dimensional arrays, we must simulate the second dimension. While our imaginary array looks like this

c0 c1 c2 c3
r0 0 1 2 3
r1 4 5 6 7
r2 8 9 10 11
r3 12 13 14 15

our real array looks like this

row# 0 1 2 3
offset# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

If you want the address of the byte in row 2, column 1, it can be computed by multiplying your row number (2) by the number of columns in each row (4) and then adding your column number (1), which indicates that you want the ninth byte in the real array. This calculation is what members of ARRAY must do at run time. You'll notice that, to perform this calculation, each member word needs to know how many columns are in each row of its particular array. For this reason, ARRAY must store this value into the beginning of the array at compile time.

For the curious, here are the stack effects of the run-time portion of array:

Operation Contents of stack
... row col pfa
ROT col pfa row
OVER @ col pfa row #cols
* col pfa row-index
+ + address
CELL+ corrected address

It is necessary to add a cell to the computed address because the first cell of the array contains the number of columns.

Our final example is the most visually exciting, if not the most useful.

\ Shapes [example][link to Forth file], using a defining word.

DECIMAL

: star  [CHAR] * EMIT ;

: .row  CR 8 0 DO
   DUP 128 AND IF  star
     ELSE  SPACE
     THEN
   1 LSHIFT
     LOOP  DROP ;

: SHAPE CREATE  8 0 DO  C,  LOOP
  DOES> DUP 7 + DO  I C@ .row  -1 +LOOP  CR ;

  HEX  18 18 3C 5A 99 24 24 24  SHAPE man
  81 42 24 18 18 24 24 81  SHAPE equis
  AA AA FE FE 38 38 38 FE  SHAPE castle
  DECIMAL

.ROW prints a pattern of stars and spaces that correspond to the 8-bit number on the stack. For instance:

2 BASE !↵ ok 
00111001 .ROW↵
  ***  * ok 
DECIMAL↵ ok 

The defining word SHAPE takes eight arguments from the stack and defines a shape which, when executed, prints an 8-by-8 grid that corresponds to the eight arguments. For example:

 MAN
   **
   **
  ****
 * ** *
*  **  *
  *  *
  *  *
  *  *
ok 

In summary, defining words can be extremely powerful tools. When you create a new defining word, you extend your compiler. Traditional languages like Fortran or BASIC do not provide this flexibility because these traditional compilers and interpreters are inflexible packages that say, "Use my instruction set or forget it!"

The real power of defining words is that they can simplify your problem. Using them well, you can shorten your programming time, reduce the size of your program, and improve readability. Forth's flexibility in this regard is so radical in comparison with traditional languages that many people don't even believe it. Well, now you've seen it.

The next section introduces still another way to extend the ability of Forth's compiler.

How to Control the Colon Compiler

Compiling words are words used inside colon definitions to do something at compile time. The most obvious examples of compiling words are control-structure words such as IF, THEN, DO, LOOP, etc. Because Forth programmers don't often change the way these particular words work, we're not going to study them any further. Instead we'll examine the group of words that control the colon compiler and thus can be used to create any type of compiling word.

Recall that the colon compiler ordinarily looks up each word of a source definition and compiles each word's address into the dictionary entry — that's all. But the colon compiler does not compile the address of a compiling word — it executes it.

How does the colon compiler know the difference? By checking the definition's "precedence bit." If the bit is "off," the address of the word is compiled. If the bit is "on," the word is executed immediately; such words are called "immediate" words.

The word IMMEDIATE makes a word "immediate." It is used in the form:

   : name definition ; IMMEDIATE

that is, it is executed right after the compilation of the definition.

To give an immediate example, let's define

   : SAY-HELLO  ." Hello" ; IMMEDIATE

We can execute SAY-HELLO interactively, just as we could if it were not immediate.

   SAY-HELLO↵ Hello ok 

But if we put SAY-HELLO inside another definition, it will execute at compile time:

   : GREET SAY-HELLO ." I speak Forth " ;↵ Hello ok 

rather than at execution time:

   GREET↵ I speak Forth ok 

Before we go on, let's clarify our terminology. Forth folks adhere to a convention regarding the terms "run time" and "compile time." In this example, the terms are defined relative to GREET. Thus we would say that SAY-HELLO has a "compile-time behavior" but no "run-time behavior." Clearly, SAY-HELLO does have a run-time behavior of its own, but relative to GREET it does not.

To keep our levels straight, let's call GREET in this example the "compilee"; that is, the definition whose compilation we're referring to. SAY-HELLO has no run-time behavior in relation to its compilee.

Here's an example of an immediate word that you're familiar with: the definition of the compiling word BEGIN. It's simpler than you might have thought:

   : BEGIN HERE ; IMMEDIATE

BEGIN simply saves the address of HERE at compile time on the stack. Why? Because sooner or later an UNTIL or REPEAT is going to come along, and either has to know what address in the dictionary to return to in the event that it must repeat. This is the address that BEGIN left on the stack.

BEGIN's compile-time behavior is leaving HERE on the stack. But BEGIN compiles nothing into the compilee; there is no run-time behavior for BEGIN.

Unlike BEGIN, most compiling words do have a run-time behavior. To have a run-time behavior, a word has to compile into the compilee the address of the run-time behavior, which must already have been defined as a word.

A good example is DO. Like BEGIN, DO must provide, at compile time, a HERE for LOOP or +LOOP to return to. But unlike BEGIN, DO also has a run-time behavior: it must push the limit and index onto the return stack.

The run-time behavior of DO is defined by a lower-level word, sometimes called (DO) or 2>R. The definition of DO is this:

   : DO  POSTPONE 2>R  HERE ; IMMEDIATE

POSTPONE and run-time behavior - using defining words

The word POSTPONE finds the address of the next word in the definition (in this case 2>R) and compiles its address into the compilee definition, so that at run-time 2>R will be executed.

Another example is the definition of ;. At compile time, semicolon must do the following things:

  1. compile the address of EXIT into the dictionary entry being compiled,
  2. expose the new word to the colon compiler, and
  3. leave compilation mode.

Here's the definition of semicolon:

   : ;  POSTPONE EXIT  REVEAL POSTPONE [ ; IMMEDIATE

The first phrase compiles EXIT, providing the run-time behavior. The second phrase, which is the compile-time behavior, first exposes the word being compiled and then gets out of the compiler.

What is the reason for REVEAL? When words are in the process of being compiled, they are not yet findable by the colon compiler. This is done to make it possible to redefine existing words in terms of the old definition plus additional code, for example:

   : CR  CR SPACE ;

If during the compilation of the new CR its name were findable, the name of the original CR would be blocked, and we would have had to do, e.g.:

   : _cr_  CR ;
   : CR  _cr_ SPACE ;

The word POSTPONE can also be used to compile an immediate word as though it were not immediate. Given our previous example, in which SAY-HELLO is an immediate definition, we might define

   : GREET  POSTPONE
            SAY-HELLO ." I speak Forth " ;↵ ok 

to force SAY-HELLO to be compiled rather than executed at compile time. Thus:

   GREET↵ Hello I speak Forth ok 

Be sure to note the "intelligence" built into POSTPONE. POSTPONE parses the next word in the input stream, decides if it is immediate or not, and proceeds accordingly. If the word was not immediate, POSTPONE compiles the address of the word into a compilee definition; think of it as deferred compilation. If the word is immediate, POSTPONE compiles the address of this word into the definition currently being defined; this is ordinary compilation, but of an immediate word which otherwise would have been executed.

To review, here are the two words which are useful in creating new compiling words:

To review, here are the two words which are useful in creating new compiling words:
IMMEDIATE ( -- )
Marks the most recently defined word as one which, when encountered during compilation, will be executed rather than being compiled.
POSTPONE xxx ( -- )
  • Used in the definition of a compiling word. When the compiling word, in turn, is used in a source definition, the execution token of xxx will be compiled into the dictionary entry so that when the new definition is executed, xxx will be executed.
  • Used in a colon definition, causes the immediate word xxx to be compiled as though it were not immediate; xxx will be executed when the definition is executed.

More Compiler-Controlling Words

There are two other compiler control words you should know. The words [ and ] can be used inside a colon definition to stop compilation and start it again, respectively. Whatever words appear between them will be executed "immediately", i.e., at compile time.

Consider this example:

   : SAY-HELLO  ." Hello " ;
   : GREET  [ SAY-HELLO ] ." I speak Forth " ;↵ Hello ok 
   GREET↵ I speak Forth ok 

In this example, SAY-HELLO is not an immediate word, yet when we compile GREET, SAY-HELLO executes "immediately."

For a better example we first need to introduce the word LITERAL.

As you may recall, a number that appears in a colon definition is called a "literal." An example is the "4" in the definition

   : FOUR-MORE  4 + ;

using literal values in colon definitions - LITERAL

The use of a literal in a colon definition requires two cells. The first contains the execution token of a routine which, when executed, will push the contents of the second cell (the number itself) onto the stack.

The name of this routine may vary; let's call it the "run-time code for a literal," or simply (LITERAL). When the colon compiler encounters a number, it first compiles the run-time code for a literal, then compiles the number itself.

The word you will use most often to compile a literal is LITERAL (no parentheses). LITERAL compiles both the run-time code and the value itself. To illustrate:

   : FOUR-MORE  [ 4 ] LITERAL + ;

Here the word LITERAL will compile as a literal the "4" that we put on the stack between the square brackets. We get a dictionary entry that is identical to the one shown above.

For a more useful application of LITERAL, recall that in Chap. 8 we created an array called LIMITS that consisted of five cells, each of which contained the temperature limit for a different burner. To simplify access to this array, we created a word called LIMIT. The two definitions looked like this:

   VARIABLE LIMITS 4 CELLS ALLOT
   : LIMIT ( index -- addr ) CELLS LIMITS + ;

Now let's assume we will only access the array through the word LIMIT. We can eliminate the head of the array (some bytes and one cell) by using this construction instead:

   HERE  5 CELLS ALLOT  BASE !
   : LIMIT ( index -- addr ) CELLS [ BASE @ ] LITERAL + ;
   DECIMAL

In the first line we put the address of the beginning of the array (HERE) in the system variable BASE (any other scratch variable will work). In the second line, we compile this address as a literal into the definition of LIMIT.

New version Old version
5 CELLS head for LIMITS
head for LIMIT 5 CELLS
CELLS head for LIMIT
(LITERAL) CELLS
addr LIMITS
+ +
EXIT EXIT

Now we know all there is to know about LITERAL, we can also give a better example of [ and ]. Imagine a colon definition in which we need to type the byte from row 2, column 3, of the array BOARD we defined in the previous section. To get the address of this byte, we could use the phrase

   BOARD  2 8 ( #cols) * 3 +  CELL+  +

but it's time consuming to execute

   2 8 * 3 +

every time we use this definition. Alternatively, we could write

   BOARD  19 CELL+ +

compile-time arithmetic specified with [ and ] (left bracket and right bracket)

but it's unclear to human readers exactly what 19 means, and it is irritating that, for portability, we still have to write CELL+ although 1 CELLS is just a constant.

The best solution is to write

   BOARD [ 2 8 ( #cols) * 3 +  CELL+ ] LITERAL +

Here the arithmetic is performed only once, at compile time, and the result is compiled as a literal.

Here's a silly example which may give you some ideas for more practical applications. This definition let's you peek into the innards of the word itself:

   : DUMP-THIS  [ HERE ] LITERAL  32 DUMP ." DUMP-THIS" ;

HERE points to the address of the next free code byte

When you execute DUMP-THIS, you will dump the memory into which DUMP-THIS was defined. You should see how your Forth compiles the literal value of "here," the literal "32," the execution token of DUMP, and then how it inlines the string "DUMP-THIS." (At compile-time, HERE points to the address of the next free code byte. LITERAL compiles this number into the definition as a literal, so that it will serve as the argument for DUMP at run-time.)

By the way, here's the definition of LITERAL:

   : LITERAL  POSTPONE (LITERAL) , ; IMMEDIATE

First it compiles the address of the run-time code, then it compiles the value itself (using comma).

To summarize, here are the additional compiler control words we introduced in this section:
LITERAL compile-time ( -- )
run-time ( -- n )
Used only inside a colon definition. At compile time, compiles a value from the stack into the definition as a literal. At run time, the value will be pushed on the stack.
[ ( -- )
Leaves compilation mode.
] ( -- )
Enters compilation mode.

Forth text interpreter and code compiler

Curtain Calls

This section gives us a chance to say "Goodbye" to the text interpreter and the colon compiler and perhaps to see them in a new light.

Here is a definition of INTERPRET that will work in most Forth systems:

: INTERPRET ( -- )
  BEGIN
         BL FIND IF  EXECUTE ?STACK ABORT" Stack empty"
                 ELSE  NUMBER
                 THEN
AGAIN ;

We've covered each of the words contained in this definition; we can describe INTERPRET in English by simply "translating" its definition, like this:

Begin a loop. Within the loop, try to look up the next word from the input stream. If it's not defined, try to convert it to a number. If it is defined, execute it, then check to see whether the stack is empty. (If it is, exit the loop and print "STACK EMPTY.") Then repeat the infinite loop.

As you can see, the Forth text interpreter is a simple yet powerful structure. Now let's compare its structure with that of the colon compiler:

: ]  ( -- )
  BEGIN
   BL FIND DUP IF -1 = IF  EXECUTE ?STACK ABORT" Stack empty"
        ELSE  ,
       THEN
       ELSE DROP (NUMBER) POSTPONE LITERAL
       THEN
  AGAIN ;

The first thing you probably noticed is that the name of the colon compiler is not :, but ]. The definition of : invokes ] after creating the dictionary head and performing a few other odd jobs.

The next thing you may have noticed is that the compiler is somewhat similar to the interpreter. Let's translate the definition into English:

  • Begin a loop. Within the loop, try to look up the next word from the input stream. If it's not defined, try to convert it to a number and, if it's a number, compile it as a literal.
  • If it is defined, FIND has tested the word's precedence bit. If the word is immediate, then execute it and check to see whether the stack is empty. If it is not immediate, FIND returned an execution token that can be compiled. Then repeat the infinite loop.

Compare this to INTERPRET and you'll see that ] could be called an interpreter with the ability to decide whether to execute or compile any given word. It is the simplicity of this design that let's you add new compiling words so easily.

In summary, we've shown two ways to extend the Forth compiler:

  1. Add new, specialized compilers, by creating new defining words.
  2. Extend the existing colon compiler by creating new compiling words.

While traditional compilers try to be universal tools, the Forth compiler is a collection of separate, simple tools … with room for more.

Which approach seems more useful:

ways of extending Forth compilers

Chapter Summary

Forth Words

Here is a summary of the words we've covered in this chapter:
DOES> run time: ( -- addr)
Used in creating a defining word; marks the end of its compile-time portion and the beginning of its run-time portion. The run-time operations are stated in higher-level Forth. At run time, the body address of the defined word will be on the stack.
IMMEDIATE ( -- )
Marks the most recently defined word as one which, when encountered during compilation, will be executed rather than being compiled.
POSTPONE xxx ( -- )
  1. Used in the definition of a compiling word. When the compiling word, in turn, is used in a source definition, the execution token of xxx will be compiled into the dictionary entry so that when the new definition is executed, xxx will be executed.
  2. Used in a colon definition, causes the immediate word xxx to be compiled as though it were not immediate; xxx will be executed when the definition is executed.
LITERAL compile-time: ( -- )
run-time: ( -- n )
Used only inside a colon definition. At compile time, compiles a value from the stack into the definition as a literal. At run time, the value will be pushed on the stack.
[ ( -- )
Leaves compilation mode.
] ( -- )
Enters compilation mode.

Review of Terms

Compile-time behavior
1. when referring to defining words: the sequence of instructions which will be carried out when the defining word is executed — these instructions perform the compilation of the member words;
2. when referring to compiling words: the behavior of a compiling word, contained within a colon definition, during compilation of the definition.
Compilee
a definition being compiled. In relation to a compiling word, the compilee is the definition whose compilation the compiling word affects.
Compiling word
a word used inside a colon definition to take some action during the compilation process.
Defining word
a word which, when executed, compiles a new dictionary entry. A defining word specifies the compile-time and run-time behavior of each member of the "family" of words that it defines.
Precedence bit
In Forth dictionary entries, a bit which indicates whether a word should be executed rather than be compiled when it is encountered during compilation.
Run-time behavior
1. when referring to defining words: the sequence of instructions which will be carried out when any member is executed;
2. when referring to compiling words: a routine which will be executed when the compilee is executed. Not all compiling words have run-time behavior.

Forth programming language - examples

Problems … Chapter 11

  1. Define a defining word named LOADED-BY that will define words which include a file when they are executed. Example:
    S" mail.forth" LOADED-BY CORRESPONDENCE
    
    

    would define the word CORRESPONDENCE. When CORRESPONDENCE is executed, the file mail.forth is included (Hint: SLITERAL is NOT useful here). [  answer ]

  2. Define a defining word BASED. which will create number output words for specific bases. For example,
    16 BASED. H.
    
    

    would define H. to be a word which prints the top of the stack in hex but does not permanently change BASE.

    >DECIMAL
    17 DUP H. .↵ 11 17 ok
    
    

    answer ]

  3. Define a defining word called PLURAL which will take the address of a word such as CR or STAR and create its plural form, such as CRS or STARS. You'll provide PLURAL with the execution token of the singular word by using tick. For instance, the phrase
    ' CR PLURAL CRS
    
    

    will define CRS in the same way as though you had defined it

    : CRS ( times -- )  0 ?DO  CR  LOOP ;
    
    

    answer ]

  4. The French words for DO and LOOP are TOURNE and RETOURNE. Using the words DO and LOOP, define TOURNE and RETOURNE as French "aliases." Now test them by writing yourself a french loop. [  answer ]
  5. Write a word called LOOPS which will cause the remainder of the input stream, up to the carriage return, to be executed the number of times specified by the value on the stack. For example,
    7 LOOPS CHAR * EMIT SPACE↵ * * * * * * * ok
    
    

    answer ]