Reva: Tutorial
Reva Forth Tutorial

Introduction

Reva is a derivative of RetroForth, and is not -- nor is it intended to be -- ANS Forth compliant. This tutorial is based on the one by Phil Burk, the author of pForth, but has been substantially altered in order to reflect Reva rather than pForth or any other Forth.

The intent of this tutorial is to provide a series of experiments which will introduce you to the major concepts of Forth as implemented in Reva. It is only a starting point. Feel free to deviate from the sequences provided. A free form investigation 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. Reva 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 Reva to interpret what you've entered. You might also note that Reva prompts you with ok> as other Forths do, when it is awaiting input from you.

Forth Syntax

Forth has the simplest syntax of any computer language. What minimal syntax it has can be summarized as follows: "Forth code is a bunch of words with spaces between them." This is even simpler than English! Each word is equivalent to a function or subroutine in a language like 'C'. "Words" are executed in the order they appear in the code. The following statement, for example, could appear in a Forth program:

WAKE_UP EAT_BREAKFAST WORK EAT_DINNER PLAY SLEEP

Notice that WAKE_UP has a dash between the WAKE and UP. The dash has no particular meaning to the Forth compiler. I simply used a dash 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. Note: In Reva, "dot" may have a special meaning. See Contexts for more information. In general, however, any character but space may be used to name words. We will encounter words with names like:

." #s swap ! @ dup . *

These are all called words. The word $%%-GL7OP is also a legal Forth name, although probably not a very good one. It is up to the programmer to name words in a sensible manner. In general, Forth (and Reva 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. Don't worry though: if you enter a word that Reva doesn't recognize like $%%-GL7OP Reva will respond $%%-GL7OP? which is its rather terse way of telling you that it has no idea what you mean.

Essentially, the Forth interpreter simply grabs the next word in its input, and looks it up in the dictionary. If it finds the word there, it executes it; if it doesn't, it tries to interpret it as a number given the current numeric base (and some other rules we'll see later). If it still is unable to understand the word, it complains as mentioned before (after giving the user programmatic control first).

Note that a word may take over the parsing of the rest of the input. For instance, as you will see below, colon, : , starts to compile the text following it into a new entry in the dictionary until it encounters a semicolon, ; . There are many such parsing words, though most words in Reva simply process input and generate output.

Now it is time to start Reva and begin experimenting. One of Forth's greatest strengths is its interactive, immediate nature.

Stack Manipulation

The Forth language is based on the concept of a stack. Imagine a stack of blocks with numbers on them. You can add or remove numbers from the top of the stack (TOS). You can also rearrange the order of the numbers. Forth uses several stacks. The data stack is the one used for passing data between Forth words so we will concentrate our attention there. The return stack is another Forth stack that is primarily for internal system use but is often used to store temporary values. In this tutorial, when we refer to the "stack," we will be referring to the data stack. For reference, we'll call the return stack "RS".

The stack is initially empty. Start up Reva, and notice you are greeted by something like:

 Reva a.b.c Windows

ok>

The interpreter is now awaiting your command. Let's start by putting some numbers on the stack. Type in:

 ok> 23 7 9182

Excellent! Now print the number on top of the stack using the Forth word ., which is pronounced "dot". This is a hard word to write about in a manual because it is just a single period.

Enter:

 ok> .

You should see the last number you entered, 9182, printed.

Reva has a very handy word for showing you what's on the stack. It is .s , which is pronounced "dot ess". The name was constructed from "dot" for print, and "s" for stack. In Reva, .s is defined to show only the topmost ten stack items, but the actual number of stack items is given in brackets.

Try entering:

 ok> .s

you 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 . removed 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 to pass data between words. Essentially, a word takes whatever it needs from the stack, and puts whatever it wants 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 (called stack effect) is important and useful.

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:

 ok> 777 dup .s

You 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:

 ok> 23 7 .s
 ok> swap .s

The stack should look like: 7 23 now. The stack diagram for swap would be:

 swap ( a b -- b a )

Now enter:

 ok> over .s

You 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:

  ok> drop .s

Another handy word for manipulating the stack is rot. Enter:

 ok> 11 22 33 44 .s
 ok> rot .s

