Skip to main content
Back to Articles
2026 / 01
| 2 min read

The Big web/ Move and Parser Migrations

Moving the web app to a proper structure, migrating parsers to Pest/Pratt, and building the Z-Machine opcode engine.

emulator refactoring parsers z-machine vite
On this page

The web app has lived at the repo root since the project started, and today I finally moved it. One git mv, then two hours chasing Vite alias formats and broken build scripts. The move itself took seconds; the consequence was that every assumption about paths had to become explicit.

The Web Move

The frontend now lives under web/src, with Vite’s root set to web/ and path aliases pointing into the right places. Rollup’s alias format needed the array form, not the object form—the dev server works with either, but the production build silently fails with the object form. That was a one-character fix with a two-hour debugging bill.

By the end of the day I could draw the project layout without guessing: CPU cores, languages, and assemblers at the repo root; the web UI in web/. Polyglot monorepo as navigable structure, not just a slogan.

Parser Migrations

I migrated three language parsers to Pest today: BASIC, Prolog, and VisiCalc. The old hand-rolled tokenizers worked, but extending them meant reopening code I’d rather not touch. Grammar files are explicit and testable.

The BASIC grammar now includes the full C64/VIC-20 keyword set. The Prolog lexer collapses a lot of edge-case code into readable rules while keeping the same token stream. VisiCalc’s formula parser moved to a Pratt parser, which handles the operator precedence that the old code special-cased everywhere.

A parser you can explain in one file is a parser you can trust.

Z-Machine, Now Executable

The Z-Machine decoder handles 2OP, 1OP, 0OP, and VAR forms, and I built tests alongside so I could catch encoding mistakes immediately instead of three weeks later when a game crashes on turn 47. The opcode execution engine can now step through real story files.

Operand decoding is the foundation—get that right, and the instruction set becomes mechanical.

Signal Cleanup

One bug cut across multiple backends: on disconnect, several CPU backends left DTR/RTS asserted, so the next session started with stale control signals. The fix was straightforward, but the effect wasn’t. Once DTR/RTS deasserted correctly on disconnect, reconnect behaviour stopped looking random.

What’s Next

The layout is settled. Tomorrow I’ll look at making it deployable—the web/ move should make containerization cleaner. After that, expanding the backend surface: more CPU cores, more peripherals, more ways to connect them.

Previous: 2026-01-26