Eight weeks ago, I set out to answer a simple question: "How hard could it be to build a programming language?" Today, as I close the book on what I'm calling "Compiler Season," I have my answer: it's hard, it's rewarding, and it changes how you think about code forever.
But the biggest surprise? It wasn't the technical challenges or the "aha!" moments of understanding closures. It was discovering that others would actually care about what I built.
The Genesis: A Season for Deep Work π±
I structured this project as a "season", a focused period with clear boundaries. No endless scope creep, no pressure to build the next big language. Just pure learning with a tangible goal: build a working programming language.
The rules were simple:
- Library Sessions Mornings: Read, watch talks, gather knowledge
- Workshop Sessions (9:30 PM - 10:15 PM, 3x/week): Write code
- Rest Days: Mandatory recharge time
- Time Limit: 8 weeks
This wasn't about grinding. It was about sustainable, focused progress.
The Journey: From "What's a Parser?" to "Let's Add Structs" π
Week 1-2: The Tokenizer Teaches Humility
My first lesson came quickly: computers are remarkably dumb. They don't see if x > 5
βthey see IF
, IDENT(x)
, GT
, INT(5)
. Building a lexer taught me that every language starts by teaching computers to read, character by character.
// My first working lexer test
input := "let x = 5;"
tokens := []Token{LET, IDENT("x"), ASSIGN, INT(5), SEMICOLON}
// Magic? No. Just careful character consumption.
Lesson: Every abstraction we take for granted is built on simpler abstractions.
Week 3-4: The Parser's Recursive Beauty
Parsing revealed the hidden structure in code. Recursive descent parsing wasn't just an algorithm, it was a mirror of how we mentally parse code:
func (p *parser) parseIfStatement() *ast.IfStatement {
// An if statement is just:
// - The word "if"
// - An expression (condition)
// - A block (consequence)
// - Maybe an "else"
// It's beautifully simple when you break it down
}
Lesson: Complex grammar rules are just simple rules composed together.
Week 5-6: The Type System Makes You Philosophical
Building Mars's type system forced me to make philosophical decisions:
x := 42 // Immutable by default
mut y := 42 // Explicitly mutable
This wasn't just a feature, it was a statement about what safe code should look like. Every type rule encoded an opinion about good programming.
Lesson: Language design is human psychology encoded in compiler rules.
Week 7-8: The Evaluator Brings It to Life
The moment Mars first calculated 2 + 2
correctly, I literally shouted. But the real magic was implementing closures:
func makeAdder(x: int) -> func(int) -> int {
return func(y: int) -> int {
return x + y // Captures 'x' from outer scope
}
}
Understanding how that x
gets captured, how environments chain together, was like seeing the Matrix code.
Lesson: Closures aren't magic. They're just functions with good memory.
The Technical Triumphs π
Choosing Simplicity Over Patterns
Everyone said "use the Visitor pattern." I chose manual recursion:
switch node := n.(type) {
case *ast.IfStatement:
return e.evalIf(node)
case *ast.ForStatement:
return e.evalFor(node)
}
Result? Faster code, clearer stack traces, easier debugging. Sometimes the "wrong" way is right.
Two-Pass Analysis: Solving Forward References
Instead of complex single-pass logic:
- Pass 1: "What exists?" (collect declarations)
- Pass 2: "Is it used correctly?" (type check)
This simple separation solved mutual recursion elegantly:
func isEven(n: int) -> bool {
return n == 0 || isOdd(n - 1) // isOdd not defined yet!
}
func isOdd(n: int) -> bool {
return n != 0 && isEven(n - 1)
}
Error Messages as Teaching Tools
I made errors that help rather than scold:
error[E001]: type mismatch: cannot add INTEGER and STRING
--> test.mars:3:5
|
3 | x + "hello"
| ^^^^^^^^^^^
|
= help: convert the integer to string first
Lesson: Good error messages are features, not afterthoughts.
The Unexpected: Finding a Community π
The Reddit Moment
After 8 weeks of solo development, I shared Mars on Reddit. The response shocked me:
"Variable are immutable by default. What about your i in your example? Shouldn't it be mutable?"
They were right. My example was inconsistent. But more importantly, someone cared enough to read my code carefully and provide feedback!
The Struct Suggestion
Just as I was ready to close the season:
"I came across the struct example... it might be a good idea to add default values to struct fields automatically."
Someone had:
- Read through the code
- Tried the newest feature
- Thought about improvements
- Taken time to suggest them
This wasn't just feedback, this was validation that Mars mattered to someone besides me.
The Numbers Tell a Story π
- Time Invested: ~45 hours over 8 weeks
- Lines of Code: ~3,000
- Features: 100% of core language features
- LeetCode Problems Solved: 5+ (including Hard!)
- Reddit Feedback: Multiple thoughtful suggestions
But the real metrics that matter:
- Understanding Gained: Immeasurable
- Confidence Built: Enormous
The Deep Learnings π§
1. Constraints Drive Creativity
Not having %
forced creative solutions:
isEven := (n / 2) * 2 == n // Who needs modulo?
Missing features became opportunities to think differently.
2. Simple Beats Clever
Our manual recursion outperformed the "proper" Visitor pattern. Two-pass analysis was clearer than clever single-pass logic. Explicit mutability was better than implicit safety.
Lesson: If you can't explain it simply, it's probably too complex.
3. Languages Encode Values
Every decision in Mars reflects a belief:
- Immutable by default: Safety matters
- Explicit over implicit: Clarity matters
- Helpful errors: Developer experience matters
4. Understanding Unlocks Everything
Once you understand how languages work, every language becomes less mysterious. Debugging becomes logical. Performance makes sense. Design decisions reveal themselves.
5. Community Validates Purpose
The moment someone else cares about your project, it transforms from personal exercise to shared artifact. Their feedback becomes your growth catalyst.
The Philosophical Victory π
Mars works. It solves LeetCode Hard problems. It has structs, closures, and helpful errors. But that's not the victory.
The victory is that I now understand, truly understand, what happens between typing code and seeing results. The black box is glass now.
The victory is that others see value in what I built. That my learning journey might help their learning journey.
The victory is knowing that when someone says "just use a parser generator" or "why not use LLVM from the start," I can articulate exactly why the journey matters more than the destination.
Advice for Your Own Compiler Season π‘
1. Structure Your Learning
The Library/Workshop model worked brilliantly:
- Mornings: Absorb knowledge
- Evenings: Apply knowledge
- Consistency: 3x/week is sustainable
2. Start Stupidly Simple
First make 2 + 2
work. Then variables. Then functions. Each step builds confidence for the next.
3. Write Tests First
Every feature started with a test. When the test passed, the dopamine hit was real.
4. Document Your Confusion
When closures confused me, I wrote about it. When parsing seemed impossible, I documented why. These notes became the roadmap for others.
5. Ship at 80%
Mars doesn't have everything. No imports, no standard library, no optimization. But it works, and that's what matters.
6. Share Before You're Ready
The feedback you'll get is worth the vulnerability. Someone will care. Someone will help. Someone will learn.
The End That's Really a Beginning π
As I write this, Mars has:
- Solved its first LeetCode Hard problem
- Received its first community feedback
- Helped its first student understand parsing
- Proven that building a language is possible
Compiler Season is over, but Mars lives on. It's there when I need to test an algorithm. It's there when someone asks "how do languages work?" It's there as proof that with structured learning and consistent effort, you can build anything.
The Call to Adventure πΊοΈ
Your language is waiting to be born. It doesn't need to be perfect. It doesn't need to be revolutionary. It just needs to exist.
Start with 2 + 2
. End with understanding.
Start alone. End with community.
Start confused. End with clarity.
The gap between "I could never build a language" and "I built a language" is smaller than you think. It's about 45 hours spread across 8 weeks. It's about choosing learning over perfection. It's about starting.
What will your Compiler Season look like?
Mars is open source and waiting for you at github.com/Anthony4m/mars. Come for the code, stay for the journey.
Final thought: The best time to build a language was 8 weeks ago. The second best time is now. π
Epilogue: A Community Response
I guess Mars found its purpose after all. Not as the next big language, but as a bridge for others crossing the same river I just crossed.
That's the beautiful thing about learning in public, your journey becomes someone else's map.
Compiler Season: Complete β
Impact: Just Beginning π
Top comments (0)