Back to blog

Building Courage: a compiler with a name

April 13, 2026
2 min read
Compilers

TL;DR — Courage is the name of a compiler I'm building as a learning project: parse a real language shape, lower it through an IR, and emit something executable. This post is the map, not the manual.


Why a compiler, and why "Courage"

Compilers sit at the boundary between what humans write and what machines run. I want that boundary in my head, not only as a user of rustc or LLVM.

Naming it Courage is half joke, half reminder: the scary part isn't the buzzwords, it's sitting down and finishing each stage before moving on. Same energy as the little dog on the home page: keep going anyway.


What "a compiler" actually means (for this project)

I'm not trying to ship a production language on day one. For Courage, the stack looks like this:

  1. Lexer — turn source text into tokens (keywords, identifiers, literals, punctuation).
  2. Parser — build an AST that matches the grammar I actually want (expression precedence, blocks, functions).
  3. Semantic pass — scopes, types (even a tiny type system), and errors that point at a line instead of at "segfault".
  4. IR — an intermediate representation that is easier to optimize and codegen than raw syntax trees.
  5. Codegen — emit assembly or a simple bytecode / C backend first, whatever keeps the feedback loop short.

You can swap step 5 for LLVM later; the point is to own the pipeline far enough that you understand what LLVM would be doing for you.


Design choices I'm holding myself to

  • Small surface syntax at first: expressions, let, functions, if, maybe a while. No macro system on week one.
  • Readable errors: if the parser fails, the message should say what was expected and where.
  • One test at a time: every phase gets a folder of tiny programs that must pass before I add features.

Where things stand

This is a living project: the repo and exact feature set will move. The invariant is the goal: Courage should stay understandable enough that I can explain each file to someone else without hand-waving.

If you're building something similar: pick a subset of C or a Lisp-y S-exp, implement end-to-end hello world, then grow. The name on the tin matters less than closing the loop from source to running binary.


What's next

  • Nail down the grammar on paper (or in a .ebnf comment) before writing more parser code.
  • Get one function compiling and returning an integer.
  • Document the IR format so future-me doesn't reverse-engineer my own project.

I'll keep updating this site as Courage moves; older long-form stuff may still live elsewhere, but compiler notes from here on live here.

About the Author

muon is trying to just learn