当我们谈论编程语言的多样性时,往往关注服务器端或系统级语言,而忽略了特定领域的 DSL(领域特定语言)在末端设备上的潜力。Cherri 是一个有趣且实用的案例:它是一门专为 Apple Shortcuts 设计的编程语言,能够将文本形式的代码直接编译为可在 iOS 上运行的 Shortcut 工作流。本文将从编译器架构的角度,分析 Cherri 如何完成从 DSL 语法到 Shortcuts runtime 的转换。

Cherri 的设计目标与语言定位

Cherri(发音为 "cherry")的核心目标并非替代 Shortcuts 应用程序的可视化编辑,而是解决大规模 Shortcut 项目的长期维护问题。Shortcuts 应用虽然提供了图形化操作界面,但随着自动化流程变得复杂,版本控制、代码复用和团队协作都变得困难。Cherri 通过提供类 Go 语言的文本语法,让开发者能够以传统编程方式编写 Shortcut 逻辑,最终编译为标准的 .shortcut 文件。

这门语言的设计哲学强调 “1:1 翻译”—— 尽可能将 Cherri 代码中的每个语句直接映射为 Shortcut 中的对应 action,而非生成高度抽象的中间层。这种策略的优势在于调试时开发者可以直接定位到对应的 action,保留 Shortcuts 原本的可理解性。同时,Cherri 采用半自举架构,其标准库中的大多数 actions 和类型定义本身也是用 Cherri 编写的,这验证了语言的表达能力。

语法层与语义层的解耦策略

从编译器架构来看,Cherri 采用了典型的「前端解析 - 中间表示 - 后端发射」三阶段设计。语法层面,Cherri 借鉴了 Go 和 Ruby 的简洁风格,提供变量声明、条件分支、循环和函数定义等常用构造。例如,一个简单的变量赋值和条件判断可以写作:

name = "World"
if name == "World" {
    showAlert("Hello, World!")
}

这段代码在编译时会被解析为抽象语法树(AST),随后经过语义分析阶段进行类型检查和作用域解析。Cherri 提供了完整的类型系统,包括基本类型(字符串、数字、布尔值)、可选类型、枚举类型以及自定义类型,并支持类型推断。这意味着开发者可以显式声明类型以获得编译期检查,也可以让编译器自动推断以保持代码简洁。

在语义层,Cherri 将高级语法糖 “脱糖”(desugar)为 Shortcuts 内部的基础 actions。例如,上面的 if 语句在 Shortcuts 中对应的是「如果」这个 action,其参数结构(条件判断、满足条件时的分支、不满足时的分支)由编译器根据语法树自动填充。这种脱糖策略在 DSL 设计中非常常见,它使得前端语法可以保持简洁直观,而后端则专注于生成正确的 Shortcuts 内部表示。

编译目标:Shortcuts 文件格式解析

理解 Cherri 的编译流程,必须了解 Shortcuts 文件的内部结构。Shortcuts 本质上是一个压缩的包(bundle),其中包含 plist 格式的主文件,描述了所有 actions 的调用顺序、数据流和参数。Cherri 编译器的核心任务就是将 AST 转换为一个符合 Shortcuts schema 的 plist 结构。

具体而言,每个 Shortcut action 在 plist 中由一个字典表示,包含 WFWorkflowActionIdentifier(action 的唯一标识符,如 is.workflow.actions.gettext)和 WFWorkflowActionParameters(参数字典)两个关键字段。Cherri 的编译器维护了一个 action 映射表,记录每种 Cherri 语法对应的 Shortcut action 标识符和参数结构。当编译流程走到特定节点时,编译器从映射表中查找对应的标识符,并以结构化方式填充参数。

这种映射并非简单的一对一。Cherri 需要处理 Shortcuts 中的特殊概念,比如魔法变量(magic variables)—— 这是 Shortcuts 中用于在 actions 之间传递数据的隐式变量。Cherri 选择不使用魔法变量语法,而是采用显式的常量命名方式,这使得生成的 Shortcut 代码更易于理解和版本控制。编译器在生成时需要将这些显式变量转换为 Shortcuts 能够识别的变量引用机制。

运行时桥接与签名机制

编译输出的 .shortcut 文件并非直接可用,还需要通过签名验证才能在 iOS 设备上运行。Cherri 默认使用 macOS 本地的代码签名工具,如果不可用则会回退到 HubSign 或其他基于 scaxyz/shortcut-signing-server 的远程签名服务。签名过程是 Shortcuts 安全模型的一部分,未经签名的自定义 Shortcut 无法直接在他人设备上运行。

在运行时桥接方面,Cherri 生成的 Shortcut 依赖 Shortcuts 应用提供的 actions 库。这些 actions 覆盖了 iOS 系统功能的很大一部分,包括文件操作、网络请求、媒体处理、自动化脚本等。Cherri 并不创造新的 runtime 能力,而是充当一种 “转译层”,将文本形式的逻辑描述转换为 Shortcuts 可执行的 action 序列。这意味着 Cherri 能做到的事情完全受限于 Shortcuts 本身的能力边界。

值得注意的是,Cherri 还提供了「Raw Actions」功能,允许开发者直接输入 action 的标识符和参数,跳过语法层的抽象。这在需要使用 Shortcuts 新增的 actions 但语言尚未内置支持时尤为有用。这种灵活性体现了 Cherri 作为 “贴近 Shortcuts 本身” 的 DSL 定位 —— 它不是要取代 Shortcuts 的能力,而是提供更高效的编写方式。

实践建议:何时采用 Cherri

基于上述分析,我们可以总结出采用 Cherri 的典型场景。首先,当 Shortcut 项目规模较大、涉及多个文件的模块化组织时,Cherri 的包管理器(基于远程 Git 仓库)和文件包含机制能够提供显著的代码管理优势。其次,如果团队成员更熟悉文本编程而非可视化操作,Cherri 可以作为标准化的开发方式接入 CI/CD 流程。第三,对于需要频繁迭代的自动化流程,基于文本的 Cherri 代码更容易进行版本控制和代码审查。

然而,也需要注意到局限性:Cherri 生成的 Shortcut 最终仍受限于 Shortcuts 本身的功能边界,无法突破 iOS 自动化能力的限制。另外,由于 Shortcuts 的可视化特性,部分复杂的数据流调试在 Shortcuts 应用中可能比在纯文本环境更直观。

总体而言,Cherri 代表了一种实用的端侧 DSL 设计思路 —— 不追求通用性,而是专注于解决特定平台上的工程化难题。对于需要构建复杂 Shortcut 自动化项目的开发者而言,理解其编译原理有助于更好地运用这门工具。

资料来源:Cherri 官方 GitHub 仓库(https://github.com/electrikmilk/Cherri)