Cirru 是怎样编译到 Clojure 的

这篇文章解释一下 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}))

你可能感兴趣的:(cirru,clojure)