2010-08-12 124 views
15

我實現我自己的Lisp語言上的Node.js的頂部,我可以運行s表達式是這樣的:如何實現Lisp宏系統?

 
(assert (= 3 (+ 1 2))) 

(def even? (fn [n] (= 0 (bit-and n 1)))) 

(assert (even? 4)) 
(assert (= false (even? 5))) 

現在我想補充的宏 - 的defmacro功能 - 但是這是我米卡住了。我想知道如何在其他Lisp實現宏系統,但我找不到許多指針(除了thisthis)。

我看了一下Clojure宏系統 - 我最熟悉的Lisp - 但這似乎太複雜了,我找不到更多的線索,我可以隨時應用(Clojure宏最終編譯爲字節碼不適用於JavaScript,我也無法理解macroexpand1函數。)

所以我的問題是:給定一個沒有宏但帶有AST的Lisp實現,如何添加像Clojure的宏這樣的宏系統系統?這個宏系統可以在Lisp中實現嗎,還是在宿主語言中需要額外的特性?

一個額外的評論:我還沒有實施quote'),但因爲我不知道返回列表中應該是什麼樣的值。它是否應該包含AST元素或對象,如SymbolKeyword(後者是Clojure的情況)?

回答

12

所有的宏都將未評估的形式作爲參數並在其主體上執行替換。實現宏系統的訣竅是告訴你的編譯器是lazy。換句話說,當編譯器遇到函數時,它首先計算它的形式參數列表,產生結果並將它們傳遞給函數。當編譯器找到一個宏時,它會將參數未評估的傳遞給主體,然後執行主體請求的任何計算,最後用它們的結果替換自己。

舉例來說,假設你有一個函數:

(defun print-3-f (x) (progn (princ x) (princ x) (princ x))) 

和宏:

