I Thought Compilers Were Scary. So I Built Sauce.

Published: (December 28, 2025 at 01:31 AM EST)
6 min read
Source: Dev.to

Source: Dev.to

Cover image for I Thought Compilers Were Scary. So I Built Sauce.

I’ve been writing code for years. I type cargo run or npm start, hit Enter, and meaningful things happen. But if you asked me what actually happens between hitting Enter and seeing “Hello World,” I’d have mumbled something about “machine code” and changed the subject.

That bothered me. I rely on these tools every day, but I didn’t understand them.

So I started building Sauce, my own programming language in Rust. Not because the world needs another language, but because I needed to stop treating my compiler like a black box.

Turns out, a language isn’t magic. It’s just a pipeline.

Why We Think It’s Hard

We usually see a compiler as this big, scary brain that judges our code. You feed it text, and it either gives you a working program or yells at you with an error.

I spent years thinking you needed to be a math genius to build one. I was wrong. You just need to break it down into small steps.

What Sauce Actually Is

Strip away the hype, and a programming language is just a way to move data around.

Sauce is a statically‑typed language that feels like a simple script. I wanted something that was clear and honest. The core ideas are simple:

  • Pipelines (|>) are default – Data flows explicitly from one step to the next, like a factory line.
  • Effects are explicit (toss) – No hidden surprises or secret jumps in your code.

But to get there, I had to build the engine. And thanks to Rust, I learned that the engine is actually pretty cool.

The Architecture: It’s Just an Assembly Line

I used to think compilation was one giant, messy function. In reality, it’s a disciplined process. I’m following a strict architecture for Sauce that separates “understanding the code” (Frontend) from “running the code” (Backend).

Sauce architecture

Here is exactly how Sauce works under the hood. The diagram above isn’t just a sketch; it’s the map I’m building against. It breaks down into two main phases.

Phase 1: Frontend (The Brain)

This phase is all about understanding what you wrote. It doesn’t run anything yet; it just reads and checks.

Lexer (Logos) – The Chopping Block

  • Job: Computers don’t read words; they read characters. The lexer groups characters into meaningful chunks called tokens.
  • Plain English: Imagine reading a sentence without spaces: thequickbrownfox. It’s hard. The lexer adds the spaces and labels every word. It turns grab x = 10 into a list:
    [Keyword(grab), Ident(x), Symbol(=), Int(10)].
  • Tool: I used the Rust crate Logos. It’s incredibly fast, but I learned a hard lesson: computers are dumb. If you don’t explicitly tell them that grab is a special keyword, they’ll treat it as a normal identifier like green. You have to set strict rules.

Parser (Chumsky) – The Grammar Police

  • Job: With a list of tokens in hand, we need to check whether they form a valid sentence. The parser organizes the flat list into a structured tree called the AST (Abstract Syntax Tree).
  • Plain English: A list like [10, =, x, grab] contains valid words but makes no sense. The parser ensures the order is correct (grab x = 10) and builds a hierarchy: “This is a variable assignment. The name is x. The value is 10.”
  • Tool: I used Chumsky, which lets you build logic like LEGO bricks. You write a tiny function to read a number, another for a variable, and glue them together.
  • Aha! Moment: Breaking the grammar into small, composable pieces made the language way easier to extend and reason about. It’s not magic; it’s just organizing data.

Type Checking – The Logic Check

  • Job: A grammatically correct sentence isn’t necessarily meaningful. “The sandwich ate the Tuesday” is syntactically valid but semantically nonsense. The type checker catches these logical errors.
  • Plain English: If you write grab x = "hello" + 5, the parser says “Looks like a valid math operation!” but the type checker steps in and says, “Wait. You can’t add a string to a number. That’s illegal.” Sauce currently has a small, explicit system that catches these basic mistakes before you ever try to run the code.

Phase 2: Backend (The Muscle)

Once the Frontend gives the “thumbs up,” we move to the Backend. This phase is about making the code actually run.

Codegen (Inkwell/LLVM) – The Translator

  • Job: This is where we leave the high‑level world of “variables” and “pipelines” and enter the low‑level world of CPU instructions. We translate our AST into LLVM IR (Intermediate Representation).
  • Plain English: Sauce is like a high‑level manager giving orders (“Calculate this pipeline”). The CPU is the worker who only understands basic tasks (“Move number to register A,” “Add register A and B”). LLVM is the translator that turns the manager’s orders into the worker’s checklist.
  • Why LLVM? It’s the same industrial‑grade machinery that powers Rust, Swift, and C++. By using it, Sauce gets decades of optimization work for free. Once you figure out how to tell LLVM to “print a number,” the rest falls into place.

Feeling So Scary

Native Binary: The Final Product

  • The Job:
    The final step is bundling all those CPU instructions into a standalone file (like an .exe on Windows or a binary on Linux).
  • In Plain English:
    This is what lets you send your program to a friend. They don’t need to install Sauce, Rust, or anything else. They just double‑click the file, and it runs. (Currently, this works for simple, effect‑free programs.)

What Works Right Now (v0.1.0)

Sauce isn’t just an idea anymore—the core compiler pipeline is alive.

  • Pipelines:
    You can write grab x = 10 |> _ and it understands it perfectly. The data flows left‑to‑right, just like reading a sentence.
  • Real Output:
    You can feed it real .sauce source code, and it parses it into a type‑safe syntax tree.
  • Explicit Effects:
    You can use toss to signal a side effect. This currently works in the interpreter, while the LLVM backend intentionally rejects effects for now.

The Road Ahead

I have a clear plan for where this is going. Since the core architecture is stable, the next updates are about making it usable.

  • v0.1.x (UX):
    Error messages are a bit cryptic right now. I’m adding a tool called Ariadne to give pretty, helpful error reports (like Rust does).
  • v0.2.0 (Effects):
    The big one. I’ll be finalizing how “Effects” work—defining rules for when you can resume a program after an error and when you have to abort.
  • v0.3.0 (Runtime):
    Merging the Interpreter and LLVM worlds so they behave exactly the same, plus adding a standard library so you can do more than just print numbers.

Why You Should Try This

I avoided building a language for years because I thought I wasn’t smart enough.

But building Sauce taught me that there’s no magic. It’s just data structures:

  • A lexer is just regex.
  • A parser is just a tree builder.
  • An interpreter is just a function that walks through that tree.

If you want to actually understand how your code runs, don’t just read a book. Build a tiny, broken compiler. Create a lexer. Define a simple tree. Parse 1 + 1.

You’ll learn more in a weekend of fighting syntax errors than you will in a year of just using cargo run.

Check out Sauce on GitHub. It’s small, it’s honest, and we are officially cooking.

Back to Blog

Related posts

Read more »

Rython - New Programming language!

Who am I Maybe you already know—if so, you’re doing great, thanks for reading my posts! If not, I’m Igor, a Ukrainian developer who created a Neovim plugin tha...

Advent of Swift

Article URL: https://leahneukirchen.org/blog/archive/2025/12/advent-of-swift.html Comments URL: https://news.ycombinator.com/item?id=46266312 Points: 18 Comment...