调用C标准库的exit函数

上一篇文章中,实现了对大于号(>)的处理,那么对if表达式的编译也就是信手拈来的事了,不解释太多。在本篇中,将会讲述一下如何产生可以调用来自于C语言标准库的exit(3)函数的汇编代码。

在Common Lisp中并没有一个叫做EXIT的内置函数,所以如同之前实现的_exit一样,我会新增一种需要识别的(first expr),即符号exit。为了可以调用C语言标准库中的exit函数,需要遵循调用约定。对于exit这种只有一个参数的函数而言,情形比较简单,只需要跟对_exit一样处理即可。刚开始,我写下的代码是这样的

Read More

编译大于运算符

原定的计划中这一篇应当是要讲如何编译if表达式的,但是我发现没什么东西可以作为if的test-form的部分的表达式,所以觉得,要不还是先实现一下比较两个数字这样子的功能吧。说干就干,我决定用大于运算符来作为例子——大于运算符就是指>啦。所以,我的目标是要编译下面这样的代码

1
(> 1 2)

Read More

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的形式

Read More

时序图绘制工具走马观花

为什么我会需要绘制时序图

我司在做一些咋看之下比较复杂的需求的时候,都需要先写设计文档,不过我猜想这种规矩应该在很多公司都有才对,我并没有其它公司没有这种规矩的言外之意。然后呢,我个人比较习惯按照“从外到内”的方式来写设计文档,因此,在文档的开篇我总是会描述一下一个需求的全局视图,一般来说,就是用绘图的方式。对于一些复杂的活动需求里的流程,咋一看觉得会涉及到多个系统间的调用的时候,我就会选择画一幅时序图了。

需要事先说明的是,我没有正儿八经地系统学习过UML方面的内容,所以我画出来的图都只是一些不算很规范的野鸡时序图,当然了,我也不知道这世界上到底有没有规范的时序图画法。

为了画时序图,用过几款工具。它们的共同点,就是都是“语绘”的,也就是通过写代码的方式来描述所想要的图,然后让这些工具帮你把这张图给“画出来”。我个人更喜欢这种方式,而不是拖拖拉拉,不过这纯粹是个人喜好的问题而已。

走马观花

www.websequencediagrams.com是我接触到的第一个“语绘”时序图的工具。打开它之后,就会看到它的实例代码和效果图了,截图如下

用这个网站的工具画出来的时序图会有一种【手绘】的感觉

sdedit是我第二款使用的绘图工具,是一款用Java开发的本地工具,只需要编写好一个.sd文件,然后用下列的命令处理即可

1
sdedit -t png -o a.png a.sd

sdedit绘制时序图的代码的语法跟WebSequenceDiagrams不同,在Emacs中似乎也没有找到别人写好的适合编辑.sd文件的主模式,后来我自己定义了一个简陋的主模式来用,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(setq sdedit-highlights
'(("Actor\\|Node" . font-lock-function-name-face)))

(define-derived-mode sdedit-mode fundamental-mode "sdedit"
"编辑.sd文件的主模式"
(setq font-lock-defaults '(sdedit-highlights)))

;;; 代码是从下面这个网页给的例子改动来的
;;; https://www.emacswiki.org/emacs/CompileCommand
(add-hook 'sdedit-mode-hook
(lambda ()
(unless (file-exists-p "Makefile")
(set (make-local-variable 'compile-command)
(let* ((buffer-name (buffer-name))
(base-name (car (split-string buffer-name "\\."))))
(format "/usr/local/bin/sdedit -t png -o %s.png %s.sd" base-name base-name))))))

(add-to-list 'auto-mode-alist
'("\\.sd$" . sdedit-mode))

勉勉强强可以接受

sequencediagram.org则是最近刚发掘到的一个不错的绘制时序图的在线工具。它的绘制语法跟WebSequenceDiagrams是一样的,并且它还有一个不错的教程。打开它的网站后,点击左侧的这个图标

便可以看到详尽的语法教程。

sequencediagram.org绘制出来的时序图是这三个工具中最符合我的审美的,今后应当会成为我绘制时序图的主力工具。

拆解嵌套的表达式

上一篇文章中,jjcc2函数已经可以处理加减乘除运算表达式中的变量了。也就是说,现在它可以处理如下的代码了

1
2
3
(progn
(setq a (+ 1 2))
(+ a a))

在我的电脑上,在SLIME中依次运行下面的代码

1
2
(defvar *globals* (make-hash-table))
(stringify (jjcc2 '(progn (setq a (+ 1 2)) (+ a a)) *globals*) *globals*)

会得到下列的汇编代码

Read More

如何编译setq?

Common Lisp中的setq类似于其它语言中的赋值语句,它可以给一个符号对象设定一个值,类似于将一个值赋值给一个变量一样。简单起见,在jjcc2中,我会将所有的符号都作为全局的一个label来实现。也就是说,如果代码中出现了

1
(setq a 1)

这样的代码,那么在最后生成的代码中,就会相应的在.data段中有一个同名的label,其中存放着数值1。

既然都是全局变量,那么只需要准备一个容器来盛这些变量名即可。现阶段,暂时认为所有的变量都是数值类型即可。简单起见,这个容器直接用Common Lisp内置的HASH-TABLE来表示。

Read More

如何编译progn

progn是什么玩意

progn是Common Lisp里面的一个special operator,见这份文档的说明:http://clhs.lisp.se/Body/s_progn.htm

为嘛要编译这东西

现在已经支持了二元四则运算了,但现在这里有一个大问题,就是这四个运算没办法嵌套着组合使用。比如,遇到下面这样的代码,jjcc2函数就懵逼了

1
(+ 1 (+ 2 3))

那要编译这种代码的话怎么办呢?一个比较直观的做法,是引入临时变量,来保存嵌套在其中的表达式的求值结果,然后再用变量来代替原本嵌套的表达式。修改后的代码可能长这个样子

1
2
(setq a (+ 2 3))
(+ 1 a)

显然,这是有先后的时间依赖关系的两条语句,因此应当使用progn将它们包裹起来,结果如下

1
2
3
(progn
(setq a (+ 2 3))
(+ 1 a))

这样整个表达式的求值结果,或者说它被编译之后的运行结果,应当就是在寄存器EAX中放入整数6了。所以,本篇将来解决对progn的编译问题。

如何编译progn

Read More

支持减、乘,以及除

上一篇文章中,初步搭建了一个输入Common Lisp代码,输出汇编代码的编译器的骨架,实现了二元整数的加法运算。在这个基础上,要想实现减法、乘法,以及除法就是手到擒来的事情了。只需依葫芦画瓢,补充更多的分支情况即可。

我自己模仿着x64的调用约定,规定四则运算的结果始终放在EAX这个寄存器中。在稍后给出的代码中,对于减法和除法运算,都是把运算符的左操作数放到EAX寄存器中,再从EAX中减去或者除掉右操作数。

在摸索除法的汇编代码怎么生成时,遇到了个费解的问题,最后才知道,原来需要把EAX寄存器的符号扩展到高位的EDX寄存器中去。对于as这个汇编器来说,需要用到CLTD指令。

最后,jjcc2stringify两个函数被修改为如下的样子

Read More