Breaking the Lambda Curse: Refactoring Our Compiler the Right Way

crowned.phoenixcrowned.phoenix
3 min read

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.

0
Subscribe to my newsletter

Read articles from crowned.phoenix directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

crowned.phoenix
crowned.phoenix