emacs lisp 求值 eval 研究 (几何画板开发笔记 八)

对基础的 lisp.h 的研究当然还没完,该文件可是有 3700+ 行之多的。前几篇基本只涉及了 15%?左右吧。
因为各部分之间是相互有关联的,单独看 lisp.h 文件也不可能理解其自身,所以在了解了很基本的 Lisp_Object,
Lisp_Symbol, Lisp_Cons 之后,我们就可以试着去研究一下 lisp 的核心:求值 eval() 了。

在研究之前,再略微啰嗦一点,Lisp 语言中有著名的 REPL -- Read,Eval,Print,Loop 循环,(当然
其它脚本语言中也有。。。),其中 eval 负责对读入的表达式进行求值,print 负责打印求值结果,
顾名思义 read 负责读入表达式。这个表达式就是著名的 s-expr。当然 read, print 部分我们只能先
假设已经存在并且按我们的愿望运转,这样我们就能专心研究 eval 部分了。

 

从外部来看,在读取一个表达式 form 后,调用函数 Feval(form, ...) 进行求值。由于 Feval() 中仅是
设置了一下词法环境(稍后再研究的),然后调用主要求值函数 eval_sub() 进行求值,因此我们主要
从函数 eval_sub(form) 开始。 

Lisp_Object eval_sub(Lisp_Object form) {
   if (form 是 symbol) 对 symbol 求值并返回。
   if (form 是 atom) 返回 atom 自身,因为 lisp 求值规则 atom 求值为自身。
   否则 form 必定是一个列表(list),对该列表求值,当做函数调用(或宏)。
}

为研究这个函数,对 lisp 求值规则的了解是不可少的。首先,对 symbol 的求值,就是得到该
symbol 绑定的值,一般而言就是 Lisp_Symbol 结构中的 value 值。如果有词法绑定,会略微复杂
一点,我们将词法绑定相关的部分放到研究函数(lambda)的时候再细究。

次之,是对 atom 的求值,在 lisp 中,数字(整数、浮点数等)、字符串、字符等非 list 的对象
都是 atom,它们求值返回其自身。

最复杂的是对 list 的求值,list 是由 Lisp_Cons 点对单元构成的单链表,car 槽存放值,cdr 槽存放
链表剩余部分。当对一个 list 求值的时候,列表的第一项被当做是函数(或宏),列表的剩余部分被
作为是函数的参数。

例如,我们以 lisp 表达式 (+ 3 4) 为例,该 form 求值为 3+4 即 7.

第一步,获得此 form 的第一个元素,如果该元素是一个 symbol 则得到该 symbol 的函数:

Lisp_Object original_fun = XCAR(form);   // 即得到 +那个 Lisp_Symbol
// 中间一些关于 gc, stack backtrace, debug 等暂时略,以后详细说明。
Lisp_Object fun = XSYMBOL (original_fun) -> function;  // 得到该符号绑定的函数

这里 ‘+’ 对应 +符号,在 emacs lisp 初始化的时候构造此符号,并将函数(function)
Fadd() 绑定到该‘+’ 符号。 Fadd() 函数负责执行数学加法。具体初始化过程以后详述。

第二步:根据第一个元素(如果是函数则得到其函数对象 fun),有三种处理分支:
   (a) 是 lisp 内建函数(builtin, primitive)或原语(special operator),则调用该函数对应的
        实现 C 函数。
   (b) 是编译之后的字节码(compiled bytecode),我们以后研究 emacs lisp 编译的时候再看。
   (c) 如果第一个元素不是 symbol,那就必须是一个 list,否则报错。到第四步详述。

第三步:执行 lisp 内建函数,或原语。
   这一执行过程需要展开描述,我们下面叙述。

第四步:fun 是一个 list,则取出这个 list 的第一个元素:
   Lisp_Object funcar = XCAR (fun)
并根据这个元素是 lambda, macro, autoload 的不同而执行。如果不是这些则报错。

最后,上面各步骤或分支的执行结果为一个 Lisp_Object, 作为结果值返回。

所以,这里的关键是第三和第四(三、四是分支)的情况。

===

由于 eval() 较复杂,第三、四步我们下一篇继续研究。

 

你可能感兴趣的:(lisp)