對基礎的 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 部分了。 oop
從外部來看,在讀取一個表達式 form 後,調用函數 Feval(form, ...) 進行求值。因爲 Feval() 中僅是
設置了一下詞法環境(稍後再研究的),而後調用主要求值函數 eval_sub() 進行求值,所以咱們主要
從函數 eval_sub(form) 開始。 ui
Lisp_Object eval_sub(Lisp_Object form) {
if (form 是 symbol) 對 symbol 求值並返回。
if (form 是 atom) 返回 atom 自身,由於 lisp 求值規則 atom 求值爲自身。
不然 form 一定是一個列表(list),對該列表求值,當作函數調用(或宏)。
} atom
爲研究這個函數,對 lisp 求值規則的瞭解是不可少的。首先,對 symbol 的求值,就是獲得該
symbol 綁定的值,通常而言就是 Lisp_Symbol 結構中的 value 值。若是有詞法綁定,會略微複雜
一點,咱們將詞法綁定相關的部分放到研究函數(lambda)的時候再細究。 debug
次之,是對 atom 的求值,在 lisp 中,數字(整數、浮點數等)、字符串、字符等非 list 的對象
都是 atom,它們求值返回其自身。 code
最複雜的是對 list 的求值,list 是由 Lisp_Cons 點對單元構成的單鏈表,car 槽存放值,cdr 槽存放
鏈表剩餘部分。當對一個 list 求值的時候,列表的第一項被當作是函數(或宏),列表的剩餘部分被
做爲是函數的參數。 orm
例如,咱們以 lisp 表達式 (+ 3 4) 爲例,該 form 求值爲 3+4 即 7. 對象
第一步,得到此 form 的第一個元素,若是該元素是一個 symbol 則獲得該 symbol 的函數: ci
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, 做爲結果值返回。
因此,這裏的關鍵是第三和第四(3、四是分支)的狀況。
===
因爲 eval() 較複雜,第3、四步咱們下一篇繼續研究。