文章

自己造一门语言:一个学习项目的开篇

0x01 一个一直没解决的问题

我写过不少代码。每次敲下 if,我大概知道它会编译成一条比较指令和一次跳转。但若有人问它是如何变成这两条指令的,我答不上来。

这个空白我填过很多次:看视频、读《编译原理》、翻 LLVM 的文档。每次都看懂了,不到半年却忘干净。

后来我找到了原因:我一直在读别人的做法,从未自己动手做过一次。

看懂和做出来是两回事。看书、看视频,是别人把理解讲给你听;自己动手,才是把理解拿去给现实检验。检验一定会失败,而失败的地方,恰恰是真正学到东西的地方。

所以这次我换了条路:不学编译原理,而是造一门语言。

这个系列写完时,它能跑成这样:

$ varde run hello.vd
hello, varde

$ varde build hello.vd && ls -lh hello
-rwxrwxrwx 1 root root 16K Sep 30 10:34 hello

0x02 为什么选“造语言”作为学习载体

学习编译原理大致有三条路:看书、看视频、读别人的编译器源码。它们都有用,却共享同一个缺口:没有必须做决定的时刻。

而造一门语言,会在每个环节逼你做决定。举三个例子。

例 1 · 词法分析

书上的“正规式 → NFA → DFA”是一套算法,三页纸讲完。轮到动手写,你要回答:

  • > 和 >= 怎么区分?(最长匹配)
  • // 是注释,/ 是除号,扫描器怎么知道该看第二个字符?
  • 源码里有中文时,报错该报第几列?字节偏移与字符列号是两回事。
  • /* /* */ */ 这种嵌套注释要不要支持?

这些问题书上都不讲,因为它们不是算法问题,是工程问题。工程问题只能自己撞一次才会记住。

例 2 · 类型系统

类型系统的教材定义只有一句:“静态类型 = 编译期检查类型”。真到自己写,问题就来了:

  • let j: i64 = i;(i 是 i32)该不该放过?let f: f64 = i; 呢?
  • 函数体里的类型标注能省吗?函数签名上的呢?
  • 泛型要单态化,还是用虚表动态派发?
  • 若子类型只有“具体类型 → dyn Trait”这一来源,那 Vec<Dog> 能不能赋给 Vec<Animal>?

每一个问题都没有标准答案,只有取舍。做过取舍的人,才看得懂别人的取舍。

例 3 · 内存管理

“引用计数”在书里是一个概念,配一张计数增减的示意图。把它真正实现出来时,你躲不开这些问题:

  • 赋值要不要拷贝对象?传参呢?
  • let b = a; 之后改 a,b 该不该跟着变?
  • 两个对象互相引用,计数永远不归零,怎么办?
  • 析构函数在什么时刻执行?抛异常时还执行吗?

你必须把答案写进代码,然后承担它的全部后果。

看书是输入,动手是输出。只有真正输出一次,你才知道自己懂没懂。


0x03 为什么是“自己造”,而不是“跟着抄”

跟着《Crafting Interpreters》抄一个 Lox,不失为一种有效的入门方式。很多人就是这么入门的,我自己也读过。

但抄有一个代价:你没有决策时刻。

抄的过程中,所有“为什么这样设计”的问题,答案都是“书里就这么写的”。抄完,你获得的是流程知识,不是判断力。换一个需求,你仍然不知道从何下手。

自己造,差别就在这里:每一个设计决策都要自己拍板,而决策过程本身就是内容。

设计语言时,每个决定都要回答:为什么这么选、放弃了什么、什么时候会咬人。这些“为什么”在任何教程里都找不到答案,因为那是你自己的取舍。


0x04 这个项目是什么

一个教学用的玩具语言 varde。它存在的唯一目的,就是让我(和跟着做的你)把编译原理和 Rust 两件事真正学透。

项目用 Rust 编写,代码总量不到 1 万行,编译后是单个可执行文件。

还有一句话提前说:这门语言不会有用户。它的用户只有我和你。

0x05 你跟着学完会得到什么

你能得到:

  • 从字符流到可执行文件的完整能力,不是片段知识
  • 一个真实项目的全部决策过程和踩坑记录
  • Rust 与编译原理的贯通
  • 一套可迁移的方法论:怎么用“造东西”来学习任何系统性知识

你得不到:

  • 一门生产可用的语言(varde 只是个玩具)
  • 速成。这是个以年计的项目

如果你看到这里,还愿意跟下去,那这个系列就是为你写的。

0x06 出发

回到开头那个问题:if 是怎么被执行的?

现在你大概知道了这条路要走哪些站——从字符流开始,切词、建树、查类型、降成中间表示、生成代码,最后落到机器上。中间每一站都有具体要做的决定,我会一站一站写下去。

设计和实现都从零开始,正好从头记起。

学一门语言最好的方式,是用它造一个东西;而造一个东西最好的方式,是造一门语言。这句话听着绕,但这个系列就是来把它跑通的。