Generating code is easy.
Building trust is hard.
Tools iterate. There are always a claimed better ones shows up on the orange site every week. The goal never really changes though: finish the task, and finish it efficiently.
An LLM can write the code, write the tests, and deliver the whole ticket on its own. And yet at some point you still need to put a human in front of it, unless you happen to be very confident in what came out, or you have no particular quality bar set. The work didn’t disappear. It moved from writing into reviewing.
The Stack Overflow piece has a metaphor we keep coming back to. If your kitchen knife kept changing shape, weight, and edge, you’d have to relearn it every time. You’d never accumulate any experience from using it, let alone building trust on it.
Which is roughly where most of our tooling sits right now. So when we sit down to work on Wok, we’re not really asking how to generate more code. We’re asking something narrower.
A standard, a specification
A defacto industrial compiler is whatever tells you what the correct behaviour is. Wok’s job is to be:
- Express the reference behaviour we want.
- Express one standard, so there’s exactly one oracle rather than a family of implementation-defined ones that happen to agree on your machine today.
That second point is doing most of the work. Most languages have several oracles pretending to be one: the spec says something, the compiler does something slightly different, and the allocator underneath does something different again on a different libc. Each gap is a place where a test passes for a reason you didn’t intend — and a passing test you can’t explain isn’t really evidence of anything.
Generating code is easy. Building trust is hard. Trust is the thing we actually want to ship.
1. A fixed round trip, so there’s one standard form
Wok’s syntax tree round trip is fixed. CST to AST, and from the same AST back to the same CST.
Two properties fall out of that. Printing then parsing gives you back the same tree, and formatting an already-formatted file changes nothing. So the formatter isn’t a style tool with a config file to argue about. It’s a canonical form: two programs that mean the same thing are byte-identical.
This matters more when code arrives faster than you can read it. If every diff is guaranteed to be a semantic difference, review time goes to semantics instead of to noise. It’s also a prerequisite for the checks we want later on, since caching, deduplication, and content-addressed builds all need “the same program” to be a decidable question.
2. A slab allocator, so memory behaves the same everywhere
Generated code goes through a slab allocator rather than calling malloc directly.
malloc isn’t one algorithm. glibc, musl, and macOS all pick different size classes,
different thresholds for switching to mmap, different per-thread arena strategies. Same
program, same input, different allocation pattern, different cache behaviour, and sometimes a different
reproduction of a bug. That variance stays invisible right up until it’s the only thing standing
between you and a repro.
The slab removes it, and it does double duty:
- When nodes are allocated in bulk, it acts as an arena. Allocation is a bump within a slab instead of a trip through a general-purpose allocator.
- It acts as the nursery for compile-time reference counting. The compiler already knows where each value dies, so it can hand the cell straight back to a fixed-size free list, and the next allocation of that shape reuses it immediately.
The result is deterministic: same program, same memory behaviour, on any machine. Which is what makes a test result mean something the second time you run it.
3. A terse language with a small standard
Wok is small on purpose, and the standard stays small.
Small isn’t only an aesthetic preference here. The surface of a language is the part you have to hold in your head, the part you have to review generated code against, and the part where features interact in ways nobody specified. Fewer features means fewer interaction corners, and interaction corners are where the bugs live.
When something falls outside that scope, Wok works as a glue language and goes to C through FFI. The escape hatch is deliberate: one explicit boundary, stated in the type, rather than the guarantees quietly leaking everywhere.
4. C17 output, so the platform is somebody else’s solved problem
Wok emits C, specifically C17.
- Portability comes along for free. Anywhere there’s a decent C toolchain, there’s Wok.
- Because the C is machine-generated, it’s tractable to keep it UB-free by construction. A human writing C has to remember not to rely on signed overflow or violate strict aliasing. A code generator can simply never emit those forms.
- GCC and Clang then do most of the optimization. They’ve had thirty years of tuning that a young compiler is not going to match, and they’re already trusted by everyone downstream of us.
We’d rather target a stable, portable, aggressively optimized backend than write our own and ask people to take it on faith.
The point
Sharp tools, in the old sense, are tools whose edges you’ve learned by hand. That takes years, and it doesn’t really scale to how fast code gets produced now.
So the alternative isn’t more trust. It’s needing less of it. A canonical form, deterministic memory, a small surface, and portable output are all properties that hold whether or not you were paying attention that day.
Where this stands today
None of the above is a plan we intend to get to eventually. Here’s what actually exists.
An interpreter prototype in Haskell, and with it the problem we most needed to settle: continuation multiplicity. A handler now resumes its continuation at most once, and that’s checked rather than promised. Once multiplicity is a compile-time fact, lifetimes are a compile-time fact too, which is what lets the compiler place reclamation without asking you for annotations.
We updated our syntax, making it terser and with fewer surprises. Getting there took a detour worth describing:
- The parser and the formatter share the same code. This is what makes the round trip in section 1 a structural property rather than an aspiration. There’s no separate pretty-printer to drift out of sync with the grammar, because there is no separate pretty-printer.
- Formatter idempotency is tested by generated programs in CI. We generate inputs, format, then format again, and require a fixpoint. Idempotency is easy to claim and easy to quietly break with one new syntax node, so it gets machine-checked on every commit rather than spot-checked by hand.
- The tokeniser, layout rules, and parser are LLM-assisted C23. Worth stating plainly, given the subject of this post: the front end was written with an LLM in the loop. Which is exactly why the checks around it aren’t optional.
- ASan clean, with macros that enforce parser coverage. The macros mean a newly added syntax node can’t be silently left unhandled; it has to be dealt with or the build complains. Generated code is fine, as long as the thing checking it isn’t also generated on a whim.
- BNFC first, hand-written parser second. We used BNFC in Haskell early on to prove the grammar had no conflicts, which is the right tool for answering “is this grammar sane.” It’s the wrong tool for diagnostics. A generated parser gives up at the first error; a hand-written one can recover, keep going, and report the other four things wrong with your file. So the grammar was validated with a generator and then implemented by hand.
Wok is experimental (v0.1) and the syntax and semantics are still settling, so none of this is advice yet. But the target is simple enough: when the tool says yes, that yes should be worth something.
Referenced above: Developers are attached to tools because tools encode trust, Stack Overflow Blog, July 2026.
Everything else we’ve written is over here.