本页目录

编译 II · 语义、类型检查与解释器

对标:Stanford CS143 中段 / Crafting Interpreters / TAPL(类型)| 前置:comp-01(AST)、pl 线(类型系统正式版在 pl-01) 有了 AST(comp-01),编译器要开始理解程序的意义:名字指向什么(作用域)、类型对不对(类型检查)、然后要么直接执行(解释器)、要么继续编译(comp-03)。这一页讲语义分析与类型检查,并把 [实验 L04] 的解释器完整跑起来——你将拥有一门自己实现的、能跑的小语言

1. 语义分析:名字与作用域

词法作用域栈:进块压入、离块弹出、内层查不到往外找。

图 comp-02.3词法作用域栈:进块压入、离块弹出、内层查不到往外找。

语法对了不代表有意义——x + 1x 是谁?语义分析在 AST 上回答这类问题:

2. 类型检查:在运行前抓错

类型系统给每个表达式一个类型,检查操作是否合法("abc" + 3 该不该报错?)——在程序运行前排除一整类错误。这是"用编译期的严格换运行期的安全",静态类型语言(Rust/Java/TS)的核心价值。

类型检查怎么做【机理】:在 AST 上自底向上推导——字面量有已知类型,e1 + e2 要求两子式类型相容并给出结果类型,if 要求条件是布尔、两分支类型一致,函数调用检查实参与形参类型匹配。本质是一组"类型推导规则"在语法树上的递归应用(pl-01 会把它形式化成推理规则 \(\frac{\text{前提}}{\text{结论}}\)——你会看到它和逻辑证明系统同构,Curry–Howard 的伏笔)。

静态 vs 动态类型的权衡

[L04] 的选择:小语言可以先做动态类型(求值时检查)跑通,理解了再考虑加静态检查——分步走,先让语言能跑,再让它更安全

3. 树遍历解释器:让语言活起来

解释器 eval(AST, env):环境链式作用域 + 闭包捕获环境。

图 comp-02.2解释器 eval(AST, env):环境链式作用域 + 闭包捕获环境。

最直接的执行方式——遍历 AST,边走边算(tree-walking interpreter)

这就是 [L04] 的收官:词法(comp-01)→ 解析(comp-01)→ 求值(本页),你得到一个完整的、能跑的解释器——能定义变量、函数、闭包、递归、做算术和分支。亲手写完一门语言的解释器,是"编程语言不再神秘"的成人礼。《Crafting Interpreters》就是带你走这一遍。

4. 从树遍历到字节码(性能进阶)

树遍历→字节码→VM→JIT 的性能阶梯。

图 comp-02.1树遍历→字节码→VM→JIT 的性能阶梯。

树遍历解释器简单但(每次执行都遍历树、大量指针跳转、对缓存不友好)。生产级解释器(CPython、JVM、V8)多走字节码路线:

这条"树 → 字节码 → JIT"的进阶路线,是性能与复杂度的阶梯——[L04] 停在树遍历(教学清晰),想深挖就往字节码走(《Crafting Interpreters》下半部正是做这个)。

5. 练习与要点

例 1(作用域推演) 写一段带嵌套函数和同名变量的代码,手推每个变量引用绑定到哪个声明(词法作用域)——理解"闭包捕获的是定义处的环境",这是 L04 最易错的点。

例 2(类型检查手动跑)if (x > 0) then 1 else "no",按类型规则检查——两分支类型不一致(int vs string)应报错。体会"类型检查在运行前抓错"的价值

例 3(把 L04 跑起来) 在你的解释器里定义一个递归的阶乘函数并调用——factorial(5) 打印出 120,你就真正拥有了一门自己造的语言。这是全站最有成就感的实验之一。\(\blacksquare\)

▶ 实验 L04 完成labs/L04-interpreter/ 全流程跑通——变量、函数、闭包、递归、算术、分支。对标《Crafting Interpreters》的 jlox。


下一页:编译 III——IR、优化与代码生成:编译器后端如何把程序变快、变成真实机器码。这也是 LLVM 的世界。