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 addressMINE
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 1010 is TOS:
.SNow try:
2020 is TOS, and 10 is below 20:
.SNow try:
DROPThe word
DROP
removed 20 from the stack. TOS is again 10:
.SNow try:
DROPThe 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 SEE
s.
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 DROPForth 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 DUP
licate 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 ROT
ate 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 CODE
d 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: redefineFAM
using3DUP
. 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.
-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.
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 LOOPYn 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 GAMEHomework: 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 .RHomework: 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:
?DO (DO) ... LOOP (+LOOP)
cannot be accessed within the loop.
?DO (DO) ... LOOP (+LOOP)
must be taken back before leaving the loop.
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.
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 ALLOT
ed, 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 CREATE
d (that is, named) and ALLOT
ed 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.
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 PUSH
ing, POP
ing, displaying, and EMPTY
ing. 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.
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 -- )
FILL
s 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 fidand 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
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 VALUE
s.
y +TO
name, available in some Forths, increments by y the value returned by name. Again, the usage is the same as that for VALUE
s.
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