Building Courage: a compiler with a name
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:
- Lexer — turn source text into tokens (keywords, identifiers, literals, punctuation).
- Parser — build an AST that matches the grammar I actually want (expression precedence, blocks, functions).
- Semantic pass — scopes, types (even a tiny type system), and errors that point at a line instead of at "segfault".
- IR — an intermediate representation that is easier to optimize and codegen than raw syntax trees.
- 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 awhile. 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
.ebnfcomment) 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