Join Nostr
2026-08-14 07:08:31 UTC

yfaming on Nostr: 看完了 Crafting Interpreters 第 13 章,Inheritance。 ...

看完了 Crafting Interpreters 第 13 章,Inheritance。
这一章支持了类的继承。

Lox 只支持单继承,所以并不复杂。

文法层面,用 `<` 号表示继承,形如 `class Dog < Animal {...}`,与 Ruby 相同。
另外增加了 super 表达式。如 `super.method()` 会将 `super.method` parse 为 super 表达式,后面的 `()` 则解析为函数调用。

LoxClass 中增加一个字段记录类的 super class,查找方法时,也要顺着继承链依次向上查找。

在处理 class 声明时,为 super 创建专门作用域。进入 class 声明,立即创建一个作用域,并将 super 定义到里面。接下来,再创建另一个作用域,将 this 定义到里面。然后再定义方法。这样在方法的代码里面引用的 super 就是当前类的 super class 了。


至此,本书第一部分,用 Java 实现 tree-walk interpreter 宣告完成。
看了下我敲下的代码,排除注释、空行、测试等,只有 2002 行,简直不可思议。
只要 2000 行代码,就能从头实现一门功能基本齐全的语言,我以前从未想过!
这些代码,包括 lexer、parser、resolver 和 interpreter。
而这门语言的功能齐全,它支持变量、表达式、函数,还支持类和继承等 OOP 特性。

今天看完了 Crafting Interpreters 第 12 章 Classes。

这一章支持了 class。作为动态类型语言,Lox 的 class 与 Python 的有些相似。用 `class` 声明类,用 `init` 方法初始化实例。

为此,增加了声明类的文法规则,并增加了 Get 表达式以支持访问实例的字段(和方法),增加了 Set 表达式以支持对实例的字段赋值。同时还支持了 This 表达式,以表示 `this`。

类用 `LoxClass` 表示,它保存着类的名字以及方法列表。方法用现成的 `LoxFunction` 表示。
同时 `LoxClass` 也实现了 `LoxCallable` 接口,以便创建类的实例。
类的实例用 `LoxInstance` 表示,它保存着所属的类,及字段值。

对于 `this` 的支持值得一提。

我们知道,Resolver 中的 scope 链,与 Interpreter 中的 Environment 链必须保持一致。scope 与 Environment 本质是同一个东西,只不过 scope 是编译时的,Environment 是运行时的。

Resolver 处理 class 声明时(`visitClassStmt`),创建了新的 scope,并将 this 定义在新的 scope 里。这也意味着,class 的 method 也是在新的 scope 中定义的。

但 `Interpreter.visitClassStmt` 却没有创建新的 Environment。而是在访问实例的属性时(`visitGetExpr`),如果这个属性对应的是一个 method,就调用 `LoxFunction.bind` 方法。`LoxFunction.bind` 会以 method 的 closure 为 enclosing Environment 创建新 Environment,把 this 绑定为对应实例, 然后组装为新的 LoxFunction 并返回。

这样的话,类的方法调用时,Environment 就与 Resolver 的 scope 一致了,而且确定了 this 所指向的值。

具体地,形如 `ins.method()` 的方法调用,`ins.method` 被 parse 为 `Expr.Get`。对 `ins.method` 求值,即对 `Expr.Get` 求值,就会执行这里所说的逻辑。得到新的 LoxFunction 后,再进行函数调用,就和其他函数没有什么区别了。

关于创建类的实例,其实包含两个步骤。
第一步是真正的创建实例,得到 LoxInstance,它是由 LoxClass 负责的。LoxClass 实现了 LoxCallable 接口,调用时返回一个 LoxInstance。
第二步是初始化,由用户提供的 init 方法完成。为了方便使用,Lox Interpreter 确保 init 方法返回实例,且禁止它返回其他值。

至此,本书第一部分,用 Java 实现的 tree-walk interpreter 只剩下最后一章 Inheritance 了。而我敲下的代码,刨除注释和空行,只有 1916 行。神奇!