在上一篇文章中,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
,也能够编译出结果来了,可喜可贺。
全文完。