Join Nostr
2026-08-26 04:49:32 UTC

yfaming on Nostr: 看完了 Crafting Interpreters 第 21 章,Global Variables。 ...

看完了 Crafting Interpreters 第 21 章,Global Variables。

本章支持了全局变量。

变量是通过变量声明语句引入的,为了支持变量,就得支持语句。本章支持了 exprStmt、printStmt、varDecl。

vm 里增加了 `globals` hash table,用于保存全局变量。增加了 3 条字节码指令,`OP_DEFINE_GLOBAL`、`OP_GET_GLOBAL` 和 `OP_SET_GLOBAL`,用于全局变量的定义、读和写。

clox 编译时生成字节码指令,运行时执行字节码指令。我们需要关注,编译时和运行时是如何相互配合的,尤其是栈的状态。

以全局变量的声明语句为例。
编译时,遇到全局变量声明语句 `var abc = <expr>;`
将变量名放到 constants 数组里面,拿到对应 index。
然后生成对 `=` 右边的表达式求值的指令。
再生成 2 个字节的字节码指令 `OP_DEFINE_GLOBAL index`,定义全局变量。
最终指令序列类似 `<eval expr> OP_DEFINE_GLOBAL index`。

而运行时,先执行 `<eval expr>`,即对 `=` 右边的表达式值的求值指令,并将结果放到栈顶。
接下来执行 `OP_DEFINE_GLOBAL index`。先根据 index 从 constants 数组里拿到全局变量的名字。再从栈顶拿到值,并在 `vm.globals` hash table 中为全局变量设置值。

`OP_GET_GLOBAL` 和 `OP_SET_GLOBAL` 指令也是类似。

赋值语句的 parsing 容易出问题。clox 是 single-pass 的 compiler,没有 AST,而且只能 look-ahead 一个 token,一边 parse 一边 codegen。但是,也要把 context 保存下来,比如 C 调用栈,及其他状态里面,并据此延迟 codegen。即,推迟产生不可逆的结果,直到一个 token 的 lookahead 足以消除歧义。
接下来,开始 Crafting Interpreters 第二部分,用 C 实现的 Lox 字节码解释器 clox。
在第一部分,用 Java 实现的 tree-walk interpreter jlox 中,我们完整实现了 Lox 解释器,包括 scanner、parser、resolver、interpreter 等等。
但是,第一部分的解释器利用了 Java 语言的许多特性。
比如,Lox 的值,全部用 Java 的 Object 类型表示。
Lox 里的 return,用 Java 的异常来实现。
而 Java 自带 GC,所以 Lox 中对象的生命周期,我们也完全没考虑过。

在第二部分,我们将使用 C 语言实现解释器。C 比 Java 更加 low-level,它不支持 OOP,不支持异常和 GC。
因此,这几个问题,我们就需要在 C 里面手动处理了。
而且,第二部分我们实现的是字节码解释器。我们需要定义字节码,并在 parsing 将 ast 编译为字节码。


用不同语言实现同一门语言,但采用略有不同的技术,可以加深我们的理解,并学习 compiler/interpreter 领域的不同主题。