8. Variables, Constants, and Arrays

As we have seen throughout the previous seven chapters, Forth programmers use the stack to store numbers temporarily while they perform calculations or to pass arguments from one word to another. When programmers need to store numbers more permanently, they use variables and constants.

In this chapter, we’ll learn how Forth treats variables and constants, and in the process we’ll see how to directly access locations in memory.

Variables

Let’s start with an example of a situation in which you’d want to use a variable — to store the day’s date. First we’ll create a variable called DATE. We do this by saying

VARIABLE DATE

If today is the twelfth, we now say

12 DATE !

that is, we put twelve on the stack, then give the name of the variable, then finally execute the word !, which is pronounced store. This phrase stores the number twelve into the variable DATE.

Conversely, we can say

DATE @

that is, we can name the variable, then execute the word @, which is pronounced fetch. This phrase fetches the twelve and puts it on the stack. Thus the phrase

DATE @ .↵12 ok 

prints the date.

To make matters even easier, there is a Forth word whose definition is this:

: ?   @ . ;

So instead of “DATE-fetch-dot,” we can simply type

DATE ?↵12 ok 

The value of DATE will be twelve until we change it. To change it, we simply store a new number

13 DATE !↵ok 
DATE ?↵13 ok 

Conceivably we could define additional variables for the month and year:

VARIABLE DATE   VARIABLE MONTH   VARIABLE YEAR

then define a word called !DATE (for “store-the-date”) like this:

: !DATE  YEAR !  DATE !  MONTH ! ;

to be used like this:

7 31 03 !DATE↵ok 

then define a word called .DATE (for “print-the-date”) like this:

: .DATE  MONTH ?  DATE ?  YEAR ? ;

Your Forth system already has a number of variables defined; one is called BASE. BASE contains the number base that you’re currently working in. In fact, the definition of HEX and DECIMAL (and OCTAL, if your system has it) are simply

: DECIMAL  10 BASE ! ;
: HEX 16 BASE ! ;
: OCTAL 8 BASE ! ;

You can work in any number base by simply storing it into BASE.

For Experts

A three-letter code such as an airport terminal name, can be stored as a single-length unsigned number in base 36. For example:

: ALPHA  36 BASE ! ;↵ok 
ALPHA↵ok
ZAP U.↵ZAP ok

Somewhere in the definitions of the system words that perform input and output number conversions, you will find the phrase

BASE @

because the current value of BASE is used in the conversion process. Thus a single routine can convert numbers in any base. This leads us to make a formal statement about the use of variables:

A Closer Look at Variables

In Forth, variables are appropriate for any value that is used inside a definition that may need to change at any time after the definition has already been compiled.

When you create a variable such as DATE by using the phrase

VARIABLE DATE

you are really compiling a new word, called DATE, into the dictionary. A simplified view would look like the view below.

Defining a variable value compiled in the Forth dictionary

DATE is like any other word in your dictionary except that you defined it with the word VARIABLE instead of the word :. As a result, you didn’t have to define what your definition would do, the word VARIABLE itself spells out what is supposed to happen. And here is what happens:

When you say

12 DATE !

Twelve goes onto the stack, after which the text interpreter looks up DATE in the dictionary and, finding it, points it out to EXECUTE.

Forth variables store a value at an address in memory

EXECUTE executes a variable by copying the address of the variable’s “empty” cell (where the value will go) onto the stack.

the address of a variable's data goes on the data stack

The word ! takes the address (on top) and the value (underneath), and stores the value into that location. Whatever number used to be at that address is replaced by the new number.

The store operator (!) takes the value and the variable's address from the stack.

(To remember what order the arguments belong in, think of setting down your parcel, then sticking the address label on top.)

The fetch operator (@) gets the value from the variable's address in memory.

The word @ expects one argument only: an address, which in this case is supplied by the name of the variable, as in

DATE @

