在上一篇文章中,新增了兩個函數:inside-out
以及inside-out/aux
——曾經想過將inside-out/aux
放到前者的函數中用labels
來定義,但擔憂很差調試,因此剝離了出來成爲一個獨立的函數——inside-out
基本上只是驅動了後者,真正地將嵌套表達式拆解開來的仍是inside-out/aux
。所以,爲了讓讓這個編譯器最終能夠處理以下形式的代碼git
(_exit (+ (+ 1 2) 3))
就須要先對inside-out/aux
進行一番改造,使其能夠處理上述代碼。github
在此以前,先處理一下inside-out/aux
目前的一些問題。在以前的實現中,因爲使用了setf
對輸入參數expr
進行了修改,所以在example3
中的列表實際上在第二次運行的時候已經不是代碼中看到的那樣子了。因此,先將inside-out/aux
改寫爲更pure的形式segmentfault
(defun inside-out/aux (expr result) "將嵌套的表達式EXPR由內而外地翻出來" (check-type expr list) ;; 出於簡單起見,暫時只處理加法運算 (cond ((member (first expr) '(+ - * /)) (let ((operands '())) (if (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) (push var operands))) (push (second expr) operands)) (if (listp (third expr)) (let ((var (gensym))) (setf result (inside-out/aux (third expr) result)) (let ((val (pop result))) (push `(setq ,var ,val) result) (push var operands))) (push (third expr) operands)) (push (cons (first expr) (nreverse operands)) result) result)) (t (push expr result) result)))
其實改動很簡單,就是使用一個新的列表operands
來承載被修改後的符號或本來的表達式而已。接下來能夠開始支持_exit
函數了。ide
其實要支持_exit
也是很簡單的,直接模仿對加減乘除的處理便可。將處理第一個操做數部分的代碼抄過來,基本上就搞定了。不過這樣子不利於之後支持更泛用的函數調用的表達式,所以這裏嘗試將其改寫爲稍微通用一點的實現方式。函數
通用的地方就在於,不是隻考慮兩個參數或者一個參數的狀況。其實在上一篇文章中應該就能夠感覺到,對加減乘除的兩個參數的處理也是至關有規律的,只須要將調用second
和third
分別提取輸入表達式的第一和第二個參數的代碼替換爲處理一個來自於循環的變量便可。最終的代碼以下調試
(defun inside-out/aux (expr result) "將嵌套的表達式EXPR由內而外地翻出來" (check-type expr list) ;; 出於簡單起見,暫時只處理加法運算 (cond ((member (first expr) '(+ - * / _exit)) (let ((operands '())) ;; 對參數列表中的全部表達式都遞歸地進行【外翻】處理 (dolist (arg (rest expr)) (if (listp arg) (let ((var (gensym))) (setf result (inside-out/aux arg result)) (let ((val (pop result))) (push `(setq ,var ,val) result) (push var operands))) (push arg operands))) (push (cons (first expr) (nreverse operands)) result) result)) (t (push expr result) result)))
哈,通用的版本反而是最短的一個XD如今,inside-out
函數能夠處理剛纔的代碼了。在REPL中運行以下代碼rest
(inside-out '(_exit (+ (+ 1 2) 3)))
即可以獲取翻轉後的「線性」的代碼code
(PROGN (SETQ #:G717 (+ 1 2)) (SETQ #:G716 (+ #:G717 3)) (_EXIT #:G716))
如此一來,也沒有必要在stringify
函數中內置調用_exit
函數的固定代碼了,stringify
改成以下的樣子orm
(defun stringify (asm globals) "根據jjcc2產生的S表達式生成彙編代碼字符串" (check-type globals hash-table) ;; 輸出globals中的全部變量 ;; FIXME: 暫時只支持輸出數字 (format t " .data~%") (maphash (lambda (k v) (format t "~A: .long ~D~%" k v)) globals) (format t " .section __TEXT,__text,regular,pure_instructions~%") (format t " .globl _main~%") (format t "_main:~%") (dolist (ins asm) (cond ((= (length ins) 3) (format t " ~A ~A, ~A~%" (first ins) (if (numberp (second ins)) (format nil "$~A" (second ins)) (second ins)) (if (numberp (third ins)) (format nil "$~A" (third ins)) (third ins)))) ((= (length ins) 2) (format t " ~A ~A~%" (first ins) (if (numberp (second ins)) (format nil "$~A" (second ins)) (second ins)))) ((= (length ins) 1) (format t " ~A~%" (first ins))))))
若是但願調用_exit
來驗證四則運算的計算結果的話,就顯式地調用_exit
函數吧,代碼以下遞歸
(defun example4 () "處理含有嵌套表達式的_exit函數調用" (let ((expr '(_exit (+ (- (* (/ 1 2) 3) 4) 5))) (ht (make-hash-table))) (let* ((expr2 (inside-out expr)) (asm (jjcc2 expr2 ht))) (stringify asm ht))))
全文完。