The 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.

To get any arbitrary item on the stack, use pick . Try entering:

 ok> 14 13 12 11 10
 ok> 3 pick . ( prints 13 )
 ok> 0 pick . ( prints 10 )
 ok> 4 pick . ( prints 14 )

pick makes a copy of the Nth item on the stack. The numbering starts with zero, therefore 0 pick is equivalent to dup, and 1 pick is equivalent to over.

pick's stack diagram looks like this:

  pick ( ... v3 v2 v1 v0 N -- ... v3 v2 v1 v0 vN )

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 )
 2swap ( a b c d -- c d a b )
 2over ( a b c d -- a b c d a b )
 nip ( a b c --  a c )
 tuck ( a b -- b a b )
 rot ( a b c -- c a b )

Problems

Start each problem by entering:
 reset 11 22 33

Then use the stack manipulation words you have learned to end up with the following numbers on the stack:

  1. 11 33 22 22
  2. 22 33
  3. 22 33 11 11 22
  4. 11 33 22 33 11
  5. 33 11 22 11 22

(Answers can be found at the end of this tutorial.)

Arithmetic

Simply moving numbers around on a stack can be a lot of fun. Eventually, however, you'll want to do something useful with them. This section describes how to perform arithmetic operations in Forth.

The first thing you should know is that Reva can be extended to recognize many kinds of numbers, but recognizes two kinds of numbers already: "single" and "double" . A "single" is any value that can fit in the native "cell" size, which is 32 bits. In other words, a "single" can be any value from 0 through 232, or about 4 billion. A "double" can be any value from 0 through 264 or about 1.8e19 - a really huge number.

The second thing you should know is that Reva doesn't do any kind of verification on your behalf. If you use a "double" math word when there is only a "single" on the stack, you will not get a result with which you would be happy. To get a double you can put a 'L' as the last character in the value, or you can use the word s>d to convert a single to a double. You will need the 'doubles' library to do double-cell math, since it is not part of Reva to begin with.

The third thing you should know is that Reva doesn't care about signedness. In other words, treating the value on the stack as "-1" rather than "4294967295" depends on the word you apply to it; the value itself has no intrinsic value to Reva. This also means that a "character" can be dealt with as a number (the ASCII code corresponding to the character) without doing any 'conversion' on the value first. This is a strong point of Forth (and Reva) but it also puts the onus for "doing the right thing" on the programmer.

The fourth thing you should know is that Reva has strict ideas on what constitutes a number, and those ideas are different from ANS Forth. Any string of digits (valid in the current "base") will be interpreted as a positive "single". If a minus sign ("-") precedes the number, it will be interpreted as negative. If a capital "L" is the last character in the number, it will be interpreted as a "double". No punctuation is permitted inside a "single" or "double" (but a period is required inside a "float", which we will not cover here).

From here on, we'll assume all "numbers" are "single" values. Some of the single-math words have double equivalents, but not all of them do.

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:

 ok> 2 3 + .
 ok> 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:

 ok> 30 5 - .
 25  | 30 - 5
 ok> 30 5 / .
 6   | 30 / 5
 ok> 30 5 * .
 150 | 30 * 5
 ok> 30 5 + 7 / .
 5   | (30 + 5) / 7

An important fact to keep in mind is that when you are doing division with integers using / , the remainder is lost. Enter:

 ok> 15 5 / .
 ok> 17 5 / .

This is true in all languages on all computers. Later we will examine /mod and mod which do give the remainder.

Defining a New Word

It's now time to write a small program in Forth. You can do this by defining a new word that is a combination of words we have already learned. Let's define and test a new word that takes the average of two numbers.

We will make use of two new words, : ( "colon"), and ; ( "semicolon") . These words start and end a typical Forth definition. Enter:

 ok> : 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 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 new word by entering:

 ok> 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:

 ok> : TEST ( --) 50 60 AVERAGE . ;

Try 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:

 ok> words

Don't worry, only a small fraction of these will be used directly in your programs. As an enhancement, you can type some text after words and only those words which contain that text in their name, will be printed:

 ok> words dup

Will print something like

 3dup ?dup dup 2dup
 4 words

More Arithmetic

