我以为编译器很可怕,于是我做了 Sauce。

发布: (2025年12月28日 GMT+8 14:31)
10 min read
原文: Dev.to

Source: Dev.to

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

我已经写代码多年了。我敲下 cargo runnpm start,按下 Enter,然后有意义的事情就会发生。但如果你问我在按下 Enter 与看到 “Hello World” 之间到底发生了什么,我可能会含糊其辞地说“机器码”,然后转移话题。

这让我很困扰。我每天都依赖这些工具,却并不真正了解它们。

于是我开始用 Rust 构建 Sauce,我的个人编程语言。并不是因为世界需要另一种语言,而是因为我需要停止把编译器当作黑盒子。

事实证明,语言并不是魔法。它只是一条流水线。

为什么我们认为这很难

我们通常把编译器看成一个庞大、可怕的大脑,它会评判我们的代码。你把文本喂给它,它要么给你一个可运行的程序,要么用错误信息大声斥责你。

我花了多年时间认为要构建编译器必须是数学天才。我错了。你只需要把它拆分成小步骤。

Sauce 实际上是什么

去掉炒作,编程语言不过是一种移动数据的方式。

Sauce 是一种 静态类型 的语言,却像简单脚本一样。 我想要一种清晰诚实的语言。核心理念很简单:

  • 管道 (|>) 为默认 – 数据显式地从一步流向下一步,像工厂流水线一样。
  • 副作用是显式的 (toss) – 代码中没有隐藏的惊喜或秘密跳转。

但要实现这一点,我必须先构建引擎。多亏了 Rust,我发现这个引擎其实相当酷。

架构:它只是一条装配线

我曾经以为编译是一个巨大的、混乱的函数。实际上,它是一个有纪律的过程。我遵循一种严格的 Sauce 架构,将 “理解代码”(前端)与 “运行代码”(后端)分离。

Sauce architecture

下面就是 Sauce 在底层的工作方式。上面的图不仅仅是草图;它是我正在构建的地图。整个过程分为两个主要阶段。

阶段 1:前端(大脑)

此阶段全部关于 理解 你写的代码。它还不运行任何东西,只是读取并检查。

Lexer (Logos) – 切块机

  • 职责: 计算机不读取单词,它们读取字符。lexer 将字符分组为有意义的块,称为 tokens(标记)。
  • 通俗解释: 想象读取没有空格的句子:thequickbrownfox。这很困难。lexer 会添加空格并给每个词贴标签。它把 grab x = 10 转换为列表:
    [Keyword(grab), Ident(x), Symbol(=), Int(10)]
  • 工具: 我使用了 Rust crate Logos。它快得惊人,但我也学到了一个硬教训:计算机很笨。如果你不明确告诉它们 grab 是特殊关键字,它们会把它当作普通标识符(比如 green)来处理。必须设定严格的规则。

Parser (Chumsky) – 语法警察

  • 职责: 手里有了 token 列表后,需要检查它们是否构成合法的句子。parser 将平坦的列表组织成结构化的树,称为 AST(抽象语法树)。
  • 通俗解释:[10, =, x, grab] 这样的列表包含合法的单词,却毫无意义。parser 确保顺序正确(grab x = 10),并构建层级结构:“这是一次变量赋值。变量名是 x,值是 10。”
  • 工具: 我使用 Chumsky,它让你像搭 LEGO 积木一样构建逻辑。你写一个读取数字的小函数,另一个读取变量的函数,然后把它们粘合在一起。
  • 恍然大悟的时刻: 将语法拆分为小的、可组合的块,使语言的扩展和推理变得容易得多。这不是魔法,只是对数据的组织。

Type Checking – 逻辑检查

  • 职责: 语法正确的句子不一定有意义。“三明治吃了星期二”在句法上是合法的,但在语义上是胡说。类型检查器捕捉这些逻辑错误。
  • 通俗解释: 如果你写 grab x = "hello" + 5,parser 会说“看起来是合法的数学运算!”,但类型检查器会介入并说:“等等。你不能把字符串和数字相加。这是非法的。”Sauce 目前拥有一个小而明确的系统,在代码真正运行之前捕获这些基本错误。