Using the value on the stack as an address, the word @ pushes the contents of that location onto the stack, “dropping” the address. (The contents of the location remain intact.)

Using a Variable as a Counter

In Forth, a variable is ideal for keeping a count of something. To reuse our egg-packer example, we might keep track of how many eggs go down the conveyor belt in a single day. (This example will work at your terminal, so enter it as we go.)

using a variable to hold a count value of something

First we can define

VARIABLE EGGS

to keep the count in. To start with a clean slate every morning, we could store a zero into EGGS by executing a word whose definition looks like this:

: RESET  0 EGGS ! ;
increment a variable's value by one

Then somewhere in our egg-packing application, we would define a word that executes the following phrase every time an egg passes an electric eye on the conveyor:

1 EGGS +!

The word +! adds the given value to the contents of the given address. (It doesn’t bother to tell you what the contents are.) Thus the phrase

1 EGGS +!

increments the count of eggs by one. For purposes of illustration, let’s put this phrase inside a definition like this:

: EGG  1 EGGS +! ;

At the end of the day, we would say

EGGS ?

to find out how many eggs went by since morning.

Let’s try it:

RESET↵ok 
EGG↵ok 
EGG↵ok 
EGG↵ok 
EGGS ?↵3 ok 

Here’s a list of words we’ve covered so far:

VARIABLE xxx
( — ) xxx: ( — addr )
Creates a variable named xxx; the word xxx returns its address when executed.
!
( n addr — )
Stores a single-length number into the address.
@
( addr — n )
Fetches that value at addr.
?
( addr — )
Prints the contents of the address, followed by one space.
+!
( n addr — )
Adds a single-length number to the contents of the address.

Constants

While variables are normally used for values that may change, constants are used for values that won’t change. In Forth, we create a constant and set its value at the same time, like this:

220 CONSTANT LIMIT

Here we have defined a constant named LIMIT, and given it the value 220. Now we can use the word LIMIT in place of the value, like this:

: ?TOO-HOT  LIMIT > IF  ." Danger -- reduce heat "  THEN ;

If the number on the stack is greater than 220, then the warning message will be printed.

Notice that when we say

LIMIT

we get the value, not the address. We don’t need the “fetch.”

This is an important difference between variables and constants. The reason for the difference is that with variables, we need the address to have the option of fetching or storing. With constants we always want the value; we absolutely never store.

One use for constants is to name a hardware address. For example, a microprocessor-controlled portable camera application might contain this definition:

: PHOTOGRAPH   SHUTTER OPEN  TIME EXPOSE  SHUTTER CLOSE ;

Here the word SHUTTER has been defined as a constant so that execution of SHUTTER returns the hardware address of the camera’s shutter. It might, for example, be defined:

HEX
FFFF3E27 CONSTANT SHUTTER
DECIMAL

The words OPEN and CLOSE might be defined simply as

: OPEN  1 SWAP ! ;
: CLOSE 0 SWAP ! ;

so that the phrase

SHUTTER OPEN

writes a “1” to the shutter address, causing the shutter to open.

Here are some situations when it’s good to define numbers as constants:

  1. When it’s important that you make your application more readable. One of the elements of Forth style is that definitions should be self-documenting, as is the definition of PHOTOGRAPH above.
  2. When it’s more convenient to use a name instead of the number. For example, if you think you may have to change the value (because, for instance, the hardware might get changed) you will only have to change the value once — in the file where the constant is defined — then recompile your application.
  3. (Only true for less sophisticated Forth compilers) When you are using the same value many times in your application. In the compiled form of a definition, reference to a constant requires less memory space.
CONSTANT xxx
( n — ) xxx: ( — n )
Creates a variable named xxx with the value n; the word xxx returns n when executed.

Double-Length Variables and Constants

You can define a double-length variable by using the word 2VARIABLE. For example,

2VARIABLE DATE

Now you can use the Forth words 2! (pronounced two-store) and 2@ (pronounced two-fetch) to access this double-length variable. You can store a double-length number into it by simply saying

