Honeylisp: Livecoding the Apple II with 6502 assembly

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.