Lexer的Clojure实现
编译原理的大作业是写一个简单绘图语言的编译器。我打算用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 | (defn -main |
简单的lexer
lexer的目标就是把整个字符分成词组和符号,并且输出他们是关键字还是变量还是符号。
我们目标的句子是类似于这样的:
1 | FOR v FROM 0 TO 7 STEP 0.1 DRAW (10 * SIN(v), 200 * 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 | (defn _lexer |
其中seperator?``operator?``key_word?
都是在事先定好的词组中通过some来寻找。大致类似于这样:
1 | (defn seperator? [x] (some (fn [y] (if (= x (second y)) y) seperator)) |
这就完成了一个简单的Clojure的lexer了。
完整代码在这里