VIJAY MATHEW

home > writings > First encounter with concatenative programming

2009 March 13

Two well written books on Forth by Leo Brodie (Starting Forth and Thinking Forth) ignited my desire to explore the territory of concatenative programming languages. In an attempt to understand them better, I decided to implement an interpreter for a Forth like language. I will describe this new language here. It is called Blithe. The interpreter is implemented in Spark-Scheme. It can be easily ported to other Schemes. A better, complete compiler should be written in a systems language like C. That will be an interesting, rewarding hobby project in its own right!

Blithe has heavy influences from both Forth and Lisp. It has post-fix notation. While functional languages are based on the application of functions to arguments, concatenative languages are based on the composition of functions. There are no formal function parameters. Instead all functions take a stack as argument and produce a stack as result. A function can take any number of arguments and return any number of values. The output of one function becomes the input of another. This relationship is expressed by juxtapositioning functions. The absence of formal parameters and expression of function composition by juxtapositioning has an important consequence - programs look like English sentences. So it is not without a reason that in Blithe parlance functions are called "words" and code libraries are called "vocabularies".

Let us get a feel of Blithe by running some small computations. When you fire up the Blithe interpreter, you get a prompt like this:


This is the interactive Blithe shell.
Type `bye' to exit.

Enter the following two lines at the prompt. (=> denotes a result).


"Add two numbers" .
2 3 + .
=> 5

The first line is a comment. Blithe do not have special syntax for comments. All comments are string literals, followed by a dot. The dot makes sure that the string literal is popped from the stack. The real computation comes next. There, you pushed two integers - 2 and 3 - on to the top of the stack. + is the word that adds the top two elements of the stack. When the interpreter encounters a word, it is applied to the values on the stack. A word will consume only the values that it actually needs. The rest of the stack is kept untouched. + will pop the first two integers, add them and push the result to the stack. The special dot operator (.) pops the top element off the stack, printing the result on the shell. (Use ^ or drop if u don't want to print the value). Blithe comes with quite a few built-in words, including the usual ones for manipulating the data stack. Here are a few common stack operations:

1 2 3 4 5                                 "Push 5 numbers on the stack" .
depth .                                   "Prints the size of the stack" .
=> 5
.S                                        "Show the stack along with its size" .
=> 5
swap                                      "Swaps the top two elements" .
.S
=> 5 < 5 4 3 2 1 >
dup                                       "Duplicates the top element" .
.S
6 < 5 5 4 3 2 1 >
rot                                       "Rotates the top element with the third" .
.S
6 < 4 5 5 3 2 1 >
drop                                      "Removes the top element" .
.S
5 < 5 5 3 2 1 >

Blithe can be extended by defining new words. The colon(:) operator is used for this purpose. The definition of a word should be terminated by a semi-colon. Here is a simple word that squares the top element on the stack:

: sqr dup * ;

It duplicates the top of the stack and applies the multiplication function.

5 sqr .
=> 25
100 sqr .
=> 10000
25 sqr

Blithe is an interactive language. You can save the state of the whole Blithe system to a file. You can later continue from that point. This is much similar to using virtual machine images in Smalltalk:

"Save and quit Blithe" .
save
bye

Later, we load the system image and continue from where we stopped:

load
.
=> 625

You can use a word even before it is defined. This facilitates both top-down and bottom-up development. A classic Forth example is an embedded program that controls a washing machine. An over simplified version of this can be coded in Blithe as:

: washer wash spin rinse spin done ;
: wash "Washing ..." . 3 sleep ;
: spin "Spinning ..." . 2 sleep ;
: rinse "Rinsing ..." . 2 sleep ;
: done "Done." . newline ;

Here is a sample run of the program:

"Run the washing machine!" .
washer
Washing ... Spinning ... Rinsing ... Spinning ... Done.

We wrote the high-level word washer first. It is the public interface of the washing machine controller. The component words are defined later. Delay in the actual working of the washing machine is simulated using the "sleep" built-in word. You can also write the software in the reverse order, bottom-up, by defining the component words first.

Words can be grouped under vocabularies (modules, libraries or namespaces in other languages). Let us place the washing machine words in a vocabulary called "washing-machine" so that they won't conflict with pre-existing words:

"washing-machine" vocabulary
: washer wash spin rinse spin done ;
: wash "Washing ..." . 3 sleep ;

...

The default vocabulary is called "system". We can switch back to the default vocabulary any time we want, but the words defined for washing machine won't be visible there:

"system" vocabulary
wash
Error: wash - word not found.

Data types and variables: Blithe has integers, floats, Unicode strings and Lisp like symbols. Boolean values are represented by #t and #f. There are C like comparison operators and basic string functions. There are words to do bit-wise and arithmetic shift operations.

Symbols are used to represent variable names. A value is assigned to a variable using the ! operator. The value of a variable can be pushed on to the stack using the @ operator.

"Assign a string to a variable" .
"Vijay Mathew" 'name !
"Get back the value of 'name" .
'name @ .
=> Vijay Mathew

Lisp like pairs are supported. They can be used to create complex data structures. The following code constructs a name-age pair and stores it in a variable. Later we extract the pair using the words first and second:

35 "Anna" pair 'name-age !
'name-age @ first .
=> Anna
'name-age @ second .
=> 35

Blithe also makes it easy to document and access help on words. The following code demonstrates this feature:

"Finds the square of a number." 'sqr doc
: sqr dup * ;

The doc word updates the documentation dictionary by mapping the name of the word to a string. Later this documentation can be accessed using the help word.

'sqr help .
=> Finds the square of a number.

To understand the complete capabilities of Blithe, please look at the source code of the Blithe interpreter (blithe.ss), especially the execute procedure.

References: Two popular concatenative programming languages, Factor and Joy.