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属性中。它们将随着函数的开始而执行。