Skip to main content

Crafting Interpreters 第 3 章笔记:The Lox Language

核心问题:Lox 是一门什么样的语言?它提供哪些语法和能力?


0. 本章一句话

第 3 章是在介绍本书后面要实现的语言:Lox

Lox 是一门小型、高层、动态类型、自动内存管理的脚本语言。

它故意设计得不大,目的是让我们能在一本书里完整实现两遍:一次用 Java,一次用 C。

本章可以理解为:

先看一眼终点,再开始造解释器。


1. Hello, Lox

最小的 Lox 程序:

print "Hello, world!";

可以看到几个特点:

  • 使用 C 风格语法

  • 字符串用双引号

  • 语句以分号结尾

  • print 是内置语句,不是函数

Lox 选择 C-like syntax,不是因为它最优雅,而是因为熟悉。

Java、C、JavaScript 用户读起来都不会太陌生。


2. Lox 是高层脚本语言

Lox 借鉴了 JavaScript、Scheme、Lua 等小型高层语言。

它有两个重要特点:

Dynamic Typing

Lox 是动态类型语言。

变量不固定类型:

var x = 123;
x = "hello";

类型错误不会在编译前检查,而是在运行时发现。

例如:

"hello" / 3

这种错误会在执行到这里时才报错。


Automatic Memory Management

Lox 自动管理内存。

用户不用手动写:

free(ptr);

后面实现 C 版解释器时,会自己写 garbage collector。

记忆点

Lox 的设计目标不是工业级完整语言,而是教学用完整语言:

足够小,但包含真实语言的重要机制。


3. Lox 的基本数据类型

Lox 内置类型很少:

Boolean

true;
false;

用于逻辑判断。


Number

Lox 只有一种数字类型:double-precision floating point。

1234;
12.34;

没有 int、float、long 的区分。


String

"I am a string";
"";
"123";

注意:

123;    // number
"123";  // string

Nil

nil;

表示“没有值”,类似其他语言里的 null


4. 表达式 Expressions

表达式的主要作用是:

产生一个值。

Arithmetic

a + b;
a - b;
a  b;
a / b;
-a;

+ 比较特殊:

1 + 2;              // 数字加法
"hello " + "lox";   // 字符串拼接

Comparison

a < b;
a <= b;
a > b;
a >= b;

比较运算返回 Boolean。


Equality

1 == 2;
"cat" != "dog";
123 == "123";

不同类型的值不会自动转换。


Logical Operators

!true;
true and false;
nil or "default";

andor 会 short-circuit:

false and expensiveCall();
true or expensiveCall();

右边可能根本不会执行。


Grouping

var average = (min + max) / 2;

括号用于改变优先级。


5. 语句 Statements

语句的主要作用是:

产生效果,而不是产生值。

例如输出、修改变量、控制流程。

print "hello";
print 1 + 2;

Expression Statement

someFunction();
a = 3;

表达式加分号,就可以当语句执行。


Block

{
  print "one";
  print "two";
}

block 可以把多个语句包成一个语句,同时也会影响作用域。


6. Variables

声明变量:

var name = "lox";
var empty;

如果没有 initializer,默认值是:

nil

读取和修改变量:

var breakfast = "bagels";
print breakfast;

breakfast = "beignets";
print breakfast;

记忆点

变量让程序可以保存状态。

没有变量,语言更像计算器。


7. Control Flow

Lox 有基本控制流。

if

if (condition) {
  print "yes";
} else {
  print "no";
}

while

var a = 1;
while (a < 10) {
  print a;
  a = a + 1;
}

for

for (var a = 1; a < 10; a = a + 1) {
  print a;
}

Lox 的 for 是 C 风格 for loop,不是 foreach。


8. Functions

函数调用:

makeBreakfast(bacon, eggs, toast);
makeBreakfast();

定义函数:

fun printSum(a, b) {
  print a + b;
}

返回值:

fun add(a, b) {
  return a + b;
}

如果没有显式 return,函数默认返回:

nil

9. 参数 vs 实参

本章特别区分两个词:

Parameter

函数声明里的变量:

fun add(a, b) {
  return a + b;
}

这里 ab 是 parameters。

Argument

调用函数时传入的实际值:

add(1, 2);

这里 12 是 arguments。


10. First-class Functions

Lox 的函数是一等值。

也就是说,函数可以:

  • 存进变量

  • 作为参数传递

  • 作为返回值返回

例子:

fun addPair(a, b) {
  return a + b;
}

fun identity(a) {
  return a;
}

print identity(addPair)(1, 2);

这里 identity(addPair) 返回函数本身,然后继续调用。


11. Closures

Lox 支持闭包。

例子:

