Forth is pretty great. In an alternate timeline, the C64 might have had Forth loaded into ROM instead of BASIC (indeed, some other 8-bit computers of the era did just that).
My goal is to keep this post brief and informative, so in that spirit, let's get a few things out of the way.
What is a Commodore 64?
A 40-year-old 8-bit computer that impressed users both then and now with its graphics and sound capabilities.
What is Forth?
A procedural, stack-based programming language.
What is DurexForth?
An actively developed version of Forth for the Commodore 64, based on the Forth 2012 core standard.
Why would I want to use Forth instead of Basic?
- It's much faster
- It allows structured programming (essentially, this means subroutines with parameters and return values)
- It's extendable
- It's as high level as you want (build as many new capabilities into the language as you want), or as low level (drop into assembly code whenever you need to go even faster).
- You might discover you love it
- You might even find it's still a handy language for programming small microcontrollers, or deep space exploration probes
- Some people even prefer it as a scripting language over the likes of Lua or Python
- Because just try to do this in Basic:
What does it look like?
To the uninitiated, perhaps a little odd. Let's take as an example the famous FizzBuzz program. So you'll have a point of reference, I'll first show the algorithm in both a modern c-like language and in Commodore Basic 2.0.
In a modern language, it would perhaps look like this
public static void Main()
{
for (int i=1; i<=100; i++) {
if (i%3==0 && i%5==0) {
Console.WriteLine("fizzbuzz");
} else if (i%3==0) {
Console.WriteLine("fizz");
} else if (i%5==0) {
Console.WriteLine("buzz");
} else {
Console.WriteLine(i);
}
}
}
In Commodore Basic 2.0 (the Commodore 64's "built-in" language), it could look like this
10 I=0
20 I=I+1 : IF I > 100 THEN END
30 IF INT(I/3)=I/3 AND INT(I/5)=I/5 THEN PRINT "FIZZBUZZ"
35 GOTO 20
40 IF INT(I/3)=I/3 THEN PRINT "FIZZ"
45 GOTO 20
50 IF INT(I/5)=I/5 THEN PRINT "BUZZ"
55 GOTO 20
60 PRINT I
65 GOTO 20
A couple of things to note here. First, there is no modulo operator in Basic 2.0, so I'm performing an inline test to check whether a number divides evenly by either 3 or 5.
If we wanted to factor out the modulo operation (sort of), we could do this:
10 DEF FN D3(X) = INT(I/3)=I/3
20 DEF FN D5(X) = INT(I/5)=I/5
Note, however, that only one parameter is allowed, so we couldn't do something like DEF FN DIVIDESBY(I,D) = INT(I/D)=I/D
. Additionally, variable names in Basic only regard the first two characters as significant, which is why I've gone for D3
and D5
over the more descriptive names DIVIDES_BY_3
and DIVIDES_BY_5
.
Secondly, I'd like to point out that there is no IF, ELSE IF, ELSE structure in basic, forcing the programmer to implement the desired behaviour with the dreaded GOTO statement.
Finally, let's take a look at some Forth
: fizzbuzz
101 1 do
i 3 mod 0= i 5 mod 0= and if
." fizzbuzz" cr
else i 3 mod 0= if
." fizz" cr
else i 5 mod 0= if
." buzz" cr
else i . cr
then then then
loop ;
Here's a quick guide to all the moving parts:
: fizzbuzz
...
;
Here we are compiling a new word into Forth, fizzbuzz
. "Word" is just Forth-speak for a subroutine. You make a program by extending Forth with your own words. In fact there are two "core" words hidden in that snippet, :
to define a word and enter the so-called compilation state, and ;
to end the current definition.
101 1 do
...
loop
First, we push two values to the parameter stack, 101
and 1
. The stack is a first-in last-out data structure which is key to how Forth programs work. It acts as a sort of universal mechanism for passing parameters and returning results.
Now we call the core word do
. This consumes the parameters that were placed on the stack and sets up some loop controls variables (on another stack... the return stack. but that's not important right now -- forget I mentioned it).
As you can probably guess, loop
is a word marking the end of the loop. In addition to do ... loop
, Forth supports begin ... again
for endless loops and both begin ... until
and begin ... while ... repeat
.
i 3 mod 0=
This places two parameters on the stack, i
and 3
and calls the mod
word, which is a proper built-in modulo operation. mod
consumes the two parameters from the stack, and replaces them with the result of the modulo operation.
But what is i
? It's a little bit magic, but not really. i
is a word that copies the current iteration value from the return stack to the top of the parameter stack. So for the first iteration we'll have 1 3 mod 0=
, the next iteration we'll have 2 3 mod 0=
and so on.
Next, 0=
checks whether the value at the top of the stack is zero, replacing it on the stack with a boolean result. The actual boolean will be represented by either 0 (false) or -1 (true), just like in Basic.
By the way, why 0 and -1 and not 0 and 1? Because the byte representation of -1 is 11111111, which is about as "true" as you can get, and 0 is 00000000, which is pretty darn false. Actually, the stack holds 16-bit numbers, but I digress further from this digression.
Now take a look at this snippet and try to imagine what's happening to the parameter stack at each step:
i 3 mod 0= i 5 mod 0= and
Let's step through it. For the purposes of this demonstration, we'll assume i
to be 10
executing... | leaves this on the stack... | note |
---|---|---|
i |
10 |
push a value to the stack |
3 |
10 3 |
...and another. |
mod |
1 |
the result of 10 % 3 |
0= |
0 |
...is not equal to zero. |
i |
0 10 |
our previous result is safe on the stack. |
5 |
0 10 5 |
|
mod |
0 0 |
|
0= |
0 -1 |
we have "false, true" on the stack |
and |
0 |
false and true = false. 10 is not a "fizzbuzz" number |
If you're familiar with Reverse Polish Notation, you'll probably see what's going on here. If not, you'll need to familiarise yourself with the concept before you can have much fun with Forth, as it goes hand-in-glove with stack-based programming.
... if ... else ... then
Well we know what this is, but in the spirit of RPN, things are a little backwards.
Let's fill in the gaps:
[predicate] if [true part] else [false part] then
Note the idiomatic role of the word "then". In Basic, you'd read it as "if this predicate is true, then do this". In some languages, e.g. Visual Basic, you'd close it out with "End If".
In Forth, then marks the end of the conditional structure, much like an "end if". So I imagine it means something more like "here's a predicate, if it's true I'll do some stuff here, then I'll carry on and do this other stuff I was going to do anyway."
The three then's near the end of the program make a lot more sense when you think of them as three "end-ifs" at the end of some nested if statements, or even three closing curly brackets if that's more your style.
The final piece of the puzzle is some console IO:
." fizzbuzz" cr
." fizzbuzz"
prints some text to the screen, equivalent to PRINT "fizzbuzz";
. Note, ."
is it's own core word, so the space between that and the text to print is required.
cr
is another core word which prints a carriage return, so ." fizzbuzz" cr
is simply equivalent to PRINT "fizzbuzz"
This code does not affect the stack in any way.
Finally, the last piece of the puzzle:
i . cr
i
pushes the value of i
to the top of the stack, then .
prints the top item on the stack to the screen (removing it from the stack in the process), and cr
as mentioned will print a carriage return.
And that's the end of this gentle introduction. I'll most probably do a few more posts showing some fun stuff that can be done on the Commodore 64 in particular. For now, I didn't want to assume too much prior knowledge. Hopefully you, the reader, now have a feel for what Forth is, and what it looks like. Next time we can really take it for a spin.
Some links for the curious:
DurexForth's Operators Manual
Jeff from Hey Birt! Installs DurexForth
Robin from 8-Bit Show and Tell explores 64Forth, an older version of Forth for the C64
Top comments (0)