Hello

Forth

Simple Forth

Rudiments of a Programming Language

2 January 2004 +

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 ERASE 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 2@ 2!
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
32. Strings 2 COUNT MOVE
33. Strings 3 /STRING FILL BLANK -TRAILING
34. Strings 4 SEARCH COMPARE
35. Values VALUE TO
36. Locals LOCALS| TO

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.

Forth is inherently interactive. 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 entering into a give-and-take with the computer. 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 programmer. 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 -1 or (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(=00) and true(<>false)
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,536 different patterns, each of which could represent:
an unsigned integer between 0 and 65,535
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 65535 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
FALSE .

\ = returns a true flag if the top two stack items are equal
FALSE FALSE = CONSTANT TRUE
TRUE .

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 ;

BYE

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 definition of INTEGERS:

Try:

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

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

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 

Notice that an integer ("1") is displayed even when 0 INTEGERS is called for.

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
0 10 -1 LOOPY

10 0 2 LOOPY
0 10 -2 LOOPY

10 0 3 LOOPY
0 10 -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 DUP . ." 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 ;

\ May take between 1 and 3 sticks, leaving at least 1 
: LEGAL  ( sticks try -- sticks taken )
   OVER 1- 3 MIN  1 SWAP CLAMP ; 

\ My play
: PROGRAMMER  ( sticks try - left )  LEGAL LEFT ;

\ 1 Round
: STICKS  ( sticks try -- left| )  PROGRAMMER COMPUTER ;
\ Alias for STICKS
: STICK  ( sticks 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 ERASE 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
BLACKBIRDS 24 CELLS DUMP

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

ERASE ( a u -- ) sets the contents u addresses starting from a to zero.

Try:

BLACKBIRDS 24 CELLS ERASE
BLACKBIRDS 24 CELLS DUMP

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+ ;

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

BYE

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 )  \ nonzero flag means a leap year
   DUP 4 MOD 0=  OVER 100 MOD AND
   SWAP 400 MOD 0= OR ;

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

2000 LEAP?
 
\ A word can be saved 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 ;

\ Using EXIT skips some tests for most years
: LEAPX  ( year -- flag )
   DUP 4 MOD IF DROP FALSE EXIT THEN
   DUP 100 MOD SWAP 400 MOD 0= OR ;

: -LEAPX  ( year -- flag )
   DUP 4 MOD IF EXIT THEN
   DUP 100 MOD 0= SWAP 400 MOD AND ;

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 2@ 2!

For an excursion, here are some words for software stacks. Each stack element occupies a cell; stack depth is checked. To create a stack, specify its maximum depth and its name. The name is used when PUSHing, POPing, displaying, and EMPTYing. For an implementation of stacks with elements of arbitrary size, see Len Zettel's User Stacks in Forth. And for a use of such user stacks see Len's Discrete Event Simulation in Forth.

First, two words to add to your repertory:

2! ( x1 x2 addr -- ) does SWAP OVER ! CELL+ !
2@ ( addr -- x1 x2 ) does DUP CELL+ @ SWAP @

2! and 2@ are typically used to store and fetch "double numbers", which are integers 2 cells wide. In our software-stack implementation, we'll use 2@ to fetch values from the two addresses starting at addr.

Try:

\ Software stack implementation

\ Two addition words
: ++  ( addr -- )   1 SWAP +! ;
: --  ( addr -- )  -1 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

\ If there's room on stack, move x from the Forth stack to it
\ else leave x on the Forth stack
: PUSH  ( x stack -- |x )
   DUP 2@ >
   IF  DUP ++  @+ CELLS + !
   ELSE  DROP ." Stack full"  THEN ;

\ If there's an x, move it from stack to the Forth stack
\ else put nothing on the Forth stack
: POP  ( stack -- x| )
   DUP @+ ?DUP
   IF  ROT --  CELLS + @
   ELSE  DROP ." Empty stack"  THEN ;

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

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

3 STACK MYSTACK

33 MYSTACK PUSH

-33 MYSTACK PUSH

MYSTACK .STACK

MYSTACK POP .

MYSTACK 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 definition 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 31 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. Using S" in a definition is an easy way to create a string constant.

Try:

: HI  S" Ni hao!" ;
HI CHARS DUMP

If your ANS Forth system has file words, S" can also 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. This S" also returns an address and a length. I'll assume that you can use S" outside a definition.

Try:

S" Xiexie."  .S CHARS DUMP
S" Bu keqi." .S CHARS 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

Lesson 32 Strings 2 COUNT MOVE

Since most string functions use the starting address of a string and the string's length, it's convenient to store the length along with the string. In Forth, a string that has its length in a character-sized address and the string itself in the following character addresses is called a counted string. For example, the counted string "Leo" is: 3 'L' 'e' 'o'. The word that takes the address of a counted string and returns the address and length of the string is:

COUNT ( c-addr1 -- c-addr2 u ) returns the address c-addr2 of the first character and the number of characters u in the counted string at c-addr1.

COUNT is effectively DUP CHAR+ SWAP C@. It is used for other things besides getting the start and "count" of a counted string and would have been better named C@+.

Since the "count" is stored in a character-sized space, the maximum length of a counted string can't be larger than the largest number that space can hold. For a Forth with 8-bit characters, the maximum length of a counted string is 255. However, longer strings are easy to accommodate in Forth.

The easiest way to create a counted string is to MOVE a string ( c-addr u ) into data space.

MOVE ( addr1 u addr2 -- ) copies the contents of u consecutive address units at addr1 to the u consecutive address units at addr2.

Since MOVE uses address units, it can be used for any type of data. The two Forth words CMOVE and CMOVE> specifically copy characters, but we'll just use MOVE and specify that we are moving CHARS.

I'm getting tired of typing addr c-addr char in comments, so from now on I'll just write a ca c. Also, I'll continue to write ANS Forth words in uppercase but start writing non-ANS Forth words in lowercase.

`s`

Try:

CREATE my-string  50 CHARS ALLOT
\ Copy ca1 u as a counted string to ca2
: place  ( ca1 u ca2 -- )
   2DUP 2>R  CHAR+ SWAP CHARS MOVE  2R> C! ;

S" The quick brown fox jumped over the lazy dog."
my-string place
my-string COUNT TYPE

\ Illustrating COUNT as C@+
: my-type  ( ca u -- )  0 ?DO COUNT EMIT LOOP DROP ;
my-string COUNT my-type

BYE

Lesson 33 Strings 3 /TRAILING FILL BLANK -TRAILING

A penknife for strings is:

/STRING ( ca1 u1 n -- ca2 u2 ) returns ca2 = ca1+nchars, u2 = u1-n. /STRING is officially "slash string"; a nicer name would be "cut string". /STRING could be defined:
: /STRING ( ca1 u1 n -- ca2 us ) TUCK - >R CHARS + R>

Try:

\ Three words useful in string processing.
\ Skip leading c's in ca u; return remaining string ca' u'
: skip  ( ca u c -- ca' u' )
   >R
   BEGIN DUP WHILE OVER C@ R@ = WHILE 1 /STRING REPEAT THEN
   R> DROP ;

\ Look for 1st c in ca u; return ca' u' including c
: scan  ( ca u c -- ca' u' )
   >R
   BEGIN DUP WHILE OVER C@ R@ <> WHILE 1 /STRING REPEAT THEN
   R> DROP ;

\ From ca u, return 1st "word" ca2 u2 and remainder ca1 u1
\ WORD is a Forth word, so I'll call this:
: "word"  ( ca u -- ca1 u1 ca2 u2 )
   BL skip  2DUP 2>R  BL scan  DUP 2R> ROT - ;

\ Illustration using word
: list-words  ( ca u -- )
   BEGIN DUP WHILE "word" CR TYPE REPEAT 2DROP ;

S" To be or not to be" list-words

FILL BLANK and -TRAILING are also handy. I give them and possible Forth definitions.

FILL ( ca u c -- ) stores c in u consecutive characters of memory beginning at ca.

: FILL ( ca u c -- ) ROT ROT 0 ?DO 2DUP C! CHAR+ LOOP 2DROP ;

BLANK ( ca u -- ) FILLs with spaces.
: BLANK ( ca u -- ) BL FILL ;

-TRAILING ( ca u1 -- ca u2 ) returns ca u1 minus any trailing spaces. "Dash trailing".

: <skip  ( ca u1 c -- ca u2 )
   >R
   BEGIN DUP WHILE 1- 2DUP CHARS + C@ R@ <> UNTIL 1+ THEN
   R> DROP ;
: -TRAILING ( ca u1 -- ca u2 > BL <skip ;

Try:

CREATE BUFFER 40 CHARS ALLOT
BUFFER 40 CHAR Y FILL
BUFFER 40 TYPE
BUFFER 20 CHARS + 20 BLANK
BUFFER 40 TYPE
BUFFER 40 -TRAILING TYPE
BUFFER 20 BLANK
BUFFER 40 TYPE
BUFFER 40 -TRAILING TYPE

BYE

Lesson 34 Strings 4 SEARCH COMPARE

String searches and compares are done with SEARCH and COMPARE:

SEARCH ( ca1 u1 ca2 u2 -- ca3|ca1 u3|u1 true|false ) searches ca1 u1 for the string specified by ca2 u2. If found, returns address ca3 of first match, characters remaining u3, and true, else returns ca1 u1 false.

COMPARE ( ca1 u1 ca2 u2 -- -1|0|1 ) compares ca1 u1 and ca2 u2 and returns -1 if ca1 u1 is less than ca2 u2, 0 if the two strings are the same, and 1 if ca1 u1 is greater than ca2 u2. (See the examples for what is meant by less, same, and greater.)

SEARCH and COMPARE are case sensitive.

Try:

: s1  S" Out out out!" ;
s1 s" Out" SEARCH . TYPE
s1 S" out" SEARCH . TYPE
s1 s" out!" SEARCH . TYPE
s1 S" OUT"  SEARCH . TYPE

\ Char is lowercase?
: lower?  ( c -- t|f)  [CHAR] a - 26 U< ;
\ Make char uppercase
: upper  ( c -- c' )  DUP lower? BL AND XOR ;
\ Make string uppercase
: supper  ( a u -- )
   0 ?DO DUP C@ upper OVER C! CHAR+ LOOP DROP ;

s1 2DUP supper S" ouT" 2DUP supper SEARCH . TYPE

: outs?  ( a u -- n )
   2DUP supper  0 >R
   BEGIN S" OUT" SEARCH
   WHILE R> 1+ >R  1 /STRING
   REPEAT  2DROP
   R> ;

S" out spout mouth" outs? .

: ABC?  S" ABC" COMPARE . ;
S" ABC" ABC?
S" abc" ABC?
S" AB" ABC?
S" ABCD" ABC?
S" B" ABC?
S" @ABC" ABC?
S" abc" 2DUP supper ABC?

\ Compare for integers
: sgn  ( n - -1|0|1 )
   -1 MAX 1 MIN ;
: ncompare  ( n1 n2 -- -1|0|1 )
   - sgn ;

-10 sgn .
0 sgn .
10 sgn .

10 5 ncompare .
5 5 ncompare .
-10 5 ncompare .

BYE

Lesson 35 Values VALUE TO

In some programs a value is set once or infrequently and used often. For example, many words that have to do with files require a file identifier (fid) that is returned when the file is created or opened. The fid could easily be stored in a variable:

VARIABLE fid
and fetched whenever needed:
fid @
Many Forths provide a cross between a constant and the variable that is called a value. It might be used as follows:
0 VALUE fid      \ Define fid as a value set to 0
...
( u -- ) TO fid  \ Set fid to u
...
fid ( -- u )     \ Return u

x VALUE name defines the word name that returns x unless the value to be returned is changed by TO.

y TO name makes name return y.

y +TO name, available in some Forths, increments by y the value returned by name.

Try:

FALSE VALUE love
love FALSE = .
love love = TO love
love TRUE = .

BYE

For a less sappy use of VALUE and a preview of file words, see Fans.

Lesson 36 Locals LOCALS| TO

A named VARIABLE or VALUE can appear in several parts of a program: its possible use is "global" within the program. Forths that implement "locals" also provide for value names that apply only within the word they are defined in.

Using locals can prevent the bug in which a change in a variable in one part of the program has unintended consequences in another part. Locals can also make a definition easier to write and to read by making values explicit and reducing stack manipulation. Here are definitions of vswap without and with locals:

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

\ Exchange values of two variables
\ With locals
: vswap2  ( addr1 addr2 -- )
    LOCALS| v1 v2 |
    v1 @ v2 @ v1 ! v2 ! ;

LOCALS| allows the creation of up to eight (in some Forths more than eight) names as local identifiers. The list of names ends with |. Given 0 1 LOCALS| a b |, a returns 1 and b returns 0. (Some Forths implement a word that allows the values and the names to be placed in the same order.)

y TO name makes name return y. The usage is the same as that for VALUEs.

y +TO name, available in some Forths, increments by y the value returned by name. Again, the usage is the same as that for VALUEs.

I wrote the following after trying to explain to my daughter how to find out if a given integer is a prime number.

\ prime.f LW 10 Jan 02002 +

\ An integer is a prime number if it can be evenly divided
\ only by itself and 1.  Integers that are not prime numbers
\ are composite numbers, divisible without a remainder by
\ numbers (factors) other than itself and 1.
\ We want to find out if a given integer n is prime or
\ composite.

\ Two English words for easier reading code.
: not  ( ? -- ? )  0= ;
: between  ( n1 n2 n3 -- ? )  1+ WITHIN ;

\ If n is evenly divisible by f, then f is a factor of n.
\ Is f a factor of n?
: factor  ( n f -- ? )  MOD 0= ;

\ 2 is a factor of even numbers.
: even  ( n -- ? )  2 factor ;

\ A composite number has at least one factor less than
\ or equal to its square root, so we only try f's whose
\ squares are less than or equal to n.
: square  ( f -- f*f )  DUP * ;

\ Find out if n has a factor between 3 and its square root
: factorable  ( n -- ? )
   3 LOCALS| f n |   \ Start with f = 3
   BEGIN 
     n f factor not WHILE  \ Continue if f not a factor of
     f 2 + TO f            \ make f the next odd number
     n f square < not WHILE  \ Continue if f squared <= n
   REPEAT THEN
   n f factor ;
   
: prime?  ( +n -- )
   LOCALS| n |
   n 1 3 between IF ." prime"     \ 1, 2, 3 : prime
   ELSE n even IF  ." composite " \ Even & > 2 : composite
   ELSE n factorable IF ." composite"  ELSE ." prime" THEN 
   THEN THEN ;

A third example of using locals multiplies complex numbers.
\ complex multiply by ward mcfarland
: ComplexMultiply ( x1\y1\x2\y2 -- xp\yp )
\ calculate (x1 + j*y1) * (x2 + j*y2) 
\            = (x1*x2 - y1*y2) + j(x1*y2 + x2*y1)
   locals| y2 x2 y1 x1 |
   x1 x2 *
   y1 y2 *  -
   x1 y2 *
   x2 y1 *  +
;

Without locals a solution might be:

: cm  2OVER 2OVER ROT ROT * >R * >R ROT * >R * R> - 2R> + ;

[To be continued....]

1999-2002 Leo Wong hello@albany.net

Forth

Hello