解释器开发笔记

解释器的名字

有一位大佬曾经说过:“计算机科学只有两个难题:命名和缓存失效。”

所以解释器目前还没有名字。

暂时用Inf这个名字吧,Infinity的意思。

如何做到代码数据统一

作为Lisp的方言,Scheme也是一款在灵魂深处就把代码和数据融为一体的语言。因此Inf在设计的时候,也有意识地贯彻了这种“代码即数据”的思想。

列表是Scheme的核心数据结构,也是Inf目前提供的唯一数据结构。作为一种数据结构,列表在Inf内部是按照地址进行引用的。由于函数式语言的纯函数特性,许多涉及表的操作并不是在传入表的基础上做修改,而是生成新表输出。也就是说,尽管Inf内部使用地址对表进行引用,但语言提供的表操作接口,却是传值引用的。

Inf程序涉及两类列表:其一是首先载入内存的代码和出现在代码中的表,包括quote列表和作为代码的S-List;其二是程序运行过程中,作为中间结果或者最终结果产生的临时表。

对于第一类列表,无论是quote还是S-List,都是按照AST遍历顺序,以AST节点的形式,在内存中统一编址、线性存储的。第二类列表是中间结果,与第一类列表共用相同的地址空间,即AST节点所在的空间。

例如,表操作(cons 'a '(b)),返回的新表'(a b)并不是在原来的表'(b)的基础上做修改,而是在内存中创建了一个新表'(a b),并将其(地址)返回。

'(b)是代码中出现的静态数据,而新表'(a b)是程序运行时产生的中间结果。二者同为列表,尽管生存时期不同,但本质上都是列表。因此,将包括代码在内的所有列表统一编址,将大大简化解释器实现。

从实现的角度考虑,两类列表共用相同的逻辑地址空间,但是在物理上未必共同存储。如果考虑到安全性问题,可以对第一类列表进行访问控制。如果要获得最大的灵活性,则可以将两类列表等同看待,甚至允许程序在运行时读取、乃至动态地修改自身代码,实现宏、反射等元编程能力。

垃圾回收

从上文cons的例子可以看到,cons是一个纯函数,执行过程中会产生大量的中间结果,如果不加清理,将很快耗尽内存。因此,必须引入垃圾回收机制,以回收不再被使用的死对象所占据的内存空间。

GC的对象?

一切运行时产生的数据对象,包括列表、闭包、字符串,都是垃圾回收的对象。由于列表地位特殊,因此目前只研究列表垃圾回收的实现。

辣鸡对象如何判定?

还是举例子:下面是一个简单的求和程序,用于求出列表中所有元素的和。为简单起见,不考虑类型检查等细节。

(define sum
  (lambda (lst)
    (if (null? lst)
        0
        (+ (car lst) (sum (cdr lst))))))
(sum '(1 2 3 4 5))

程序执行前,静态数据'(1 2 3 4 5)已经被保存在代码区,这部分数据是不参与垃圾回收的。

执行sum函数,内部调用cdr,生成了一个新的中间结果'(2 3 4 5),作为递归调用sum的参数再次调用sum。随着递归调用的进行,内存中陆续产生了'(3 4 5)、'(4 5)、'(5)和'()这几个中间结果。最终,递归收敛,栈帧陆续退栈,结果15被计算出来并返回,但是刚才产生的5个中间结果,却留在了内存空间中,后续分配的内存,便不能使用这部分空间。这就是垃圾回收的对象。

在C/C++等提供指针/引用传递机制,甚至显式内存管理接口的语言中,程序员可以在代码中手动释放掉这些死对象。但是在Scheme中,只要是纯函数式的操作,逻辑上都是传值调用,语言层面上无法控制内存的分配。因此,辣鸡回收功能需要由语言内部(即解释器/运行时)去实现。

猜你喜欢

转载自blog.csdn.net/weixin_34014555/article/details/87525481