拆解嵌套的表達式

上一篇文章中,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,也能夠編譯出結果來了,可喜可賀。

全文完。

閱讀原文

相關文章
相關標籤/搜索