inside-out/aux如何支持对_exit的调用

上一篇文章中,新增了两个函数:inside-out以及inside-out/aux——曾经想过将inside-out/aux放到前者的函数中用labels来定义,但担心不好调试,所以剥离了出来成为一个独立的函数——inside-out基本上只是驱动了后者,真正地将嵌套表达式拆解开来的还是inside-out/aux。因此,为了让让这个编译器最终可以处理如下形式的代码

1
(_exit (+ (+ 1 2) 3))

就需要先对inside-out/aux进行一番改造,使其可以处理上述代码。

在此之前,先处理一下inside-out/aux目前的一些问题。在之前的实现中,由于使用了setf对输入参数expr进行了修改,因此在example3中的列表实际上在第二次运行的时候已经不是代码中看到的那样子了。所以,先将inside-out/aux改写为更pure的形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(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也是很简单的,直接模仿对加减乘除的处理即可。将处理第一个操作数部分的代码抄过来,基本上就搞定了。不过这样子不利于以后支持更泛用的函数调用的表达式,因此这里尝试将其改写为稍微通用一点的实现方式。

通用的地方就在于,不是只考虑两个参数或者一个参数的情况。其实在上一篇文章中应该就可以感受到,对加减乘除的两个参数的处理也是相当有规律的,只需要将调用secondthird分别提取输入表达式的第一和第二个参数的代码替换为处理一个来自于循环的变量即可。最终的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(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中运行如下代码

1
(inside-out '(_exit (+ (+ 1 2) 3)))

便可以获取翻转后的“线性”的代码

1
2
3
4
(PROGN
(SETQ #:G717 (+ 1 2))
(SETQ #:G716 (+ #:G717 3))
(_EXIT #:G716))

如此一来,也没有必要在stringify函数中内置调用_exit函数的固定代码了,stringify改为如下的样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
(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函数吧,代码如下

1
2
3
4
5
6
7
(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))))

全文完。