编译原理的大作业是写一个简单绘图语言的编译器。我打算用lisp来写这个东西,于是还是我熟悉一点的Clojure就好了,其中遇到了很多麻烦的事情,有很大一部分是不熟悉Java的API,然而当时在写的时候,旁边是写安卓的Boiler Yao强者。这里就记一下遇到的别的问题好了。

这篇是记录完成lexer的过程。因为实现比较简单,打算连着Clojure建立项目的部分一起在这里总结一下。

Clojure新建项目

跟之前的不一样的是,我们这回想写的不是就在repl里面跑的东西,而是一个在文件里面的东西。

Clojure最基本的运行一个脚本文件的方式就是java通过加载jar包来运行:从此处下载要加载的jar包

然后通过:

1
java -cp clojure-x.x.x.jar clojure.main <your file>

的方式来运行一个文件。

如果使用leiningen来运行Clojure的话,可以通过使用

1
lein new app <porject_name>

来建立一个新的Clojure方程。这之后通过lein run来运行它。

这样新建的工程项目结构跟Java的差不多。

如果习惯用IDE的话,可以用intellij来打开这个项目,尤其是对于厌恶处理Lisp那一堆对称的括号的人。

Intellij里面需要安装一个叫Clojure-Kit的插件。让它能解析CLojure的语法,以及在IDE里面运行Clojure的repl。

然后因为我们最后需要写的是一个编译器,lexer只是其中的一个部分,我们需要把它写到一个新的文件,以实现代码的分离。比如我是写到lexer.clj里然后在main.clj中引用它。

1
(require 'cairo-draw.lexer)

然后使用它:

1
2
3
(defn -main
[& args]
(lexer "FOR v FROM 0 TO 7 STEP 0.1 DRAW (200 * SIN(v), 10 * COS(v));"))

简单的lexer

lexer的目标就是把整个字符分成词组和符号,并且输出他们是关键字还是变量还是符号。

我们目标的句子是类似于这样的:

1
2
3
4
5
6
7
8
FOR v FROM 0 TO 7 STEP 0.1 DRAW (10 * SIN(v), 200 * COS(v));
FOR v FROM 0 TO 7 STEP 0.1 DRAW (200 * SIN(v), 10 * COS(v));
ROTATION IS PI / 6;
FOR v FROM 0 TO 7 STEP 0.1 DRAW (10 * SIN(v), 200 * COS(v));
FOR v FROM 0 TO 7 STEP 0.1 DRAW (200 * SIN(v), 10 * COS(v));
ROTATION IS 2 * PI / 3;
FOR v FROM 0 TO 7 STEP 0.1 DRAW (10 * SIN(v), 200 * COS(v));
FOR v FROM 0 TO 7 STEP 0.1 DRAW (200 * SIN(v), 10 * COS(v));

对于这种方式,使用正则表达式无疑是最简单的办法。Boiler Yao强者给提供的方案是这样的:

1
(def reg_exp #"\w+\b|[)(,;+*/\-]")

其实已经很不错了,只是发现对于小数的情况,这个表达式会出现一点偏差:小数点两侧会被分成两个数,于是改进一下,变成了这样:

1
(def reg_exp #"\d*\.\d+|\w+\b|[)(,;+*/\-\^]")

实际尝试一下这个。

1
(re-seq reg_exp "FOR v FROM 0 TO 7 STEP 0.1 DRAW (200 * SIN(v), 10 * COS(v));")

这样我们就能得到每一个词组或符号了。接着我们用它们去与我们定义好的数组去比较,就可得到每个东西是什么了。

1
2
3
4
5
6
7
8
(defn _lexer
[word]
(cond
(seperator? word) (println "seperator")
(operator? word) (println "operator")
(key_word? word) (println "keyword")

:else (println "variable")))

其中seperator?``operator?``key_word?都是在事先定好的词组中通过some来寻找。大致类似于这样:

1
(defn seperator? [x] (some (fn [y] (if (= x (second y)) y) seperator))

这就完成了一个简单的Clojure的lexer了。

完整代码在这里