I first ran into Forth about 20 years ago when reading a book about designing embedded hardware. The reason I got the book back then was to actually learn more about the HW aspects, so having skimmed the Forth chapter I just registered an "oh, this is neat" mental note and moved on with my life. Over the last two decades I heard about Forth a few more times here and there, such as that time when Factor was talked about for a brief period, maybe 10-12 years ago or so.
It always occupied a slot in the "weird language" category inside my brain, and I never paid it much attention. Until June this year, when a couple of factors combined fortuitously:
After spending much of the earlier part of 2025 exploring the inner workings of LLMs and digging in random mathy and algorithmic topics, I had an itch to just write some code.
I somehow found Dave Gauer's page about Forth and also the one on Implementing a Forth.
And something clicked. I'm going to implement a Forth, because... why not?
So I spent much of my free hacking time over the past two months learning about Forth and implementing two of them.
Forth: the user level and the hacker level It's useful to think of Forth (at least standard Forth, not offshoots like Factor) as having two different "levels": User level: you just want to use the language to write programs. Maybe you're indeed bringing up new hardware, and find Forth a useful calculator + REPL + script language. You don't care about Forth's implementation or its soul, you just want to complete your task. Hacker level: you're interested in the deeper soul of Forth. Isn't it amazing that even control flow constructs like IF...THEN or loops like BEGIN...UNTIL are just Forth words, and if you wanted, you could implement your own control flow constructs and have them be first-class citizens, as seamless and efficient as the standard ones? Another way to look at it (useful if you belong to a certain crowd) is that user-level Forth is like Lisp without macros, and hacker-level Forth has macros enabled. Lisp can still be great and useful without macros, but macros take it to an entire new level and also unlock the deeper soul of the language. This distinction will be important when discussing my Forth implementations below.
goforth and ctil There's a certain way Forth is supposed to be implemented; this is how it was originally designed, and if you get closer to the hacker level, it becomes apparent that you're pretty much required to implement it this way - otherwise supporting all of the language's standard words will be very difficult. I'm talking about the classical approach of a linked dictionary, where a word is represented as a "threaded" list , and this dictionary is available for user code to augment and modify. Thus, much of the Forth implementation can be written in Forth itself. The first implementation I tried is stubbornly different. Can we just make a pure interpreter? This is what goforth is trying to explore (the Go implementation located in the root directory of that repository). Many built-in words are supported - definitely enough to write useful programs - and compilation (the definition of new Forth words using : word ... ; ) is implemented by storing the actual string following the word name in the dictionary, so it can be interpreted when the word is invoked. This was an interesting approach and in some sense, it "works". For the user level of Forth, this is perfectly usable (albeit slow). However, it's insufficient for the hacker level, because the host language interpreter (the one in Go) has all the control, so it's impossible to implement IF...THEN in Forth, for example (it has to be implemented in the host language). That was a fun way to get a deeper sense of what Forth is about, but I did want to implement the hacker level as well, so the second implementation - ctil - does just that. It's inspired by the jonesforth assembly implementation, but done in C instead . ctil actually lets us implement major parts of Forth in Forth itself. For example, variable : : variable create 1 cells allot ; Conditionals: \ IF, ELSE, THEN work together to compile to lower-level branches. \ \ IF ... THEN compiles to: \ 0BRANCH OFFSET true-part rest \ where OFFSET is the offset of rest \ \ IF ... ELSE ... THEN compiles to : \ 0BRANCH OFFSET true-part BRANCH OFFSET2 false-part rest \ where OFFSET is the offset of false-part and OFFSET2 is the offset of rest : if immediate ' 0 branch , here 0 , ; : then immediate dup here swap - swap ! ; : else immediate ' branch , here 0 , swap dup here swap - swap ! ; These are actual examples of ctil's "prelude" - a Forth file loaded before any user code. If you understand Forth, this code is actually rather mind-blowing. We compile IF and the other words by directly laying our their low-level representation in memory, and different words communicate with each other using the data stack during compilation.