Rudiments of a Language

(Copied by permission of Leo Wong from http://www.albany.net/~hello/simple.htm)

Lesson:
0. What is Simple Forth ?
1. The Forth Programmer Interface
2. Forth in One Sentence
3. "The Stack" .S DROP
4. The Dictionary WORDS SEE
5. Adding to the Dictionary : ;
6. Forth Arithmetic 1 + - * /
7. Forth Arithmetic 2 /MOD MOD .
8. Forth Arithmetic 3 1+ 1- ABS NEGATE MAX MIN */ */MOD
9. Managing the Stack 1 DEPTH
10. Managing the Stack 2 DUP ?DUP SWAP OVER ROT PICK ROLL NIP TUCK 2DROP 2DUP 2OVER 2SWAP
11. Programming by Teaching and Learning
12. Thoroughly Modern Forth
13. Comparisons TRUE FALSE = < > 0= 0<
14. Conditional Execution IF ELSE THEN
15. Repeated Execution EXIT BEGIN AGAIN UNTIL WHILE REPEAT
16. Counted Loops ?DO DO I J LEAVE UNLOOP LOOP +LOOP
17. Source Files S" INCLUDED
18. Game of Sticks CR SPACE SPACES ." .(
19. More to Display PAGE AT-XY .R MS
20. Data on the Return Stack >R R@ R> 2>R 2R@ 2R>
21. Named Storage CONSTANT VARIABLE U. U.R ! @ +! ?
22. Accessing Memory 1 UNUSED CELLS
23. Accessing Memory 2 CREATE HERE ALLOT CELL+
24. Accessing Memory 3 ,
25. Decimal Hexadecimal Binary BASE DECIMAL HEX DUMP
26. Booleans and Bits AND OR XOR 0<> INVERT 2* 2/ LSHIFT RSHIFT
27. A Software Stack
28. Characters A EMIT CHAR BL
29. Characters B [CHAR] KEY
30. Characters C C, C@ C! CHARS CHAR+ ALIGN
31. Strings 1 S" TYPE

Please e-mail comments and suggestions to Leo Wong hello@albany.net

'Tis the gift to be simple, 'tis the gift to be free,
'Tis the gift to come down where we ought to be,
And when we find ourselves in the place just right,
'Twill be in the valley of love and delight.

When true simplicity is gain'd
To bow and to bend we shan't be asham'd,
To turn, turn will be our delight
'Till by turning, turning we come round right.

"Simple Gifts," a Shaker song by Joseph Brackett (1848)

Ground 0 What is Simple Forth ?

Forth is, among other things, a programming language whose ideals are freedom and simplicity. Freedom requires knowledge and responsibility, and simplicity is yet more demanding. But "simple" can also mean rudimentary. Simple Forth attempts to teach the rudiments of Forth.

How to try the examples.

Programming in Forth is carried on as a dialog between you and a machine that understands Forth. You send the machine some code. Depending on the circumstances, the machine responds by trying either to perform ("execute") or to remember ("compile") the code. If the machine then says something like "ok", that's good. If it doesn't say ok, it might still be ok. Try sending more code until the machine explicitly objects (or pretends to be dead).

A machine that knows Forth is: a Forth Machine. Sometimes I don't distinguish between Forth and the machine. If this is confusing, let me know.

1. Start up a Forth system, and type in the examples. Most Forths accept source from disk, but you'll get a better feeling for Forth by trying it interactively. Where you see a space, press the spacebar. At the end of a line, press the key on your keyboard that means "Enter".

2. If the machine doesn't understand something you entered it may respond by repeating what it didn't understand and adding an error message or maybe just: ?. Check to see if you typed in the example correctly. If not, type it in again. If you and your machine continue to be baffled, write to me at hello@albany.net.

3. In Forth named procedures are called words. Some frequently used words are very short. For example, the word . displays a signed (plus/minus) integer. In general, what is punctuation in other languages are procedures in Forth.

4. Many Forths are case sensitive. All the examples will work in uppercase. If you try lowercase and the machine says ok, use whatever case you wish.

Start up a Forth and try:

1 .

Forth reads from left to right:

1 . 2 . 3 .

Spaces are separators:

12 .  3 .  123 .

Your machine probably won't understand this:

aieee!

It should understand when it's time to go:

BYE

Lesson 1 The Forth Programmer Interface

According to Leo Brodie's book Thinking Forth, the three characteristics of the Forth Programmer Interface are implicit calls, implicit data passing, and direct access to memory. None of these characteristics is unique to Forth, but they form quite a combination.

The following examples use Forth words that will be explained later. For now just type each line exactly as you see it and observe how your Forth responds.

1. Implicit calls. The word .S is called by entering its name, .S.

Try:

.S

2. Implicit data passing. The word 1+ takes a number and returns another without specifying either. The word DROP takes a number without returning one.

Try:

.S
7
.S
1+
.S
1+
.S
DROP
.S

3. Direct access to memory. In Forth you can name addresses of memory and see and change the data at those addresses.

An address of memory identifies an "address unit". The size of an address unit is often 8 bits - too small to hold a number greater than 255. Forth typically works with "cells". The size of a cell is usually two, four, or more address units. Numbers considerably larger than 255 fit comfortably in a cell.

To name a memory address MINE and reserve a cell of memory at MINE try:
CREATE MINE  1 CELLS ALLOT

To put a number in the cell of memory at MINE try:

1 MINE !
To see the contents (might look rather strange if you're just beginning to learn programming) of the address units in the cell of memory at MINE try:
MINE 1 CELLS DUMP

To display the number in the cell of memory at MINE try:

MINE ?

To change the number in the cell of memory at MINE try:

1024 MINE !
MINE 1 CELLS DUMP
MINE ?

-1 MINE !
MINE 1 CELLS DUMP
MINE ?

BYE

Lesson 2 Forth in One Sentence

The sentence is: "Forth is a programming language that uses two stacks and a dictionary of words that a programmer adds to in writing a program."

Words, dictionary, two stacks: the essence of Forth.

In Lesson 1, I wrote that the Forth programmer interface is characterized by implicit calls, implicit data passing, and direct access to data.

Calls are implicit because when a Forth sees space-delimited text, it thinks "word" and tries to find it in its dictionary. If it finds it, it will immediately do something with it (if Forth doesn't find it in its dictionary it will try to understand it as a number; if that fails, Forth will confess its failure).

Data passing is implicit because Forth words get their input from and return their output to the same stack.

Access to data is direct because the addresses of the data are recorded in the dictionary.

Lesson 3 discusses the Forth data stack. Lesson 4 discusses the Forth dictionary. Addresses will wait until Lesson 21.

Lesson 3 "The Stack" .S DROP

All Forths have at least two stacks. The stacks work similarly but serve different purposes.

A stack is a way of managing data. With a stack, data is added to and taken from the "top", as with a stack of dishes. The acronym for this is LIFO: Last In First Out. The top of the stack is called TOS. If there's no TOS, the stack is empty. A stack is such a convenient way of managing data that most (all?) programming languages use it internally.

The stack discussed in this lesson is the Forth data stack: Forth programmers call it "the stack". The second Forth stack, called the return stack, will be discussed later. Some Forths have special-purpose stacks for floating-point numbers or for strings. These special stacks hold data that it may be convenient to keep separate from the data on "the stack".

Words take and return numbers from the stack. When you type in a number its destination is the stack. The following examples use the words .S and DROP. .S displays (at least some of) the numbers on the stack in stack order, TOS (top of stack) on the right. In some Forths, .S also tells how many numbers are on the stack. DROP removes TOS, reducing the number of numbers on the stack by one.

Try:

.S
10
10 is TOS:
.S
Now try:
20
20 is TOS, and 10 is below 20:
.S
Now try:
DROP
The word DROP removed 20 from the stack. TOS is again 10:
.S
Now try:
DROP
The word DROP removed 10 from the stack. The stack is now (probably) empty:
.S

BYE

Lesson 4 The Dictionary WORDS SEE

About all a Forth system knows is contained in its dictionary. The dictionary contains words - words the system came with and words defined by the progammer. By "word" is meant: the word's name, which Forth uses to look up the word, and the word's action, which is code that Forth either executes or compiles.

The dictionary comes with the Forth system. The programmer writes a program by adding to the dictionary words defined in terms of words in the dictionary.

As a rule, Forth finds a word by starting with the most recently defined word and working backwards. If two or more words in the dictionary have the same name, Forth will find the most recently defined and be satisfied. Re-use of names is allowed but probably isn't Simple Forth.

Most Forths have the words WORDS and SEE. WORDS displays the names of words currently in the dictionary. A "fat" Forth will have lots a words - the names will whizz by unless the display pauses. A "thin" Forth will have far fewer - the names might fit into a single screen. SEE followed by a word's name tries to display the word's definition. SEE may or may not display definitions of words defined in assembly language. There's no harm in seeing what Forth SEEs.

Try:

WORDS
SEE WORDS
SEE SEE
SEE .S
SEE ?
SEE !
Short definitions are ok. Try (exactly as written):
: HELLO  ." Hi! " ;
HELLO
SEE HELLO

: GOODBYE  ." Bye! " ;
GOODBYE
SEE GOODBYE

: QUICK   HELLO GOODBYE ;
QUICK
SEE QUICK

BYE

Lesson 5 Adding to the Dictionary : ;

By far the most common way of adding a word to the dictionary is to write a colon definition. A colon definition starts with the Forth word : and ends with the Forth word ;. After : comes the name, then the definition, then ;.

This is a commonly cited Forth definition:

: SQUARED  DUP * ;

Forth reads from left to right. The definition of SQUARED tells Forth: when you see SQUARED, first do DUP, then do *.

Because in Forth data is passed implicitly, it is considered insane to define a word without documenting what data (if any) it takes from the stack and what data (if any) it returns to the stack. The canonical way of doing this is to use the Forth word ( which tells the system to ignore what follows up to and including the next ). Expectations ("before") and results ("after") are separated by --. The resulting ( before -- after ) is a "stack-effect comment".

So:

: SQUARED  ( n -- n*n )  DUP * ;

It's also wise to document what the new word does. For this the Forth word \ is useful. \ tells Forth to ignore the rest of the line.

So try:

\ Return square of n
: SQUARED  ( n -- n*n )  DUP * ;

\ Skip next line if your system doesn't SEE
SEE SQUARED

2 SQUARED
.S
SQUARED
.S
SQUARED
.S
DROP

For extra credit read:
Leo Brodie's Forth Style Guide and Paul Bennett's Forth Coding Rules.

BYE

Lesson 6 Forth Arithmetic 1 + - * /

Most words don't wait before acting. The word + doesn't ask "plus what?"; it removes the top two stack numbers and returns their sum. The words - * / also take the top two stack numbers and return a result.

Try:

1 1 + 
.S
1 +
.S
1 1 +
.S
+
.S
DROP

- subtracts TOS from the number below it. Try:

1 1 -
.S
1 -
.S
1 1 -
.S
-
.S
DROP

* multiplies the top two stack numbers. Try:

2 2 *
.S
2 *
.S
2 2 *
.S
*
.S
DROP

/ takes the number below TOS and divides it by TOS. Try:

100 2 /
.S
2 /
.S
2 /
.S
2 2 /
.S
/
.S
DROP
Forth reads from left to right. There is no "operator precedence." Try:
\ 1 + (2 * 3)
2 3 * 1 +
.S
DROP
1 2 3 * +
.S
DROP

\ (1 + 2) * 3
1 2 + 3 *
.S
DROP

There's a lot to observe in these examples. You might want to repeat them with the same and with different numbers before saying:

BYE

Lesson 7 Forth Arithmetic 2 /MOD MOD .

I hope you noticed in Lesson 6 that 100 2 / 2 / 2 / left 12 on the stack. The words + - * / are integer operators. / gives the quotient of n1 divided by n2. To get the remainder, use either /MOD, which returns the quotient as TOS and the remainder below TOS, or MOD, which just returns the reminder.

Here are the stack effects of + - * /MOD / MOD:

+  ( n1 n2 -- n1+n2 )
-  ( n1 n2 -- n1-n2 )
*  ( n1 n2 -- n1*n2 )
/MOD  ( n1 n2 -- remainder quotient )
/  ( n1 n2 -- quotient )
MOD  ( n1 n2 -- remainder )

. ( n -- ) displays the signed integer n.

Try:

1 .
-1 . 
0 -1 + .
10 10 /MOD . .
10 11 /MOD . .
10 3 /MOD . .
10 3 / .
10 3 MOD .
10 3 /MOD 3 * + .

BYE

Lesson 8 Forth Arithmetic 3 1+ 1- ABS NEGATE MAX MIN */ */MOD

Here are some other arithmetic words to try in your free time. The words are categorized by stack effect.

Take one, leave one:

1+  ( n -- n+1 )  \ Faster 1 +
1-  ( n -- n-1 )  \ Faster 1 -
ABS  ( n -- u )  \ Return absolute value of n
NEGATE  ( +n|-n -- -n|+n ) \ Return arithmetic inverse of n

Take two, leave one:

MAX  ( n1 n2 -- n1|n2 ) \ Return the greater of n1 and n2
MIN  ( n1 n2 -- n1|n2 ) \ Return the lesser of n1 and n2

Take three, leave one:

*/  ( n1 n2 n3 -- quotient ) \ Similar to n1 n2 * n3 / 

Take three, leave two:

*/MOD  ( n1 n2 n3 -- rem quot ) \ Similar to n1 n2 * n3 /MOD

*/ and */MOD avoid overflow during the multiplication, but the quotient must be within the range of signed integers.

\ Celsius <-> Fahrenheit 
: C  ( Celsius -- Fahrenheit )  9 5 */  32 + ;

100 C .
0 C .

: F  ( Fahrenheit -- Celsius )  32 -  5 9 */ ;

212 F .
32 F .

BYE

Lesson 9 Managing the Stack 1 DEPTH

.S, discussed in Lesson 3, displays the numbers on the stack. DEPTH ( -- +n ) returns how many of those numbers they are. "Number" can stand for various things: signed integer, unsigned integer, address, count, ASCII code, UNICODE, true/false flag, etc, so I will now refer to stack items. All the items on the stack are the same size: one cell. The size of a cell depends on the implementation. A cell must be at least 16-bits; nowadays 32-bits is probably most common. How many bits there are in a cell determines how much information a cell can hold - more about this in a later lesson.

So in somewhat technical terms, DEPTH ( -- +n ) returns as TOS the number of one-celled items that were on the stack before DEPTH was called. Note that the stack-effect comment doesn't indicate these items, because DEPTH doesn't require that there be any items on the stack and doesn't affect the items that may be on the stack, except of course, to push them one item deeper in the stack by making +n TOS.

Try:

\ Tell how many items are on the stack
:  DEPTH?  ( -- )  DEPTH . ;

DEPTH?
10
DEPTH?
20
DEPTH?
30
DEPTH?
.
DEPTH?
.
DEPTH?
.
DEPTH?

Some Forths empty the stack when they encounter a word not in the dictionary; others don't. To find out what your Forth does, try:

10 20 30
DEPTH?
\ Unlikely word:
Nonce
DEPTH?

BYE

Lesson 10 Managing the Stack 2 DUP ?DUP OVER SWAP ROT PICK ROLL NIP TUCK 2DROP 2DUP 2OVER 2SWAP

When you said BYE in the last lesson, your dictionary forgot DEPTH?, the word you so carefully added to it. You could have preserved DEPTH? in either of two ways: saving the system before saying BYE, or saving the colon definition of DEPTH? as source code and having your Forth read the definition again to add it back to the dictionary. So are real programs written. For now you are learning Forth by writing little definitions that you won't miss when they're gone.

With something as strict as a stack (Last In First Out), some legerdemain is necessary, and Forth provides the words for it. If you want a copy of TOS, you DUPlicate it. If you want a copy of the item below TOS, you bring it OVER If you want TOS and the item below it to switch positions, SWAP them. If you want the third stack item to be TOS, you ROTate it to the top.

DUP  ( x -- x x )  Copy TOS
?DUP  ( x|0 -- x x | 0 ) Copy TOS if it isn't zero
OVER  ( x1 x2 -- x1 x2 x1 ) Copy x1 to TOS
SWAP  ( x1 x2 -- x2 x1 )  Switch positions of x1 and x2
ROT  ( x1 x2 x3 -- x2 x3 x1 )  Rotate x1 to TOS

These words could be (but likely aren't) defined in terms of the words PICK and ROLL:

PICK  ( xu ... x0 u -- xu ... x0 xu )  Copy xu to TOS
ROLL  ( xu ... x0 u -- xu-1 ... x0 xu )  Rotate xu to TOS
: DUP  ( x -- x x )  0 PICK ;
: OVER  ( x1 x2 -- x1 x2 x1 ) 1 PICK ;
: SWAP  ( x1 x2 -- x2 x1 )  1 ROLL ;
: ROT  ( x1 x2 x3 -- x2 x3 x1 ) 2 ROLL ;

Forth programmers generally don't use PICK and ROLL much, preferring specific to general solutions and avoiding the temptation to treat the stack as an array.

On the other hand, these definitions wouldn't be unusual:

\ Drop the second stack item
: NIP  ( x1 x2 -- x2 ) SWAP DROP ;

: /  ( n1 n2 -- quotient )  /MOD NIP ;
: MOD  ( n1 n2 -- remainder )  /MOD DROP ;

NIP and TUCK are probably CODEd in assembly language, but one could define:

\ Copy TOS below the second stack item
: TUCK  ( x1 x2 -- x2 x1 x2 )  SWAP OVER ;

These words that concern pairs of stack elements are probably also in assembly language:

\ Drop top two stack items
: 2DROP  ( x1 x2 -- )  DROP DROP ;

\ Duplicate top two stack items
: 2DUP  ( x1 x2 -- x1 x2 x1 x2 )  OVER OVER ;

\ Copy lower pair over top pair
: 2OVER  ( x1 x2 x3 x4  -- x1 x2 x3 x4 x1 x2)
   3 PICK  3 PICK ;

\ Exchange top two cell pairs
: 2SWAP  ( x1 x2 x3 x4 -- x3 x4 x1 x2 )
   3 ROLL  3 ROLL ;

Managing the stack well comes with experience. Meanwhile my 7-year-old daughter could use this:

\ Math Family
\ ." and CR to be discussed later
: "+"  ." + " ;
: "-"  ." - " ;
: "="  ." = " ;

: FAM  ( n1 n2 -- )
   2DUP +
   DUP 2OVER           CR . "+" . "=" .
   DUP 2OVER SWAP      CR . "+" . "=" .
   DUP 2OVER SWAP ROT  CR . "-" . "=" .
                       CR . "-" . "=" . ;


\ Did somebody say 3DUP ?
: 3DUP  ( x y z -- x y z x y z )
   2 PICK  2 PICK  2 PICK ;

Homework: redefine FAM using 3DUP.

BYE

Lesson 11 Programming by Teaching and Learning

You program in Forth by teaching a machine new actions that you and the machine know by name. Each new action is a novel arrangement of known actions, perhaps mixing in some data. By being added to the dictionary the new action can be used to teach still newer actions. You choose the names, so a good part of the communication between you and machine is in your terms. By your pupil you'll be taught - the machine knows a lot of Forth, probably more than you do - and prefers certain ways of doing things, but it will make your ways its own if you teach it well.

As you saw, among the Forth words the machine knows are DUP and *. You might therefore teach it:

: SQUARED  ( n -- n**2 )  DUP * ;
: CUBED  ( n -- n**3 )  DUP SQUARED * ;
: 4TH  ( n -- n**4 )  SQUARED SQUARED ;
...and so on until you have taught it all the words your program needs to do.

With Forth you can teach the machine whatever a computer can do. As you teach you learn, because any word you add to the dictionary you can (and should) immediately test.

\ ." displays text
: ME  ( -- )  ." Leonardo DiCaprio" ;
ME
\ Oops!
: ME  ( -- )  ." Leo Wong" ;
ME

BYE

Lesson 12 Thoroughly Modern Forth

In Lesson 9 I talked about stack items the size of a cell, noting that an item could be represent different kinds of data: a signed integer, an unsigned integer, an ASCII code (character), a true/false (or Boolean) flag, etc. It used to be that a cell was 16-bits, period. Then came 32-bit processors and 32-bit Forths, and now there are 64-bit and 128-bit processors. The notion that in every Forth a stack item (and by implication some other kinds of data) is the size of a cell, but that the size of a cell is determined by the implementation allows for easier porting of Forth programs between different kinds of computers.

The number of bits (binary digits) in a cell determines how many different bit patterns the cell can distinguish, which determines how many different values of a particular kind of data a cell can distinguish.

For instance, one bit can distinguish two different patterns:0 1, which could be interpreted as:
the unsigned integers 0 and 1
the signed integers 0 and (maybe) -0
the Boolean flags false and true
etc.

Two bits can distinguish four patterns: 00 01 10 11, which could be interpreted as:
the unsigned integers 0 1 2 3
the signed integers 0 1 -2 -1
the Boolean flags false(=0) and true(<>0)
etc.

We can generalize and say that n-bits can distinguish 2**n (2 to the power of n) different patterns.

Getting down to cases,
a 16-bit cell can distinguish 65,636 different patterns, each of which could represent:
an unsigned integer between 0 and 65,635
a signed integer between -32,768 and 32,767
the Boolean flags false(=0000000000000000) and true(<>false)
etc.

a 32-bit cell can distinguish 4294967296 patterns, each of which could represent:
an unsigned integer between 0 and 4,294,967,295
a signed integer between -2,147,483,648 and 2,147,483,647
the Boolean flags false(=00000000000000000000000000000000) and true(<>false)
etc.

Try the following:
-1 U.

If 65635 is displayed, your Forth has 16-bit cells. If 4294967295, 32-bit cells. If another number is displayed, then either the size of cell is other than 16 or 32 bits, or your Forth represents numbers atypically.

Forth easily handles data bigger and smaller than the size of one cell: "double" (2-cell) numbers and "floating-point" numbers, "bits", "characters", "strings" of characters, database fields and records, files, "objects", etc.

But for now we still stay with one kind of data - cell-sized signed integers.

Lesson 13 Comparisons TRUE FALSE = < > 0= 0<

I lied, because I will now talk about flags. But I lied only a little, because Forth flags are cell-size bit patterns like Forth integers and because Forth integers can be used as Forth flags.

A flag tells whether a condition is true or false, for instance whether TOS is equal to the stack item beneath TOS. Your Forth may already have the words TRUE and FALSE, which return to TOS "well formed" true and false flags. A well-formed true flag has all bits set (1), while a well-formed false flag as all bits "reset" (0). It is highly likely that in your Forth all bits set has the same bit pattern as the integer -1 and all bits reset has the same pattern as the integer 0.

Try:
TRUE .
FALSE .
If your Forth doesn't have TRUE or FALSE you can teach it:
\ CONSTANT defines a word that always returns the same value
0 CONSTANT FALSE
\ = returns a true flag if the top two stack items are equal
FALSE FALSE = CONSTANT TRUE
TRUE .
FALSE .

Each of these words returns a well-formed flag:

=  ( n1 n2 -- flag )  \ Does n1 equal n2 ?
>  ( n1 n2 -- flag )  \ Is n1 greater than n2 ?
<  ( n1 n2 -- flag )  \ Is n1 less than n2 ?
0=  ( n -- flag )     \ Does n equal 0 ?
0<  ( n -- flag )     \ Is n less than 0 ?
Try:
1 2 +  2 1 + = .
1 2 -  2 1 - = .
3 4 *  4 3 * < .
3 4 /  4 3 / < .
5 6 /  6 5 / > .
5 6 MOD  6 5 MOD > .
1 -1 + 0= .
1 -1 - 0= .
-1  0< .
-1 -1 * 0< .

Given these comparison words, others are easy to define. Your Forth may already have:

\ Does n1 not equal n2 ?
: <> ( n1 n2 -- flag )  = 0= ;

\ Does n not equal 0 ?
: 0<> ( n -- flag)  0= 0= ;

\ Frequently seen synonym for 0=
: NOT  ( n -- flag ) 0= ;

\ Is n1 greater than or equal to n2 ?
: >=  ( n1 n2 -- flag ) < NOT ;

\ Is n1 less than or equal to n2 ?
: <=  ( n1 n2 -- flag ) > NOT ;

Lesson 14 Conditional Execution IF ELSE THEN

This is how to tell Forth to execute words conditionally:

true-flag IF do-this THEN
true-flag IF do-this ELSE do-that THEN

Use IF ELSE THEN only colon definitions ( that is between : and ;). IF and THEN must be in the same definition. ELSE is optional and if used must come between IF and THEN. Words can come before IF and follow THEN.

Try writing two inefficient definitions:

: EVEN?  ( n -- )  2 MOD IF ." odd" ELSE ." even" THEN ;
1 EVEN? 
2 EVEN? 

\ The stack effect of IF is ( flag -- ),
\ so if TOS is needed after the test
\ make a copy of it before the test.
: ABS  ( n -- +n )  DUP O< IF -1 * THEN ;
1 ABS .
-1 ABS .

IF ELSE THEN can be "nested". Try:

: SCORE  ( n -- )
   DUP 89 > IF  ." Honors"
            ELSE  DUP 69 > IF  ." Pass"
                           ELSE  ." Fail"
                           THEN
            THEN  DROP ;

100 SCORE
80 SCORE
60 SCORE

In Forth the true flag need not be "well formed" - have all bits set. Any flag with at least one bit set is taken to be true. Try:

       0 CONSTANT FALSE  
FALSE 0= CONSTANT TRUE

: TRUE?  ( n -- )
   DUP TRUE = IF  ." Well-formed true"  ELSE
   DUP        IF  ." Still true"        ELSE
                  ." False"
              THEN THEN  DROP ;

TRUE TRUE?
FALSE TRUE?
-1 TRUE?
1 TRUE?
2 TRUE?
0 TRUE?

BYE

Lesson 15 Repeated Execution EXIT BEGIN AGAIN UNTIL WHILE REPEAT

Much of the computer's power comes from its ability to do something repeatedly. One way to tell Forth to something again is to repeat the word or phrase: Try:

\ Add five numbers
: SUM5  ( n1 n2 n3 n4 n5 -- sum ) + + + + ;
14 26 33 25 18 SUM5 .

Repeating words or phrases is quite legitimate but limiting. In this lesson I'll show you three other ways of repeating execution ("looping"). Each begins with the word BEGIN:

BEGIN ... AGAIN
BEGIN ... UNTIL
BEGIN ... WHILE ... REPEAT

BEGIN AGAIN UNTIL WHILE REPEAT, like IF ELSE THEN, are used colon definitions, with BEGIN and AGAIN or UNTIL or WHILE ... REPEAT in the same definition (in Forth, other "control structures" can be developed, but we'll just use these for now). I'll soon use these words in three definitions of a word I'll name INTEGERS. But first another word that is also used only in colon definitions: EXIT.

EXIT says: stop executing the word I'm in.

Try:

\ Display 1 2 3
: JUST3  ( -- ) 1 . 2 . 3 . EXIT 4 . 5 . ;
JUST3

EXIT just affects the word it's in. Try:

\ Display 1 2 3 4 5
: TO5  ( -- )  JUST3  4 . 5 . ;
TO5

We need EXIT because in my first definition of INTEGERS, the words between BEGIN ... AGAIN repeat unconditionally.

Try:

\ Display integers 1 ... +n using BEGIN ... AGAIN
: INTEGERS  ( +n -- )
   1 BEGIN  2DUP < IF 2DROP EXIT THEN  DUP . 1+  AGAIN ;
10 INTEGERS
1 INTEGERS
0 INTEGERS
BEGIN ... AGAIN executes the words between BEGIN and AGAIN again and again unless Forth executes EXIT, which in INTEGERS happens when +n is less than TOS.

Many published Forth definitions are very short - one or two lines. The best way to understand them (and to go about defining your own words) is to write them out one word or phrase to a line along with stack and other comments. Here is the definition of INTEGERS one word to a line, explaining the action of each word and the resulting stack:

: INTEGERS  ( +n -- )
   1            \ Initialize i to 1     ( +n i=1 )         
   BEGIN        \ Start loop: i is TOS  ( +n i )
      2DUP      \ Duplicate 2 items     ( +n i +n i )
      <         \ Is +n less than i ?   ( +n i flag )
      IF        \ Act on flag           ( +n i )
         2DROP  \ True: drop 2 items    (  )
         EXIT   \ True: leave word      (  )
      THEN      \ End IF ... THEN       (  )
      DUP       \ DUPlicate TOS         ( +n i i )
      .         \ Display TOS           ( +n i )
      1+        \ Increment TOS         ( +n i=i+1 )
   AGAIN        \ Loop back             ( +n i )
   ;            \ End definition        
BEGIN ... UNTIL repeats execution until there's a true flag just before UNTIL. The words between BEGIN and UNTIL execute at least once, since UNTIL tests whether to leave the loop. Like IF, UNTIL swallows the flag, and as with IF, a true flag need not be "well formed". Here's the second definion of INTEGERS:

Try:

\ Display integers 1 ... +n using BEGIN ... UNTIL
: INTEGERS  ( +n -- )
   1 BEGIN  DUP .  1+ 2DUP < UNTIL  2DROP ;
10 INTEGERS
1 INTEGERS
0 INTEGERS

\ Don't do this:
: INTEGERS ( +n -- )
   1 BEGIN 2DUP < 0= IF DUP . THEN 1+ 2DUP < UNTIL 2DROP ;
10 INTEGERS
1 INTEGERS
0 INTEGERS

Here is the second definition one word to a line:

: INTEGERS  ( +n -- )
   1        \ Initialize i to 1      ( +n i=1 )         
   BEGIN    \ Start loop: i is TOS   ( +n i )
      DUP   \ DUPlicate TOS          ( +n i i )
      .     \ Display TOS            ( +n i )
      1+    \ Increment TOS          ( +n i=i+1 )
      2DUP  \ Duplicate 2 items      ( +n i +n i )
      <     \ Is +n less than i ?    ( +n i flag )
   UNTIL    \ Loop back unless true  ( +n i )
   2DROP    \ Drop two items         (  )
   ;        \ End definition 

BEGIN ... WHILE ... REPEAT executes the words between BEGIN and WHILE, continues execution to REPEAT while there's a true flag just before WHILE, then at REPEAT loops back to the word just after BEGIN. If the flag just before WHILE is false, execution skips to after REPEAT. The words between BEGIN and WHILE will execute at least once. If the flag before WHILE is false the first time through, the words between WHILE and REPEAT will not execute at all. WHILE, like UNTIL, swallows its flag, which need not be well formed. Here's the third definition:

Try:

\ Display integers 1 ... +n using BEGIN ... WHILE ... REPEAT
: INTEGERS  ( +n -- )
   1  BEGIN  2DUP < 0= WHILE  DUP .  1+  REPEAT  2DROP ;
10 INTEGERS
1 INTEGERS
0 INTEGERS

The third definition with line comments:

: INTEGERS  ( +n -- )
   1        \ Initialize i to 1        ( +n i=1 )         
   BEGIN    \ Start loop: i is TOS     ( +n i )
      2DUP  \ Duplicate 2 items        ( +n i +n i )
      < 0=  \ Is +n not less than i ?  ( +n i flag )
   WHILE    \ If true, continue else
            \ jump to after REPEAT     ( +n i )
      DUP   \ DUPlicate TOS            ( +n i i )
      .     \ Display TOS              ( +n i )
      1+    \ Increment TOS            ( +n i=i+1 )
   REPEAT   \ Loop back                ( +n i )
   2DROP    \ Drop two items           (  )
   ;        \ End definition 

BYE

Lesson 16 Counted Loops ?DO DO I J LEAVE UNLOOP LOOP +LOOP

Forth understands several kinds of counted loops, in which stack arguments before the loop determine the number of iterations. I recommend that you just use:

+n 0 ?DO ... LOOP
which executes the words between ?DO and LOOP exactly +n times and not at all if +n equals zero. Like BEGIN, etc. ?DO, etc. should only be used in colon definitions.

Try:

: INTEGERS  ( +n -- )
   1 SWAP  0 ?DO  DUP .  1+  LOOP  DROP ;
10 INTEGERS
1 INTEGERS
0 INTEGERS

With line comments:

: INTEGERS  ( +n -- )
   1        \ Initialize i        ( +n i=1 )
   SWAP     \ Set limit for ?DO   ( i limit=+n )
   0        \ Set index for ?DO   ( i limit index=0 )
   ?DO      \ Start counted loop  ( i )
      DUP   \ DUPlicate i         ( i i )
      .     \ Display i           ( i )
      1+    \ Increment i         ( i=i+1 )
   LOOP     \ Increment index.
            \ Loop back if
            \ index < limit
            \ else leave loop     ( i )
   DROP     \ Drop i              (  )
   ;        \ End definition

This is all and more than all you need to know about counted loops. Please skip the rest of this lesson.

I recommended that you use ?DO, use a positive limit, and set the starting index to zero: +n 0 ?DO. ?DO actually takes any limit and starting index: limit start ?DO. You may experiment by writing words that accept various values of limit and start.

We saw when commenting on INTEGERS that ?DO removes the limit and the starting index from the stack and that LOOP increments the index and compares it to the limit, leaving the loop then the index equals the limit. It's quite probable that your Forth keeps the limit and the index on Forth's second stack, called the return stack, and drops the limit and index from the return stack on leaving the loop. This likelihood results in rules about using the return stack that I may or may not discuss later.

?DO checks if the limit and the starting index are equal, skipping the loop entirely if the are. The word DO acts like ?DO but doesn't check if the limit and the starting index are equal. DO may be a tiny bit faster than ?DO, but a condition like 0 0 DO ... LOOP will likely give you more than you bargained for.

I returns the current loop index. This is sometimes useful. For example one could have defined:

: INTEGERS ( +n -- )
   1+ 1 ?DO I . LOOP ;

This program translated from a famous book also uses I:

\ Power
\ after Kernighan and Ritchie,
\ The C Programming Language, 2nd ed., pp. 24-25

\  Raise base to n-th power; n >= 0
: power  ( base n -- base**n )
   1 SWAP 0 ?DO OVER * LOOP  NIP ;

\ Test power
: test  ( -- )
   10 0 ?DO  CR I .  2 I power .  -3 I power .  LOOP ;

J returns the index of the next outer loop when counted loops are nested in the same word. This useful for stepping through two-dimensional arrays:

\ Display indexes of a 2D array
: INDEXES  ( rows columns -- )  
   SWAP          ( columns rows )
   0 ?DO         \ For number of rows 
      CR         \ CR goes to next line
      DUP        \ DUPlicate columns
      0 ?DO      \ For number of rows  
          J .    \ Display row index
          I .    \ Display column index
          SPACE  \ SPACE displays a space
     LOOP        \ End of inner loop
  LOOP ;         \ End of outer loop
3 4 INDEXES

LEAVE and UNLOOP EXIT exit a counted loop or counted loops "prematurely". LEAVE immediately leaves the loop it is in. UNLOOP EXIT exits the word it is in, and can exit a word with nested loops. Here are some phrases to ponder:

DO ... IF LEAVE THEN ... LOOP
DO ... IF UNLOOP EXIT THEN ... LOOP
DO ... DO ... IF UNLOOP UNLOOP EXIT THEN ... LOOP ... LOOP

The phrase n +LOOP changes the loop index by n. n can be positive or negative, permitting stepping up or stepping down through the loop. Depending on whether it's up or down, the ending conditions, I think, "equal or greater than" or "less than". Try if you like:

: LOOPY ( limit start step -- )
   ROT ROT ?DO I . DUP +LOOP DROP ;
10 0 1 LOOPY
10 0 -1 LOOPY

10 0 2 LOOPY
10 0 -2 LOOPY

10 0 3 LOOPY
10 0 -3 LOOPY
n before +LOOP can vary within the loop. Here's a cute definition by Wil Baden:
: SQRT   ( n1 -- n2 )
   0 TUCK  DO  1+  DUP 2* 1+  +LOOP ;

Didn't I ask you not to read this?

BYE

Lesson 17 Source Files S" INCLUDED

I think I shall write a little game. Since the game will take a few lines that I will revise as I develop the game I'll put the source code in a file. I'll write the code with a text editor and then load the source into Forth by entering: S" filename" INCLUDED where filename" is the name of the file followed by a double quotation mark. As I develop and correct my program I will get in and out of Forth and go back to my text editor. This isn't the most efficient way of developing a Forth program - Forth is an "integrated development environment" in which Forth and the source editor can work much more closely together - but it will work regardless of Forth implementation.

Since the game could use a little unpredictability, I'll need a "random number generator". I'll put that in a file too, so you can test S" filename" INCLUDED.

The random number generator in Leo Brodie's Starting Forth will be more than good enough for our purposes. Either type or copy and paste the following into a file that you name random.f. The code has a few words that I might explain in another lesson. For now just copy the source.

\ random.f
\ Simple random number generator
\ from Leo Brodie, _Starting Forth_

VARIABLE RND  \ Holds current result

\ Generate a random integer
: RANDOM  ( -- u )  RND @  31421 *  6927 +  DUP RND ! ;

\ Return a random integer between 0 and u-1
: CHOOSE  ( u -- 0...u-1 ) RANDOM UM* NIP ;

\ Initialize
: RANDOMIZE  ( -- )  TIME&DATE + + + + + RND ! ;

Now try:

S" random.f" INCLUDED
100 CHOOSE .
100 CHOOSE .
100 CHOOSE .
100 CHOOSE .
100 CHOOSE .

Excuse me while I write the game.

BYE

Lesson 18 Game of Sticks CR SPACE SPACES ." .(

I'm back. It didn't take very long because I copied the game from David H. Ahl's BASIC Computer Games. If you can find a copy of that book you might wish to compare the BASIC code for "23 Matches" with my Forth code for "Sticks." Of course, I just didn't sit down and write "Sticks" without changing things and testing words as I went along. I just give the result. However, being a careless coder, I appreciate bug reports: hello@albany.net.

Nor do I particularly recommend my style of "coding". I will say that I value a plain style.

"Sticks" uses a few words that haven't been formally introduced:

CR ( -- ) sends the display cursor to the next line.
SPACE ( -- ) displays a space.
SPACES ( n -- ) displays n spaces (SPACES isn't used in sticks.f).
." text" ( -- ), used only in definitions, remembers (compiles) text up to but not including ". text is displayed when the word being defined is executed.
.( text) ( -- ) immediately displays text up to but not including ). You don't see .( much. I'll use it in the source for "Sticks" to give you an indication of how quickly Forth reads code.

Try:

.( Four score and seven years ago )
: brought  .( our fathers ) ." Forth on this continent." ;
brought 

Type or copy and paste the following code into a file called sticks.f:

\ sticks.f
\ After "23 Matches" in Ahl, _Basic Computer Games_
\ Ahl attributes the original to Bob Albrecht

CR .( Reading sticks.f)

\ random number generator
s" random.f" INCLUDED

\ Rules of the game
: RULES  ( -- )
   CR ." Sticks"
   CR
   CR ." The game starts with 23 sticks.  "
      ." By turns, you and Forth take"
   CR ." from 1 to 3 sticks.  " 
      ." Whoever takes the last stick loses."
   CR
   CR ." You take sticks by entering:  n STICKS"
   CR ." where n is 1, 2, or 3"
   CR ;

\ Display sticks
: .STICKS  ( n -- )  0 ?DO  ." |"  LOOP ;

\ Report remaining sticks
: LEFT  ( sticks taken -- left )
   -  DUP CR .STICKS  SPACE ." left." ;

\ The fates of Forth 
: YOU-WIN  ( sticks -- )  DROP  ." You win! " ;
: FORTH-WINS  ( sticks -- ) 
   ." Forth took "  1- .
   CR ." 1 left - sorry!" ;
: 4-PLAY  ( sticks -- left )
   ." Forth took " 3 CHOOSE 1+ DUP . LEFT ;

\ My esteemed opponent
: COMPUTER  ( sticks -- left| )
   CR  
   DUP 1 = IF  YOU-WIN     ELSE
   DUP 5 < IF  FORTH-WINS  ELSE
               4-PLAY
           THEN THEN ;

\ First play
: COIN  ( 23 -- n )
   2 CHOOSE  
   CR ." A coin has been flipped:  " 
   IF   ." Heads, Forth is first."  COMPUTER 
   ELSE ." Tails, you start."
   THEN ;

\ Confine n between min and max
: CLAMP  ( n min max -- n )  ROT MIN MAX ;

\ Between 1 and 3, leaving at least 1 
: LEGAL  ( stones try -- taken )
   OVER 1- 3 MIN  1 SWAP CLAMP ; 

\ My play
: PROGRAMMER  ( stones try - taken )  LEGAL LEFT ;

\ 1 Round
: STICKS  ( stones try -- left| )  PROGRAMMER COMPUTER ;
\ Alias for STICKS
: STICK  ( stones try -- left| )  STICKS ;

: GAME  ( -- )
   RULES  23 DUP CR .STICKS  RANDOMIZE COIN ;

CR .( Ready.  To play, enter: GAME)

Now try:
S" sticks.f" INCLUDED
GAME
Homework: write your own version of Sticks or another game.
BYE

Lesson 19 More to Display PAGE AT-XY .R MS

Sticks used CR SPACE SPACES . ." .( for displaying on the screen. Here are three other display words:

PAGE ( -- ) clears the screen and puts the cursor in the top left corner.
AT-XY ( col row -- ) moves the cursor to col row. Top left is 0 0 .
.R ( n u -- ) displays the signed integer n right aligned in a field u spaces wide. .R is often used to display numbers in columns.

Not technically a display word but useful in this context is:

MS ( +n -- ) Wait +n milliseconds.

Try:

PAGE  30 12 AT-XY .( Simple Forth )
PAGE  CR 1 3 .R  CR 12 3 .R  CR 123 3 .R
PAGE  33 12 2DUP AT-XY  100 5 .R  2000 MS  AT-XY 1 5 .R

Homework: Improve Sticks by using words introduced in this lesson.
BYE

Lesson 20 Data on the Return Stack >R R@ R> 2>R 2R@ 2R>

You will occasionally find it convenient to be able to put a stack item temporarily in another place. "Another" place is the return stack. Your Forth almost certainly uses the return stack for its own purposes, so your use of the return stack must follow certain rules:

  1. Data put on the return stack must be taken back within the same word.
  2. Data put on the return stack outside a ?DO (DO) ... LOOP (+LOOP) cannot be accessed within the loop.
  3. Data put on the return stack within a ?DO (DO) ... LOOP (+LOOP) must be taken back before leaving the loop.
  4. Data cannot be on the return stack when executing I or J in a loop.

In the following descriptions of return stack words,

( S: ... -- ... ) shows the (data) stack effect
( R: ... -- ... ) shows the return stack effect.

>R ( S: x -- ) ( R: -- x ) moves x from the stack to the return stack.
R@ ( R: x -- x ) ( S: -- x ) copies x from the return stack to the stack.
R> ( R: x -- ) ( S: -- x ) moves x from the return stack to the stack.
2>R ( S: x1 x2 -- ) ( R: -- x1 x2 ) moves x1 x2 from the stack to the return stack.
2R@ ( R: x1 x2 -- x1 x2 ) ( S: -- x1 s2 ) copies x1 x2 from the return stack to the stack.
2R> ( R: x1 x2 -- ) ( S: -- x1 x2) moves x1 x2 from the return stack to the stack.

I'll use return stack words in future lessons. The uses will simplify coding. You won't find anything like this:

\ pascal.f
\ Pascal's Triangle
: POSITION  ( row -- )  CR  33 SWAP 2 *  - SPACES ;
: PAS ( 0 ... 0 -- 0 ... 0 )
   0 >R    
   BEGIN  OVER + >R  DUP 0= UNTIL
   BEGIN  R> DUP WHILE  DUP 4 .R  REPEAT ;
: PASS  ( -- )
   0 1 0  
   13 0 ?DO  DUP POSITION  >R  PAS  R>  1+  LOOP  DROP ;
: PAX  ( 0 ... 0 -- )  DROP BEGIN 0= UNTIL ;
: PASCAL  ( -- )  PASS PAX ;

BYE

Lesson 21 Named Storage CONSTANT VARIABLE U. U.R ! @ +! ?

Stack items are anonymous, documented only by the stack effect comments you are urged but not required to write. Forth can also name data. We start with two words:

x CONSTANT name defines the word name ( -- x ) that returns x.
VARIABLE name defines the word name ( -- addr ) that returns the memory address addr where is reserved space for one cell of data (remember that a stack item is the size of one cell).

Your Forth dictionary probably comes with the constants TRUE and FALSE, which return the well-formed flags all bits set and all bit reset respectively. If not, they are easy to define (I think we've defined them twice before).

Try:

0 CONSTANT FALSE
FALSE FALSE = CONSTANT TRUE
FALSE .
TRUE .

123 CONSTANT EASY
EASY .

We defined the variable RND in random.f. Let's define another variable and display its address.

Try:

VARIABLE x
x U.

U. displays an unsigned integer. Memory addresses are always positive integers.

U. ( u -- ) displays the unsigned integer u.
U.R ( u n -- ) displays the unsigned integer u right aligned in a field n spaces wide.

A constant's value doesn't change (in Forth one can change anything, but this is Simple Forth). A variable returns an address at which a value can be "stored" and from which a copy of the stored value can be "fetched". To change what is fetched from a variable's address, store a different value at it. The words for storing and fetching are:

! ( x addr -- ) stores x at the address addr.
@ ( addr -- x ) fetches a copy of x from the address addr.
+! ( x addr -- ) adds x to the value at addr.

Try:
VARIABLE y
y U.
50 y !
y @ .

100 y !
y @ .

1 y +!
y @ .

Most Forths have a word for @ .:
? ( addr -- ) displays the value at addr.

Try:

VARIABLE z
7 z !
z ?

VARIABLE w
11 w !
w ?

z @ w @ z ! w !
z ?
w ?

\ Exchange values of two variables
: VSWAP  ( addr1 addr2 -- )  2DUP 2>R  @ SWAP @  R> !  R> ! ;

w ?
z ?
w z VSWAP
w ?
z ?

\ Addresses are cell-sized data
VARIABLE OLD
VARIABLE NEW
VARIABLE INDIRECTION

67 OLD !
70 NEW ! 

OLD INDIRECTION !

\ EMIT displays a character
INDIRECTION @ @ EMIT

NEW INDIRECTION !
INDIRECTION @ @ EMIT

BYE

Lesson 22 Accessing Memory 1 UNUSED CELLS

In the last lesson, we learned how to access a cell of data in memory by using ! +! @. In this and the next few lessons we'll work with groups of cells in memory. Much of the discussion will be applicable when working with data that may be more or less than a cell in size.

Forth systems tend to be small. What the system doesn't use is available to the programmer. Your system may have the word UNUSED that tells how many memory addresses are currently available for data.

UNUSED ( -- u ) returns u, the number of addresses still available.

Try:

UNUSED U.

As you add words and data structures to your program the amount of unused space gets smaller.

Try:

UNUSED U.
VARIABLE x
UNUSED U.

Since we've been working with cell-sized data, we now introduce:

CELLS ( n1 -- n2 ) returns n2, the number of address units in n1 cells.

It's likely that a cell occupies 2 address units if you have a 16-bit Forth and 4 address units if you have a 32-bit Forth. I say address units, since the address of the cell is the address of the first address unit occupied by the cell.

To see for yourself, try:

1 CELLS .
2 CELLS .
10 CELLS .

To see how many cells could fit in currently unused memory, try:

UNUSED 1 CELLS / .

BYE

Lesson 23 Accessing Memory 2 CREATE HERE ALLOT CELL+

Let us claim some unused memory.

CREATE name defines name, which returns a memory address.
HERE ( -- addr ) returns the next free memory address.
ALLOT ( n -- ) reserves n address units of memory.

CREATE name resembles VARIABLE name but doesn't reserve space. To reserve space after CREATE name, ALLOT it. The first address that is reserved is the address returned by HERE.

Try:

CREATE ITEMS
ITEMS U.
HERE U.
1 CELLS ALLOT
HERE U.
4 CELLS ALLOT
HERE U.

A word that combines CREATE and ALLOT can be defined.

Try:

\ Reserve n address units starting at name
\ n BUFFER: name
: BUFFER:   ( n -- )  CREATE ALLOT ;

4 20 + CELLS BUFFER: BLACKBIRDS

BUFFER: could be "enhanced" by making it initialize (typically set to zeroes) the space ALLOTed, but we'll leave it as it is.

Once you have CREATEd (that is, named) and ALLOTed space in memory, you can ! (store), +! ( add to or "plus" store), @ (fetch), and ? (question) data from within that space by calculating the particular address needed. The starting address is returned by name. The addresses of the following cells are calculated by adding n CELLS to the starting address.

Given 5 cells starting at ITEMS try:

ITEMS U.
50 ITEMS !
ITEMS ?

ITEMS 0 CELLS + U.
ITEMS 0 CELLS + ?

ITEMS 1 CELLS + U.
95 ITEMS 1 CELLS + !
ITEMS 1 CELLS + ?

ITEMS 2 CELLS + U.
77 ITEMS 2 CELLS + !
ITEMS 2 CELLS + ?
23 ITEMS 2 CELLS + +!
ITEMS 2 CELLS + ?

There are many ways of not having to repeatedly write n CELLS to calculate memory addresses. Here are two simple ways:

Try:

\ Return address of cell n from addr
: TH  ( addr n -- n-addr )  CELLS + ;

ITEMS ?
ITEMS 0 TH ?

ITEMS 3 TH U.
33 ITEMS 3 TH !
ITEMS 3 TH ?

\ Return address of cell n of ITEMS
: ITEM  ( n -- addr )  CELLS ITEMS +

ITEMS ?
0 ITEM ?

25 4 ITEM !
4 ITEM ?

The word for going from one cell address to the next is:

CELL+ ( addr1 -- addr2 ) adds 1 CELLS to addr1.

Try:

\ Display n cells beginning at addr
: .CELLS  ( addr n -- )
   0 ?DO  DUP ?  CELL+  LOOP  DROP ;

ITEMS 5 .CELLS

\ Sum of n cells beginning at addr
: SUM-CELLS  ( addr n -- sum )
   0 ROT ROT  0 ?DO  DUP >R  @ +  R> CELL+  LOOP  DROP ;

ITEMS 5 SUM-CELLS

For another definition of SUM-CELLS try:

: UNDER+  ( n1 x n2 -- n1+n2 x )  ROT + SWAP ;
: SUM-CELLS  ( addr n -- sum )
   0 ROT ROT  0 ?DO  DUP @ UNDER+  CELL+  LOOP  DROP ; 

ITEMS 5 SUM-CELLS

Homework: write yet another definition of SUM-CELLS by using

: @+  ( addr1 -- addr2 n )  DUP CELL+  SWAP @ ;
and any other words you know or care to define.

BYE

Lesson 24 Accessing Memory 3 ,

After a long lesson a short one on a short word:

, ( x -- ) reserves a cell of memory and stores x in it.

Try:

: TH  ( addr1 n -- addr2 )  CELLS + ;
\ Powers of 2 from 2^0 to 2^8
CREATE 2^  1 ,  2 ,  4 ,  8 ,  16 ,  32 ,  64 ,  128 ,  256 ,
2^ ?
2^ CELL+ ?
2^ 2 CELLS + ?

2^ 0 TH ?
2^ 1 TH ?
2^ 2 TH ?
2^ 8 TH ?

A word by Wil Baden can make this procedure even nicer.

Try:

\ "commas" by Wil Baden
: ,S  ( x1 ... xn n -- )
   BEGIN ?DUP WHILE  DUP ROLL ,  1-  REPEAT ;

\ Redefine   2^ 0 1 2 3  4  5  6   7   8
CREATE 2^       1 2 4 8 16 32 64 128 256   9 ,S

: ?+  ( addr1 -- addr2 )  DUP CELL+  SWAP ? ;

2^ ?+ ?+ ?+ ?+ ?+ ?+ ?+ ?+ ?

Lesson 25 Decimal Hexadecimal Binary BASE DECIMAL HEX DUMP

Mostly we've been using ordinary-looking numbers like 5 12 33. These are decimal - or base 10 - numbers represented with the 10 digits from 0 to 9. In Forth, numbers can be entered or displayed in any base from 2 to 36 (the computer stores the numbers in binary - base 2). The base in which numbers are input and displayed is stored in a variable:

BASE ( -- addr) holds the current base.

Forth has words for making the base decimal or hexadecimal:

DECIMAL ( -- ) stores 10 (decimal) in BASE
HEX ( -- ) stores 16 (decimal) in BASE

Try:

DECIMAL
16 .
16 HEX .
FF .
FF DECIMAL .
1024 .
1024 HEX .
DECIMAL

Usually the base is decimal but hexadecimal - base 16, where digits range from 0 to F - is very convenient in computing because a hexadecimal digit represents 4 bits (0=0001, 1=0001, 2=0010, 3=0011 ... F=1111). That means that 8 bits can be represented by 2 hexadecimal digits, 16 bits by 4 hexadecimal digits, 32 bits by 8 hexadecimal digits, and so on. This grouping of bits makes it easier to understand what's going on inside a computer. The word DUMP reports in hexadecimal:

DUMP ( addr u -- ) displays the contents of u consecutive addresses beginning at addr. The display depends on the implementation. You'll often see on each line an address, then 16 2-digit hexadecimal numbers representing the contents of 16 addresses, then the ASCII equivalents of these contents.

We can write words that restore the current base after displaying in another base.

Try:

\ Display as an unsigned hexadecimal number
: H.  ( n -- )  BASE @ >R  HEX U.  R> BASE ! ;
\ Display as an unsigned binary number
: B.  ( n -- )  BASE @ >R  2 BASE !  U.  R> BASE ! ;
\ Display as an unsigned decimal number
\ D. is already used in Forth 
: D#.  ( n -- )  BASE @ >R  DECIMAL .  R> BASE ! ;

\ Display as decimal, hexadecimal, binary
: DHB.  ( n -- )  DUP D#.  DUP H.  B. ;

DECIMAL
1 DHB.
2 DHB.
3 DHB.
4 DHB.
10 DHB.
15 DHB.
16 DHB.
1023 DHB.
1024 DHB.

HEX
1 DHB.
2 DHB.
A DHB.
F DHB.
10 DHB.
FF DHB.
100 DHB.
1000 DHB.
FFFF DHB.
1000 DHB.

CREATE POTS  3 CELLS ALLOT

10 POTS !
100 POTS CELL+ !
1000 POTS 2 CELLS + !

POTS ?
POTS CELL+ ?
POTS 2 CELLS + ?

DECIMAL
POTS 3 CELLS DUMP
POTS @ DHB.
POTS CELL+ @ DHB.
POTS 2 CELLS + @ DHB.

2 BASE !
1 DHB.
10 DHB.
11 DHB.
100 DHB.

DECIMAL

BYE

Lesson 26 Booleans and Bits AND OR XOR 0<> INVERT 2* 2/ LSHIFT RSHIFT

Forth's "logical operators" are all bit operators.

AND ( x1 x2 -- x3 )
OR ( x1 x2 -- x3 )
XOR ( x1 x2 -- x3 )

Try:

1 1 AND .
1 0 AND .
0 1 AND .
0 0 AND .

1 1 OR .
1 0 OR .
0 1 OR .
0 0 OR .

1 1 XOR .
1 0 XOR .
0 1 XOR .
0 0 XOR .

: B.  ( u -- )  BASE @ >R  2 BASE ! U.  R> BASE ! ;

1 DUP B. 2 DUP B. AND B.
1 DUP B. 2 DUP B. OR B.
1 DUP B. 2 DUP B. XOR B.

1 DUP B. 3 DUP B. AND B.
1 DUP B. 3 DUP B. OR B.
1 DUP B. 3 DUP B. XOR B.

Any non-zero flag is treated as true, but 1 2 AND leaves zero, so it's wise to AND only well-formed flags. You can make a flag well formed with 0<>:

0<> ( x -- flag ) \ Does x not equal zero?

A definition of 0<> might be:

: 0<>  ( x -- flag)  0= 0= ;

Try:

1 2 AND .
1 0<> 2 0<> AND .

\ A year is a leap year if:
\ it's divisible by 4 but not by 100 
\ or it's divisible by 400
: LEAP ( year -- flag )
   DUP 4 MOD 0=  OVER 100 MOD 0<> AND
   SWAP 400 MOD 0= OR ;

: LEAP?  ( year -- )
   DUP .
   LEAP IF ." is" ELSE  ." isn't" THEN  ."  a leap year." ;

2000 LEAP?

\ You could save a couple of words by
\ deciding if a year isn't a leap year
: -LEAP  ( year -- flag )  \ nonzero flag means not a leap year
   DUP 100 MOD 0= OVER 400 MOD AND  SWAP 4 MOD OR ;

Forth's other bit operators are:

INVERT ( x1 -- x2 ) toggles bits of x1
2* ( x1 -- x2 ) shifts bits of x1 one place left, leaving the least significant bit zero. A fast 2 * on most systems.
2/ ( x1 -- x2 ) shifts bits of x1 one place right, leaving the most significant bit unchanged. A fast 2 / on most systems.
LSHIFT ( x1 u -- x2 ) shifts bits of x1 u places to the left, putting zeros in the empty places on the right.
RSHIFT ( x1 u -- x2 ) shifts bits of x1 u places to the right, putting zeroes in the into empty places on the left.

Try:

0 DUP B. INVERT B.
1 DUP B. INVERT B.
-1 DUP B. INVERT B.

1 DUP B. 2* DUP B. .
-1 DUP B. 2* DUP B. .

2 DUP B. 2/ DUP B. .
-2 DUP B. 2/ DUP B. .

1 DUP B. 4 LSHIFT DUP B. .
1 DUP B. 8 LSHIFT DUP B. .

-1 DUP B. 4 LSHIFT DUP B. .
-1 DUP B. 8 LSHIFT DUP B. .

\ 2 to the nth power
: 2^  ( +n -- u )  1 SWAP LSHIFT ;

0 2^ U.
1 2^ U.
2 2^ U.
3 2^ U.
4 2^ U.
10 2^ U.

-1 DUP B. 4 RSHIFT DUP B. .
-1 DUP B. 8 RSHIFT DUP B. .

The following code uses several bit operators.

\ lights.f
\ Usage: S" lights.f" INCLUDED

VARIABLE LIGHTS  \ holds state of lights

\ Set nth bit only
: MASK  ( n1 -- n2 ) 1 SWAP LSHIFT ;

: LAMP  ( -- )  PAGE
   26  4 AT-XY  ." LIGHTS"
    4  7 AT-XY  0 16 0 ?DO  DUP 3 .R  1+  LOOP  DROP
   13  9 AT-XY  ." n ON/OFF/TOGGLE / BRIGHT/DARK/BYE " ;

: ON?  ( n1 -- n2|0 )  MASK LIGHTS @ AND ;

: LIGHT  ( -- )  ."  @ " ;
: .LIGHT?  ( u -- )  ON? IF LIGHT ELSE 3 SPACES THEN ;
: .LIGHTS  ( -- )
   5  6 AT-XY  0 16 0 ?DO  DUP .LIGHT?  1+  LOOP  DROP
   0 11 AT-XY  80 SPACES  0 10 AT-XY ;

\ Set individual lights
: ON  ( u -- )  MASK LIGHTS @ OR LIGHTS !  .LIGHTS ;
: OFF ( u -- )  MASK INVERT LIGHTS @ AND LIGHTS !  .LIGHTS ;
: TOGGLE ( u -- )  MASK LIGHTS @ XOR LIGHTS !  .LIGHTS ;

\ Set all lights
: DARK  ( -- )  FALSE LIGHTS !  .LIGHTS ;
: BRIGHT  ( -- )  TRUE LIGHTS !  LAMP .LIGHTS ;

BRIGHT

Bit data can extend beyond one cell. Try:

\ highest on-bit in x
: HI-BIT  ( x -- u )
   0 SWAP
   BEGIN DUP WHILE  1 RSHIFT  1 UNDER+  REPEAT  DROP ;

\ Number of bits in a cell
TRUE HI-BIT CONSTANT BITS/CELL

\ bit is the nth bit of cell
: BIT  ( bit - n cell )  BITS/CELL /MOD ;

\ Number of cells needed to hold bits
: BITS  ( bits - cells )  BIT SWAP IF 1+ THEN ;

\ Create bit array
: BIT-ARRAY  ( bits -- ) 
   CREATE             \ Name of array  
   DUP ,              \ Size in bits
   BITS CELLS ALLOT   \ Reserve space in memory

64 BIT-ARRAY LIGHT

\ Set nth bit only
: MASK  ( n1 -- n2 ) 1 SWAP LSHIFT ;

\ Individual bits
: >BIT  ( addr1 bit1 -- bit2 addr2 )  BIT CELLS ROT + CELL+ ;
: ON  ( addr bit -- )  >BIT >R  MASK R@ @ OR  R> ! ;
: OFF ( addr bit -- )  >BIT >R  MASK INVERT R@ @ AND  R> ! ;
: TOGGLE ( addr bit -- )  >BIT >R  MASK R@ @ XOR  R> ! ;
: ON?  ( addr bit -- flag )  >BIT >R  MASK R> @ AND 0<> ;

\ Set cells
: >CELL  ( addr1 cell -- addr2 ) 1+ CELLS + ;
: CELL-ON  ( addr cell -- ) >CELL  TRUE SWAP ! ;
: CELL-OFF  ( addr cell -- ) >CELL  FALSE SWAP ! ;
: CELL-TOGGLE ( addr cell -- ) >CELL DUP @ INVERT  SWAP ! ;

\ Set all bits
: @+  ( addr1 -- addr2 x )  DUP CELL+  SWAP @ ;
: !S  ( x addr1 u -- )  0 ?DO  2DUP !  CELL+  LOOP  2DROP ;

: DARK  ( addr -- )  FALSE SWAP @+ BITS !S ;
: BRIGHT  ( addr -- )  TRUE SWAP @+ BITS !S ; 

\ Inspect bit-array in memory
: MEMORY  ( bit-array -- )  @+ BITS CELLS DUMP ;

\ Display bit array
: SHOW  ( addr )  CR
   0 OVER @
   0 ?DO  2DUP ON? 1 AND 0 .R  1+  LOOP  2DROP ;

LIGHT BRIGHT
LIGHT MEMORY
LIGHT SHOW

LIGHT DARK
LIGHT MEMORY
LIGHT SHOW

LIGHT 0 CELL-ON
LIGHT MEMORY
LIGHT SHOW

LIGHT 0 CELL-OFF
LIGHT MEMORY
LIGHT SHOW

LIGHT 1 CELL-TOGGLE
LIGHT MEMORY
LIGHT SHOW

LIGHT 1 CELL-TOGGLE
LIGHT MEMORY
LIGHT SHOW

LIGHT 32 ON
LIGHT 32 ON? .
LIGHT SHOW

LIGHT 32 OFF
LIGHT SHOW
LIGHT 32 ON? .
LIGHT SHOW

LIGHT 32 TOGGLE
LIGHT 32 ON? .
LIGHT SHOW

LIGHT 32 TOGGLE
LIGHT 32 ON? .
LIGHT SHOW

BYE

Lesson 27 A Software Stack

I should discuss characters now, but I'm avoiding it. Actually, I should be doing my taxes now, but I'm avoiding that. I'm so depressed I'm not even going to give you any new ANS Forth words. Here's a stack word to play with. To create the stack, gives its maximum depth and its name. The name is needed when pushing, popping, displaying, and emptying. Stack depth is checked.

Try:

\ Software stack implementation

\ Two tools
\ Addition word
: UNDER+  ( a b c -- a+b c )  ROT + SWAP ;
\ Fetch from and advance address
: @+  ( addr -- addr+ x )  DUP CELL+ SWAP @ ;

\ Make a stack u cells deep
\ Usage u STACK <name>
: STACK  ( u -- )
   CREATE         \ stack <name>
   0 ,            \ initialize depth
   DUP ,          \ set maximum depth
   CELLS ALLOT ;  \ reserve space

\ I lied: 2@ is a new word.  It works like: DUP CELL+ @ SWAP @ 
\ Move x from the Forth stack to a stack
: PUSH  ( x stack-addr -- )
   DUP 2@ > IF  1 OVER +!  @+ CELLS + !  
            ELSE  DROP ." Stack full"  THEN ;

\ Move x from a stack to the Forth stack
: POP  ( stack-addr -- x )
   DUP @ ?DUP IF  1+ CELLS OVER + @  -1 ROT +!  
              ELSE  DROP ." Empty stack"  THEN ;

\ Display stack items, bottom --> top 
: .STACK  ( a -- )
   DUP @ ?DUP IF  2 CELLS UNDER+ 
                  0 ?DO  @+ .  LOOP DROP
              ELSE  DROP ." Empty stack"  THEN ;

\ Clear a stack
: EMPTY  ( a -- )  0 SWAP ! ;

5 STACK MINE

25 MINE PUSH

-25 MINE PUSH

MINE .STACK

MINE POP .

MINE EMPTY

BYE

Lesson 28 Characters A EMIT CHAR BL

Characters, like other data in a computer, are bit patterns treated in a particular way. An integer and a character can have the same bit pattern.

EMIT ( char -- ) displays char.
CHAR ( -- char ) puts the next char on the stack.
BL ( -- char ) puts the value for space (decimal 32) on the stack.

Try:

BL DUP . EMIT
BL DUP EMIT .
65 DUP . EMIT
CHAR A DUP . EMIT

Decimal 65 is the ASCII (American Standard Code for Information Interchange) and ISO IRV (International Standards Organization? International Reverence Version) code for 'A'. Both standards have the same character interpretation for codes 32-126 except for 36.

Try:

36 EMIT

If $ is displayed, it's ASCII.

Codes 0-31 are control codes with generally accepted meanings. Codes beyond 126 exist in other standards. We'll stick to 32-126. Here are characters 32-126 with the implementation defined character 127 thrown in.

Try:

\ Display next n characters and their codes
: .CHARS ( c n -- c+n )


  0 ?DO  DUP 5 .R  DUP SPACE EMIT  1+  LOOP ;

\ Display characters 32-127 and their codes
: .CHARS32-127 ( -- )
   BL 12 0 ?DO  CR 8 .CHARS  LOOP  DROP ;

.CHARS32-127

BYE

Lesson 29 Characters B [CHAR] KEY

To put a particular character in a definition, use [CHAR]. To wait for a character from the keyboard, use KEY.

[CHAR] <char> in a defintion puts char on the stack when the definition is executed.
KEY ( -- char ) waits for a key press, then puts the character pressed on the stack.

Try:

\ Is character Y or y?
: Y?  ( char -- flag )
   DUP [CHAR] Y =  SWAP [CHAR] y = OR ;

: YES?  ( -- )
   ." Press Y for yes."  KEY
   Y? IF  ." Yes"  ELSE  ." No"  THEN ;

YES?

\ KEY doesn't automatically EMIT
: PRESS  ( -- )
   ." Please press a key. " KEY
   ." You pressed " EMIT ;

PRESS

BYE

Lesson 30 Characters C C, C@ C! CHARS CHAR+ ALIGN

C, C@ C! CHARS CHAR+ are character versions of , @ ! CELLS CELL+. Although on the stack a character occupies a cell, in many Forths a character in memory occupies less space than a cell. Character codes 0-127 fit into 7 bits, and 8 bits (a byte) can hold codes 0-255. A Forth with 8-bit characters can therefore store two characters in 16 bits and 4 characters in 32 bits. Since , @ ! CELLS CELL+ are intended for cell-size values in memory, character versions are need for character-size values.

C, ( char -- ) reserves a character of memory and stores character-size value in it.
C@ ( c-addr -- char ) fetches char from c-addr.
C! ( char c-addr -- ) stores char at c-addr.
CHARS ( n1 -- n2 ) n1 chars fit into n2 address units.
CHAR+ ( c-addr1 -- c-addr2 ) advances c-addr1 by one character.

ALIGN assures proper placement of cell values. Use after C, and CHARS ALLOT.

Try:

CREATE CHARACTER  CHAR A C, CHAR  B C, ALIGN
CHARACTER C@ EMIT
CHARACTER CHAR+ C@ EMIT
CHAR Z CHARACTER C!
CHAR ! CHARACTER CHAR+ C!
CHARACTER C@ EMIT
CHARACTER CHAR+ C@ EMIT  

: C!+  ( c-addr char -- c-addr+ )  OVER C! CHAR+ ;
\ C@+ and EMITS we'll meet again as COUNT and TYPE
: C@+  ( c-addr -- c-addr+ char )  DUP CHAR+ SWAP C@ ;
: EMITS  ( c-addr n )  0 MAX  0 ?DO  C@+ EMIT  LOOP  DROP ;
CREATE 5CHARS 5 CHARS ALLOT ALIGN
5CHARS CHAR F C!+  CHAR o C!+  CHAR r C!+  CHAR t C!+  CHAR h C!+
DROP  \ drop the c-addr remaining after the last C!+
 
5CHARS 5 EMITS

BYE

Lesson 32 Strings 1 S" TYPE

A string is an array of characters. We already used ." and .( to display strings. To just define a string one can use:

S" text" ( -- a u ), used in a definition, compiles text up to but not including the next ", returning the character address c-addr and the length of the string u when the definition is executed. If the Forth system has file words, S" can be used outside a definition to put text in a temporary buffer that could be overwritten by the next use of S" outside a definition.

Try:

: HI  S" Ni hao!" ;
HI DUMP

\ If you have an ANS Forth system with file words:
S" Xiexie." DUMP
S" Bu keqi." DUMP

TYPE ( c-addr u -- ) displays the first u characters of a string, starting at address c-addr.

Try:

\ Assumes HI is defined
HI TYPE

\ Equivalent to : GOODBYE  ." Zaijian" ;
: GOOD-BYE  S" Zaijian" TYPE ;

GOOD-BYE

BYE

[To be continued....]

Copyright 1999 Leo Wong hello@albany.net

Forth

Hello