Forth is a language, like Lisp, that has a certain fascination for me. I’ve tried Forth a few times, but have always gone back to other languages. Recently I tried creating a Forth myself as a learning exercise: ultimc [1]. It compiles on Linux, Windows (theoretically), and under the Ultibo [2] unikernel for a bare metal Raspberry Pi Forth.
Creating my own Forth has taught me a lot about the language, and really made me understand the way it works. In this post, I am going to peer a little under the hood to show that Forth, like Lisp, has a secret sauce that you just don’t get with most other languages. Maybe it will excite you a little as to the unique possibilities of Forth.
Recently I came across some Forth code [3] that was as follows:
0 constant: GPIO_INTTYPE_NONE
1 constant: GPIO_INTTYPE_EDGE_POS
2 constant: GPIO_INTTYPE_EDGE_NEG
3 constant: GPIO_INTTYPE_EDGE_ANY
4 constant: GPIO_INTTYPE_LEVEL_LOW
5 constant: GPIO_INTTYPE_LEVEL_HIGH
Notice how there’s a lot boilerplate code there: “0 constant:”, “1 constant:”, etc. Wouldn’t it be nice to write something like:
enum: GPIO_INTTYPE_NONE GPIO_INTTYPE_EDGE_POS GPIO_INTTYPE_EDGE_NEG ...
and have Forth “do the right thing”. Well, let’s see …
Here’s the code, written in gforth:
variable enum \ the current enumerated value, starting at 0
: enum++ enum @ dup 1+ enum ! ; \ increment enum, leaving the original enum value on the stack
: >enum nextname enum++ constant ; \ Given a name, set it to the value of enum as a constant
: enum1 parse-word dup if >enum 0 else 2drop 1 then ; \ scan the input buffer, and call >enum if legit
: enum: begin enum1 until ; \ keep going until the input buffer is exhausted
Just 5 lines of code, although admittedly it did take me a lot of time to figure out. The code needs a little elaboration.
PARSE-WORD and NEXTNAME act a little bit like CREATE, except that PARSE-WORD gets the next word from the input buffer, and NEXTNAME creates a word from the input buffer.
Now, if we’re at the end of the buffer PARSE-WORD returns a zero length string. This is what ENUM1 checks for. It only calls >ENUM if the string has at least 1 character. What it returns on the stack is the value 0 to signify success, and 1 to signify failure.
ENUM: then just loops around until the value 0 appears on the stack, implying that the input buffer is exhausted.
Let’s test it, using more condensed names:
enum: foo bar baz
And check our handiwork:
foo . bar . baz . \ outputs: 0 1 2
Can your language do that?
References
[1] https://github.com/blippy/ultimc
[2] https://ultibo.org/
[3] https://github.com/zeroflag/punyforth/blob/master/arch/esp8266/forth/gpio.forth