blog dot information dash superhighway dot net

or, discovering and recovering a lost programming language over the course of a weekend

I go down rabbit holes. One of the great pleasures of this dumb future we live in is that you can dig through the milk crates of our culture forever, following whatever interests you, and there is no bottom. My latest rabbit hole looked something like this:

Wait, hold on a minute. This kind of sounds exactly like the weird idealized programming environment I keep in my head. Obviously with serious restrictions and caveats, but, as a learning tool? They implemented this on an Apple II in 1984? How far exactly did they go?

I mean, look at this:

  • “Everything a Spellcaster program does leaves marks on the screen. You watch all its inner workings in motion.” Programming is frustratingly opaque – it can be incredibly difficult, especially for a beginner, to answer the question, “why is the computer doing this?” But for the purposes of a teaching language, why not put all of the program's state on the screen?
  • “Debugging a Spellcaster program is easy, because you can stop it, make it back up to the mistake (while you watch), change it, and let it run forward again.” I have been fighting to build this literal exact workflow for YEARS. Time travel debugging crossed with edit and continue??
  • “Imagine an editor and interpreter so wed that every keystroke, as it is typed, is syntactically checked and executed, so you instantly see its effects. If you backspace, the program reconstructs its previous state — even in the middle of conditions and loops.” A special livecoding editor built for the tightest possible feedback loop, with no invalid states?

So of course I went looking for disk images.

There were no disk images. There were a couple of scanned magazines with the same ad. That was all I could turn up.

I started asking around on the internet and a couple of people tracked down a few more references; a couple more short reviews in some magazines. The jackpot, though, was this reimplementation of Spellcaster written in Processing. The readme gave me names, and the names gave me email addresses.

Long story short, I reached out to Scott Gilkeson, the original programmer of the C64 version, and it turned out that he had conveniently made disk images a few years ago. He also put me in touch with John Fairfield, the original designer and programmer of the Apple II version, who gave his blessing to share them. (The Apple II version is lost, as far as anyone knows.) There was also a PC version, written in C; nobody seems to know where that is, either. Within a day of reaching out, I had a copy.

You can use Spellcaster now, right from your browser.

