这篇文章解释一下 Cirru 到 Clojure 代码的编译步骤.
目前编辑使用的是 Cumulo Editor, 参考这里的 Demo.
Cirru 是整个大的项目的名字, Cirru 本身的目标是 AST 编程,
而 Cumulo Editor 是目前项目下最新的语法树编辑器. 也就是 AST 里的 Syntax Tree.
所以在新闻中你看到的 Cirru 的存在形态, 目前是这样的:
这个函数表示的是下面这段 Clojure 代码:
(defcomp
comp-search
(buffer query mock-ssr?)
(div
{:style (merge ui/column-center style-searcher)}
(div
{}
(if mock-ssr?
(div {:style style-mock} (<> span "Loading..." nil))
(input
{:style (merge ui/input style-input),
:value buffer,
:placeholder "Press Enter to search...",
:on {:input on-search, :keydown (on-keydown buffer)}})))
(=< nil 16)
(if (>= (count query) 2)
(let [results (->> information
(filter
(fn [item]
(string/includes?
(string/lower-case (:title item))
(string/lower-case query)))))]
(if (empty? results)
(div {:style style-empty} (<> span (str "找不到" (pr-str query) ".") nil))
(div
{:style style-results}
(->> results (map-indexed (fn [idx item] [idx (comp-item item)])))))))
(=< nil 16)
(if (>= (count query) 2) (comp-search-engine query))))
编辑器数据表示
这中间有比较复杂的编译过程. 我首先把上面的内容, 简化.
比如图片当中的 DOM, 在浏览器当中实际上是 HTML, 大致可以表示成:
这个结构用 JSON 表示的话, 可以是:
["defcomp", "comp-search", [],
["div"]]
这在 Clojure 里, 类似的 JSON 的数据格式叫做 EDN, 和 JSON 相似, 看上去这样:
["defcomp" "comp-search" []
["div"]]
不过实际当中考虑到原信息和优化方案, 这个结构其实很复杂
比如说这 "comp-search"
实际存储当中的会带上很多信息:
{:type :leaf, :author "root", :time 1503305265979, :text "comp-search", :id "B1_VAM_Ob"}
完整的结构非常繁琐, 需要借助工具才能查看, 如果非要查看的话:
https://github.com/tiye/tiye....
http://pretty-print.net/
归结一下来说, 大致上 Cirru 在程序中使用和最后存储的格式对应是这样:
["defcomp" "comp-search" []
["div"]]
这是 S-Expression 的一套简写. 只要用 Vector 和 String 就能直白地表示.
虽然 Clojure 语法不仅仅是 S-Expression, 但是说大部分可以用 S-Expression 表示没问题.
当然, 这样肯定不能表示 Macros, 也不能表示各种 Reader 生成的语法.
实际开发当中可以和 Clojure 代码混用, 好在很多代码其实这套方案是足够的.
语法转换
还要考虑的一个问题是 Cirru 当中为了统一前缀表达式约定了一些写法,
在 Clojure 当中存在惯用的写法, 这就需要预先进行转换了.
比如 Cirru 当中 ["{}" [":a" "1"]]
对应 {:a 1}
, ["[]" "1"]
对应 [1]
,
甚至字符串标记 "|str1"
对应 "str1"
, 都需要做转换.
为了完成这个功能, 我写了一个模块专门在 Symbol 层面做了数据转换:
https://github.com/Cirru/sepa...
在这个模块当中, 甚至的 case
let
defn
等函数或特殊格式做了调整,
某种程度上和在 Clojure 当中习惯是不一致的, 但是在 Cirru Editor 当中很更清晰.
也是得益于 Lisp 整个语法设计的精巧, 才能很容易达成.
代码生成
生成代码反而是简单的, 由于现成的类库比较强大:
https://github.com/brandonblo...
fipp 可以接受 Symbol 类型的 S-Expression, 返回格式化完成的代码.
而且这个代码的布局和缩进都比较符合人们阅读的习惯.
这是 fipp 给出的一个将 Quote List 编译到 Clojure 代码字符串的 API:
(fipp.clojure/pprint '(let [foo "abc 123"
bar {:x 1 :y 2 :z 3}]
(do-stuff foo (assoc bar :w 4)))
{:width 40})
结尾
所以总结下来, 从最初的 Cirru Editor 采用的存储格式:
["defcomp" "comp-search" []
["div" ["{}" [":style" "style-searcher"]]]]
首先会在 Symbol 层面进行一些转化, 得到一个 Quoted List:
'(defcomp comp-search ()
(div {:style style-searcher}))
最后由 fipp 生成带缩进的代码字符串:
(defcomp comp-search ()
(div {:style style-searcher}))