When you need to know the remainder of a divide operation. /mod will return the remainder as well as the quotient, whereas mod will only return the remainder. Enter:

 ok> 53 10 /mod .s
 ok> 7 5 mod .s

Two other handy words are min and max . They accept two numbers and return the MINimum or MAXimum value respectively. Try entering the following:

 ok> 56 34 max .
 ok> 56 34 min .
 ok> -17 0 min .

Some other useful words are:

 abs ( a -- abs(a) )
 negate ( a -- -a )
 invert ( a -- ~a ) | Invert each bit
 << ( a n -- (a<<n) )
 >> ( a n -- (a>>n) )

Arithmetic Overflow

If you are having problems with your calculation overflowing the 32-bit precision of the stack, then you can use */ . This produces an intermediate result that is 64 bits long. Try the following three methods of doing the same calculation. Only the one using */ will yield the correct answer, 5197799.

 ok> 34867312 99154 * 665134 / .
 ok> 34867312 665134 / 99154 * .
 ok> 34867312 99154 665134 */ .

Convert Algebraic Expressions to Forth

How do we express complex algebraic expressions in Forth? For example: 20 + (3 * 4)?

To convert this to Forth you must order the operations in the order of evaluation. In Forth, therefore, this would look like:

 ok> 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 .

Problems:

Convert the following algebraic expressions to their equivalent Forth expressions. (Do not enter these because they are not Forth code!)

  1. (12 * (20 - 17))
  2. (1 - (4 * (-18) / 6))
  3. (6 * 13) - (4 * 2 * 7)

Use the words you have learned to write these new words:

  1. SQUARE ( N -- N*N )
  2. DIFF.SQUARES ( A B -- A*A-B*B )
  3. AVERAGE4 ( A B C D -- (A+B+C+D)/4 )
  4. HMS>SECONDS ( HOURS MINUTES SECONDS -- TOTAL-SECONDS )

(Answers can be found at the end of this tutorial.)

Character Input and Output

Because Forth is not a typed language, the numbers on top of the stack can represent anything. The top number might be how many blue whales are left on Earth or your weight in kilograms. It might also be an ASCII character, or maybe the address of a buffer of some kind. Try entering the following:
 ok> 72 emit 105 emit

You 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:

 ok> 'W .
 ok> '% dup . emit
 ok> 'A dup .
 ok> 32 + emit

The use of the single-quote character is a bit unusual. It tells the Reva 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 Reva which can make inputting numbers simpler:

 'a        | Gives 97, the ASCII value of 'a'
 %1100     | Gives 12, or binary 1100
 $ff       | Gives 255, or hexadecimal FF
 &10       | Gives 8, or octal 10
 #123      | Gives 123 (decimal)

Using emit to output character strings would be very tedious. Luckily there is a better way. Enter:

 ok> : TOFU ." Yummy bean curd!" ;

The 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:

 ok> : SPROUTS ." Miniature vegetables." ;
 ok> : MENU cr TOFU cr SPROUTS cr ;
 ok> MENU

You can emit a blank space with space . One may output more than one space with the word spaces.

 ok> TOFU SPROUTS
 ok> TOFU space SPROUTS
 ok> cr 10 spaces TOFU cr 20 spaces SPROUTS

A Forth definition of spaces might look like this:

 : spaces 0 do space loop ;
Let's write a similar word to emit a series of periods, like this:
 ok> : periods 0 do '. emit loop ;
 ok> TOFU SPROUTS 
 ok> TOFU space SPROUTS 
 ok> cr 10 spaces TOFU cr 10 periods SPROUTS

Notice that the new word we created, periods, uses a "do...loop construct". The word do starts a series of words which will be run repeatedly. It takes the two top words on the stack as the upper limit and the starting value, respectively. So in this case, the loop starts at "0" and goes until the "10", that is 0..10, or ten iterations of the loop. The word loop adds one to the current value of the loop; if it's not yet the upper limit, then it jumps back to just after the do. 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, but the input is buffered so you need to hit the ENTER key after typing your character. Try the following:

 ok> : TESTKEY ( -- )
        ." Hit a key: " key cr
        ." The ASCII value=" . cr
     ;
 ok> TESTKEY

