未完待续

我们已经实现了Lua解释器最核心的功能。不过,离我们最初的目标——一个完整的、高性能的、生产级别的解释器——还差得很远。我会继续完善这个解释器,但是由于工作繁忙业余时间不足,会暂停这个系列的文章。写文章比写代码累多了。结合我上学时读于渊的《自己动手写操作系统》的经历,当时也只是跟着书的前半部分实践,在掌握了基本的开发方法,对写操作系统入了门后,后面就是完全自己写了。我认为这个系列文章到目前已经完成的部分也应该可以提供对实现一个Lua解释器的入门知识,有心的读者可以独立地实现剩余部分。

下面是一些未完成功能的部分列表:

  • 元表,是Lua语言一个很重要的特性,提供灵活且强大的特性。不过其实现原理很简单,只需要在虚拟机执行相关字节码时做一层额外判断即可,甚至不需要修改语法分析的部分。这里有一个实现上的细节:我们解释器的垃圾回收是用的RC,这就可能造成循环引用进而导致内存泄漏。一个表把自己设置为自己的元表就是一个常见的循环引用。为了避免这个常见场景的循环引用,需要对这种情况做特殊处理。

  • UserData,是Lua的基本类型之一。不过我们目前还没有遇到使用UserData的需求。可以等后面在实现标准库时,遇到这个需求的时候再来实现这个类型。Lua官方实现中,新建UserData是在Lua中申请内存,然后交由C函数来初始化。而Rust中是不允许未初始化的内存的,所以需要考虑UserData的创建方式。

  • LightUserData,也是Lua的基本类型之一。不过就是一个裸指针,并不需要对此做什么特殊处理。

  • 错误处理。我们目前对所有错误的处理方式都是panic,这自然是不可行的。至少需要区分预期的错误和程序bug。而前者可能还需要细分词法分析、语法分析、虚拟机执行、Rust API等类型。错误处理也是Rust语言的一个特色。这也是个很好的体验Rust错误处理的机会。

  • 性能测试。高性能是我们最初的目标之一,在实现时也做了些优化,比如字符串类型的设计,但最终结果如何目前并没有底,还是需要测试才能知道。网上有些Lua性能测试的代码,可以跟Lua官方实现做对比测试。这也可以顺便验证正确性。

  • 优化表构造。对于全部是常量元素的表构造,可以无需加载到栈上,甚至可以直接在语法分析阶段创建好表。

  • Rust API。Lua语言更多的使用场景是胶水语言,所以对外的API是非常重要的。我们这个解释器主要是给Rust语言编写的程序使用的,所以对外提供的应该是一套符合Rust调用方式的API。这就跟Lua官方实现提供的C API不一致。我们目前已经实现了些基本的API,比如读取栈上的值等,就使用了泛型,简化了API和调用方式,也就跟C API不一致。这里有份对Rust实现的脚本语言的调用方式的对比调研,很有参考价值。

  • 库。当前解释器是个独立的程序,但Lua最常见的使用场景是一个库,被其他程序调用。所以需要把我们的项目改造为一个库。

  • 支持整个代码段的传参和返回值。

  • 标准库,是解释器核心以外的特性,涉及更多的方面。标准库中除了下面罗列的几个包外,还有一些基础函数,比如type()ipairs()等是我们已经实现的,剩下的也大多不难,唯一麻烦的是pairs()函数。Lua官方实现中的pairs()函数的高效实现,依赖于表的实现方式。而我们是用Rust的HashMap来实现表的字典部分,可能没有简单的实现方式了。

  • math库,大部分函数在Rust标准库中都有对应的实现,唯一需要手动实现的是生成随机数的函数。由于C语言标准中并没有提供这个函数,所以Lua的官方实现是自己实现的这个函数。我们虽然也可以使用random crate,但更好是参考Lua官方实现,并自己实现这个随机数的生成函数。另外,生成随机数需要维护一个全局的状态,在Lua官方实现中,这个状态是一个UserData类型并被添加到Lua的Register中。而我们可以利用Rust闭包的特性,把这个状态放到闭包中,更方便也更高效。

  • string库,麻烦的是正则匹配。Lua语言为了轻便,自己定义并实现了一套正则匹配规则。所以我们也只能遵循其定义,并用Rust重新实现一遍。这里应该会很复杂,不过完成后也会对正则匹配有更深的了解。

  • io库,麻烦的是对文件的表示。在C语言标准中提供了FILE类型,可以代表所有文件类型,包括标准输入输出、普通文件等,也可以表示只读、只写、读写等多种模式。但在Rust语言中似乎这些都是独立的。如果要提供跟io库一致的API,需要做封装。

  • coroutine库,需要对Lua的协程有彻底的了解,也会对现有的函数调用流程做出很大的调整。

  • debug库,我没有使用过这个库,了解不多,但感觉如果要实现这个库,感觉要么需要大量的unsafe代码,要么对现有流程做出很大的改动。所以最终可能选择不实现这个库。

除了上述的未完成功能列表外,还有对目前代码的一些小改进,比如完善注释、应用Rust新版本中支持的let..else语法、一些代码小优化等。为此在代码目录下新增了to_be_continued。这也可以看做是这系列文章对应代码的最终版本。