fun returnFunction() {
  var outside = "outside";

  fun inner() {
    print outside;
  }

  return inner;
}

var fn = returnFunction();
fn();

inner() 使用了外层函数里的变量 outside

即使 returnFunction() 已经结束,inner() 仍然能记住这个变量。

这就是 closure。

记忆点

closure 的核心是:

函数不仅保存自己的代码,也保存它需要的外部变量环境。


模块你需要知道什么例子重要性
Lox 定位小型、高层、动态类型脚本语言类似 JavaScript / Lua / Scheme 的简化版理解后面为什么设计得很小
基本程序print 是内置语句,不是函数print "Hello";用来测试解释器是否能输出
动态类型变量类型可变,错误运行时发现var x = 1; x = "hi";后面解释器用 Object 存值
自动内存管理用户不用手动释放内存不写 free()后面 C 版会实现 GC
Boolean真假值true, false条件判断基础
Number只有一种数字,double123, 12.34简化数字系统
String双引号字符串"hello"+ 可用于字符串拼接
Nil表示“没有值”nil类似 null
表达式产生值的代码1 + 2 * 3解释器最先实现的部分
算术运算数字运算,+ 也可拼字符串1 + 2, "a" + "b"需要运行时类型检查
比较运算比较数字,返回 Booleana < b, a >= b控制流常用
相等判断可比较任意类型123 == "123"不做隐式转换
逻辑运算and / or 会短路false and call()本质上也带控制流
分组括号改变优先级(min + max) / 2影响 AST 结构
语句产生效果,不一定产生值print x;程序不只是算值
Block多条语句组成一个作用域{ var a = 1; }后面作用域和闭包的基础
变量保存状态var a = 1; a = 2;让程序从计算器变成语言
if条件执行if (ok) print "yes";分支控制流
while条件循环while (i < 10) { ... }重复执行
forC 风格循环for (var i = 0; i < 10; i = i + 1)后面会被 desugar 成 while
函数定义fun 声明函数fun add(a, b) { return a + b; }代码复用
参数 / 实参parameter 是声明里的变量,argument 是调用时传的值fun f(a) / f(1)避免概念混淆
返回值return 返回结果;没有返回则是 nilreturn a + b;函数调用结果
一等函数函数可以存变量、传参、返回var f = add;闭包和函数式写法基础
闭包函数能记住外层变量inner function 使用 outer variable后面实现作用域的难点

12. Classes:本章重点

Lox 既有函数式语言特征,也有面向对象特征。

本章最值得重点关注的是 class。


12.1 为什么 Lox 要支持类

作者加入 class,不是因为 Lox 要做成大型语言,而是因为:

  • OOP 很常见

  • 很多程序员熟悉 class

  • class 能把状态和行为组织在一起

  • 语言实现书经常略过 OOP,实现它很有价值

在动态类型语言里,object 特别有用,因为它提供了一种组织复合数据的方式。


12.2 Class vs Prototype

对象系统常见有两条路线:

Class-based

代表:

  • Java

  • C++

  • C#

  • Ruby

  • Python

核心概念:

class → 描述对象的行为
instance → 具体对象,保存状态

调用方法时,通常是:

instance → 找到 class → 在 class 中查找 method

Prototype-based

代表:

  • JavaScript

核心概念:

只有 object,没有 class

对象可以直接继承另一个对象。

作者认为 prototype 更简单、更灵活,但很多用户最后会自己模拟 class。

所以 Lox 直接内置 class。


12.3 声明 Class

Lox 中声明类:

class Breakfast {
  cook() {
    print "Eggs a-fryin'!";
  }

  serve(who) {
    print "Enjoy your breakfast, " + who + ".";
  }
}

特点:

  • class 关键字

  • 方法写在 class body 里

  • 方法声明不写 fun

  • class 本身也是一个值


12.4 Class 是 first-class value

Lox 中 class 也是一等值。

可以存进变量:

var SomeClass = Breakfast;

可以传给函数:

someFunction(Breakfast);

这和函数是一等值类似。

记忆点

在 Lox 里,class 不只是语法结构,也是运行时对象。


12.5 创建实例

Lox 没有 new 关键字。

创建实例的方式是:

var breakfast = Breakfast();

也就是:

调用 class,得到 instance。

这和 Python 很像。

print breakfast;

会得到类似:

Breakfast instance

12.6 Fields:对象字段

Lox 的对象可以自由添加字段。

breakfast.meat = "sausage";
breakfast.bread = "sourdough";

读取字段:

print breakfast.meat;
print breakfast.bread;

特点:

  • 不需要提前声明字段

  • 给字段赋值时,如果字段不存在,就创建它

  • 这和 JavaScript、Python 比较像