(defmacro print-3-m (x) `(progn (princ ,x) (princ ,x) (princ ,x))) 

然後,你可以看到立竿見影的區別:

CL-USER> (print-3-f (rand)) 
* 234 
* 234 
* 234 

CL-USER> (print-3-m (rand)) 
* 24 
* 642 
* 85 

要了解爲什麼這是,你需要以一種說話的方式,在你的腦海中運行編譯器。

當Lisp遇到函數時,它會構建一棵樹,其中(rand)首先被計算,並將結果傳遞給函數,該函數將三次打印結果。

在另一方面,當Lisp中遇到的宏時,它把形式(rand)不變到主體,它返回其中x(rand)替換引用列表,得到:

(progn (princ (rand)) (princ (rand)) (princ (rand))) 

和替換這個新表單的宏調用。

Here你會發現大量關於各種語言的宏的文檔,包括Lisp。

+0

感謝您的回覆,我可以總結您的答案,如下所示: 對於'defun':1)評估AST(返回javascript'function'對象)2)執行javascripts函數3)將結果值作爲參數傳遞給lisp函數。這就是我已經在做的事情。 對於'defmacro':1)idem 2)skip 3)將javascript函數作爲參數傳遞給宏。 ''返回的結果應該是應該評估和執行的AST元素。 這留下了一個沒有答案的問題:「報價」應該返回什麼?它應該是AST元素或JavaScript函數和其他objs的列表嗎? – 2010-08-12 11:03:59

+2

(quote ...)返回「stuff」列表,其中「stuff」的格式可能稍後會進行評估。 lisp的美妙之處在於它的列表表示與AST表示相同,因此返回列表或AST是等同的。 – dsm 2010-08-12 11:17:38

+1

宏觀不評價其論點似乎是其本質的副作用,而不是它的主要定義屬性,對我來說。 – Svante 2010-08-12 11:41:30

2

你需要有一個宏觀擴張階段在評價鏈:

text-input -> read -> macroexpand -> compile -> load 

注意的是,宏擴展應該是遞歸(macroexpand直到沒有macroexpandable是左)。

您的環境需要能夠「保留」可在此階段按名稱查找的宏擴展功能。請注意,defmacro是Common Lisp中的一個宏,該宏設置正確的調用以將該名稱與該環境中的宏擴展函數相關聯。

+0

感謝您的回覆。如果我理解正確,macroexpand函數將宏及其參數轉換爲無宏的lisp代碼。正確? – 2010-08-12 12:03:02

+0

你的描述有些不準確,但我認爲你有正確的想法。 :) – Svante 2010-08-12 12:39:45

+1

好吧,我已經做了心理點擊。我正在重構我的實現,如下所示:1 /讀取用戶代碼,轉換爲包含AstSymbol,AstKeyword,AstString,AstInteger,...的列表當然還有其他清單; 2 /將庫宏('defmacro')轉換爲AST; 3 /遍歷用戶AST尋找宏('defmacro'); 4 /實際宏觀展開階段:用宏的AST元素(用相關用戶AST元素替換宏參數)替換調用宏(在此檢測宏調用)的AST元素; 5/AST現在應該是無宏觀的解釋。我發現[this](http://bit.ly/CpcCU)也很有幫助。 – 2010-08-13 00:22:44

1

看一看this的例子。這是一個Arc類編譯器的玩具實現,具有體面的宏觀支持。

+0

鏈接已損壞。你能找到它嗎? – 2011-11-09 20:45:29

+0

@AndréCaron,爲我工作 – 2011-11-09 21:47:42

+0

似乎現在工作,我想我只是在一個不好的時機得到它! – 2011-11-09 21:55:02

3

這是來自Peter Norvig的Paradigms of Artificial Intelligence Programming - 這是任何LISP程序員書架的必備書籍。

他假設你正在實施一種解釋型語言,並提供了在LISP中運行的Scheme Interpreter的示例。

The following two examples here展示他是如何增加宏到主eval功能(interp

下面是函數來處理宏之前解釋的S-表達:

(defun interp (x &optional env) 
    "Interpret (evaluate) the expression x in the environment env." 
    (cond 
    ((symbolp x) (get-var x env)) 
    ((atom x) x) 
    ((case (first x) 
     (QUOTE (second x)) 
     (BEGIN (last1 (mapcar #'(lambda (y) (interp y env)) 
           (rest x)))) 
     (SET! (set-var! (second x) (interp (third x) env) env)) 
     (IF  (if (interp (second x) env) 
        (interp (third x) env) 
        (interp (fourth x) env))) 
     (LAMBDA (let ((parms (second x)) 
        (code (maybe-add 'begin (rest2 x)))) 
       #'(lambda (&rest args) 
        (interp code (extend-env parms args env))))) 
     (t  ;; a procedure application 
       (apply (interp (first x) env) 
         (mapcar #'(lambda (v) (interp v env)) 
           (rest x)))))))) 

這裏,它是後已添加宏評估(子方法已在參考鏈接中以獲得清晰度

(defun interp (x &optional env) 
    "Interpret (evaluate) the expression x in the environment env. 
    This version handles macros." 
    (cond 
    ((symbolp x) (get-var x env)) 
    ((atom x) x) 

    ((scheme-macro (first x))    
    (interp (scheme-macro-expand x) env)) 

    ((case (first x) 
     (QUOTE (second x)) 
     (BEGIN (last1 (mapcar #'(lambda (y) (interp y env)) 
           (rest x)))) 
     (SET! (set-var! (second x) (interp (third x) env) env)) 
     (IF  (if (interp (second x) env) 
        (interp (third x) env) 
        (interp (fourth x) env))) 
     (LAMBDA (let ((parms (second x)) 
        (code (maybe-add 'begin (rest2 x)))) 
       #'(lambda (&rest args) 
        (interp code (extend-env parms args env))))) 
     (t  ;; a procedure application 
       (apply (interp (first x) env) 
         (mapcar #'(lambda (v) (interp v env)) 
           (rest x)))))))) 

有趣的是,Christian Queinnec'sLisp In Small Pieces的開頭章節有着非常類似的功能,他稱之爲eval