Cirru 是一个使用语法树编辑器来编写代码, 以此绕开语法限制的方案.
目前成熟的编辑器方案有 Stack EditorCumulo Editor,
其中 Cumulo Editor 是我目前开发当中正在持续维护的, 用于开发 ClojureScript.

Cirru Editor 首先会用 Vector 和 String 的递归结构来表示代码.
更进一步, 基于 Clojure 的 Namespace 设计, 对文件的组织方式进行了拆分,
比如下面是 Cirru Editor 存储格式的例子, 每个文件被拆分成 ns defs proc 三部分:

{"app.comp.container" {:ns ["ns"
                            "app.comp.container"
                            [":require"
                             ["[]" "respo.comp.space" ":refer" ["[]" "=<"]]]],
                       :defs {"comp-container" ["defcomp"
                                                "comp-container"
                                                ["store"]],
                              "on-click" ["defn" "on-click" ["e" "dispatch!"] ["dispatch!" ":inc" "nil"]]},
                       :procs []},
 "app.main" {:ns ["ns"
                  "app.main"
                  [":require"
                   ["[]" "app.schema" ":as" "schema"]]],
             :defs {"dispatch!" ["defn"
                                 "dispatch!"
                                 ["op" "op-data"]],
                    "*store" ["defonce" "*store" ["atom" "schema/store"]],
                    "ssr?" ["def" "ssr?" ["some?" ["js/document.querySelector" "|meta.respo-ssr"]]],
                    "main!" ["defn"
                             "main!"
                             []
                             ["println" "|App started."]],
                    "mount-target" ["def" "mount-target" [".querySelector" "js/document" "|.app"]]},
             :procs [["set!" [".-onload" "js/window"] "main!"]]},
 "app.updater.core" {:ns ["ns" "app.updater.core"],
                     :defs {"updater" ["defn"
                                       "updater"
                                       ["store" "op" "op-data"]
                                       ["case" "op" [":inc" ["update" "store" ":data" "inc"]] "store"]]},
                     :procs []},
 "app.schema" {:ns ["ns" "app.schema"],
               :defs {"store" ["def" "store" ["{}" [":states" ["{}"]] [":data" "0"]]]},
               :procs []}}

完整的例子, 比如 Stack Editor 使用的存储格式, 在 namespace 稍微有区别:
https://github.com/mvc-works/...
而 Cumulo Editor 使用的格式存储了更多的信息(因而没有做格式化):
https://github.com/mvc-works/...

总体上使用的格式还是统一的, 依据以下规则:

  • 文件结构按照 namespace 划分, 用 Map 存储
  • 每个文件当中按照 ns defs proc 拆分, 用 Map 存储
    • ns 存储命名空间的定义
    • defs 存储变量的定义, 用 Map 存储
    • proc 存储脚本代码序列, 大多数为空的 Vector
  • 代码对应 S-Expression, 语法强行拆减

这个格式是为了编辑器操作的便利而设计, 并不是最终代码,
因而也就需要编译出代码, 从而可以被其他编译器或解释器解析.
Cirru 格式的编译器需要做的事情有:

  • 对 namespace 进行转换生成文件
  • 单个文件内对 ns defs proc 进行组合拼接
    • 其中 defs 存在依赖关系, 需要进行简单依赖分析和排序
  • 解释 S-Expression 生成目标语言源码

Cirru 目前对应的是动态类型的脚本语言, 比如 Clojure.
由于使用了语法树作为存储格式, 一定程度上会给代码分析带来方便,
但也由于动态语言缺少类型提示, 实际上可供分析的信息有限,
从而难以提供模仿 VS Code 提供的代码提示功能和重构功能.

这个格式的好处是整个项目容易被放进 Cirru 的网页编辑器当中.
然后借助 Cirru Editor 的实现, 提供布局管理和实时同步.


题叶
17.3k 声望2.6k 粉丝

Calcit 语言作者


引用和评论

0 条评论