Session 1: The Blank File
Every compiler starts the same way: an empty main.rs. The question is how you get from there to a working language.
FLIN's compiler was built in 26 sessions, each one advancing the pipeline by one significant capability. Here is the technical story.
The Lexer (Sessions 1-4)
The lexer converts source code into tokens. FLIN's lexer handles:
- 9 statement types, 16 expression types, 11 type variants
- Soft keywords -- words like
count,order,textthat are keywords in some contexts but valid identifiers in others - Full Unicode support for string content
- 107 tests passing by Session 4
The soft keyword system was the first real design challenge. count = 0 must parse as an assignment, but ask "count users" must parse as an intent query. The solution: Keyword::can_be_identifier() checked in three parser locations.
The Parser (Sessions 5-8)
FLIN uses a Pratt parser for expressions -- the same algorithm used by V8, rustc, and Clang. By Session 6: 149 tests, full operator precedence, 72% complete.
Session 8 brought the Hindley-Milner type system: let-polymorphism with TypeScheme and polymorphic instantiation. This enables generic programming while maintaining full type safety -- no any escape hatch.
The Virtual Machine (Sessions 9-10)
A stack-based VM with:
- 75+ opcodes (arithmetic, logic, control flow, entity operations, string methods)
- 65K max stack depth, 1024 call frame depth
- 251 tests passing by Session 10
- ~2,850 lines of Rust
The VM executes bytecode generated by the code generator. No interpretation of the AST -- everything is compiled to opcodes first.
First Render (Session 26)
Historic milestone: FLIN code rendered in Chrome.
An interactive counter demo -- click a button, see the count update. Two critical bugs discovered during first render:
1. Stack underflow in match expressions -- Pop instruction count mismatch in the code generator 2. String comparison failures -- ObjectId comparison instead of content comparison
Both fixed with the addition of values_equal(), a helper for proper value comparison across all FLIN types.
The Full Pipeline
Source Code (.flin)
|
v
[Lexer] → Tokens
|
v
[Parser] → AST (Abstract Syntax Tree)
|
v
[Type Checker] → Typed AST (Hindley-Milner inference)
|
v
[Code Generator] → Bytecode
|
v
[Virtual Machine] → Execution → HTML/CSS/JS output
|
v
[Dev Server] → Browser with hot reloadSource to browser in milliseconds. No intermediate transpilation to JavaScript. No Babel. No Webpack. The VM outputs the final HTML, CSS, and JavaScript directly.
What We Learned
Build the boring parts first. The lexer and parser are not exciting. They are essential. Getting them right -- with comprehensive tests -- meant every subsequent session could trust the foundation.
Soft keywords are hard. The intersection of user-friendly syntax and unambiguous parsing requires careful design. Every "convenient" keyword is a potential parsing ambiguity.
Test everything, immediately. FLIN had 107 tests before it could parse a single function. By Session 26, over 600. The tests caught both critical bugs during first render before they reached users.
26 sessions. One founder. One AI CTO. A working programming language.