devlog

Building in public.

Notes on implementing a language from scratch in C — the decisions, the dead ends, and the things that surprised me.

Closures in first didn't need a special case
first just got fn function literals, calls, and closures. I expected closures to need a capture list or a snapshot of variables. Instead they fell out of the Environment struct that was already backing every block.
Why I wrote my own hash map instead of using a library
JMap is a simple open-addressing hash map in about 120 lines of C. Here's why writing it myself was the right call, and what I learned about linear probing along the way.
Designing a streaming lexer that never loads the file
Most toy lexer implementations slurp the whole source into a string buffer first. Here's why I went with a FILE*-based streaming approach and what get_while() and get_until() ended up looking like.
Tagged unions in C: representing an AST without malloc hell
Adding a new AST node type in many C implementations means a new malloc, a new free, and an audit of every switch statement. Here's how first's tagged union design keeps that manageable.
Testing a C lexer with Criterion: what worked and what didn't
Criterion is a great testing framework but its CMake integration has a few rough edges. This is the setup that actually worked, and the test patterns I wish I'd started with.
How operator precedence emerges from a recursive descent parser
I didn't need a Pratt parser or a precedence table. Precedence fell out of the grammar structure for free — and understanding why is one of the most satisfying moments in building first.