共计 2824 个字符,预计需要花费 8 分钟才能阅读完成。
在上一篇文章中,新增了两个函数:inside-out
以及 inside-out/aux
——曾经想过将inside-out/aux
放到前者的函数中用 labels
来定义,但担心不好调试,所以剥离了出来成为一个独立的函数——inside-out
基本上只是驱动了后者,真正地将嵌套表达式拆解开来的还是inside-out/aux
。因此,为了让让这个编译器最终可以处理如下形式的代码
(_exit (+ (+ 1 2) 3))
就需要先对 inside-out/aux
进行一番改造,使其可以处理上述代码。
在此之前,先处理一下 inside-out/aux
目前的一些问题。在之前的实现中,由于使用了 setf
对输入参数 expr
进行了修改,因此在 example3
中的列表实际上在第二次运行的时候已经不是代码中看到的那样子了。所以,先将 inside-out/aux
改写为更 pure 的形式
(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
函数了。
其实要支持 _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 中运行如下代码
(inside-out '(_exit (+ (+ 1 2) 3)))
便可以获取翻转后的“线性”的代码
(PROGN
(SETQ #:G717 (+ 1 2))
(SETQ #:G716 (+ #:G717 3))
(_EXIT #:G716))
如此一来,也没有必要在 stringify
函数中内置调用 _exit
函数的固定代码了,stringify
改为如下的样子
(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))))
全文完。
阅读原文