chibi-scheme源码阅读3
这篇文章会介绍一下 chibi-scheme
语法分析的其他部分,因为我总觉得这其实是很有趣的部分。
从sexp中可以看到所有的ast类型的定义。可见其中比较复杂的是 lambda
类型。这一次打算看一看lambda这个结构体的语法分析,因为其中牵涉到Context类型、我打算整篇博客就介绍这一个部分。
Context
Context表示的是执行所需要的状态,Context中的内容包括:
1 | struct { |
可见在其间有环境、栈、堆结构;Context是执行一个sexp所需要的结构。它记录了解释器当前的状态,当有多个解释器线程同时运行的时候,每一个线程都会有一个它自己的Context结构体。这个Context结构中的(部分)变量和意义记录如下:
变量名 | 意义 |
---|---|
heap | 程序运行的 堆 |
stack | 程序运行的 栈 |
env | 当前的环境 |
parent | 指向父Context |
child | 指向子Context |
globals | 用于保存一些全局的信息 |
dk | 一些预先定义的函数 |
specific | 在编译阶段使用的一个vector,其中包含:bc、fv、lambda等属性 |
我们可以通过C语言调用FFI的方式输出globals的内容,这个之后再进行介绍。输出的结果是一串很长很长的全局变量串。
构造一个环境变量的函数是 sexp_make_context
(在sexp.c
文件中)。
第一个参数ctx表示传进来一个context作为当前构造的Context的父Context。
它里面会构造一个极其简单的全局Context,这里面只有两个稍微复杂的地方:最开始部分的如果使用全局堆的时候、会复制堆的一些属性,否则会新分配一个堆;最后部分,如果没有ctx参数的时候,会初始化一个全局变量,如果有ctx参数,则会复制ctx变量的globals和dk属性。
在外面创建一个独立的上下文的时候都会使用sexp_make_context
函数,但是用这个函数产生的上下文环境虽然能用于构造并不能执行、因为它没有将Context与实际的堆栈、环境等与实际的代码关联起来。所以我们需要在外面给它套上一个实现在eval.c
中的sexp_make_eval_context
的函数。
这里虽然文档说它是
Similar to sexp_make_context
,但是源代码里面可见sexp_make_eval_context
实际是直接调用sexp_make_context
的。
在这个函数里对每个属性已经依次的赋值、最后得到一个完整的的Context结构体给出去。其中函数sexp_context_lambda/sexp_context_bc/sexp_context_fv
等函数都是对specific里的属性进行赋值。specific是一个有七个内容的vector,主要是在编译阶段使用。其中:
bc
属性是指字节码(bytecode),chibi-scheme
在vm.c
文件中实现了一个虚拟机,我们需要将代码转换为字节码(也就是中间代码)然后交给vm来执行。vm的主要好处是隔离了硬件,使得不必去关注硬件的实现细节、这里的种种展开这里就不提。fv
属性指自由变量(free variable),指的是没有在作用域中进行声明的变量。lambda
属性用于指定这个Context的作用域。
之后就可以使用ctx来执行语句了:Context变量是执行chibi-scheme语句的函数sexp_eval
和sexp_eval_string
的第一个参数。
还有关于它的操作都是关于加载文件/环境/输入输出端口的。此处就略过了,详细可以参见文档的这里
lambda 类型
lambda类型结构体的定义如下:
1 | struct { |
与lambda 类型相通的是 eval.c
里面的 analyze_lambda
函数。正常的lambda表达式应该是类似这样的:
1 | > (lambda (x y) (+ x y)) |
(忽视gc相关的内容后)最开始的 verify syntax
内容里面对应的是下面这三种错误:
1 | > (lambda x) ; 参数数量不对 |
lambda结构体的定义可以在上面看到,这个结构的属性和意义(部分)如下:
sexp | 意义 |
---|---|
name | lambda函数通过define绑定的名字(如果有的话) |
params | lambda的参数 |
body | lambda的函数体 |
defs | lambda中出现的define语句 |
fv | 函数中出现的自由变量 |
sv | 保存保护变量 |
ret | 返回值的类型 |
types | 参数的类型 |
source | lambda的完整内容 |
source 是编译器为了支持 debug 弄的。几乎每一个ast类型都有一个source属性。
在函数中、接下来这段build lambda and analyze body
就是填充 lambda中的source、body、params部分,同时为函数构造一个新的Context结构体(作为当前Context的子Context),给结构体的env和lambda变量赋值。在这里面其实还有一个特殊的操作,也就是sexp_flatten_dot
这个函数,将点后面的所有参数都解释为一个列表,使得你可以引入不定数量的参数。下面是一个例子:
1 | > (define func (lambda (num . nums) nums)) |
之后这段delayed analyze internal defines
通过一个for循环遍历整个lambda,然后找到其中的define语句,延迟进行这些语句,获得它们的name和value,将它们都放到defs属性中。它们将随着函数的开始而执行。