Reva has a similar word ekey which does not wait for the ENTER key. Try redefining TESTKEY above using ekey instead of key.

Compiling from Files

Reva can read read from ordinary text files so you can use any editor that you wish to write your programs.

Sample Program

Enter into your file, the following code.

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. Start Reva, then type:

 ok> include test.f
 ok> TEST.SQUARE

You might also use the file directly on the Reva command line:

 reva test.f

The 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.

Variables

Forth does not rely as heavily on the use of variables as other compiled languages. This is because values normally reside on the stack. There are situations, of course, where variables are required. To create a variable, use the word variable as follows:

 variable MY-VAR

This created a variable named MY-VAR . A space in memory is now reserved to hold its 32-bit value, and that space has been initialized to the value 0. It is also possible to initialize the variable to some other value using the word variable, .

The word variable is what's known as a "defining word" since it creates new words in the dictionary.

Now enter:

 ok> 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:

 ok> 513 MY-VAR !
 ok> 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:

 ok> 513 variable, MY-VAR2 
 ok> 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:

Save the file to disk, then load this code using the include word. Test your word as follows:
 ok> 123 REPORT.SCORE
 ok> 9845 REPORT.SCORE
 ok> 534 REPORT.SCORE

The 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.

Constants

If you have a number that is appearing often in your program, it's recommended you define it as a "constant." Enter:

 ok> 128 constant MAX_CHARS
 ok> MAX_CHARS .

We just defined a word called MAX_CHARS that returns the value on the stack when it was defined. It cannot be 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.

Logical Operators

These next two sections are concerned with decision making. This first section deals with answering questions like "Is this value too large?" or "Does the guess match the answer?". The answers to questions like these are either true or false. Forth uses a 0 to represent false and a -1 to represent true. In Reva, true and false are defined as Forth constants. The word = takes the two top values and produces either false or true . Try this:

 ok> 23 71 = .
 ok> 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. Other words that make comparisons of the top two stack values (similar to =) are < and > . Both of these words will produce false or true. The words = < and > are available singularly for pure logical operations. But because they are almost always coupled with an "if" statement, Reva has special forms of "if" available for speed of execution. Logically there is no difference between "= if" and "=if", but "=if" compiles to shorter and faster code, and Reva programmers are encouraged to use it. Here is an example of its use:

 : DRINK? ( age -- flag )
   20 >if ." OK" else ." Underage!" then cr
 ;

20 DRINK? 21 DRINK? 43 DRINK?

Here you are introduced to the if/else/then structure of Forth conditional statements. Other useful "if" constructs are:

  >if ( if second stack item is greater than TOS )
  <if ( if second stack item is less than TOS )
  =if ( if top two stack items are equal )
  if0 ( if TOS is zero )
  if ( if TOS is true )

These constructs are not the same as most Forths, except for if.

When I said that true is -1, that was not quite true; Reva accepts any non-zero as being true, as do most Forths.

For more complex decisions, you can use the boolean operators or , and, xor and not . or returns a true if either one or both of the top two stack items are true. and returns true if both are true. xor does an "exclusive or" of the two top items. not converts false to true, and any non-zero value to false. Actually, this isn't exactly true. The 'boolean' words mentioned above actually do bitwise operations. That is, they "and" or "or", etc., each bit with the corresponding bit in the other operand. For real "boolean" and use &&, and use || for boolean "or".

 ok> true true or .
 ok> true false or .
 ok> false false or .
 ok> true true and .
 ok> true false and .
 ok> true not .

Logical operators can be combined:

 ok> 56 3 and 57 123 or and .

Problems

Write a word called LOWERCASE? that returns true if the number on top of the stack is an ASCII lowercase character. An ASCII 'a' is 97 . An ASCII 'z' is 122 . Test using the characters " A ` a q z { ".

 ok> 'A LOWERCASE? . | should print 0
 ok> 'a LOWERCASE? . | should print -1

Write a word called DEDUCT that subtracts a value from a variable containing your checking account balance. Assume the balance is in dollars. Print the balance. Print a warning if the balance is negative.

 variable ACCOUNT

: DEDUCT ( n -- ) ????????????????????????????????? ( you fill this in ) ;