800,000 DATE 2!

and fetch it back with

DATE 2@ D.↵800000 ok 
Double-length variables and constants: 2VARIABLE, 2CONSTANT, 2@ and 2! double operators

Or you can store the full month/date/year into it, like this:

7/17/03 DATE 2!

and fetch it back with

DATE 2@ .DATE↵7/17/03 ok 

assuming that you’ve loaded the version of .DATE we gave in the last chapter.

You can define a double-length constant by using the Forth word 2CONSTANT, like this:

200,000 2CONSTANT APPLES

Now the word APPLES will place the double-length number on the stack.

APPLES D.↵200000 ok 

Of course, we can do:

400,000 2CONSTANT MUCH
: MUCH-MORE  200,000 D+  MUCH D+ ;

in order to be able to say

APPLES MUCH-MORE D.↵800000 ok 

As the prefix “2” reminds us, we can also use 2CONSTANT to define a pair of single-length numbers. The reason for putting two numbers under the same name is a matter of convenience and of saving space in the dictionary.

As an example, recall (from Chap. 5) that we can use the phrase

355 113 */

to multiply a number by a crude approximation of π. We could store these two integers as a 2CONSTANT as follows:

355 113 2CONSTANT PI

then simply use the phrase

PI */

as in

10000 PI */ .↵31415 ok 

Here is a review of the double-length data-structure words:

2VARIABLE XXX
( — ) xxx: ( — addr )
Creates a double-length variable named xxx; the word xxx returns its address when executed.
2CONSTANT XXX
( d — ) xxx: ( — d )
Creates a double-length constant named xxx with the value d; the word xxx returns d when executed.
2!
( d addr — ) or ( n1 n2 addr — )
Stores a double-length number (or a pair of single-length numbers) at address addr.
2@
( addr — d ) or ( addr – n1 n2 )
Fetches a double-length number (or a pair of single-length numbers) from addr.

Arrays

As you know, the phrase

VARIABLE DATE

creates a definition that conceptually looks like this:

4 D A T EcodeRoom for one cell

Now if you say

1 CELLS ALLOT

an additional cell is allotted in the definition, like this:

4 D A T EcodeRoom for first cellRoom for second cells

The result is the same as if you had used 2VARIABLE. By changing the argument to ALLOT, however, you can define any number of variables under the same name. Such a group of variables is called an “array.”

ALLOT creates an array of multiple variables of the same name

For example, let’s say that in our laboratory, we have not just one, but five burners that heat various kinds of liquids.

We can make our word ?TOO-HOT check that all five burners have not exceeded their individual limit if we define LIMIT using an array rather than a constant.

Let’s give the array the name LIMITS, like this:

VARIABLE LIMITS  4 CELLS ALLOT

The phrase “4 CELLS ALLOT” gives the array an extra four cells (five cells in all).

6LIMITScodeBurner0Burner1Burner2Burner3Burner4

Suppose we want the limit for burner 0 to be 220. We can store this value by simply saying

220 LIMITS !

because LIMITS returns the address of the first cell in the array. Suppose we want the limit for burner 1 to be 340. We can store this value by adding 1 CELLS to the address of the original cell, like this:

340 LIMITS 1 CELLS + !

We can store limits for burners 2, 3, and 4 by adding the “offsets” 2 CELLS, 3 CELLS, and 4 CELLS, respectively, to the original address. We can define the convenient word

