- rui314 的 minilisp 專案,是一個九百多行的 LISP 解譯器,包含垃圾蒐集 gc,去掉 gc 的版本只有 668 行,除了 cons, car, cdr, print 之外,函式庫完全自己從頭開始寫,所以打算以此翻譯做為報告,從翻譯的過程中深入了解這個經過半個世紀卻還在使用的語言
MiniLisp 是個用 1000 行 C 語言寫的 Lisp 解釋器,其支持:
- 整數、符號、構造函數
- 全域變數
- 局部變數
- 閉包:閉包可以用來在一個函式與一組「私有」變數之間建立關聯關係。在給定函式被多次呼叫的過程中,這些私有變數能夠保持其永續性。變數的作用域僅限於包含它們的函式,因此無法從其它程式碼部分進行存取。不過,變數的生存期是可以很長,在一次函式呼叫期間所建立所生成的值在下次函式呼叫時仍然存在。
- if 條件句
- 原始函數,例如 +、=、< 或 list
- 使用者自訂函數
- 巨集系統
- 垃圾回收
總共1000行的C寫成,作者沒有因為大小而犧牲可讀性,相反的作者認為有大量註解讓使用者好理解
$ make
MiniLisp 已經在 Linux x86/x86-64 和 64 位 macOS 上進行了測試。代碼與體系結構無關,因此應該能夠在其他類 Unix 操作系統上編譯和運行。
MiniLisp 帶有一個全面的測試套件。為了運行測試,給出 “test” 參數。 $ make test
MiniLisp 是一個傳統的 Lisp 解釋器。它一次從標準輸入中讀取一個表達式,對其求值,然後打印出表達式的返回值。這是有效輸入的示例。
(+ 1 2)
上述結果是 "3".
MiniLisp 支援整數定數, ()
, t
, 符號, 串列定數.
- Integer literals are positive or negative integers.
()
是唯一假值. 也代表空值.t
是對自身求值的預定變義量。這是表示真值的首選方式,而任何非()值都被認為是真值。- 符號是有獨立名字的物件. 他們通常代表識別字,由於 MiniLisp 沒有字串類別,所以符號有時候被當字串使用
- 串列定數是構造函數. 它的cdr元素會是ˋ()ˋ,或是一個點狀列表沒有任何ˋ()ˋ值,一個點狀列表會這樣表示
(a . b)
cons
接受兩個參數並得出新的串列定數,使第一個參數為car,第二個參數為 cdr。
(cons 'a 'b) ; -> (a . b)
(cons 'a '(b)) ; -> (a b)
car
and cdr
are accessors for cons cells. car
returns the car, and cdr
returns the cdr.
(car '(a . b)) ; -> a
(cdr '(a . b)) ; -> b
setcar
改變一個 cons cell. setcar
接受兩個參數並假設第一個參數為 cons cell. It sets the second argument's value to the cons cell's
car.
(define cell (cons 'a 'b))
cell ; -> (a . b)
(setcar cell 'x)
cell ; -> (x . b)
+
操作域裡的總和
(+ 1) ; -> 1
(+ 1 2) ; -> 3
(+ 1 2 3) ; -> 6
-
如果操作域裡只有一個參數則否定操作域裡的參數
(- 3) ; -> -3
(- -5) ; -> 5
如果操作域裡不只一個參數,則由第一個參數減去後面的參數
(- 5 2) ; -> 3
(- 5 2 7) ; -> -4
=
接受兩個參數,如果兩者是相同的整數則輸出 t ,不是則輸出()。
(= 11 11) ; -> t
(= 11 6) ; -> ()
<
接受兩個參數,如果第一個參數小於第二個參數,則輸出 t 。
(< 2 3) ; -> t
(< 3 3) ; -> ()
(< 4 3) ; -> ()
(if cond then else)
是語言中唯一的條件。首先執行cond。如果結果為真值,執行then不然就是執行else 。
(while cond expr ...)
執行 expr ...
直到 cond
得出為()
. 這是 MiniLisp 唯一支持的循環
如果您熟悉 Scheme,您可能想知道是否可以在 MiniLisp 中通過尾呼叫編寫循環。答案是不。尾呼叫會消耗 MiniLisp 中的堆疊空間,因此將會因為內存耗盡錯誤而失敗。
eq
執行兩格參數如果兩個參數一樣則輸出 t 。 而 eq
比較的是地址,用它對symbols(符號)快速地比較,測試兩個cons cells是否物理上是同一個對象。
println
將指定對象輸出.
(println 3) ; prints "3"
(println '(hello world)) ; prints "(hello world)"
MiniLisp s支持變量跟函數 可以用 define
來定義
(define a (+ 1 2))
(+ a a) ; -> 6
定義函數有兩種方式。一種方法是使用特殊形式 lambda。(lambda (args ...) expr ... 輸出得函數可以使用ˊdefineˊ指定變數
(define double (lambda (x) (+ x x)))
(double 6) ; -> 12
((lambda (x) (+ x x)) 6) ; 沒有指定也是同樣的結果
另一個則是 defun
. (defun fn (args ...) expr ...)
比起(define fn (lambda (args ...) expr ...)
比較短
;; Define "double" using defun
(defun double (x) (+ x x))
(defun fn (expr . rest) rest)
(fn 1) ; -> ()
(fn 1 2 3) ; -> (2 3)
Variables are lexically scoped and have indefinite extent. References to "outer" variables remain valid even after the function that created the variables returns.
;; A countup function. We use lambda to introduce local variables because we
;; do not have "let" and the like.
(define counter
((lambda (count)
(lambda ()
(setq count (+ count 1))
count))
0))
(counter) ; -> 1
(counter) ; -> 2
;; This will not return 12345 but 3. Variable "count" in counter function
;; is resolved based on its lexical context rather than dynamic context.
((lambda (count) (counter)) 12345) ; -> 3
setq
為現有變量設置新值。如果未定義變量,則出現錯誤。
(define val (+ 3 5))
(setq val (+ val 1)) ; increment "val"
巨集與函數的功能相同,但在編譯時,編譯時會以巨集取代原來的敘述,而函數則是一個跳躍敘述;在程式執行期間,由於巨集已經展開為它所代表的敘述,程式會一行一行執行下去,而碰到函數則是跳到函數定義的副程式去執行。 (defmacro macro-name (args ...) body ...)
下面是定義巨集的示範
(defmacro unless (condition expr)
(list 'if condition () expr))
上述的 defmacro
定義一個新巨集 unless. unless 是expr新的執行條件除非條件是真值。函數不能做同樣的事因為所有參數會在控件傳給函數前被執行
(define x 0)
(unless (= x 0) '(x is not 0)) ; -> ()
(unless (= x 1) '(x is not 1)) ; -> (x is not 1)
macroexpand
是一個方便的特殊形式看到巨集的擴充
(macroexpand (unless (= x 1) '(x is not 1)))
;; -> (if (= x 1) () (quote (x is not 1)))
gensym
建立一個新的符號 gensym經常在Lisp 巨集中用於代碼生成,當巨集需要創建新標識符時,這些標識符不會與現有標識符發生衝突。
(gensym) ; -> a new symbol
與傳統的 Lisp 語法一樣, ;
(分號)開始單行註釋。持續到行尾。
- https://github.com/rui314/minilisp/blob/master/README.md
- https://zh.wikipedia.org/zh-tw/%E9%97%AD%E5%8C%85_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)#%E8%AF%8D%E6%BA%90
- https://www.cs.cornell.edu/courses/cs312/2003fa/lectures/sec24.htm
- https://cythilya.github.io/2018/10/18/lexical-scope/
- https://ithelp.ithome.com.tw/articles/10271062
- https://acl.readthedocs.io/en/latest/zhTW/ch3.html
- https://zh.m.wikibooks.org/wiki/Lisp_%E5%85%A5%E9%96%80/%E5%BA%8F%E7%AB%A0_%E5%A6%82%E4%BD%95%E5%BC%80%E5%A7%8B