Ferale.art


Forth

Forth is a simple yet powerful programming language invented a few years before C, and which was during the 70’s almost as popular (well, depending on who you ask).

It is one of the simplest languages to implement when a new processor is created, which made it a good candidate for experimenting and testing new hardware while it’s being built.

Forth code looks like this:

0 value CURRENT-SOCKET

: start-server { server }
  begin
    server 255 listen  \ program waits here...

    server accept-socket to CURRENT-SOCKET
    CURRENT-SOCKET read-request prepare-response
    CURRENT-SOCKET send-response

    free-strbuf
  again ;

This is an actual extract from the code running this very website.

One of the beauty of Forth is that it has only one syntax rule: words separated by spaces.

What is called a “word” is a procedure being executed, and the code consists only of words, separated by at least one space character. That. Is. All. In the example above, : is a word, { is one too, begin and again and ; are also words. There is no special syntax nor special characters.

Forth is neither a compiled, nor an interpreted language. It is both at the same time: it reads words, one after the other, and either compiles it if it’s inside a definition, or executes it if it’s outside.

It’s a bit like an “interactive” compiler.

Execution example

Let’s take a simple program, and execute it step by step.

12 48 + cr .

  STACK(0):
  We start with an empty stack of values.

12 48 + cr .
^
  STACK(1): 12
  We put the value 12 on the stack.

12 48 + cr .
   ^
  STACK(2): 12 48
  We put the value 48 on the stack.

12 48 + cr .
      ^
  STACK(1): 60
  We add the two values together and get 60 on the stack.

12 48 + cr .
        ^
  STACK(1): 60
  We output a carriage return to the terminal (new line).

12 48 + cr .
           ^
  STACK(0):
  We print the number 60 to the terminal.

Forth code is not read by a compiler program, understood and executed, like we are used to.

Forth code is its own compiler: everything happens live, word after word. Code is executed while your source file is being read, at the exact same time.

When the word + is executed, Forth doesn’t know yet what’s to come next (words cr and . in this example). It will discover and execute them as it reads through your code.

You can even manipulate the source input directly. For example, the word s" (pronounced “s-quote”) creates a new string in memory by reading through the rest of the source code until it finds closing quotes.

s" Hello, world!"

The first space after s" is not part of the string. It’s there to separate Forth words. Then s" executes, reading the next characters in the source code until it finds the character ", at which point the program continues its execution/compilation like nothing happened.

Forth has no string literal syntax. The word s" was coded in Forth by someone who wanted to be able to parse strings from the code. It then became a standard word to define in every good Forth around. But it’s not an inherent part of the language, and some Forth don’t have it!

What I want to emphasize in this example is that to really understand Forth, you must understand that it’s not a programming language like what you would expect from other programming languages (reading source code, checking syntax, compiling it, executing it). Forth is really just a program that reads characters from a file, one by one, and executes code every time it reaches a space.

It’s that simple. Read a character, then another. Is it a space? Yes, lets execute the word we got so far. Then we go back to reading characters.

What’s impressive is that with such a simple rule, you can create a language that is as powerful as C, and can even step up to higher levels of abstraction (with a few words, you implement structures, and then a few more, you’ve got classes and an object-oriented language).

Struct example

The following example is the implementation of a linked list to store strings.

struct
  cell% field list-prev
  cell% field list-data
end-struct list%

: list-add  ( val list -- list )
  list% %alloc >r
  r@ list-prev !
  r@ list-data !
  r> ;

: groceries  ( -- list )
  c" potato"    0 list-add
  c" salad"  swap list-add
  c" garlic" swap list-add ;

: show-list  { list -- }
  begin
    list
  while
    cr ." - "
    list list-data @ count type
    list list-prev @ to list
  repeat ;

groceries show-list

This code outputs:

- garlic
- salad
- potato