: LIMIT  ( burner# -- addr ) CELLS LIMITS + ;

to take a burner number on the stack and compute an address that reflects the appropriate offset.

Now if we want the value 170 to be the limit for burner 2, we simply say

170 2 LIMIT !

or similarly, we can fetch the limit for burner 2 with the phrase

2 LIMIT ?↵170 ok 

This technique increases the usefulness of the word LIMIT, so that we can redefine ?TOO-HOT as follows:

: ?TOO-HOT ( temp burner# -- )   LIMIT @ > IF  ." Danger -- reduce heat "  THEN ;

which works like this:

210 0 ?TOO-HOT↵ok 
230 0 ?TOO-HOT↵Danger -- reduce heat ok
300 1 ?TOO-HOT↵ok
350 1 ?TOO-HOT↵Danger -- reduce heat ok

etc.

Another Example — Using an Array for Counting

Meanwhile, back at the egg ranch:

Here’s another example of an array. In this example, each element of the array is used as a separate counter. Thus we can keep track of how many cartons of “extra large” eggs the machine has packed, how many “large,” and so forth.

Recall from our previous definition of EGGSIZE (in Chap. 4) that we used four categories of acceptable eggs, plus two categories of “bad eggs.”

0 CONSTANT REJECT
1 CONSTANT SMALL
2 CONSTANT MEDIUM
3 CONSTANT LARGE
4 CONSTANT EXTRA-LARGE
5 CONSTANT ERROR

So let’s create an array that is six cells long:

VARIABLE COUNTS  5 CELLS ALLOT

The counts will be incremented using the word +!, so we must be able to set all the elements of the array to zero before we begin counting. The phrase

COUNTS 6 CELLS 0 FILL

will fill 6 cells , starting at the address of COUNTS, with zeros. If your Forth system includes the word ERASE, it’s better to use it in this situation. ERASE fills the given number of bytes with zeroes. Use it like this:

COUNTS 6 CELLS ERASE
FILL
( addr n char — )
Fills n bytes of memory, beginning the address addr, with value char.
ERASE
( addr n — )
Stores zeroes into n bytes of memory, beginning at address addr.

For convenience, we can put the phrase inside a definition, like this:

: RESET  COUNTS 6 CELLS ERASE ;

Now let’s define a word which will give us the address of one of the counters, depending on the category number it is given (0 through 5), like this:

: COUNTER   CELLS COUNTS + ;

and another word which will add one to the counter whose number is given, like this:

: TALLY  COUNTER 1 SWAP +! ;

The “1” serves as the increment for +!, and SWAP puts the arguments for +! in the order they belong, i.e., ( n addr — ).

Now, for instance, the phrase

LARGE TALLY

will increment the counter that corresponds to large eggs.

Now let’s define a word which converts the weight per dozen into a category number:

: CATEGORY ( weight -- category )
DUP 18 < IF REJECT ELSE
DUP 21 < IF SMALL ELSE
DUP 24 < IF MEDIUM ELSE
DUP 27 < IF LARGE ELSE
DUP 30 < IF EXTRA-LARGE ELSE
ERROR
THEN THEN THEN THEN THEN NIP ;

(By the time we’ll get to the NIP, we will have two values on the stack: the weight which we have been DUPping and the category number, which will be on top. We want only the category number; “NIP” eliminates the weight.)

For instance, the phrase

25 CATEGORY

will leave the number 3 (LARGE) on the stack. The above definition of CATEGORY resembles our old definition of EGGSIZE, but, in the true Forth style of keeping words as short as possible, we have removed the output messages from the definition. Instead, we’ll define an additional word which expects a category number and prints an output message, like this:

: LABEL ( category -- )
CASE
REJECT OF ." reject " ENDOF
SMALL OF ." small " ENDOF
MEDIUM OF ." medium " ENDOF
LARGE OF ." large " ENDOF
EXTRA-LARGE OF ." extra large " ENDOF
ERROR OF ." error " ENDOF
ENDCASE ;

For example:

SMALL LABEL↵small ok 

Now we can define EGGSIZE using three of our own words:

: EGGSIZE  CATEGORY  DUP LABEL  TALLY ;

Thus the phrase

23 EGGSIZE

will print

medium ok 

at your terminal and update the counter for medium eggs.

How will we read the counters at the end of the day? We could check each cell in the array separately with a phrase such as

LARGE COUNTER ?

(which would tell us how many “large” cartons were packed). But let’s get a little fancier and define our own word to print a table of the day’s results in this format:

QUANTITY    SIZE
1 reject
112 small
132 medium
143 large
159 extra large
0 error

Since we have already devised category numbers, we can simply use a DO and index on the category number, like this:

: REPORT ( -- )   PAGE ." QUANTITY       SIZE" CR CR
6 0 DO I COUNTER @ 5 U.R 7 SPACES I LABEL CR LOOP ;

(The phrase “I COUNTER @ 5 U.R” takes the category number given by I, indexes into the array, and prints the contents of the proper element in a five-column field.)

Factoring Definitions

This is a good time to talk about factoring as it applies to Forth definitions. We’ve just seen an example in which factoring simplified our problem.

Our first definition of EGGSIZE from Chap. 4, categorized eggs by weight and printed the name of the categories at the terminal. In our present version we factored out the “categorizing” and the “printing” into two separate words. We can use the word CATEGORY to provide the argument either for the printing word or the counter-tallying word (or both). And we can use the printing word, LABEL, in both EGGSIZE and REPORT.

As Charles Moore, the inventor of Forth, has written:

A good Forth vocabulary contains a large number of small words. It is not enough to break a problem into small pieces. The object is to isolate words that can be reused.

For example, in the recipe:

Get a can of tomato sauce.
Open can of tomato sauce.
Pour tomato sauce into pan.
Get can of mushrooms.
Open can of mushrooms.
Pour mushrooms into pan.

you can “factor out” the getting, opening, and pouring, since they are common to both cans. Then you can give the factored-out process a name and simply write:

TOMATOES ADD
MUSHROOMS ADD

and any chef who’s graduated from the Postfix School of Cookery will know exactly what you mean.

Not only does factoring make a program easier to write (and fix!), it saves memory space, too. A reusable word such as ADD gets defined only once. The more complicated the application, the greater the savings.

Here is another thought about Forth style before we leave the egg ranch. Recall our definition of EGGSIZE

: EGGSIZE  CATEGORY  DUP LABEL  TALLY ;

CATEGORY gave us a value which we wanted to pas on to both LABEL and TALLY, so we included the DUP. To make the definition “cleaner,” we might have been tempted to take the DUP out and put it inside the definition of LABEL, at the beginning. Thus we might have written:

: EGGSIZE  CATEGORY  LABEL TALLY ;

where CATEGORY passes the value to LABEL, and LABEL passes it on to TALLY. Certainly this approach would have worked. But then, when we defined REPORT, we would have had to say

I LABEL DROP

instead of simply

I LABEL

Forth programmers tend to follow this convention: when possible, words should destroy their own parameters. In general, it’s better to put the DUP inside the “calling definition” (EGGSIZE, here) than in the “called” definition (LABEL, here).

Another Example — “Looping” through an Array

We’d like to introduce a little technique that is relevant to arrays. We can best illustrate this technique by writing our own definition of a Forth word called DUMP. DUMP is used to print out the contents of a series of memory addresses. The usage is

addr count DUMP

For instance, we could enter

COUNTS 6 DUMP

to print the contents of our egg-counting array called COUNTS. Since DUMP is primarily designed as a programming tool to print out the contents of memory locations, it prints either byte-by-byte or cell-by-cell, depending on the type of addressing our computer uses. Our version of DUMP will print cell-by-cell.

Obviously DUMP will involve a DO loop. The question is: what should we use for an index? Although we might use the count itself (0 – 6) as the loop index, it’s better to use the address as the index.

The address of COUNTS will be the starting index for the loop, while the address plus the count will serve as the limit, like this:

: DUMP ( addr cell-count -- )   CELLS OVER + SWAP
DO CR I @ 5 U.R 1 CELLS +LOOP ;

The key phrase here is

CELLS OVER + SWAP

which immediately precedes the DO.

Using DO...LOOP to read values from an array

The ending and starting addresses are now on the stack, ready to serve as the limit and index for the DO loop. Since we are “indexing on the addresses,” once we are inside the loop we merely have to say

I @  5 U.R

to print the contents of each element of the array. Since we are examining cells (@ fetches a single-length, single cell value), we increment the index by one cell each time, by using

1 CELLS +LOOP
Byte arrays hold single, 8-bit values such as strings of ASCII characters - saves memory space

Byte Arrays

Forth lets you create an array in which each element consists of a single byte rather than a full cell. This is useful any time you are storing a series of numbers whose range fits into that which can be expressed within eight bits.

The range of an unsigned 8-bit number is 0 to 255. Byte arrays are also used to store ASCII character strings. The benefit of using a byte array instead of a cell array is that you can get the same amount of data in 25% (32-bit Forth) of the memory space.

C! and C@ operate on values in byte arrays

The mechanics of using a byte array are the same as using a cell array except that

  1. you don’t have to use CELLS to manipulate the offset, since each element corresponds to one address unit, and
  2. you must use the words C! and C@ instead of ! and @. These words, which operate on byte values only, have the prefix “C” because their typical use is accepting ASCII characters.
C!
( char addr — )
Stores byte char at address addr.
C@
( char addr — )
Fetches byte value from the address.

Initializing an Array

Many situations call for an array whose values never change during the operation of the application and which may as well be stored into the array at the same time that the array is created, just as CONSTANTs are. Forth provides the means to accomplish this through the two words CREATE and , (pronounced create and comma).

Suppose we want permanent values in our LIMITS array. Instead of saying

VARIABLE LIMITS 4 CELLS ALLOT

we can say

CREATE LIMITS  220 , 340 , 170 , 100 , 190 ,

Usually the above line would be included from a disk file, but it also works interactively.

Like the word VARIABLE, CREATE puts a new name in the dictionary at compile time and returns the address of that definition when it is executed. But it does not “allot” any bytes for a value.

The word , takes a number off the stack and stores it into the array. So each time you express a number and follow it with ,, you add one cell to the array.

Create arrays of constant values using the CREATE and , (comma) operators.

For Newcomers

Ingrained habits, learned from English writing, lead some newcomers to forget to type the final , in the line. Remember that , does not separate the numbers, it compiles them.

You can access the elements in a CREATE array just as you would the elements in a VARIABLE array. For example:

LIMITS CELL+ @ .↵340 ok 

You can even store new values into the array, just as you would into a VARIABLE array.

To initialize a byte-array that has been defined with CREATE, you can use the word C, (c-comma). For instance, we could store each of the values used in our egg-sorting definition CATEGORY as follows:

CREATE SIZES   18 C, 21 C, 24 C, 27 C, 30 C, 255 C,

This would allow us to redefine CATEGORY using a DO loop rather than as a series of nested IF…THEN statements, as follows

: CATEGORY  6 0 DO  DUP SIZES I + C@  < IF  DROP I LEAVE THEN  LOOP ;

Note that we have added a maximum (255) to the array to simplify our definition regarding category 5.

Including the initialization of the SIZES array, this version takes only three lines of source text as opposed to six and takes less space in the dictionary, too.

For People Who Don’t Like Guessing How It Works

The idea here is this: since there are five possible categories, we can use the category numbers as our loop index. Each time around, we compare the number on the stack against the element in SIZES, offset by the current loop index. As soon as the weight on the stack is greater than one of the elements in the array, we leave the loop and use I to tell us how many times we had looped before we “left.” Since this number is our offset into the array, it will also be our category number.

Chapter Summary

Forth Words

Here’s a list of the Forth words we’ve covered in this chapter:

VARIABLE xxx
( — ) xxx: ( — addr )
Creates a variable named xxx; the word xxx returns its address when executed.
!
( n addr — )
Stores a single-length number into the address.
@
( addr — n )
Fetches that value at addr.
?
( addr — )
Prints the contents of the address, followed by one space.
+!
( n addr — )
Adds a single-length number to the contents of the address.
CONSTANT xxx
( n — ) xxx: ( — n )
Creates a variable named xxx with the value n; the word xxx returns n when executed.
2VARIABLE XXX
( — ) xxx: ( — addr )
Creates a double-length variable named xxx; the word xxx returns its address when executed.
2CONSTANT XXX
( d — ) xxx: ( — d )
Creates a double-length constant named xxx with the value d; the word xxx returns d when executed.
2!
( d addr — ) or ( n1 n2 addr — )
Stores a double-length number (or a pair of single-length numbers) at address addr.
2@
( addr — d ) or ( addr – n1 n2 )
Fetches a double-length number (or a pair of single-length numbers) from addr.
FILL
( addr n char — )
Fills n bytes of memory, beginning the address addr, with value char.
ERASE
( addr n — )
Stores zeroes into n bytes of memory, beginning at address addr.
C!
( char addr — )
Stores byte char at address addr.
C@
( char addr — )
Fetches byte value from the address.

Key to stack comment notation:

n, n1, …Single-length signed
d, d1, …Double-length signed
u, u1 …Single-length unsigned
ud, ud1, …Double-length unsigned
addrAddress
lenLength (of string or buffer)
charASCII character value

Review of Terms

Array
a series of memory locations with a single name. Values can be stored and fetched into the individual locations by giving the name of the array and adding an offset to the address.
Constant
a value that has a name. The value is stored in memory and usually never changes.
Factoring
as it applies to programming in Forth, simplifying a large job by extracting those elements that might be reused and defining those elements as operations.
Fetch
to retrieve a value from a given memory location.
Initialize
to give a variable (or array) its initial value(s) before the rest of the program begins.
Offset
a number that can be added to the address of the beginning of an array to produce the address of the desired location within the array.
Store
to place a value in a given memory location.
Variable
a location in memory that has a name and in which values are frequently stored and fetched.
Forth programming language - examples

Problems — Chapter 8

  1. Write two words called BAKE-PIE and EAT-PIE. The first word increases the number of available PIES by one. The second decreases the number by one and thanks you for the pie. But if there are no pies, it types “What pie?” (make sure you start out with no pies.)
    EAT-PIE↵What pie? ok 
    BAKE-PIE↵ok 
    EAT-PIE↵Thank you! ok 
    
  2. Write a word called FREEZE-PIES that takes all the available pies and adds them to the number of pies in the freezer. Remember that frozen pies cannot be eaten.
    BAKE-PIE BAKE-PIE FREEZE-PIES↵ok 
    PIES ?↵0 ok 
    FROZEN-PIES ?↵2 ok 
    
  3. Define a word called .BASE that prints the current value of the variable BASE in decimal. Test it by first changing BASE to some value other than ten. (This one is trickier than it may seem.)
    DECIMAL .BASE 10↵ok 
    HEX .BASE 16↵ok 
    
  4. Define a number-formatting word called M. that prints a double-length number with a decimal point. The position of the decimal point within the number is movable and depends on the value of a variable that you will define as PLACES. For example, if you store a “1” into PLACES, you will get
    200,000 M.↵20000.0 ok 
    
    that is, with the decimal point one place from the right. A zero in PLACES should produce no decimal point at all.
  5. In order to keep track of the inventory of colored pencils in your office, create an array, each cell of which contains the count of a different colored pencil. Define a set of words so that, for example, the phrase RED PENCILS returns the address of the cell that contains the count of red pencils, etc. Then set these variables to indicate the following counts:
    23 red pencils
    15 blue pencils
    12 green pencils
    0 orange pencils
    
  6. A histogram is a graphic representation of a series of values. Each value is shown by the height or length of a bar. In this exercise you will create an array of values and print a histogram that displays a line of “*”s for each value. First create an array with about ten cells. Initialize each element of the array with a value in the range of zero to seventy. Then define a word PLOT that will print a line for each value. On each line print the number of the cell followed by a number of “*”s equal to the contents of that cell. For example, if the array has four cells and contains the values 1, 2, 3 and 4, then PLOT would produce:
    1 *
    2 **
    3 ***
    4 ****
    
  7. Create an application that displays a tic-tac-toe board, so that two human players can make their moves by entering them from the keyboard. For example, the phrase 4 X! puts an “X” in box 4 (counting starts with 1) and produces this display:
      |   |
    ---------
    X |   |
    ---------
      |   |
    
    Then the phrase 3 O! puts a “O” in box 3 and prints the display:
      |   | O
    ---------
    X |   |
    ---------
      |   |
    
    Use a byte array to remember the contents of the board, with the value 1 to signify “X,” a -1 to signify a “O,” and a 0 to signify an empty box.
Answers
  1. VARIABLE PIES  0 PIES !
    : BAKE-PIE   1 PIES +! ;
    : EAT-PIE   
       PIES @ IF  -1 PIES +!  ." Thank you "
       ELSE  ." What pie? "  THEN ;
    
  2. VARIABLE FROZEN-PIES  0 FROZEN-PIES !
    : FREEZE-PIES   PIES @  FROZEN-PIES +!  0 PIES ! ;
    
  3. : .BASE   BASE @  DUP DECIMAL .  BASE ! ;
    
  4. VARIABLE PLACES  2 PLACES !
    : M.  ( s|d -- )  TUCK DABS
      <#  PLACES @  DUP -1 <> IF  0 DO # LOOP  [CHAR] . HOLD  
       ELSE  DROP  S>D  THEN  #S  ROT SIGN  #>  TYPE SPACE ;
    
  5. CREATE #PENCILS  4 CELLS ALLOT
    0 CELLS CONSTANT RED
    1 CELLS CONSTANT GREEN
    2 CELLS CONSTANT BLUE
    3 CELLS CONSTANT ORANGE
    : PENCILS  ( colour -- addr )  #PENCILS + ;
    23 RED PENCILS !
    12 GREEN PENCILS !
    15 BLUE PENCILS !
    0 ORANGE PENCILS !
    
  6. CREATE 'SAMPLES  10 CELLS ALLOT
    : STAR     [CHAR] * EMIT ;
    : STARS    ( #stars -- )  0 ?DO  STAR  LOOP ;
    : STARS    ( n -- )  ?DUP IF  STARS  THEN ;
    : SAMPLES  ( sample# -- addr )  CELLS  'SAMPLES + ;
    : INIT-SAMPLES   10 0 DO  I 6 MOD  I SAMPLES !  LOOP ;
    : PLOT  10 0 DO CR  I 2 .R  SPACE  I SAMPLES @  STARS LOOP CR ;
    INIT-SAMPLES
    
  7. CREATE BOARD  9 ALLOT
    : SQUARE  ( square# -- addr )  BOARD + ;
    : CLEAR   BOARD  9 ERASE ;  CLEAR
    : BAR   ." | " ;
    : DASHES   CR  9 0 DO  [CHAR] - EMIT  LOOP CR ;
    : .BOX  ( square# -- )  
       SQUARE C@  DUP 0= IF  2 SPACES  
          ELSE  DUP 1 = IF ." X "   ELSE ." O "  
       THEN THEN DROP ;
    : DISPLAY  ( -- )
       CR  9 0 DO  I IF  I 3 MOD  0= IF DASHES  
          ELSE BAR  THEN THEN  I .BOX  
       LOOP  CR QUIT ;
    : PLAY  ( player square# -- )
       1-  0 MAX  8 MIN  SQUARE C! ;
    : X!  ( square# -- )   1 SWAP  PLAY  DISPLAY ;
    : O!  ( square# -- )  -1 SWAP  PLAY  DISPLAY ;