Search This Site Full Web

LEARNING FORTH

Now you've read read some more about Forth, and you'd like to try it out?

The Free Forths to try page contains links to a number of systems you can download. I particularly recommend Pygmy and 4th as excellent sytems for beginners. If you need to run under Windows 95, I would suggest Aztec or Win32Forth, for which Dave Pochin has produced a Beginner's Guide

Every Forth is different, and each has its own tutorial, but I shall be referring mainly to the ANSI Standard, so I would also recommend you download the zipped version for your own reference.


Rather than attempt a complete programming course, I intend to highlight those aspects of Forth that differ from other, more conventional languages. For a more step-by-step approach, see Leo Wong's Inching Forth

Send me your queries, suggestions and criticisms


Lots of little words

TOP

Factoring is the one trick that Forth does really well. A definition can be likened to a sentence in English. It should be short enough for you to understand the sense of it (and spot any bugs) at a glance. Another test is to say the definition aloud and see if it still makes sense. All Standard Forth words have pronounceable names, and so should those that you define. Forth may sometimes be unreadable, but it should never be unspeakable!

In fact, Forth is a conversation between the programmer and the hardware.

"My chief hurdle when learning Forth was learning to re-think my words down to requiring a small number of arguments, rather than schlepping a large amount of program state around on the stack. It's not only a better way to write Forth, but a better way to write any code. Until you get the hang of it, you'll be continually frustrated trying to get at the n'th data element on the stack, and getting in your own way. The use of ROLL and PICK in beginner code is evidence of this.

Forth requires this skill. Learning it will make you a better programmer".
Neal Bridges

Stack Diagrams

TOP

Local variables are rarely used in Forth. Instead, each word takes its arguments from the stack, and there a special stack-stirring words which can be used to ensure they are in the proper order:

2DROP 2DUP 2OVER 2SWAP DROP DUP OVER ROT SWAP

As a rule of thumb, these stack-stirring words should not outnumber other words in a definition. They can be thought of as the equivalent of pronouns in English. Just as there should be no more than three pronouns in a sentence - this, that and th'on - so there should be no more than three 'live' items on the stack at any one time. Any more and you start to get confused.

The stack diagram is the equivalent of a function declaration. Even though it produces no code, it is important for readability.

This is the form I use:

: NAME  \ "pronounciation" input -- output ; what it does

"\" comments to the end of the line, and I prefer it to "(" which comments to a closing bracket. Both are Forth words - they need a space directly after them to work. In the list of items input and output, those on top of the stack are on the right, just as they would be if entered from the command line. (See the ANS Standard for conventions on pronounciation and stack notation).

In addition, I indent each line of code two spaces for each item that is on the stack at that point. If there is nothing on the stack, the code starts in the same column as the name of the definition. This is purely my personal style. Some people indent control structures (IF..THEN, DO...LOOP etc.) instead, but I just start a new line.


Syntax

TOP

Each word in a definition performs its own task, and their order depends on the nature of the task to be accomplished rather than any rules of grammar laid down by Forth. There is no typechecking. You might think of words that place data on the stack as 'nouns' and words that act on that data as verbs, but if it makes the sense clearer the word that provides the data could be an adjective, and the 'actor' a noun. The 'actor' does not need to know where the data comes from. The interpreter just executes each word as it comes to it. There is no inherent type-checking. If you want, for example, to do arithmetic on flags, there is nothing to stop you. Forth has virtually no syntax, apart from what you invent.

Here are some conventions which help your words work naturally and with the minimum of stack manipulation:

Text follows names

That's because each the interpreter simply parses and executes each word in turn, so anything that it won't recognise - such as a name or a literal string - must be preceded by a recognised word which deals with it.

: .$  \  ++ ;  type following text up to $

[CHAR] $ PARSE CR TYPE ;

.$ Hello World!$
Hello World!

Not a very useful definition, except as a demonstration ;-)

Words which deal with following text use either PARSE or WORD (or other words defined using PARSE or WORD). When I write such words, I use ++ in the stack diagram instead of -- , just to remind myself.

WORD is best used for parsing a word to be found in the dictionary, (as in the Forth interpreter itself). It skips leading delimiters and returns a counted string. PARSE does not skip anything, and returns an address/count pair. In either case, the next parsing action will begin with the character after the trailing delimiter (in this case, the char $)