和 Java 的区别

Java 里字段通常在 class 里提前声明:

class Breakfast {
  String meat;
  String bread;
}

Lox 不需要这样。


12.7 Methods:对象方法

方法定义在 class 里:

class Breakfast {
  cook() {
    print "Eggs a-fryin'!";
  }
}

调用方法:

var breakfast = Breakfast();
breakfast.cook();

方法和函数很像,但方法通常会操作当前对象。


12.8 this

在方法内部,用 this 指向当前对象。

class Breakfast {
  serve(who) {
    print "Enjoy your " + this.meat + " and " +
          this.bread + ", " + who + ".";
  }
}

使用:

var breakfast = Breakfast();
breakfast.meat = "bacon";
breakfast.bread = "toast";

breakfast.serve("reader");

this.meatthis.bread 读取的是当前实例上的字段。

记忆点

this 让方法知道“我正在操作哪个对象”。


12.9 init:初始化方法

Lox 用特殊方法 init() 作为 initializer。

class Breakfast {
  init(meat, bread) {
    this.meat = meat;
    this.bread = bread;
  }

  serve(who) {
    print "Enjoy your " + this.meat + " and " +
          this.bread + ", " + who + ".";
  }
}

创建实例时传参:

var baconAndToast = Breakfast("bacon", "toast");
baconAndToast.serve("Dear Reader");

执行逻辑是:

调用 class
→ 创建 instance
→ 自动调用 init
→ 返回 instance

记忆点

init() 不是普通函数意义上的 constructor,但承担初始化对象状态的作用。


12.10 Inheritance:继承

Lox 支持单继承。

语法:

class Brunch < Breakfast {
  drink() {
    print "How about a Bloody Mary?";
  }
}

含义:

Brunch 继承 Breakfast

所以 Brunch 的实例可以使用 Breakfast 的方法。

var benedict = Brunch("ham", "English muffin");
benedict.serve("Noble Reader");

这里 serve() 来自 superclass Breakfast


12.11 superclass 和 subclass

术语:

Breakfast → superclass / base class
Brunch    → subclass / derived class

继承表示:

subclass 拥有 superclass 的方法,并可以添加自己的方法。

Lox 只支持单继承,也就是一个类最多继承一个父类。


12.12 super

如果子类重写了方法,但还想调用父类的方法,用 super

例子:

class Brunch < Breakfast {
  init(meat, bread, drink) {
    super.init(meat, bread);
    this.drink = drink;
  }
}

这里:

super.init(meat, bread);

调用的是父类 Breakfastinit()

记忆点

this 指当前对象。

super 指父类方法查找起点。


12.13 Lox 的 OOP 特点总结

Lox 的 class 系统包括:

  • class declaration

  • instance creation

  • fields

  • methods

  • this

  • init initializer

  • single inheritance

  • super

但它不是纯 OOP 语言。

因为:

  • number、string、boolean、nil 不是 class instance

  • primitive values 没有方法

  • 标准库很小

如果把 Lox 做成真正给用户用的语言,这部分可能需要补强。


13. Standard Library

Lox 的标准库非常小。

本书主要只需要:

print
clock()

print 用于输出。

clock() 用于后面做性能测试。

作者没有加入复杂标准库,因为本书重点是语言实现,不是库设计。

缺少的东西包括:

  • 文件 I/O

  • 字符串库

  • 数学库

  • 网络

  • 用户输入

  • 数据结构库


14. 本章关键词

Lox

本书要实现的小型脚本语言。

Dynamic Typing

变量没有固定类型,类型错误在运行时检查。

Nil

表示没有值。

Expression

产生值的代码片段。

Statement

产生效果的代码片段。

Function

可调用代码块。

First-class Function

函数可以像普通值一样传递和返回。

Closure

函数保存它需要的外部变量环境。

Class

创建对象的模板,同时也是运行时值。

Instance

由 class 创建出来的具体对象。

Field

对象上的数据属性。

Method

定义在 class 里的函数。

this

当前实例。

init

初始化实例的方法。

Inheritance

子类复用父类方法的机制。

super

从父类开始查找方法。


15. 复习总结

第 3 章介绍了 Lox 的语言特性。

Lox 有动态类型、自动内存管理、基本数据类型、表达式、语句、变量、控制流、函数和闭包。

其中 class 是本章最重要的高级结构:它让 Lox 支持面向对象编程。

Lox 的 class 系统比较简洁:

class 定义行为
instance 保存状态
field 保存数据
method 操作对象
this 指当前对象
init 初始化对象
super 调用父类方法

最后要记住:

Lox 不是为了功能完整,而是为了教学完整。它足够小,但覆盖了解释器实现中最重要的语言机制。