Sift 通过为 Scheme、Emacs Lisp 和 Common Lisp 提供幂等格式化程序、基于规则的 linter 和语言服务器 (LSP),为 Lisp 开发提供了一种统一的方法。通过将 Lisp 源代码视为形式树而非文本行序列,该工具确保格式化保持为“固定点”,即重复应用不会改变输出。

Sift Linting 和格式化工具运行的动画演示。
Sift 在 Lisp 源代码上进行 Linting 和格式化能力的演示。github.com

此动画展示了 Sift 作为格式化和 Linting 统一工具链的核心功能。

特定方言逻辑

Lisp 工具链面临的一个主要挑战是,不同的方言使用相同的字符用于不同的目的。例如,方括号在一种方言中可能代表向量,而在另一种方言中可能代表子句。Sift 通过为每个方言使用特定的报告来避免在这些规则之间做出折中。

这种方法确保在特定项目(如 Guile 和 Guix 代码库或 Emacs 软件包开发)上工作的学生和开发人员获得的格式符合他们特定的语言惯例,而不是通用的标准。

“一个 Lisp 程序是一个具有读取语法的资料,而将它视为这样的格式化程序可以同时做到精确且廉价。”

— GitHub - jjba23/sift: Lisp, Scheme and Emacs Lisp formatter, linter and LSP (language server)

无损重写的机制

Sift 的操作前提是 Lisp 程序是一个具有读取语法的资料。通过将文件读取为携带源位置的形式树,该工具可以执行关于注释和特殊形式的“字节精确”重写。

Sift 项目文档中的 Lisp 代码列表截图。
由 Sift 工具链处理的 Lisp 源代码示例。github.com

此代码列表提供了 Sift 在不同方言中处理的 Lisp 语法的具体示例。

由于读取器和打印器是逆操作,Sift 可以在移动空白的同时确保底层结构保持不变。这使得该工具能够提供差异(diff),其块(hunks)是具体的更改,使其适用于人工审查和自动化流水线。

工具化与集成

该项目作为单个可执行文件和 Emacs 软件包发布,在不同编辑器中提供一致的体验。它支持各种输出格式,包括用于结构化数据的 JSON、用于代码扫描的 SARIF 2.1.0 以及用于拉取请求的 GitHub 注释。

语言服务器 (sift lsp) 为所有三种方言提供诊断、快速修复和格式化。它还提供“跳转到定义”、内联提示和文件重命名等功能,并在可用时使用该方言自身的编译器作为“第二意见”。

局限性与安全性

Sift 优先考虑正确性而非激进的自动化。“fix”命令仅应用被声明为等价的更改,并将丢弃任何可能引入解析错误的修复。它还避免重命名标识符或发明新的分隔符。

虽然该工具提供了 92 条具有明确依据的规则,但当前文档中未提供针对大规模仓库的具体性能基准。用户可以使用 “sift explain” 命令查询任何特定规则的推理。