Numbers precede names

e.g. : SPACES  \  n -- ;  print n spaces
            0 DO SPACE LOOP ;

64 SPACES

The interpreter tries to convert any word it does not recognize into a number, so the word that uses it does not need to fetch it itself. You see here how nicely this works with the usual order of parameters for counted loops (limit start)

Because SPACES doesn't do any parsing it doesn't have to worry how the number will get there - it just uses whatever is on the stack.

From precedes To

Two good reasons:

  1. That's the way you say it in English "bring that (from) here (to)". Even though Forth puts the verb in a different place ("that here bring"), the "nouns" are still in the same order. That applies for example to all the maths words such > and / . "a > b" in another language translates to " a b >" in Forth. It's a general rule you should stick to when defining your own words.
  2. The destination is more likely than the source to remain constant. In storing a value with ! , for example, you are much more likely to want to store differing values to the same address than the same value to a number of differing addresses. Having the least variable parameter nearest the verb makes it easier to factor - as we have also seen with SPACES above.

Addresses precede counts

Strings are often held in memory in counted format, where the address points to the first byte, which holds the number of characters that follow, thus requiring only one item on the stack. Most words which manipulate strings use the more flexible address count format. The advantage of this is that they can deal with parts of a string, or indeed any area of memory. For example, to type the first 20 characters at address PAD:

               PAD 20 TYPE

and the next 20:

               PAD 20 +   20 TYPE

what could be simpler?

Words consume their arguments

Each word should stand alone, taking only what it needs from the stack, and returning only what it has to. If you make it do 'favours' for other words, you limit its usability. If a calling word needs the agruments again, it is up to that word to save them first. Take the above example - suppose you want a word that prints y lines of x characters. The syntax is:

              addr x   y LINES

The first thought is to write a word that prints one line and increments the address for the next, but where would you use such a word again? A better solution is to factor out the address-incrementing part:

   : NEXT$  \ "next-string" addr n -- addr+n n
         TUCK + SWAP ;

And the rest is easy:

  : LINES \ addr n1 n2 -- ; print n2 lines of width n1
          O DO
        2DUP TYPE CR
\ print line, saving address and count
        NEXT$ LOOP   \ get next line
        2DROP ;      \ get rid of address and count

Use zero-relative numbering

The lines in the above example can be seen as an array, in which the start of line n is PAD 20 n * + , so long as you count the first line as line zero. Notice that in this case we are stepping through the array sequentially so there is no need to use multiplication to calculate the address of each line.


Stack Tips

TOP

Even when you try your best to write small definitions, stack juggling can sometimes be tricky. Here are a couple of tips to help. The examples given have been used to argue that "Forth needs local variables" (most fat Forths now have them). Judge for yourself:

Work backwards towards a solution

Problem - draw a box, given the diagonal corners and the words

MOVETO \ x y -- ; move to graphic position x,y
DRAWTO \ x y -- ; draw a line from current position to x,y

DRAWBOX \ t l b r ; finish at top left corner

The trick is to start with the stack as required for the last action and then work backwards, introducing new items to the top of the stack as required. Here the last action will be:

t l DRAWTO

And the previous action (assuming a clockwise direction) is

b l DRAWTO

To get there with least stack-stirring:

t l b OVER DRAWTO

and so backwards to the start:

t l DRAWTO
t l b OVER DRAWTO
t l b r OVER SWAP DRAWTO
t l b r t OVER DRAWTO
t l b r t l OVER SWAP MOVETO
t l b r 2OVER

Making the final definition:

: DRAWBOX \ t l b r -- ;
          2OVER OVER SWAP MOVETO
            OVER DRAWTO
          OVER SWAP DRAWTO
        OVER DRAWTO
      DRAWTO
  ;

Redefine the question

Problem - calculate the surface of a cube given length, width and height:

SURFACE \ l w h -- 2((l*w)+(w*h)+(l*h))

Three stack items, each needed twice. Try to reproduce the formula directly, and you'll find it very difficult. However, if you refactor it as: 2(l*(w+h)+(w*h)) a solution becomes clearer (note the use of >R..R> to temporarily store the intermediate result:

      2DUP * >R \ save w*h
      + * \ l*(w+h)
  R> + 2*


HOME Site Map TOP