关于 Cirru Vector 语法以及近期回顾

0

Cirru Vector 语法

上周给 Cirru 增加了一个简单的向量语法, 基于 EDN 弄的.
之前 Cirru 已经有 .cirru 格式的缩进写法, 但是不合适,
原因是我的代码普遍用 Cirru Editor 生成的, 结构比较复杂,
而 Cirru 缩进语法通过程序生成的代码, 总是不够可靠,
于是我改用了 JSON 存储, 但 JSON 的问题是查看 git diff 不方便,
于是基于 Clojure EDN 语法定制了一个版本, 用于存储源码:

repo https://github.com/Cirru/vect...

大致的样子是这样的, 注意中间故意加上的一些空格:

[
[ "a" "b" "c d" ]
[ "e" [ "f" [ "g" ] "h" ] ]
[ "i" ]
]

我实际在使用当中生成的代码会成很多, 比如下面这样,
由于 git diff --word-diff 依据空格来分割, 其实会有点用处:

https://github.com/mvc-works/...

[
[ "ns" "boot-workflow.core" [ ":require" [ "[]" "respo.core" ":refer" [ "[]" "render!" "clear-cache!" ] ] [ "[]" "boot-workflow.component.container" ":refer" [ "[]" "comp-container" ] ] [ "[]" "cljs.reader" ":refer" [ "[]" "read-string" ] ] ] ]
[ "defonce" "store-ref" [ "atom" [ "{}" ] ] ]
[ "defonce" "states-ref" [ "atom" [ "{}" ] ] ]
[ "defn" "dispatch!" [ "op" "op-data" ] ]
[ "defn" "render-app!" [  ] [ "let" [ [ "target" [ ".querySelector" "js/document" "|#app" ] ] ] [ "render!" [ "comp-container" "@store-ref" ] "target" "dispatch!" "states-ref" ] ] ]
[ "defn" "-main" [  ] [ "enable-console-print!" ] [ "render-app!" ] [ "add-watch" "store-ref" ":changes" "render-app!" ] [ "add-watch" "states-ref" ":changes" "render-app!" ] [ "println" "|app started!" ] [ "let" [ [ "configEl" [ ".querySelector" "js/document" "|#config" ] ] [ "config" [ "read-string" [ ".-innerHTML" "configEl" ] ] ] ] [ "if" [ "and" [ "some?" "navigator.serviceWorker" ] [ ":build?" "config" ] ] [ "->" "navigator.serviceWorker" [ ".register" "|./sw.js" ] [ ".then" [ "fn" [ "registration" ] [ "println" "|resigtered:" "registration.scope" ] ] ] [ ".catch" [ "fn" [ "error" ] [ "println" "|failed:" "error" ] ] ] ] ] ] ]
[ "set!" "js/window.onload" "-main" ]
[ "defn" "on-jsload" [  ] [ "clear-cache!" ] [ "render-app!" ] [ "println" "|code updated." ] ]
]

解析过程

当然作为一个图形编辑器生成出来文件, 其实没有多大看的必要.
可能中间唯一有意思的就是解析语法的过程, 稍微有点意思:
https://github.com/Cirru/vect...
这段代码大致用了我之前学习 Parser Combinator 的套路,
也就是用高阶函数不可变数据做的一个 parser, 可以解析简单的文本.

比如怎样解析一个字符串, 可以看到我第一步判断引号,
第二步判断内部的内容, 第三步判断结尾的引号, 完成一个字符串,
如果解析成功, 将返回一个 ok: yes 的对象, rest 字段是剩下的文本,
如果解析失败, 将返回一个 ok: no 的对象, rest 字段是原先的文本不变,
data 字段这存放和解析到的内容有关的信息:

parseString = (text) ->
  quote1 = parseDoubleQuote text
  if quote1.ok
    inside = parseStringInside quote1.rest
    if inside.ok
      quote2 = parseDoubleQuote inside.rest
      if quote2.ok
        ok: yes, data: inside.data, rest: quote2.rest
      else ok: no, data: 'no close quote', rest: text
    else ok: no, data: 'failed to parse string inside', rest: text
  else ok: no, data: 'no open quote', rest: text

而字符串内容, 同样进行分解, 可能是普通的字符, 可能是转义字符,
注意 data 字段有时候需要对内容做一些拼接, 以便最后返回:

parseStringInside = (text) ->
  tryChar = parseCharInString text
  if tryChar.ok
    tryInside = parseStringInside tryChar.rest
    if tryInside.ok
      ok: yes, data: "#{tryChar.data}#{tryInside.data}", rest: tryInside.rest
    else ok: yes, data: tryChar.data, rest: tryChar.rest
  else
    tryEscape = parseEscape text
    if tryEscape.ok
      tryInside = parseStringInside tryEscape.rest
      if tryInside.ok
        ok: yes, data: "#{tryEscape.data}#{tryInside.data}", rest: tryInside.rest
      else ok: yes, data: tryEscape.data, rest: tryEscape.rest
    else ok: yes, data: '', rest: text

我之前学的版本里, 高阶函数还能做 or 或者 sequence 这样的抽象,
用了高阶的抽象, 代码还可以更精简, 确定是查找 bug 会困难不少,
大体上我语法解析还不够深入, 这个 case 足够简单, 所以意外地很快搞定了.
这个例子大概对于新手来说比较直观, 作为参考应该可以吧,,
深层的 Parser Combinator 还是需要找更深入的资料去学习.

Cirru 发展回顾

Cirru Editor 在微博贴了很多图, 细节不重复了, 已经用了而三个多月,
而后端存储的语法支持了 .cirru .json .edn 三种格式,
其中 .edn 会触发 boot-reload 的 bug, 我一般用 .ir 后缀,
另外 .cirru 在处理 cond 那样复杂的表达式, 生成的代码有 bug, 不建议用.
将来 Cirru 主要会是以图形编辑器的形式存在.
考虑到这个节点已经稳定, 可以思考一下所处的发展方向.

当然 Cirru Editor 本身如果有更多人用, 其实应该增加一些高级功能,
比如基于树的 token 替换, 比如编辑颜色的控件, 比如自动 watch 文件夹,
文本编辑器发展了很多年, 有相当多实用的技巧, 有时间可以跟进,
不过考虑现在的情况, 我大概要放弃 React 转而 Respo 重写一遍,
其中的副作用比如光标跳动其实不好处理, 还得想想办法.

另一个是 Clouditor 展开的方向, 就是在 Cirru 基础上, 把文件也抽象掉,
那样话其实能得到一个更有意思的开发环境, 减少依赖管理的成本,
不过之前也想了, 干掉文件的话, 跟编程语言本身就不好交互了,
比如我还要编译到 Clojure 的话, 那么改写语法树是少不了的,
而且为了过程更容易排查错误, 我还得加入强大的静态分析,
我还真没有能力把那么牛逼的东西做出来... 好多细节还得想想.

Cirru 的一个目标是抹掉日常开发中的某些语法痛点,
另外一个目标确实有装逼的成分, 不然写代码也太无趣了.
有机会的话我也许应该在 Quamolit 上实现一套 Cirru Editor,
那样就不是 DOM layout 而是自动写算法来布局了...
想想都觉得很装逼. 我觉得张泉灵说得有道理, 泡沫有其存在的价值,
轻微的泡沫可以帮助增长, 随后需要将泡沫夯实,
然后在可靠的基础之上继续泡沫跟夯实, 从而加快进度. 值得试一试.


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

载入中...