A manual is also available. (Scott is seeing about creating a higher-quality scan, but what's there is much better than nothing.)

John Fairfield would go on to cofound the company that produced the Rosetta Stone language learning software. Spellcaster is, in a deep way, also language learning software. There are some truly fascinating design decisions made, not only in the Spellcaster programming environment, as advertised, but also as a language.

  • It is the only programming language I am aware of that is designed to be spoken. The way the editor works, you generally press keys corresponding to syllables, rather than letters. There are occasionally some mnemonic properties to those syllables, but they're pretty arbitrary; you end up writing words like NUBOBORIBOBOLIAKA. Presumably, in a group learning setting like a classroom, this means programs can be unambiguously talked about, down to the last instruction – an often overlooked but useful property, as anyone who has tried to read code off a slide can attest.
  • There is a base 4 numbering system that's used in a few different places; 4 of the 5 vowels are used as digits, and paired with a starting consonant to mean different things that are done with that number (MABO means “repeat BO once”, KA means “set the pen to colour #1”). The fifth vowel, U, is used to choose a random number between 0 and 3. Because there are four cardinal directions, and 16 colours, this means something like MURI (turn clockwise a random number of times) is a nice concise way to write “point my pen in a random direction”, and KUKU is a nice concise way to write “choose a random colour”. Again, designed to be spoken, but also designed to be concise.
  • You can have spaces in identifiers, but not the letter Z. Z is short for the ZIM syllable, which ends free text entry. Honestly? I kind of really love that tradeoff. A beginner is much more likely to run into the question of “why can't I write two words here” long before they ask “why can't I write a Z”.
  • You can also give a spell an empty name. I did this by accident but the manual says “Well, that's the name that has no letters in it, and it's a perfectly good name.” And indeed, it just works, to call it you just write TUZIM, a little like if there was a special function you could call in Lisp by writing ().
  • Reading just a little further into the manual, it turns out the spell with the empty name has an actual, specific purpose! VUZIM means “see if the user pressed a key, and if they did, run a spell corresponding to that key”. So if the user hits the “A” key, it will run the A spell. So of course since you can't have a Z spell, if the user hits the “Z” key, the empty spell is called. Given that you do have to hit the “Z” key to create the spell, there is a certain strange elegance to it; I don't entirely know if I think it's a good design but it's sure not a choice I would have thought to make.

Honeylisp is a programming environment for the Apple II which attempts to leverage the absurd computational horsepower of a modern laptop to make programming a 1mhz 8-bit processor more magical.

Step 1: Write a 6502 assembler

The Honeylisp assembler is written in Fennel, which is a Lisp dialect that compiles to Lua. My goal in writing it was that the input to the assembler would be simple lists, making Fennel into a super-powerful macro assembler basically for free. 6502 assembly is treated as simple data, a kind of embedded DSL that can trivially be generated by simple Fennel functions.

Step 2: Integrate into a text editor

The Honeylisp environment runs on top of lite, a very small and extensible programmer's text editor written in Lua. It's trivial to add new commands that trigger whatever kind of interactive build process I want. It's also straightforward to extend to create custom UI; I put together the bones of a simple tile editor in an evening. I ended up porting lite to the love2d runtime to allow me more flexibility in how I could build these custom editors.

It is also a fairly straightforward matter to add hot code reloading, though it requires a bit of forethought to take full advantage of it. When I put together the tile editor, I could make changes to it, hit Alt-R, and those changes were immediately live. It's cool to be able to use your editor to live-edit your editor.

Step 3: Integrate into an emulator

Back in July, I came across this video of Dagen Brock talking about an Apple IIgs emulator project he was working on that had an interesting property. Instead of integrating a debugger into the emulator, he decided he would implement a socket-based debugging interface that anyone could write a front-end for. Any external program could use this to gain full control of the emulated computer – read and write memory, set registers, set breakpoints, anything.

It turns out this idea kind of languished, and he never quite got to polishing up and releasing his debugger. But this emulator, GSPlus, exists, and it gave me the core idea at the heart of Honeylisp – if I can arbitrarily read and write memory, and I have the ability to augment my assembler, then I can do anything. I can take snapshots and jump back and forth between them, recovering from hard crashes. I can push code updates while the game is running; I just need to be able to specify an appropriate “merge point”. I can map variables from one version of the program to another, so even if data moves around in memory, I can compensate. I can put breakpoints on memory I know should be read-only, to catch wild pointer accesses. I can implement a REPL entirely on the PC side; assemble tiny snippets of code and trigger them. I can integrate with my tools, so making changes to a graphical tile in my editor instantly updates the screen of my emulated Apple II. The possibilities are truly vast.

Step 4: Integrate with hardware

One of the pretty unique features of the Apple II is that it comes with a built-in machine language monitor, complete with a mini assembler and disassembler. It turns out that this can trivially be controlled over a serial port to do things like... write arbitrary memory. So I can plug my laptop directly into a stock Apple II, type IN #1, and then run a single command to bootstrap my environment. In fact writing arbitrary memory in response to serial port commands is not very complicated, and many of the magical abilities I imagine for my environment can be done directly on real hardware.

Step 5: Write a game

There is no point in investing all of this effort into software development tools if you don't plan to develop any software. I intend to port my MS-DOS game Neut Tower to the Apple II using this system. Neut Tower was written using a hand-rolled Forth system; I've built a simple Forth-like stack VM to allow for non-performance-critical code to be written compactly. Because my assembler is so easily extensible, I can also easily use simple Fennel code to generate VM bytecode, which means I can do without a huge amount of the overhead of a full Forth as all of the compiler-y bits live on my laptop. This VM can also be the basis of my interactive REPL, which will be much nicer to write than little assembly snippets.

Step 6: Tell people about it

It's fairly early stages for all of these steps – I have a good start in all of them, but there is still lots of work to be done. I'm really excited about this project, though, and I want to talk about it! Hopefully this blog will be a useful place to do that. I'm looking forward to continuing to share my progress.

So I should probably have a blog post that I can point to about this whole retrocomputing project that I've been up to the past year and a half.

I wrote a game on an MS-DOS 286 PC, using only tools I built myself or tools that were available during the era where they were still selling 286 PCs. It's called Neut Tower and you can play it on your MS-DOS PC, in DOSBox, or in your browser. As part of this project, I implemented a Forth system, and built most of my game and its tools using it.

My motivation at the start of the project was this: I was enjoying using my 286. I liked the single-tasking workflow; there were no distractions. I was downloading games and apps and it was fun! So I figured I'd take the next step and write a little game or something.

When I was a teenager, I had a 286, and I tried to learn low-level programming on it because my options were “low-level programming” and “BASIC”, and I had hit my limit with BASIC. Assembly might as well have been Martian to me, but I got a book about C, and I got a book about game programming, and I sort of got some stuff working. But mostly the stuff I tried to do myself from scratch, or port from other sources, didn't work, and I didn't know why. Eventually I also got access to a 486, and then a Pentium, and the internet, and djgpp and Allegro, and suddenly I had an embarrassment of nice graphics and sound libraries and tooling, segment:offset addressing didn't matter, and I never had to worry about trying to understand how Mode X worked ever again.

Twentyish years later, I wanted to learn all the stuff that never quite clicked for me. I wanted to dig into how everything worked, to make sense of the tutorials that once baffled me. I wanted to really understand it all. So I started writing little prototypes, and pretty soon, yeah, I had a cool EGA graphics engine, with two way scrolling of a tilemap and 16x16 sprites drawn on top, running at a decent speed on actual hardware. Everything fell into place one tiny experiment at a time.

With the hardware programming side of things, I learned that my teenage understanding hadn't really been all that far off the mark – my problems weren't so much that I didn't understand the tutorials and resources that were available to me, it was more that I was simply bad at debugging my beginner code, and didn't have the tools or the patience to fix it. With 20 years of professional programming experience under my belt, and a wealth of resources on the internet that explained how things worked in depth, this was no longer an issue.

Then I started to write a game loop in C, and didn't really like it. I knew in the back of my head that, for what I wanted to do, I really wanted some kind of scripting language. And I remembered Forth existed.

In my 20s, obsessed with both the world of programming languages and the world of embedded systems, it was inevitable that I would learn about Forth – it's a particularly uncommon blend of small and powerful, that could run directly on hardware, that people who loved it really loved. I'd tried seriously to learn it but couldn't really wrap my head around it – the weird postfix syntax, the confusing levels of meta. Why could I not use IF statements at the REPL? How was I supposed to remember all these finicky rules? I filed it away as “interesting, but not for me.”

This project was the perfect opportunity to revisit that evaluation. Forth fit the bill exactly – it was a tool that could be built quickly, using minimal resources, and made to do what I wanted, AND I already had a hazy half-remembered foundation from decades ago. I dove headfirst into it.

Relearning Forth was an altogether different experience. It turned out that once I built one myself, I understood it completely. The design of Forth is to write as little code as you possibly can, to make the computer do only as much work as it needs to. When I had to write it all myself, I had to decide – is it worth building this language feature, or can I do without it? Usually I could do without it. Usually there was a tinier way to do it. The code that I had to write wasn't really all that much uglier or worse for it, once I got used to the constraints. And I had proven designs I could pilfer; there are lots of existing open-source Forth implementations to get inspiration from. There are guides for building Forth systems. Doing Forth is not learning an existing language set in stone, it is building a language to solve your problem, and sharing ideas about useful building blocks. Chuck Moore, the inventor of Forth, hated its standardization; thought the goal of portability was absurd, thought everyone should change it as they needed, to fit their problem. He is still trying out new ideas, rebuilding, simplifying, making a system uniquely his own.

So why do I think all this is important enough to write about?

When I was a kid, I had this persistent idea in my head, that computing was a skill I could work at, get better at, and that doing so would allow me to accomplish things that were impossible for me without it. “Once I got good enough”, I could make a computer game, by myself. I could draw the graphics, I could write the code, I could make the music, I could design it all. I could make it and I could put it out into the world and it would be mine, start to finish. Every time I learned something new about computers, got some new piece of software, I gained abilities. I could do things I couldn't do before. My vision of computer literacy is that everyone has this experience, that everyone can learn the skills they want, is provided with the tools they need, to make their imagination real. I have never really let go of this idea.

I'm still trying to find ways to make it true, still trying to explore the different ways that computing can be empowering. Retrocomputing is one avenue for that – people in the past had a lot of good ideas that didn't catch on. And while emulators are wonderful, running them inside a modern computing system makes it harder to experience what using an old computing system really felt like.

When I show people my setup, they are often curious about the qualitative difference between old tools and modern tools; it must be so much harder, right? And... for me, it's really not! I write bugs at about the same rate; I fix them at about the same rate. There are many things I can't do because of resource constraints, but that keeps the scope manageable and makes for an interesting challenge to find cool stuff I can do. The biggest thing I miss is having a second editor that I can use to look at & edit code while my game is running — I have often resorted to taking a photo of some code with my phone so I can read it while I have the game up.

And I gain really valuable things from the constraints. The biggest thing is that there's no alt-tab away from the work – it's so much easier to focus without a web browser instantly at my fingertips. (I'm procrastinating at work writing this right now!) The resource constraints mean I have to focus ruthlessly on solving the problems I have, not the problems I imagine I'll have – there's no perfect, elegant, general solution if I think hard enough, there's only adding things and cleaning up what I've got, one small piece at a time. And I can take workflow seriously as one of those problems! When I'm fed up with the tools that are available for DOS on a 286 (and this happened multiple times!), I make my own that work the way I want, and I'm able to integrate them seamlessly into my engine. I'm able to intentionally craft my environment to be comfortable. I'm no artist, but multiple people have complimented my art – partly, the secret is that 16x16 sprites and tiles can only look so good with a fixed ugly 16-colour palette, so I'm able to focus on broad colour and style choices. But really, if you put me into my ugly, limited pixel editor that's two pages of code but instantly shows me what my sprite looks like in my game, I will mess around until I'm happy. Put me in front of Photoshop with 16 million colours and I will go crazy from decision fatigue; I'll avoid making more art, and I'll get myself stuck.

So for me, the tradeoffs are incredibly worth it. I've spent decades trying to make games as a hobby; I've put out reams of junk – failed prototypes, bad joke games, quick jam games, failed engines, half-finished tools. I've tried every way of making games that I can think of; coding engines from scratch, using Unity, Godot, Love2D, Klik & Play, Game Maker, Twine, Construct, Adventure Game Studio, pygame, Allegro. Some approaches I've had more success with than others, but I've not ever been as happy with anything I've made as I am with Neut Tower. Not as a retrocomputing exercise — as a game.

Neut Tower is done, for now, and I am taking a break from it. (Perhaps someday I will return to it to create the next two episodes.) I'm quickly finding myself using all of these lessons and starting to build some tools for myself in Linux. I don't quite know what they'll turn into yet, but I'm looking forward to finding out, one small piece at a time.

I've been seriously writing Forth, with my homebrew Forth dialect, for about a year now, off and on, and I've noticed something interesting with how things end up structured.

Forth and Lisp are often spoken of as though they are similar in some deep way. In Lisp circles, you often hear “code is data.” This is generally held to mean “Lisp has macros”, more or less – a Lisp program's source code is a syntax tree made of Lisp lists, that your Lisp program can introspect into and transform into new syntax trees and execute. Your program is literally a data structure.

My Forth code has very few things I would refer to as “data structures”. There is no significant language for defining them – I write one-off words that do pointer arithmetic. I only have a handful, so I haven't felt the need to generalize. It does zero transformation of them – they have been carefully chosen to be directly useful for everything the program needs them for, in-place, as-is.

Instead, the common pattern is that everything is code, which, thanks to Forth's flexible non-syntax, can be made to look a lot like data. Often data is compiled directly into the code that uses it – instead of naming a constant that's passed into a function to do a particular thing, you name a function that takes no arguments that just does the thing. (There are lots of flexible ways to make this sort of thing easy and inexpensive in Forth.) Forth is hyper-imperative to a degree that, as modern programmers, we've largely forgotten is even possible. Even, say, the number 4 is arguably a word executed for its side effects (push the value 4 onto the current stack). Of course, this is how CPUs work, too – you don't have a concept of “4” on its own in assembly, you have the concept of moving “4” into a register, or into memory. The only thing you can tell a CPU is to do things. Forth is the same.

One consequence is that a Forth word that represents a constant is invoked in exactly the same way as a word that makes decisions. What this means is that it is virtually impossible to write yourself into a corner by “hard-coding” something. You can start with the most direct implementation, and expand it into something more flexible as you need to. I often find myself turning a word that was very static into something dynamic, and not having to change any of the code that depends on it. And my Forth has developed lots of facilities for sophisticated decision-making and dispatch. It turns out that most sophisticated decision-making is largely just indirection, and is easy to accomplish even in extremely resource-constrained environments. Many things I used to think of as modern, expensive conveniences – anonymous functions! polymorphism! green threads! – are actually extremely cheap and simple to build, they just... don't exist in C.

In “Programming a Problem-Oriented Language”, Chuck Moore defines “input” as “...information that controls a program.” Forth and Lisp share the idea that, most of the time, it's more powerful and flexible to use the language's parser to read a program's input. Before JSON, there was the s-expression, the universal data structure, and in Lisp, you usually are either using macros to turn that data into code directly, or writing an interpreter for that data. You can often think of a Lisp program as a collection of small, domain-specific virtual machines.

However, Forth doesn't really have a parser; it has a tokenizer, a symbol table, an interpreter, and a virtual machine. Parsing Forth and executing Forth are synonymous; hell, compiling Forth and executing Forth are synonymous. Forth says you don't need a domain-specific virtual machine; you already have a perfectly good machine right here! Why not just solve your problem directly, right now?

You may need sophisticated abstractions to succinctly describe the logic of how your problem is solved, and writing good Forth code is all about investing in those. But Forth makes an argument that most of the data that your program deals with is actually about controlling what your program should do, and making decisions about what your program should do is the job of code.

There are drawbacks to this approach, of course; plenty of things that are inconvenient to express as text, plenty of times I wished I had a “live” data structure I could update on the fly and persist while my program is running, rather than having to exit my program and update my code. But if you can work within the constraints, there is enormous flexibility in it. I'm writing a puzzle game, and while I have a terse vocabulary for defining levels, it's also trivial for me to add little custom setpieces to a given level, to throw in dialogue in response to weird events, to add weird constraints that only apply in that space, because at every step, I have the full power of the language at my disposal. If I'd taken a data-driven approach, I would have needed to plan everything in advance, to design my little problem-oriented VM and and hope I thought of everything I needed. But with a code-first approach, I can be much more exploratory – try to build things, and if they work well, factor them out to be used more generally. Architecture arises naturally from need, as I build.

Forth is perhaps the tiniest possible useful interactive programming language. It is tiny along a number of dimensions:

  • The amount of code required to implement it
  • The size of the code that is generated
  • The amount of memory used
  • The number of features it considers necessary for useful work

It is a language that makes complexity painful, but which reveals that a surprising amount can be accomplished without introducing any. Forth is the opposite of “bloat”. If you've ever been like “Oh my God this Electron-based chat app is taking up 10% of my CPU at idle, what the HELL is it DOING, modern computing has gone MAD”, Forth is there to tell you that computing went mad decades ago, and that programs could be doing SO MUCH MORE with SO MUCH LESS.


There is an expression about Forth: “If you've seen one Forth, you've seen one Forth.” Forth isn't a strictly-defined language, though there is a standardized dialect; it's more a set of ideas that tend to work well together.

In the past month, I wrote a tiny Forth system on a 286 running MS-DOS using Turbo C++ 1.01. It is my first time using Forth in anger, though I read a lot about it 15 years ago. When I refer to my Forth, I am referring to a system literally thrown together in two weeks, written by someone who does not really know Forth that well. It is slow and wildly nonstandard and it doesn't do very much, but I have enjoyed the process of writing it very much. If you are a grizzled old Forth grognard, please let me know if I have misrepresented anything.


Here is an incomplete list of things you may take for granted as a programmer that Forth, in its purest form, generally considers unnecessary waste:

  • Garbage collection
  • Dynamic memory allocation
  • Garbage
  • Memory safety
  • Static types
  • Dynamic types
  • Objects
  • Polymorphic methods
  • Closures
  • Lexical scoping
  • The concept of global variables being in any way “bad”
  • Local variables
  • The ability to write “IF” statements at the REPL

Most or all of these can be added to the language – the Forth standard, ANS Forth, specifies words for dynamic memory allocation and local variables. There are lots of object systems that people have built on top of Forth. Forth is a flexible medium, if you're willing to put in the work.

But the inventor of Forth, Chuck Moore, literally said, in 1999: “I remain adamant that local variables are not only useless, they are harmful.” In the Forth philosophy, needing to use local variables is a sign that you have not simplified the problem enough; that you should restructure things so that the meaning is clear without them.


A core part of Forth is that all functions, or “words” in Forth terminology, operate on “the stack”. Words take arguments from the stack, and return their results on the stack. There are a handful of primitive built-in words that do no useful work besides manipulating the stack.

What this means is that writing an expression tree as Forth code ends up turning into postfix notation. (1 + 2) * (3 - 4) becomes 1 2 + 3 4 - *. Writing a number in Forth means “push that number onto the stack”.

Forth syntax is, with a few exceptions, radically, stupefyingly simple: Everything that's not whitespace is a word. Once the interpreter has found a word, it looks it up in the global dictionary, and if it has an entry, it executes it. If it doesn't have an entry, the interpreter tries to parse it as a number; if that works, it pushes that number on the stack. If it's not a number either, it prints out an error and pushes on.

Oops, I meant to describe the syntax but instead I wrote down the entire interpreter semantics, because it fits in three sentences.

The exception to the “whatever is not whitespace is a word” rule is that the interpreter is not the only piece of Forth code that can consume input. For example, ( is a word that reads input and discards it until it finds a ) character. That's how comments work – the interpreter sees the ( with a space after it, runs the word, and then the next character it looks at is after the comment has ended. You can trivially define ( in one line of Forth.


There are practical reasons:

  • You need something tiny and reasonably powerful, and you don't care about memory safety
  • I'm not sure I can think of any others

And there are intangible reasons:

  • Implementing a programming language that fits into a few kilobytes of RAM, that you understand every line of, that you can build one piece at a time and extend infinitely, makes you feel like a god-damn all-powerful wizard

Part of the mystique of Forth is that you can get very metacircular with it – control flow words like IF and FOR are implemented in Forth, not part of the compiler/interpreter. So are comments, and string literals. The compiler/interpreter itself is usually, in some way, written in Forth. It turns out that you can discard virtually every creature comfort of modern programming and still end up with a useful language that is extensible in whatever direction you choose to put effort into.

Forth enters that rarefied pantheon of languages where the interpreter is, like, half a page of code, written in itself. In many ways it's kind of like a weird backwards lisp with no parentheses. And it can be made to run on the tiniest hardware!

The mental model for bootstrapping a Forth system goes something like:

  • Write primitive words in assembly – this includes the complete Forth “VM”, as distinct from the Forth language interpreter/compiler. The set of built-in words can be very, very small – in the document “eForth Overview” by C. H. Ting, which I have seen recommended as an excellent deep-dive into the details of how to build a Forth environment, Ting states that his system is built with 31 “primitive” words written in assembly.
  • Hand-assemble “VM bytecode” for the interpreter/compiler and required dependencies – because of the extreme simplicity of the VM, you can generally program your macro assembler to do this job, and so this can meaningfully resemble the act of simply writing Forth code directly
  • Write all new words using the interpreter/compiler you just got running

I say “interpreter/compiler” and not “interpreter and compiler” because they are literally mixed together; there is a global flag that determines whether the interpreter is in “compile mode” or not. It is done this way because it turns out that if you add the ability to mark a word as “always interpret, even in compile mode”, you have added the ability to extend the compiler in arbitrary ways.


Any word that takes more than two or three parameters is a nightmare to read or write

Right now in my codebase I have a word that uses two global variables because I cannot deal with juggling all of the values on the stack. This word is absolutely not re-entrant and at some point I'm going to need to rewrite it so that it is, and I am not looking forward to it. If I had local variables, it would be substantially less of a problem. But there's also part of me that thinks there must be some way to rewrite it to be simpler that I haven't figured out yet.

There's another word in my codebase that takes 4 or 5 parameters that I managed to write by breaking it up into, like, 8 smaller words, over the course of writing / rewriting for like an hour or two. I felt pretty proud when I finally got it working, but honestly I think it would have been pretty trivial to write in C with local variables. I miss them.

Shit crashes

Remember the part about no memory safety? Yeah, there's all kinds of ways a wayward Forth system can go wrong. I forgot a DROP once in a frequently-used word and my computer hard-locked when the stack overflowed. (To be fair: my computer was a 286 running MS-DOS, so I was already in a situation where programming it meant rebooting it when I inevitably fucked something up.)

Nonexistent error messages

The only error message my Forth system has is, if it doesn't recognize the word “foo”, it prints “foo?” If, for example, I write an IF statement, but forget to end it with THEN, I don't get a compile error, I get — you guessed it — a runtime hard crash.


It's compact as hell

The majority of words I write are literally one line of code. They do a small job and get out.

It's direct as hell

Building abstractions in Forth is... different than building abstractions in other languages. It's still a really core, important thing, but as building complex / expensive code is so much work, stacking expensive abstractions on top of each other is not really tenable. So you're left with very basic building blocks to do your job as straightforwardly as possible.

You are absolutely empowered to fix any problems with your particular workflow and environment

People turn Forth systems into tiny OSes, complete with text editors, and I absolutely did not understand this impulse until I wrote my own. The Forth interpreter is an interactive commandline, and you can absolutely make it your own. Early on I wrote a decompiler, because it was easy. It's like half a screen of code. There are some cases it falls down on, but I wrote it in like a half hour and it works well enough for what I need.

Everything is tiny and easy to change or extend

Remember when I said I wrote a decompiler because it was easy? Other things I changed in an evening or two:

  • Added co-operative multitasking (green threads)
  • Custom I/O overrides, so my interactive REPL sessions could be saved to disk
  • Rewrote the core interpreter loop in Forth
  • Rewrote the VM loop to not use the C stack
  • Instrumenting the VM with debug output to catch a crash bug

One of the things on my todo list is a basic interactive step-through debugger, which I suspect I'll be able to get basically up and running within, like, an hour or two? When things stay tiny and simple, you don't worry too much about changing them to make them better, you just do it.

If you have ever wanted an assembly code REPL, this is about as close as you're going to get

Forth is a dynamic language in which the only type is “a 16-bit number” and you can do whatever the fuck you want with that number. This is dangerous as hell, of course, but if you are writing code that has no chance of having to handle arbitrary adversarial input from the internet (like my aforementioned MS-DOS 286), it is surprising how refreshing and fun this is.


I honestly do not know if there is a better way to understand Forth than just trying to build your own, and referring to other Forth implementations and documents when you get stuck. It's been my experience that they just don't make sense until you're neck deep into it. And it's tiny enough that you feel good about throwing away pieces that aren't working once you understand what does work.

I've found the process of writing my own Forth and working within its constraints to be far more rewarding than any time I have tried working with existing Forths, even if on occasion I have wished for more complex functionality than I'm willing to build on my own.


I'm very interested in alternate visions of what computing can look like, and who it can be for. Forth has some very interesting ideas embedded in it:

  • A system does not have to be complex to be flexible, extensible, and customizable
  • A single person should be able to understand a computing system in its entirety, so that they can change it to fit their needs

I find myself wondering a lot what a more accessible Forth might look like; are there more flexible, composable, simple abstractions like the Forth “word” out there? Our current GUI paradigms can't be irreducible in complexity; is there a radically simpler alternative that empowers individuals? What else could an individual-scale programming language look like, that is not only designed to enable simplicity, but to outright disallow complexity?

Forth is a radical language because it does not “scale up”; you cannot build a huge system in it that no one person understands and expect it to work. Most systems I have used that don't scale up – Klik & Play, Hypercard, Scratch, that sort of thing – are designed for accessibility. Forth is not; it's designed for leverage. That's an interesting design space I wasn't even really aware of.

The lesson that implementing abstractions as directly as possible enables you to more easily change them is a useful one. And the experience of succeeding in building a programming environment from scratch on an underpowered computer in a couple of weeks is something I will bring with me to other stalled projects – you can sit down for a couple of hours, radically simplify, make progress, and learn.