RetroForth is not -- nor is it intended to be -- ANS compliant. This tutorial is based on the one by Phil Burk of SoftSynth.com but has been substantially altered in order to reflect RetroForth rather than pForth or any other Forth.
The intent of this tutorial is to provide a series of experiments that will introduce you to the major concepts of Forth as implemented in RetroForth. It is only a starting point. Feel free to deviate from the sequences I provide. A free form investigation that is based on your curiosity is probably the best way to learn any language. Forth is especially well adapted to this type of learning. If you are only interested in learning ANS Forth, there are other tutorials which will serve your purpose better.
In the tutorials, I will print the things you need to type in monospaced font, and indent them. RetroForth is case-sensitive, unlike some other Forths; so the words which are built-in must be entered as shown, but words you create can be any combination of case you prefer.
At the end of each line, press the RETURN (or ENTER) key; this causes RetroForth to interpret what you've entered. You might also note that RetroForth doesn't prompt you with ok like most other Forths do.
WAKE.UP EAT.BREAKFAST WORK EAT.DINNER PLAY SLEEPNotice that WAKE.UP has a dot between the WAKE and UP. The dot has no particular meaning to the Forth compiler. I simply used a dot to connect the two words together to make one word, and to make that word easier for a human to read. Forth word's names can use any combination of letters, numbers, or punctuation. We will encounter words with names like:
." #s swap ! @ dup . *These are all called words. The word $%%-GL7OP is a legal Forth name, although not a very good one. It is up to the programmer to name words in a sensible manner. In general, Forth (and RetroForth in particular) give the programmer ultimate freedom to make whatever design decisions are appropriate, and does not get in the way of making bad decisions.
Now it is time to start RetroForth and begin experimenting. One of Forth's greatest strengths is its interactive, immediate nature.
The stack is initially empty. Start up RetroForth, and notice you are greeted by something like:
RetroForth Release 8 :: http://www.retroforth.org :: Build 912The interpreter is now awaiting your command. Let's start by putting some numbers on the stack. Type in:
Enter: .
You should see the last number you entered, 9182, printed. RetroForth has a very handy word for showing you what's on the stack. It is .s , which is pronounced "dot S". The name was constructed from "dot" for print, and "S" for stack. If you enter:
.syou will see your numbers 23 7, in a list. The number at the far right is the one on top of the stack. Notice that 9182 is not on the stack. The word ' . ' removes the number on top of the stack before printing it. In contrast, '.s' leaves the stack untouched.
Forth uses the stack to hold data being operated on, and it uses the stack to pass data from word to word. Essentially, a word takes whatever it needs from the stack, and puts whatever its results are on the stack. This is a very powerful aspect of Forth, but one which requires practice to understand. It also means that documenting what each word does to the stack is important and useful.
The standard technique for documenting the effect words have on the stack is by means of a stack diagram. Stack diagrams begin with a left-parenthesis, contain the "stack-effect diagram", and end with a right-parenthesis. In Forth, parentheses indicate a comment, and everything between them is ignored. So while you could put whatever you like between parentheses and treat them as ordinary comments, the usual use of parentheses is for stack-comments. For example, the stack-digram for the word 'dot' which we used before, would be:
. ( n -- )That is to say, ' . ' takes one word off the stack (the 'n') and puts nothing on the stack. In other words, it consumes the top stack item (hereafter called TOS).
In the examples that follow, you do not need to type in the comments. When you are programming, of course, liberal use of comments and stack diagrams may make your code more readable and maintainable. Besides the parenthesis, you may use the vertical-bar character | as comment to end-of-line. In other words, anything after the | on that line is ignored:
dup swap | This is all a comment
Between examples, you may wish to clear the stack. If you enter reset, the stack will be cleared. Since the stack is central to Forth, it is important to be able to alter it easily. Let's look at some more words that manipulate the stack. Enter:
777 dup .sYou will notice that there are two copies of 777 on the stack. The word dup duplicates TOS. This is useful when you want to use the TOS and still have a copy. The stack diagram for DUP would be:
DUP ( n -- n n )Another useful word is swap. Enter:
23 7 .s swap .sThe stack should have 7 23 now. The stack diagram for swap would be:
swap ( a b -- b a )Now enter:
over .sYou should see 23 7 23 . The word over causes a copy of the second item on the stack to leapfrog over the first. Its stack diagram would be:
over ( a b -- a b a )
Here is another commonly used Forth word:
drop ( a -- )
Can you guess what we will see if we enter:
drop .sAnother handy word for manipulating the stack is rot. Enter:
11 22 33 44 .s rot .sThe stack diagram for rot is, therefore:
rot ( a b c -- b c a )
You have now learned the more important stack manipulation words. You will see these in almost every Forth program. I should caution you that if you see too many stack manipulation words being used in your code then you may want to reexamine and perhaps reorganize your code. You will often find that you can avoid excessive stack manipulations by using variables, which will be discussed later. It is also likely that factoring your code -- that is, breaking it into smaller words -- may help reduce the stack juggling.
I have included the stack diagrams for some other useful stack manipulation words. Try experimenting with them by putting numbers on the stack and calling them to get a feel for what they do. Again, the text in parentheses is just a comment and need not be entered.
2drop | ( a b c -- a ) |
2dup | ( a b -- a b a b ) |
nip | ( a b c -- a c ) |
tuck | ( a b -- b a b ) |
-rot | ( a b c -- c a b ) |
The Forth arithmetic operators work on the numbers currently on top of the stack. If you want to add the top two numbers together, use the Forth word + , pronounced "plus". Enter:
2 3 + . 2 3 + 10 + .This style of expressing arithmetic operations is called Reverse Polish Notation, or RPN. It will already be familiar to those of you with HP calculators. In the following examples, I have put the algebraic equivalent representation in a comment.
Some other arithmetic operators are - * / . Enter:
30 5 - . | 25=30-5 30 5 / . | 6=30/5 30 5 * . | 150=30*5 30 5 + 7 / . | 5=(30+5)/7One thing that you should be aware of is that when you are doing division with integers using / , the remainder is lost. Enter:
15 5 / . 17 5 / .This is true in all languages on all computers. Later we will examine /mod and mod which do give the remainder.
We will make use of two new words, : ( "colon"), and ; ("semicolon") . These words start and end a typical Forth definition. Enter:
: AVERAGE ( a b -- avg ) + 2 / ;Congratulations! You have just written a Forth program. Let's look more closely at what just happened. The colon told Forth to add a new word to its list of words. This list is called the Forth dictionary. The name of the new word will be whatever name follows the colon. Any Forth words entered after the name will be compiled into the new word. This continues until the semicolon is reached which finishes the definition.
Let's test this word by entering:
10 20 AVERAGE . ( should print 15 )Once a word has been defined, it can be used to define more words. Let's write a word that tests our word.. Enter:
: TEST ( --) 50 60 AVERAGE . ; TESTTry combining some of the words you have learned into new Forth definitions of your choice. If you promise not to be overwhelmed, you can get a list of the words that are available for programming by entering:
wordsDon't worry, only a small fraction of these will be used directly in yourprograms.
53 10 /mod .s 7 5 mod .s
negate | ( a -- -a ) |
<< | ( a n -- (a<<n) ) |
>> | ( a n -- (a>>n) ) |
To convert this to Forth you must order the operations in the order of evaluation. In Forth, therefore, this would look like:
3 4 * 20 +Evaluation proceeds from left to right in Forth so there is no ambiguity. Compare the following algebraic expressions and their Forth equivalents: (Do not enter these!)
(100+50)/2 ==> 100 50 + 2/ ((2*7) + (13*5)) ==> 2 7 * 13 5 * +If any of these expressions puzzle you, try entering them one word at a time, while viewing the stack with .s
72 emit 105 emitYou should see the word "Hi" appear. 72 is an ASCII 'H' and 105 is an 'i'. emit takes the number on the stack and outputs it as a character. To get the ASCII value of a character, prepend the character with a single-quote. Enter:
'W . '% dup . emit 'A dup . 32 + emitThe use of the single-quote character is a bit unusual. It tells the RetroForth interpreter that the character that follows should be converted to the ASCII code representing it, rather than being considered a word. There are other such modifiers in RetroForth which can make inputting numbers simpler:
'a | Gives 97, the ASCII value of 'a' |
%1100 | Gives 12, or 1100 binary |
$ff | 255, or hexadecimal FF |
&010 | 8, or octal 10 |
#123 | 123 - decimal 123 |
Using emit to output character strings would be very tedious. Luckily there is a better way. Enter:
: TOFU ." Yummy bean curd!" ; TOFUThe word ." , pronounced "dot quote", will take everything up to the next quotation mark and print it to the screen. Make sure you leave a space after the first quotation mark. When you want to have text begin on a new line, you can issue a carriage return using the word cr . Enter:
: SPROUTS ." Miniature vegetables." ; : MENU cr TOFU cr SPROUTS cr ; MENUYou can emit a blank space with space . In other Forths one may output more than one space with the word spaces. Let's write one for RetroForth:
: spaces repeat space until ; TOFU SPROUTS TOFU space SPROUTS cr 10 spaces TOFU cr 20 spaces SPROUTSNotice that the new word we created, spaces, uses a "loop construct". The word repeat starts a series of words which will be run repeatedly. The word until subtracts one from TOS; if it's not zero then it jumps back to just after the repeat. We'll see more of these kinds of words later on.
For character input, Forth uses the word key which corresponds to the word emit for output. key waits for the user to press a key then leaves its value on the stack. Try the following.
: TESTKEY ( -- ) ." Hit a key: " key cr ." The ASCII value = " . cr ; TESTKEY[Note: On some computers, the input if buffered so you will need to hit the ENTER key after typing your character.]
load lib/files \ windows | if you're using Windows
load lib/files \ linux | if you're using Linux
| Sample Forth Code
| Author: your name : SQUARE ( n -- n*n ) dup * ; : TEST.SQUARE ( -- ) cr ." 7 squared = " 7 SQUARE . cr ;
Now save the file to disk as 'test.f'.
The text following the | character is treated as a comment. Note that in ANS Forth, this would be the \ character. If you were writing in Basic, the equivalent would be "REM". In C it would be /* .... */, and in C++ it would be // ... The text between parentheses is also a comment.
Using this file is trivial:
include test.f TEST.SQUAREThe word include means to read in and interpret words from a file, rather than from the keyboard. After the file has been read, input returns to the keyboard.
If you have a big project that needs lots of files, you can have a file that will load all the files you need. It is also possible to write blockfiles, but that is beyond the scope of this tutorial.
variable MY-VARThis created a variable named MY-VAR . A space in memory is now reserved to hold its 32-bit value. The word VARIABLE is what's known as a "defining word" since it creates new words in the dictionary. Now enter:
MY-VAR .The number you see is the address, or location, of the memory that was reserved for MY-VAR. To store data into memory you use the word ! , pronounced "store". It looks like an exclamation point, but to a Forth programmer it is the way to write 32-bit data to memory. To read the value contained in memory at a given address, use the Forth word @ , pronounced "fetch". Try entering the following:
513 MY-VAR ! MY-VAR @ .This sets the variable MY-VAR to 513 , then reads the value back and prints it. You can also create a variable and set its value at the same time:
513 variable: MY-VAR2 MY-VAR2 @ .The stack diagrams for these words follows:
@ | ( addr -- val ) |
! | ( val addr -- ) |
variable | ( [name] -- ) |
variable: | ( val [name] -- ) |
Imagine you are writing a game and you want to keep track of the highest score. You could keep the highest score in a variable. When you reported a new score, you could check it against the highest score. Try entering this code in a file as described in the previous section:
variable HIGH-SCORE : REPORT.SCORE ( score -- , print out score ) dup cr ." Your Score = " . cr HIGH-SCORE @ max ( calculate new high ) dup ." Highest Score = " . cr HIGH-SCORE ! ( update variable ) ;Save the file to disk, then load this code using the include word. Test your word as follows:
123 REPORT.SCORE 9845 REPORT.SCORE 534 REPORT.SCOREThe Forth words @ and ! work on 32-bit quantities. Some Forths are "16-bit" Forths. They fetch and store 16-bit quantities. Forth has some words that will work on 8 and 16-bit values. c@ and c! work on characters which are usually for 8-bit bytes. The 'c' stands for "Character" since ASCII characters are 8-bit numbers.
A word of warning about fetching and storing to memory: You have now learned enough about Forth to be dangerous. The operation of a computer is based on having the right numbers in the right place in memory. You now know how to write new numbers to any place in memory. Since an address is just a number, you could, but shouldn't, enter:
73 253000 ! ( Do NOT do this. )The 253000 would be treated as an address and you would set that memory location to 73. I have no idea what will happen after that, maybe nothing. This would be like firing a rifle through the walls of your apartment building. You don't know who or what you are going to hit. Since you share memory with other programs including the operating system, you could easily cause the computer to behave strangely, even crash. Don't let this bother you too much, however. Crashing a computer, unlike crashing a car, does not hurt the computer. You just have to reboot. The worst that could happen is that if you crash while the computer is writing to a disk, you could lose a file. That's why we make backups. This same potential problem exists in any powerful language, not just Forth. This might be less likely in BASIC, however, because BASIC protects you from a lot of things, including the danger of writing powerful programs.
: MAX_CHARS 128 ; MAX_CHARS .We just defined a word called MAX_CHARS that returns the value on the stack when it was defined. It cannot be directly changed unless you edit the program and recompile. Using constant can improve the readability of your programs and reduce some bugs. Imagine if you refer to the number 128 very often in your program, say 8 times. Then you decide to change this number to 256. If you globally change 128 to 256 you might change something you didn't intend. If you change it by hand you might miss one, especially if your program occupies more than one file. Using constant will make it easy to change. The code that results is equally as fast and small as putting the numbers in directly. I recommend defining a constant for almost any number used more than two or three times.
: FALSE 0 ; : TRUE -1 ; : = - not ;
Now try:
23 71 = . 18 18 = .You will notice that the first line printed a 0, or FALSE, and the second line a -1, or TRUE. The equal sign in Forth is used as a question, not a statement. It asks whether the top two items on the stack are equal. It does not set them equal. In other Forths you could ask other questions that you can ask, like < or >. RetroForth doesn't have these words (or =) because they are almost always coupled with an "if" statement. RetroForth therefore has special forms of "if", which are used directly with a condition. In California, the drinking age for alcohol is 21. You could write a simple word now to help bartenders. Enter:
: DRINK? ( age -- flag ) 20 >if ." OK" cr ;; then ." Underage!" cr ; 20 DRINK? 21 DRINK? 43 DRINK?Here you are introduced to the if/;;/then structure of Forth conditional statements. Other useful "if" constructs are:
<if | If second stack item is less than TOS |
=if | Two top stack items are equal |
<>if | Top two stack items are not equal |
;; | Exit the current word |
Most loops begin with the word repeat. The first kind of loop is between repeat ... until. This loops until a condition is true. Try this:
: COUNTDOWN ( N -- ) repeat dup . cr | print number on top of stack until | if TOS is not zero, decrement and goto repeat. ; 16 COUNTDOWNThis word will count down from N to zero. The second type of loop RetroForth has is an "unconditional" loop, repeat ... again. This keeps going until you break out of it, perhaps by using ;; or pressing Ctrl-C :
: MAIN-LOOP repeat ." looping again ..." cr again ;
Consider the following word for doing character graphics. Enter:
: PLOT# ( n -- ) repeat '- emit until ; cr 9 PLOT# 37 PLOT#
RetroForth also has a for ... next construct similar to ANS Forth. It is used like this:
: looper 10 for ." Iteration #" r . cr next ;If you type looper you will see 10 lines, starting with "Iteration #10".
The "normal" string is a Forth string, consisting of an address,count pair. That is, it is represented on the stack by an address which points to the start of the character data, and a count of characters. In stack diagrams it is often listed as ( a n -- ). To create such a string, you may use a double-quote character, " . That word parses until if finds another double-quote, and then it puts the address, count on the stack. Inside a colon-definition, it compiles a reference to that string data so that at runtime, the string data will appear on the stack.
" This is a string" type cr : STR s" Hi there!" ; STR type crThe word type seen above, prints out the Forth string on top of the stack.
The second type of string supported by RetroForth is the "ASCIIZ" string. That is, a string which is terminated by a NUL byte (ASCII-ZERO). These are the native strings for C, and both Windows and Linux API functions expect such strings. Use the word zt to temorarily convert a Forth string to an ASCIIZ string.
" RetroForth rocks!" zt | Make an ASCIIZ string
When using " inside a colon-definition, the strings will be compiled
to a separate area which by default is 8K in size. So any "compiled" string will
be physically separate from the "temporary" string area, and there is no danger
of overwriting them (unless you have a bug in your code...). If you try to
compile more than 8K of string data, RetroForth will likely crash. You
can allocate mores space by doing: here st0 ! xxxx allot
where
xxxx is the amount of memory to allocate.
Forth makes it very easy to explore different numeric bases because it can work in any base. Try entering the following:
decimal 6 binary . 1 1 + . 1101 decimal .Another useful numeric base is hexadecimal. which is base 16. One problem with bases over 10 is that our normal numbering system only has digits 0 to 9. For hex numbers we use the letters A to F for the digits 10 to 15. Thus the hex number 3E7 is equal to (3*256 + 14*16 + 7*1). Try entering:
decimal 12 hex . | print C decimal 12 256 * 7 16 * + 10 + .s dup binary . hex .A variable called base is used to keep track of the current numeric base. The words hex , decimal , and binary work by changing this variable. You can change the base to anything you want. As mentioned before, certain modifier characters allow you to enter decimal, hex, binary or octal (base 8) numbers no matter what the current base is. Try:
7 base ! 6 1 + . base @ . | surprise!You are now in base 7 . When you fetched and printed the value of BASE, it said 10 because 7, in base 7, is 10.