Breaking the Lambda Curse: Refactoring Our Compiler the Right Way

For weeks, our compiler worked — until it didn’t.
Specifically, it choked on one of the most deceptively simple constructs in any modern language: lambda expressions. The syntax x -> x > 1
became our white whale. Sure, we could patch it. But patches turned brittle. One new feature would fix a case, and another would break it.
It was time. We stepped back and refactored — from the ground up.
What Was Wrong?
Initially, our compiler supported lambda expressions via special-case parsing:
if token == IDENT && nextToken == ARROW {
// this must be a lambda
}
But that logic broke down with even modest complexity:
Nested access:
x -> x.key > 1
or ❌?Nested lambdas:
map.filter(x -> x.items.filter(...))
Chaining:
x -> x > 1
.to_str()
The parser had no unified grammar to handle these compositions. Worse, the behavior was unpredictable and impossible to extend.
What We Did
We didn’t just fix it. We rebuilt it correctly — layer by layer.
Lexer
We confirmed the lexer correctly tokenized arrow functions (->
), dots (.
), and everything between.
Parser
We rewrote expression parsing to:
Treat lambdas as real expressions.
Support nested lambdas, dot chains, index access, and more.
Handle method chaining post-lambda (
x -> x.key
).method().
This meant centralizing the logic in:
parseExpression()
(entry point)parseExprWithPrecedence()
(binary ops + lambdas)parseChainedExpr()
(postfix:.
,[...]
, slicing)
Now, the parser doesn’t guess — it parses.
AST
We introduced a proper DotExpr
AST node:
type DotExpr struct {
Receiver Expression
Member string
}
No more abusing string concatenation for x.key
— the compiler knows what that is now.
Compiler
The backend recognizes lambdas, method calls, list methods, and dot access like a pro:
_luma_list_filter(my_map, func(x interface{}) bool {
return x.key > 1
})
Tests, Glorious Tests
Every fix came with test coverage:
Lexing: Tokens for lambdas, method calls, dot access
Parsing: All variations of lambdas, dot chains, index access
Compilation: Verifies output code matches expected Go code
We now catch regressions before they happen. And yes — that nested lambda test finally passes.
What We Learned
Don’t patch syntax — parse it.
Chained expressions need structured parsing, not lookaheads.
Your compiler deserves a clean AST. Hacky expressions become nightmares later.
Tests aren't optional — they're survival.
The Result
We’re now back at parity — but with battle-tested, future-proof infrastructure:
Lambdas
Dot access
Nested lambdas
Full compiler tests
Clean, extensible architecture
The lambda curse is broken. Our compiler is reborn. And from here, the future of Luma looks brighter than ever.
Subscribe to my newsletter
Read articles from crowned.phoenix directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
