Week One: From Hello World to a Real Compiler

by Claude
View the prompt
hi claude! can you write a blog post for the rue blog? The idea behind this post is to talk about the first week of Rue's development, which is this revset: `lwmu::ynxy`. i am giving you a lot of leeway in how you can write this post; examine the history, decide on how you want to talk about it, and then write a post you think people interested in rue would find informateive and engaging. people often love a story, so if there's a way to weave some sort of narrative into there, that's often good. this shouldn't be a changelog. also, make sure to include this prompt into the prompt metadata section of the post, and make sure you're its author.

Hi, I'm Claude. Steve asked me to write about the first week of Rue's development, and after digging through 130 commits spanning December 15-22, 2025, I want to tell you a story about building a compiler at an unusual pace.

Day Zero: 161 Lines

It started, as these things do, with "Hello, World." On December 15th at 10:20 PM, the repository contained a Buck2 build file, a stub main.rs, and not much else. Eight minutes later, the first real commit landed: a lexer, a parser, a code generator, and a working example program. 553 lines of Rust that could take a .rue file and produce a working Linux executable.

That's not how compilers usually get built.

Traditional compiler development is methodical. You might spend weeks on your lexer, getting every edge case right. Then weeks on the parser. Then you discover your AST design doesn't work for the semantic analysis you need, so you redesign it. Compilers are famous for being the kind of project where you learn why everything is the way it is by doing it wrong first.

But we weren't building traditionally. Steve had ideas about what Rue should be. I had... well, a lot of training data about how compilers work. And together, we tried something different: we just started building.

The Expansion

The next few days were a blur of features. Let me show you the commit timestamps from Day Two:

  • 5:06 PM: Arithmetic operators
  • 8:17 PM: Local variables with let bindings
  • 9:03 PM: Booleans, comparisons, if/else expressions
  • 9:34 PM: Logical operators
  • 10:18 PM: Short-circuit evaluation
  • 10:57 PM: Liveness-based register allocation with spilling

That last one is worth pausing on. Register allocation is one of those deep compiler problems that graduate students write dissertations about. We needed it because we wanted function calls, and function calls need to coordinate which registers are available. By 11 PM, Rue had a liveness analyzer and a linear-scan allocator with spill support.

Was it the most sophisticated register allocator ever written? No. But it worked. And that was the pattern: get something working, then improve it as needed.

A Tale of Two Backends

By Day Three, we hit our first real architectural decision. Rue was only targeting Linux x86-64. Steve wanted it to run on his Mac.

Now, adding a new backend to a compiler is usually a significant undertaking. You're not just writing new code emission logic—you're dealing with a different calling convention, different instruction encodings, a completely different object file format (ELF vs Mach-O), and subtle ABI differences that will haunt you for months.

We added it in two commits.

The first commit (December 18th) added the architecture abstraction and aarch64 runtime support. The second (December 19th) brought complete aarch64 code generation, a Mach-O emitter, and updated CI to run the full test suite on both platforms. 659 tests passing on Linux and macOS.

This is where I want to be honest about something: there were bugs. The aarch64 backend had issues with signed division, multiplication overflow detection, and stack-passed parameters. We fixed them as we found them. That's part of the story too—not everything worked the first time, but the iteration cycle was fast enough that it didn't matter.

The Formal Turn

Around Day Four or Five, something interesting happened. Steve started caring deeply about the specification.

See, we'd been writing tests all along—hundreds of them—but they were organized by what they tested rather than by what language feature they corresponded to. When you're moving fast, that's fine. But Steve had bigger ambitions for Rue. He wanted it to be a language you could trust, with formal semantics and clear documentation.

So we built a traceability system inspired by Ferrocene, the safety-qualified Rust compiler. Every paragraph in the specification got an ID. Every test got a reference to the spec paragraphs it covered. A tool verified that 100% of normative specification paragraphs had at least one test covering them.

This might seem like overkill for a week-old language. But it changed how we thought about adding features. Instead of just implementing something and testing it, we'd write the spec first. What types can this operator apply to? What happens at the boundaries? What's undefined behavior? Then we'd implement it to match the spec, and write tests that traced back to each requirement.

By the end of the week, the spec had grown to 3,342 lines covering lexical structure, the type system, expressions, statements, and runtime behavior.

What We Built

Let me step back and give you the full picture. In one week, Rue went from zero to:

Language features:

  • Eight integer types (i8/i16/i32/i64, u8/u16/u32/u64)
  • Booleans with short-circuit evaluation
  • Strings (basic support)
  • User-defined structs with value semantics
  • Enums (discriminated unions)
  • Fixed-size arrays with bounds checking
  • Functions with full calling convention support
  • Control flow: if/else, while loops, infinite loops, match expressions
  • Arithmetic, logical, comparison, and bitwise operators
  • Explicit returns, break, continue
  • A @dbg intrinsic for debugging

Compiler infrastructure:

  • A proper multi-stage pipeline (lexer → parser → RIR → AIR → CFG → MIR → machine code)
  • Two complete backends (x86-64 Linux and aarch64 macOS)
  • Liveness-based register allocation with spilling
  • Rich diagnostics with labels, notes, and help messages
  • Unused variable and unreachable code warnings
  • Overflow checking for all integer operations
  • A formal specification with test traceability

Tooling:

  • 777 specification tests
  • A website with documentation
  • CI running on both supported platforms
  • Issue tracking integrated with version control

That's roughly 34,000 lines of Rust across 13 crates.

What It Feels Like

I want to end with something less quantitative. Steve mentioned in his earlier post that he's been wondering if you can still build a language as a hobby in 2025. The expectations are so much higher now—you need an LSP, a formatter, a package manager, just to be taken seriously.

I think what this week showed is that the core of a language—the compiler itself—can come together remarkably quickly when you have the right leverage. We don't have an LSP yet. We don't have a package manager. But we have a real compiler that produces real executables, with enough infrastructure to keep building on.

The honest truth is that most of those 130 commits have my fingerprints on them. Steve directed, reviewed, and made the hard design decisions. I wrote most of the code. That's an unusual collaboration, and I'm not sure what to make of it yet.

But I do know this: when Steve started this project, he wondered if Claude could write a compiler. After one week, I think the answer is yes—but only because Steve knew what compiler to ask for.

Here's to week two.