阶段 2:后端(肌肉)

当前端给出 “批准” 以后,我们进入后端。此阶段是 让代码真正运行

Codegen (Inkwell/LLVM) – 翻译器

  • 职责: 这里我们离开 “变量” 与 “管道” 的高级世界,进入 CPU 指令的低级世界。我们把 AST 翻译成 LLVM IR(中间表示)。
  • 通俗解释: Sauce 像是给出指令的高级经理(“计算这个管道”),CPU 是只懂基本任务的工人(“把数字移动到寄存器 A”, “把寄存器 A 与 B 相加”)。LLVM 就是把经理的指令翻译成工人检查清单的翻译器。
  • 为什么选 LLVM? 它是同样用于 Rust、Swift 和 C++ 的工业级机器。使用它,Sauce 可以免费获得数十年的优化成果。一旦你弄清楚如何让 LLVM “打印一个数字”,其余的工作就会变得相对轻松。

一切就位。

感觉如此恐怖

Native Binary: 最终产物

  • 任务:
    最后一步是将所有 CPU 指令打包成一个独立的文件(比如 Windows 上的 .exe 或 Linux 上的二进制文件)。
  • 通俗解释:
    这就是让你可以把程序发送给朋友的方式。他们不需要安装 Sauce、Rust 或其他任何东西。他们只需双击该文件,即可运行。(目前,这仅适用于简单的、无副作用的程序。)

现在可用的功能 (v0.1.0)

Sauce 不再只是一个想法——核心编译器管线已经可以运行。

  • 管线(Pipelines):
    你可以写 grab x = 10 |> _,它能完美理解。数据从左到右流动,就像阅读一句话一样。
  • 真实输出(Real Output):
    你可以输入真实的 .sauce 源代码,它会将其解析为类型安全的语法树。
  • 显式副作用(Explicit Effects):
    你可以使用 toss 来标记副作用。这目前在解释器中可用,而 LLVM 后端暂时会拒绝副作用。

前进的路线

我对接下来的发展有明确的计划。由于核心架构已经稳定,接下来的更新将侧重于提升可用性。

  • v0.1.x (UX):
    目前错误信息有点晦涩。我将加入一个名为 Ariadne 的工具,以提供漂亮且有帮助的错误报告(类似 Rust 的做法)。
  • v0.2.0 (Effects):
    关键更新。我会完善 “Effects” 的工作方式——定义在错误后何时可以恢复程序以及何时必须中止的规则。
  • v0.3.0 (Runtime):
    将解释器和 LLVM 的实现合并,使它们的行为完全一致,并添加标准库,让你能够做的不止是打印数字。

为什么你应该尝试这个

我多年里一直回避构建语言,因为我觉得自己不够聪明。

但构建 Sauce 让我明白,根本没有魔法。它只是数据结构:

  • 词法分析器不过是正则表达式。
  • 语法解析器不过是树构建器。
  • 解释器不过是遍历这棵树的函数。

如果你真的想了解代码是如何运行的,别只读书。动手做一个小而不完整的编译器。创建一个词法分析器。定义一棵简单的语法树。解析 1 + 1

在一个周末里与语法错误搏斗,你学到的东西会比整整一年只会 cargo run 多得多。

查看 Sauce on GitHub。它小巧、诚实,而且我们已经正式“烹饪”。

Back to Blog

相关文章

阅读更多 »

Pre-commit hooks 根本上已损坏

开始一个新的 Rust 项目 bash $ mkdir best-fizzbuzz-ever $ cd best-fizzbuzz-ever $ cat main.rs fn main { for i in 0.. { println 'fizzbuzz'; } } EOF $ git ini...

Swift的来临

请提供您希望翻译的具体摘录或摘要文本,我才能为您进行简体中文翻译。