컴파일러가 무섭다고 생각했어요. 그래서 저는 Sauce를 만들었어요.

발행: (2025년 12월 28일 오후 03:31 GMT+9)
10 min read
원문: Dev.to

Source: Dev.to

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

나는 수년간 코드를 작성해 왔습니다. cargo run이나 npm start를 입력하고 Enter를 치면 의미 있는 일이 일어납니다. 하지만 Enter를 누르고 “Hello World”가 보이기까지 실제로 무엇이 일어나는지 물어본다면, 저는 “머신 코드” 같은 말을 중얼거리며 주제를 바꿨을 겁니다.

그게 저를 불편하게 만들었습니다. 매일 이 도구들에 의존하지만, 저는 그것들을 이해하지 못했습니다.

그래서 저는 Rust로 만든 나만의 프로그래밍 언어 Sauce를 만들기 시작했습니다. 세상이 또 다른 언어를 필요로 해서가 아니라, 컴파일러를 블랙 박스로 대하는 것을 멈추기 위해서였습니다.

알고 보니, 언어는 마법이 아니라 단지 파이프라인일 뿐입니다.

왜 우리는 이것이 어렵다고 생각하는가

우리는 보통 컴파일러를 우리 코드를 판단하는 크고 무서운 두뇌로 생각합니다. 텍스트를 넣으면 작동하는 프로그램을 주거나 오류와 함께 소리를 지릅니다.

나는 컴파일러를 만들려면 수학 천재여야 한다고 수년간 생각했습니다. 나는 틀렸습니다. 작은 단계로 나누기만 하면 됩니다.

소스가 실제로 무엇인가

과대광고를 벗겨내면, 프로그래밍 언어는 단지 데이터를 이동시키는 방법일 뿐이다.

Sauce는 정적 타입 언어이며 간단한 스크립트처럼 느껴진다. 나는 명확하고 솔직한 무언가를 원했다. 핵심 아이디어는 간단하다:

  • 파이프라인 (|>)이 기본 – 데이터가 한 단계에서 다음 단계로 명시적으로 흐르며, 마치 공장 라인과 같다.
  • 효과는 명시적 (toss) – 코드에 숨겨진 놀라움이나 비밀 점프가 없다.

하지만 그 단계에 도달하려면 엔진을 직접 만들어야 했다. 그리고 Rust 덕분에 엔진이 실제로 꽤 멋지다는 것을 알게 되었다.

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…

제자리를 잡다.

무섭게 느껴지는

네이티브 바이너리: 최종 제품

  • 작업:
    최종 단계는 모든 CPU 명령어를 독립 실행 파일(예: Windows의 .exe 또는 Linux의 바이너리)로 묶는 것입니다.
  • 쉽게 말하면:
    이것은 프로그램을 친구에게 보낼 수 있게 해줍니다. 친구는 Sauce, Rust 혹은 다른 어떤 것을 설치할 필요가 없습니다. 파일을 더블 클릭하면 바로 실행됩니다. (현재는 간단하고 부작용이 없는 프로그램에만 작동합니다.)

현재 작동하는 것 (v0.1.0)

Sauce는 이제 단순한 아이디어가 아니라—핵심 컴파일러 파이프라인이 살아 있습니다.

  • 파이프라인:
    grab x = 10 |> _ 를 작성할 수 있으며, 이를 완벽히 이해합니다. 데이터가 왼쪽에서 오른쪽으로 흐르며, 마치 문장을 읽는 것과 같습니다.
  • 실제 출력:
    실제 .sauce 소스 코드를 입력하면, 이를 타입‑안전한 구문 트리로 파싱합니다.
  • 명시적 효과:
    toss 를 사용해 부수 효과를 신호할 수 있습니다. 현재는 인터프리터에서 동작하며, LLVM 백엔드는 의도적으로 아직 효과를 거부합니다.

앞으로의 로드맵

  • v0.1.x (UX):
    오류 메시지가 현재 다소 난해합니다. Ariadne이라는 도구를 추가하여 보기 좋고 도움이 되는 오류 보고서를 제공하려고 합니다 (Rust처럼).

  • v0.2.0 (Effects):
    큰 업데이트입니다. “Effects”가 어떻게 동작하는지 최종 확정할 예정이며—오류 발생 후 프로그램을 재개할 수 있는 경우와 중단해야 하는 경우에 대한 규칙을 정의합니다.

  • v0.3.0 (Runtime):
    인터프리터와 LLVM 세계를 통합하여 동일하게 동작하도록 만들고, 숫자를 출력하는 것 이상의 작업을 할 수 있도록 표준 라이브러리를 추가합니다.

왜 이것을 시도해야 할까

나는 내가 충분히 똑똑하지 않다고 생각해서 수년간 언어를 만드는 것을 피했다.

하지만 Sauce를 만들면서 마법 같은 것이 없다는 것을 배웠다. 그것은 단지 데이터 구조일 뿐이다:

  • 렉서는 단지 정규식이다.
  • 파서는 단지 트리 빌더이다.
  • 인터프리터는 그 트리를 순회하는 함수일 뿐이다.

코드가 어떻게 실행되는지 실제로 이해하고 싶다면, 책만 읽지 말고 작은, 아직 완성되지 않은 컴파일러를 만들어 보라. 렉서를 만들고, 간단한 트리를 정의하고, 1 + 1을 파싱해 보라.

구문 오류와 씨름하는 주말 한 주가 cargo run만 사용한 1년보다 더 많은 것을 가르쳐 줄 것이다.

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

Back to Blog

관련 글

더 보기 »

Swift의 도래

번역하려는 텍스트를 제공해 주시겠어요? 텍스트를 주시면 한국어로 번역해 드리겠습니다.