Skip to content

project1

patrick901218 edited this page Jun 14, 2022 · 2 revisions

本專案研究自 Minilisp 。 (注:使用他人專案)

Minilisp

是一個由一千行(653左右)內的C語言所寫出的解譯器。他可實現以下功能:

  • 整數,代號,以及虛數。
  • 全域變數(C語言)
  • 局部變數
  • 匿名函式
  • if/else
  • 原始函數
  • 自訂函數(user-defined functions)
  • a macro system 注: 使用維基百科
  • 垃圾回收(節省記憶體空間)

Compile

$ make

Minilisp 已經在 Linux x86/x86-64 和 64 位 Mac OS 上進行了測試。它並不是建構於體系結構上,因此可以在大部分類Unix操作系統上進行編譯和運行。

Test

MiniLisp 帶有一個全面的測試套件。為了運行測試,給 make 提供“test”參數。

$ make test

語言特點

MiniLisp 是一個傳統的 Lisp 解釋器。它一次從標準輸入中讀取一個表達式,對其求值,然後打印出表達式的返回值。這是一個有效輸入的示例。

(+ 1 2)

上面的表達式打印“3”。

字元

MiniLisp 支持整數文字、()、t、符號和列表文字。

  • 整數文字是正整數或負整數。
  • ()是唯一的假值。它也代表空列表。
  • t是對自身求值的預定義變量。這是表示真值的首選方式,而任何非()值都被認為是真值。
  • 符號是具有唯一名稱的對象。它們用於表示標識符。因為 MiniLisp 沒有字符串類型,所以符號有時也被用作字符串的替代品。
  • 列表文字是缺點單元格。它要么是最後一個元素的 cdr 為的常規列表,要么是以任何非值()結尾的點列表。()一個虛線列表寫為(a . b)。

列出運算符

cons 接受兩個參數並返回一個新的 cons 單元格,使第一個參數為car,第二個參數為 cdr。

(cons 'a '(b)) ; -> (a b)

carcdr是 cons 單元的訪問器。car歸還carcdr歸還cdr

(car '(a . b)) ; -> a
(cdr '(a . b)) ; -> b

setcar突變一個 cons 單元格。setcar接受兩個參數,假設第一個參數是一個 cons 單元格。它將第二個參數的值設置為 cons 單元的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...else)

(if cond then else)是語言中唯一的條件。它首先評估cond。如果結果為true(1),則進行評估。否則 評估else(0) 。

循環 (while)

(while cond expr ...)執行expr ...直到cond被評估為 ()。這是 MiniLisp 支持的唯一循環。

等價測試運算符

eq接受兩個參數,t如果對象相同則返回。真正做eq的是指針比較,因此兩個對象碰巧具有相同的內容但實際上不同的對像被認為是不相同的eq

輸出運算符

println將給定對象打印到標準輸出。

(println 3)               ; prints "3"
(println '(hello world))  ; prints "(hello world)"

定義(define)

MiniLisp 支持變量和函數。它們可以使用來定義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)  ; do the same thing without assignment

另一種方式是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)

變量是詞法範圍的並且具有不確定的範圍。即使在創建變量的函數返回後,對“外部”變量的引用仍然有效。

;; 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"

Macro

Macro看起來類似於函數,但它們的不同之處在於Macro將表達式作為輸入並返回一個新的表達式作為輸出。(defmacro macro-name (args ...) body ...)定義一個Macro。這是一個例子。

(defmacro unless (condition expr)
  (list 'if condition () expr))

上面defmacro定義了一個新的Macro。除非. unless是一個新的條件,它評估expr,除非條件是一個真值。您不能對函數做同樣的事情,因為所有參數都將在控件傳遞給函數之前進行評估。

(define x 0)
(unless (= x 0) '(x is not 0))  ; -> ()
(unless (= x 1) '(x is not 1))  ; -> (x is not 1)

macroexpand是一種方便的特殊形式,可以查看Macro的擴展形式。

(macroexpand (unless (= x 1) '(x is not 1)))
;; -> (if (= x 1) () (quote (x is not 1)))

gensym創建一個新符號,它永遠不會出現eq在除自身之外的任何其他符號上。對於編寫引入新標識符的Macro很有用。

(gensym)   ; -> a new symbol

詮釋

與傳統的 Lisp 語法一樣,;(分號)開始單行註釋。註釋繼續到行尾。

Clone this wiki locally