写在前面的屁话
无聊,用 Rust 实现一下 craftinginterpreters,这本书后面的垃圾回收使用得是标记清除算法,最近也在看 垃圾回收算法手册:自动内在管理的艺术,刚好可以尝试把里面的理论知识应用到这上面。
我的本地环境
- x86_64
- macOS
- rustc 1.57.0-nightly (73422130e 2021-09-24)
语言处理器
随便补充点基础,虽然后续会详细讲。
语言处理器,通常分为解释器跟编译器。
拿 JavaScript
来讲,我们把代码保存到文本文件中,然后浏览器或者其他的 JavaScript
解释器读取我们写好的文本之后执行出结果。
拿 C
语言来讲, 我们把代码写好保存成相关的文本文件, 然后编译器读取文件中代码将其生成一个可执行文件.
然后你可能会发现不管是解释器还是编译器, 它好像都要经过这么几个一样的过程
- 词法分析 (scanning)
- 语法分析 (parsing)
- 静态分析 (static analysis)
其实这些基本是一个编译器(或解释器)的前端
词法分析 (scanning)
现在来举一个例子,下面有这么一段东西
8 + 2 * 3
复制代码
你的思路肯定是 2 乘以 3 再加 8。
然后现在来写段程序解析这一行为。把 "8", "+", "2", "*", "3" 分出一个一个词,这一过程其实就是一个扫描的过程,所以词法分析的程序,我们可以称为 lexical analyzer(词法分析器) 或者 scanner(扫描器)。
按照我的理解,其实就是一个分词的行为,然后要考虑语言中的关键字(或保留字, 譬如 JavaScript
的 var
while
这种)。
词法分析这部分其实有很多工具,甚至你可以用正则表达式来做这桩事,主要是一个自动机的理论实践,我选择手工处理。
语法分析 (parsing)
经过之前的分词阶段,我们就要面临如何处理这些分好的词。一般情况下, 会根据预先定义好的语法把之前分好的词描述成一个树形结构,很多人看 babel
相关的文章肯定听说过抽象语法树(abstract syntax tree, ast),其实就是语法分析阶段处理的。
8 + 2 * 3;
复制代码
这段代码,我们会把它解析成一个树形结构
|--- + ---|
8 |--- * ---|
2 3
复制代码
一般这部分也是有工具的,不过我还是决定手写。
静态分析
其实这阶段是语义分析,譬如我们现在有这段代码
var a = 10;
var b = 11;
a + b
复制代码
这里看到 a
加上 b
,我们可以很快地知道 a
代表 10
,b
代表 11
,然后把 10
跟 11
相加,算出结果 21
。人是这样思考的,我们也得让程序知道 a
跟 b
代表什么,是全局变量还是局部变量,得知晓它们的值,然后计算。如果是静态类型的,需要进行类型检查,如果是动态类型,只要在运行时进行类型检查。
Lox 语言简介
Lox 语言看起来有点像 JavaScript,动态类型带 GC 的高级语言,基础类型有 Booleans
Numbers
Strings
Nil
,还支持 class 风格的面向对象复合类型,当然 if else 这些语法肯定也有,还有函数闭包之类的特性。
var a = 10;
var b = true;
var c = 12.34;
var d = "test";
01 / c;
-a;
if (b) {
print d;
} else {
while (a < 10) {
a = a + 1;
}
}
for (var i = 1; i < 10; i = i + 1) {
print i;
}
fun sum(a, b) {
return a + b;
}
fun fn() {
var outside = "outside";
fun inner() {
print outside;
}
return inner;
}
var f = fn();
f();
class Breakfast {
cook() {
print "Eggs a-fryin'!";
}
serve(who) {
print "Enjoy your breakfast, " + who + ".";
}
}
var someVariable = Breakfast;
someFunction(Breakfast);
var breakfast = Breakfast();
print breakfast; // "Breakfast instance".
breakfast.meat = "sausage";
breakfast.bread = "sourdough";
// 使用 this 来获取对象的属性
class Breakfast {
serve(who) {
print "Enjoy your " + this.meat + " and " +
this.bread + ", " + who + ".";
}
// ...
}
// 带参数的构造器
class Breakfast {
init(meat, bread) {
this.meat = meat;
this.bread = bread;
}
// ...
}
var baconAndToast = Breakfast("bacon", "toast");
baconAndToast.serve("Dear Reader");
// 通过 < 表达继承
class Brunch < Breakfast {
drink() {
print "How about a Bloody Mary?";
}
}
var benedict = Brunch("ham", "English muffin");
benedict.serve("Noble Reader");
// 展示 super
class Brunch < Breakfast {
init(meat, bread, drink) {
super.init(meat, bread);
this.drink = drink;
}
}
复制代码
基本上语法就这些,后续可以自己拓展一下数组跟字典类型的支持,或者其他语法
var arr = [1, 2, "test"];
var keyValue = { "a": 1, "b": "b" };
复制代码
因为我们不使用自带 GC 的语言来实现 Lox,所以还得自己给编译器实现一下 GC 算法,如果不想自己实现 GC,可以用 Go 来实现编译器。