300 ACCOUNT ! ( initial funds ) 40 DEDUCT ( print 260 ) 200 DEDUCT ( print 60 ) 100 DEDUCT ( print -40 and give warning! )

(Answers can be found at the end of this tutorial.)

Loops

Reva has a completely different set of loop constructs than ANS Forth, so you'll want to pay attention.

Most loops begin with the word repeat. The first kind of loop is between repeat ... while. This loops until a condition is true. Try this:

 : COUNTDOWN ( N -- )
    repeat
      dup . cr  | print number on top of stack
      1-        | decrement
    dup while   | if TOS is not zero, repeat.
    drop        | get rid of value
 ;

16 COUNTDOWN

This word will count down from N to zero. The second type of loop Reva 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
 ;

Reva also has the do ... loop construct like ANS Forths do. It is used like this:

 : looper
    10 0
    do
       ." Iteration #" i . cr
    loop
 ;

If you type looper you will see 10 lines, starting with "Iteration #0".

Reva does not have the do ... +loop construct provided for by many other Forths (which allows one to step through a loop in increments other than 1). Instead, Reva uses the normal do...loop structure but adds the word skip . Here is an example of a do...loop with skip:

 : skip-looper
    100 0
    do
       ." skip-iteration # " i . cr
    10 skip loop
 ;

If you now type skip-looper you will see 10 lines, each line's output incrementing by ten."

Please note that

 : wrong
    0 101
       do i .
    -10 skip loop
 ;
is not the same as:
 : something
    0 101
      DO I .
    -10 +LOOP ;
in other Forths. skip only decrements a counter on the return stack.

Another method of looping is recursion. You might rewrite the "forever" loop given above as:

 : MAIN-LOOP
    ." looping again ..." cr
    MAIN-LOOP
 ;

In this example, an infinite loop is effectively made by recursion, that is, a word which calls itself. Unlike ANS Forths, Reva allows one to recurse simply by adding the name of the word being defined inside its own definition. Unlike other Forths, Reva does not "smudge" the dictionary while defining a word. This allows you to easily recurse.

But what if one wants to use an earlier definition that is similarly named, rather than recursing? Reva provides the word prior to accomplish this task. For example:

 ok> : cleave ." separates" ;
 ok> : cleave ." joins " prior cleave ;

Text Input and Output

You learned earlier how to do single character I/O. This section concentrates on using strings of characters. Reva has several varieties of strings, and it is good to know when to use each type.

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 it 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 " Hi there!" ;

STR type cr

Other Forths use the word s" to accomplish the same thing Reva does with ". The word type seen above, prints out the Forth string on top of the stack.

The second type of string Reva allows is a "counted" string. This is an address ( a -- ), where the first byte (or int, if a 'long' counted string) contains the length of the string data. The string data follow the count byte (or int) directly. There are no words to create such a string directly, but the word place will take a Forth string and "place" it into a counted-string in the area requested. For a string longer than 255 characters, use lplace rather than place. To convert a counted string to a Forth string, use the word count (or lcount). To append a Forth string to a counted string, use +place (or +lplace). Finally, append a character to a counted string with c+place.

A third type of string supported by Reva 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. The " word automatically NUL terminates the string it creates, and the counted-string words also NUL terminate their strings. This means that you can pass a Forth string to a Windows or Linux API call (just remember to drop the count first!).

 " Reva rocks!" pad place  | 'pad' now contains a counted -string
 pad count type cr         | see what it is...

A good thing to know is that in interactive mode, the " word uses a 4096 byte buffer to create the strings. When it reaches the end of this buffer, it wraps around. This means that you can do stuff like:

...and the strings will be distinct. When using " inside a colon-definition, the strings will be compiled to a separate area which by default is 64K 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 64K of string data, Reva will allocate more "compiled string space", in increments of cstrsize (you can change that size, which defaults to 64K, simply by writing a new value like 100,000 cstrsize ! before you need to reallocate).

If you need to have a double-quote character inside your string, you simply need to preface it with a \ character like so:

 " \"Mary!\", her mother said, \"Come in for dinner!\""
This will result in "Mary!", her mother said, "Come in for dinner!".

Changing Numeric Base

