在上一篇文章中,jjcc2
函數已經能夠處理加減乘除運算表達式中的變量了。也就是說,如今它能夠處理以下的代碼了git
(progn (setq a (+ 1 2)) (+ a a))
在個人電腦上,在SLIME中依次運行下面的代碼github
(defvar *globals* (make-hash-table)) (stringify (jjcc2 '(progn (setq a (+ 1 2)) (+ a a)) *globals*) *globals*)
會獲得下列的彙編代碼segmentfault
.data A: .long 0 .section __TEXT,__text,regular,pure_instructions .globl _main _main: MOVL $1, %EAX MOVL $2, %EBX ADDL %EBX, %EAX MOVL %EAX, A(%RIP) MOVL A(%RIP), %EAX MOVL A(%RIP), %EBX ADDL %EBX, %EAX movl %eax, %edi movl $0x2000001, %eax syscall
如今所須要的,就是要實現一個功能(通常是一個函數),能夠將ide
(+ (+ 1 2) (+ 1 2))
自動轉換爲上面所給出的progn
的形式了。我這裏給的例子很差,上面這段代碼就算可以自動轉換,也不會是最上面那段progn
的形式的,起碼會有兩個變量哈哈。好了,那麼怎麼把上面的含有嵌套表達式的代碼給轉換成progn
的形式呢?函數
跑個題,能夠作個CPS變換呀。好比,你能夠先把(+ (+ 1 2) (+ 1 2))
寫成這種形式code
(+& 1 2 (lambda (a) (+& 1 2 (lambda (b) (+ a b)))))
上面的+&
表示它是一個帶continuation
版本的加法運算,它會把兩個操做相加以後調用它的continuation。這個寫法若是沒有記錯的話,我是從PG的《On Lisp》裏面學來的(逃遞歸
你看,這多簡單呀。作完CPS變換以後,只要把每個有continuation的函數調用都重寫成setq
,符號就用回調裏的參數名,值就是帶回調的表達式自己;沒有回調的就繼續沒有。最後把這些setq
放到一個progn
裏去就能夠了get
(progn (setq a (+ 1 2)) (setq b (+ 1 2)) (+ a b))
好久之前還真的寫過一個對錶達式作CPS變換的玩意,有興趣的請移步這篇文章。string
言歸正傳。由於jjcc2
只須要處理兩個參數的加減乘除運算,因此不須要作通用的CPS變換那麼複雜。我是這麼想的:既然只有兩個參數,那麼我就真的在代碼裏先處理第一個再處理第二個。對兩個參數,我都把它們放到一個setq
的求值部分,而後把原來的表達式中的對應位置用一個新的變量名來代替便可,新變量名也好辦,只要用gensym
來生成就能夠了。hash
其實這樣是不夠的,由於做爲加減乘除運算的操做數的表達式自己,也可能還有嵌套的子表達式。這裏必然有一個遞歸的過程。新的辦法是,我用一個棧來存放全部再也不須要被拆解的setq
表達式,而後把這個棧在每次遞歸調用的時候傳進去。這樣一來,當全部的遞歸都結束的時候,就獲得了一個充滿了setq
表達式的棧,以及一個全部的嵌套表達式都被替換爲變量名的「頂層」表達式。
好了,說完了思路,上代碼吧
(defun inside-out/aux (expr result) "將嵌套的表達式EXPR由內而外地翻出來" (check-type expr list) ;; 出於簡單起見,暫時只處理加法運算 (cond ((eq (first expr) '+) (when (listp (second expr)) ;; 第一個操做數也是須要翻出來的 ;; 翻出來後,result中的第一個元素就是一個沒有嵌套表達式的葉子表達式了,能夠做爲setq的第二個操做數 (let ((var (gensym))) (setf result (inside-out/aux (second expr) result)) (let ((val (pop result))) (push `(setq ,var ,val) result) (setf (second expr) var)))) (when (listp (third expr)) (let ((var (gensym))) (setf result (inside-out/aux (third expr) result)) (let ((val (pop result))) (push `(setq ,var ,val) result) (setf (third expr) var)))) (push expr result) result) (t (push expr result) result))) (defun inside-out (expr) (cons 'progn (nreverse (inside-out/aux expr '()))))
由於用的是棧(其實就是個list),因此最後須要用nreverse
反轉一下,才能拼上progn
。如今,若是餵給inside-out
一個嵌套的表達式
(inside-out '(+ (+ 1 2) (+ 3 4)))
就會獲得一個由內而外地翻出來的版本
(PROGN (SETQ #:G688 (+ 1 2)) (SETQ #:G689 (+ 3 4)) (+ #:G688 #:G689))
鏘鏘鏘,Common Lisp中的unintern symbol再次登場。好了,如今即使是嵌套的加減乘除運算的表達式,只要先通過inside-out
處理一下,再餵給jjcc2
,也能夠編譯出結果來了,可喜可賀。
全文完。