Our numbering system is decimal, or "base 10." This means that a number like 527 is equal to (5*100 + 2*10 + 7*1). The use of 10 for the numeric base is a completely arbitrary decision. It no doubt has something to do with the fact that most people have 10 fingers (including thumbs). The Babylonians used base 60, which is where we got saddled with the concept of 60 minutes in an hour. Computer hardware uses base 2, or "binary". A computer number like 1101 is equal to (1*8 + 1*4 + 0*2 + 1*1). If you add these up, you get 8+4+1=13 . A 10 in binary is (1*2 + 0*1), or 2. Likewise 10 in any base N is N .

Forth makes it very easy to explore different numeric bases because it can work in any base. Try entering the following:

 ok> decimal 6 binary .
 ok> 1 1 + .
 ok> 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:

 ok> decimal 12 hex .	| print C
 ok> decimal 12 256 * 7 16 * + 10 + .s
 ok> dup binary .
 ok> 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:

 ok> 7 base !
 ok> 6 1 + .
 ok> 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.

Libraries

Reva includes a number of "libraries", which implement more specialized functionality which is not part of the base Reva program. This was done to keep Reva small, and particularly since many of the library words are not needed for every application.

As an example, "double-cell math" is in a library, not directly part of Reva. To get the double-cell words, you have to load the library. This is done very easily, like this:

 ok> needs math/doubles

To find out what other libraries are distributed with Reva, you can type:

 ok> help libraries

Files & File Handling

For details on all the file handling words in Reva, use the help facility:

 ok> help file-io

As a simple example, let's create a file which will have "Hello world!" in it:

 ok> " hello.txt" creat
 ok> dup " Hello world!" rot write
 ok> close

The first line creates the file "hello.txt" in the current directory, if it did not already exist (use open/rw to open an existing file for writing). The return value is the 'handle' of the file, which we immediately dup. The string to write follows, so the stack at this point looks like:

file-handle file-handle address-of-string size-of-string

The word write expects the handle to write to in TOS, so we do rot, and then write out the buffer to file. All that remains is to close the file (this is almost always a good idea, even though the OS will close files on our behalf if we forget).

Verify that you now have a file "hello.txt" with the text indicated.

Note that we did not check for errors. The Reva file-io philosophy is that "it normally works", so make the code easier to read by not having error code checks everywhere. However, you can check for errors by consulting the value held in ioerr after each I/O operation.

Note that a simple way to read in an entire file is via slurp. That word allocates memory for a buffer big enough to hold the file, and returns the contents of that file as a string. This works great for small files, but you don't want to try it on big ones, probably...

Answers to the problems

If your answer doesn't exactly match these but it works, don't fret. In Forth, there are usually many ways to the same thing.

Stack Manipulations

  1. swap dup
  2. rot drop
  3. rot dup 3 pick
  4. swap over 3 pick
  5. -rot 2dup

Arithmetic

  1. (12 * (20 - 17)) ==> 20 17 - 12 *
  2. (1 - (4 * (-18) / 6)) ==> 1 4 -18 * 6 / -
  3. (6 * 13) - (4 * 2 * 7) ==> 6 13 * 4 2 * 7 * -
  1. : SQUARE ( N -- N*N ) DUP * ;
  2. : DIFF.SQUARES ( A B -- A*A-B*B ) swap SQUARE swap SQUARE - ;
  3. : AVERAGE4 ( A B C D -- (A+B+C+D)/4 ) + + + ( add'em up ) 4 / ;
  4. : HMS>SECONDS ( HOURS MINUTES SECONDS -- TOTAL-SECONDS ) -rot swap ( -- seconds minutes hours ) 60 * + ( -- seconds total-minutes ) 60 * + ( -- seconds ) ;

Logical Operators

 : > >if -1 else 0 then ;
 : < <if -1 else 0 then ;
 : LOWERCASE? ( CHAR -- FLAG , true if lowercase )
   dup 123 <
   swap 96 >
   and
 ;
 : DEDUCT ( n -- )
   ACCOUNT @
   swap - dup ACCOUNT !
   ." Balance = $" dup . cr
   0 <if
      ." Warning!! Your account is overdrawn!" CR
   then
 ;