Roc 编译器快照测试深度解析:match 表达式与布尔标签模式(boolean_patterns)
2026/9/18 20:34:56 网站建设 项目流程

Roc 编译器快照测试深度解析:match 表达式与布尔标签模式(boolean_patterns)

【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc

导读

本文以 Roc 编译器测试套件中的test/snapshots/match_expr/boolean_patterns.md快照文件为骨架,逐段拆解一个match表达式从词法分析(TOKENS)、语法解析(PARSE)、格式化(FORMATTED)、规范化(CANONICALIZE)到类型检查(TYPES)的完整编译管线输出,并结合 src/parse/Parser.zig、src/parse/AST.zig 等源码以及 docs/langref/tag-unions.md 语言参考,说明 Roc 中True/False这类“布尔风格标签”的本质、快照测试文件的格式规范与阅读方法。读完本文,你将能够独立读懂任意一个 Roc 编译器快照测试文件,并理解match+ 标签模式在 Roc 编译器中各阶段的中间表示形态。

一、快照文件是什么:Roc 编译器的“输入-输出”实证档案

test/snapshots/match_expr/目录下存放着数十个针对match表达式的快照(snapshot)测试,例如basic_tag_union.mdguards_1.mdwildcard_patterns.mdtag_with_payload.mdpattern_alternatives_basic.md等。每一个.md文件本质上是一个可执行的编译测试用例:它给定一小段 Roc 源码(SOURCE),然后记录编译器在各阶段产生的权威输出(TOKENS / PARSE / FORMATTED / CANONICALIZE / TYPES),以及期望的诊断信息(EXPECTED / PROBLEMS)。

这种测试形态的价值在于:

  • 回归保护:任何对词法器、解析器、规范化器或类型检查器的改动,如果导致输出与快照不一致,测试即失败,从而精确定位行为变更;
  • 文档化编译过程:快照本身就是编译器内部工作方式的“活文档”,比阅读源码更容易直观理解每一层变换;
  • 可机器校验:快照由编译器的测试基建统一执行比对(相关任务可见 src/build/minici.zig 中的run-check-snapshots等 job 定义)。

因此,读懂快照文件 = 读懂 Roc 编译器对某类语法结构的完整处理流程。本文以boolean_patterns.md为例进行逐段解剖。

二、用例总览:源码与预期

boolean_patterns.md的 META 部分给出了本用例的元信息:

description=Match expression with boolean-like tag patterns type=expr
  • description:说明本用例覆盖的是“带布尔风格标签模式的 match 表达式”;
  • type=expr:表示 SOURCE 是一段裸表达式(expression snippet),而非完整的.roc文件或函数定义。这一点与guards_1.mdtype=snippet,带类型注解与函数声明)形成对照。

被测试的源码非常简短:

match isReady { True => "ready to go!" False => "not ready yet" }

而用例的 EXPECTED 与 PROBLEMS 均为NIL,含义是:这段代码经过解析、规范化和类型检查后,预期不产生任何错误——这是一个正向用例(positive test case),验证合法代码能够顺利通过全部编译阶段。

三、TOKENS:词法分析阶段

KwMatch,LowerIdent,OpenCurly, UpperIdent,OpFatArrow,StringStart,StringPart,StringEnd, UpperIdent,OpFatArrow,StringStart,StringPart,StringEnd, CloseCurly, EndOfFile,

词法器(tokenizer,源码位于 src/parse/tokenize.zig)将源码切分为如下 token 序列:

Token对应源码说明
KwMatchmatch关键字,标记 match 表达式开始
LowerIdentisReady小写开头的标识符,即被匹配的 scrutinee(被匹配对象)
OpenCurly{分支块开始
UpperIdentTrue大写开头的标识符 ——标签(tag)
OpFatArrow=>分支箭头,连接模式与分支体
StringStart/StringPart/StringEnd"ready to go!"字符串字面量的三段式 token
UpperIdentFalse第二个标签
OpFatArrow=>第二个分支箭头
StringStart/StringPart/StringEnd"not ready yet"第二个字符串字面量
CloseCurly}分支块结束
EndOfFile文件结束标记

值得注意的词法要点:TrueFalse在 Roc 中并不是关键字,而是以大写字母开头的普通标签(UpperIdent)。这与许多语言中布尔值是内建字面量的设计截然不同。在 Roc 中,布尔值本质上是 tag union[True, False]的两个标签,因此它们可以像任何其他标签一样出现在 match 分支的模式位置。这也解释了为什么本用例的 META 描述刻意使用 "boolean-like tag patterns"(类布尔标签模式)——它们并非语言内建的布尔字面量,而是“长得像布尔”的标签。

四、PARSE:语法分析阶段——语法树的形状

解析器(src/parse/Parser.zig)将 token 流组装为 AST(抽象语法树),快照以 S 表达式(Clojure 风格)形式呈现:

(e-match (e-ident (raw "isReady")) (branches (branch (p-tag (raw "True")) (e-string (e-string-part (raw "ready to go!")))) (branch (p-tag (raw "False")) (e-string (e-string-part (raw "not ready yet"))))))

逐层解读:

  • 顶层节点(e-match <scrutinee> (branches ...))表示一个 match 表达式;
  • 第一个子节点(e-ident (raw "isReady"))是被匹配的表达式——一个标识符引用;
  • branches下列出两个branch节点,每个 branch 由模式(pattern)分支体表达式组成;
  • (p-tag (raw "True"))/(p-tag (raw "False")):分支模式是无载荷(不带 payload)的标签模式;
  • 分支体是(e-string ...)字符串表达式,其内部e-string-part记录原始字符串内容。

这里揭示了 Roc 语法的一个核心设计:match 分支的左侧是“模式”而非表达式True/False在这个位置被解析为p-tag(标签模式)。对照同目录的 wildcard_patterns.md 可以看到,如果分支左侧是小写标识符(如other),会被解析为p-ident(变量捕获模式);而 guards_1.md 展示了带守卫(guard)的分支如何额外携带(guard ...)子节点。模式类别由标识符的大小写首字母驱动,这是 Roc 词法/语法层最直观的约定。

从源码看,解析器为 match 表达式维护了一套精细的状态机:expr_matchexpr_match_guardexpr_match_bodyexpr_match_pattern等表达式种类定义于 src/parse/Parser.zig,并配有ExprMatchBranchStateExprMatchBranchAfterPatternState等状态结构(见 src/parse/Parser.zig),依次处理“匹配对象 → 分支模式 → 守卫 → 箭头 → 分支体”的推进过程;对于分支箭头缺失或错误(如wrong_arrow.md用例),解析器还会产生match_branch_wrong_arrowmatch_branch_missing_arrow等诊断(见 src/parse/Parser.zig)。

五、FORMATTED:格式化器输出

NO CHANGE

FORMATTED 段展示的是官方格式化器(formatter)对源码重排后的结果。NO CHANGE表示该源码已符合官方格式规范,格式化前后完全一致。这意味着快照中的源码本身就是规范书写样板:match关键字后跟空格、左花括号独占一行、每个分支缩进一个 Tab、分支体与模式之间用=>连接。

对比同目录其他用例可以印证格式化器的行为:wildcard_patterns.md 的 FORMATTED 段给出了实际的重排输出(缩进统一为 Tab),而 basic_tag_union.md 同样是NO CHANGE。读者可借助此段检验自己书写的 match 代码是否符合 Roc 官方风格。

六、CANONICALIZE:规范化阶段——从 AST 到规范 IR

规范化(canonicalization)是 Roc 编译器中连接语法与类型检查的中间变换:它将带语法糖的 AST 降级为语义上更明确的规范 IR(Can IR)。本用例的规范化输出为:

(e-match (match (cond (e-runtime-error (tag "ident_not_in_scope"))) (branches (branch (patterns (pattern (degenerate false) (p-applied-tag))) (value (e-string (e-literal (string "ready to go!"))))) (branch (patterns (pattern (degenerate false) (p-applied-tag))) (value (e-string (e-literal (string "not ready yet"))))))))

对比 PARSE 阶段的 AST,规范化发生了三处关键变换:

  1. cond中的错误节点(e-runtime-error (tag "ident_not_in_scope"))。由于本用例是type=expr的裸表达式,被匹配的isReady标识符在快照上下文中未定义(不在作用域内),规范化器将其替换为一个运行时错误节点ident_not_in_scope。这说明快照机制对未定义变量是“宽容”的:它允许编译继续进行到后续阶段,同时在规范化 IR 中明确标记错误位置。对比 guards_1.md 中完整的函数定义用例,其cond位置则是正常的(e-lookup-local (p-assign (ident "value")))局部变量查找节点——两者差异恰好展示了“裸表达式快照”与“完整代码片段快照”在规范化层的不同表现。

  2. 模式被包装为pattern节点:每个分支的模式从(p-tag (raw "True"))变为(pattern (degenerate false) (p-applied-tag))。其中:

    • p-applied-tag表示这是一个“被应用的标签”模式(空载荷的标签应用形式);
    • degenerate false标记该模式不是退化模式(degenerate pattern)。所谓退化模式,通常指无法实际匹配到任何值、或匹配行为退化的模式(如对不存在的标签的匹配)。这里显式标注false,表明这两个标签模式是正常的、有意义的模式。
  3. 字符串字面量归一化:分支体中的字符串从(e-string-part (raw "ready to go!"))变为(e-literal (string "ready to go!"))——AST 中的“字符串片段”被合成为单一的规范字面量e-literal,为后续代码生成准备好统一的常量表示。

规范化阶段的实现位于 src/canonicalize/ 目录(如 src/canonicalize/Can.zig),它对 match 的每个分支独立处理patternsvalue,并统一处理守卫、作用域与错误传播。从源码结构看,canonicalize 模块按ExprStmtPatternType等类别组织变换逻辑,是本用例中p-tag → p-applied-tage-string-part → e-literal等归一化规则的实现载体。

七、TYPES:类型检查阶段

(expr (type "Str"))

类型检查器推断出整个 match 表达式的类型为Str。这是一个合理且值得玩味的结果:

  • 两个分支体都是字符串字面量("ready to go!""not ready yet"),类型均为Str
  • match 表达式要求所有分支的返回值类型一致,因此整个表达式的类型就是Str
  • 被匹配对象isReady的类型则由两个标签模式反推为 tag union[True, False](尽管在快照的隔离上下文中该标识符未定义、被替换为错误节点,类型推断仍能依据模式集合收敛出一致的结论)。

对比同目录用例可以更深刻地理解类型推断的联动:basic_tag_union.md 中三个分支分别返回12"3",导致类型冲突,EXPECTED 段给出TYPE MISMATCH诊断,并在 PROBLEMS 中详细说明“字符串字面量被用于需要非字符串类型的位置,已推断类型为Dec”——正反两个用例共同展示了 Roc 对 match 分支“类型必须统一”的强约束。

7.1 与守卫(guard)用例的对照

guards_1.md 展示了更复杂的场景——数值比较守卫与字符串插值:

describe : I64 -> Str describe = |value| match value { x if x > 0 => "positive: ${x.to_str()}" x if x < 0 => "negative: ${x.to_str()}" _ => "other" }

其 TYPES 输出为I64 -> Str,且规范化 IR 中将守卫编译为(e-dispatch-call (method "is_gt") ...)/(method "is_lt")调度调用、将插值编译为#interp_0等临时变量与e-interpolation节点。这体现了 match 语法在守卫与捕获变量场景下的完整语义,与本文的“纯标签模式”用例形成互补——前者是分支的最简形态,后者是分支的增强形态。

八、延伸:标签模式与 Roc 的 tag union 体系

理解了快照后,再把它放回语言层面:Roc 的match与标签(tag)体系密不可分。根据语言参考 docs/langref/tag-unions.md:

  • 标签(tag)是 tag union 中某个备选项的名字,可以带载荷:x = Fooy = Foo(4)z = Foo(4, 2)分别是无载荷、单载荷、多载荷的标签构造。运行期Foo(4, 2)Foo((4, 2))经过优化后编译产物完全相同。
  • tag union 是结构化的(structural)且可扩展的(extensible):类型无需预先命名声明,两个结构相同的类型即视为等价;条件分支可以引入新标签从而“扩展”类型。例如add_blue : [Red, Green, ..others], Bool -> [Red, Green, Blue, ..others]借助类型参数..others表示“还可能包含其他标签”。
  • 匹配带扩展类型的 tag union 时,可以使用通配(catch-all)模式to_str : [Red, Green, .._others] -> Str中最后一个_ =>分支接受任意其他标签,这正是 wildcard_patterns.md 用例对应的语言特性(p-ident变量捕获与_通配在 exhaustiveness 检查中充当兜底)。
  • 带载荷的标签匹配:tag_with_payload.md 展示了Circle(radius) => ...Rectangle(width, height) => ...这种在模式中解构载荷的写法,其规范化后仍为p-applied-tag(应用标签模式),载荷变量则成为分支体内的局部绑定。

回到本文用例:True => .../False => ...正是无载荷标签模式的最简形态。在 Roc 中布尔值就是[True, False]这个 tag union,因此match isReady { True => ...; False => ... }本质上是对布尔型 tag union 做穷尽匹配(exhaustive match)——两个标签全部覆盖,无需通配分支,类型检查器即可确认匹配是完备的。这种“布尔即标签”的设计让布尔值可以无缝融入更广泛的标签模式体系(如与Ok/Err风格的结果类型共用同一套 match 语义)。

九、如何亲自运行与验证快照

仓库是只读的,但你可以在本地克隆后按以下方式复现与扩展验证(对应构建脚本见 build.zig):

  1. 运行全部快照检查:使用roc项目的测试基建执行快照比对任务(run-check-snapshots,定义于 src/build/minici.zig),任一阶段的输出与快照不一致即报错;
  2. 单点修改实验:复制boolean_patterns.md到自己的实验目录(或本地新增快照文件),改动 SOURCE 后重新运行快照工具,观察 TOKENS/PARSE/CANONICALIZE/TYPES 各段如何联动变化——这是理解编译器各阶段职责的最快路径;
  3. 交叉对照:将boolean_patterns.md与 basic_tag_union.md(类型不匹配负例)、wildcard_patterns.md(变量捕获与通配)、guards_1.md(守卫与插值)、tag_with_payload.md(载荷解构)放在一起通读,即可系统掌握match分支模式的完整谱系。

十、总结:一张快照,一条完整编译管线

test/snapshots/match_expr/boolean_patterns.md虽然只有寥寥几十行,却完整记录了 Roc 编译器对一个match表达式的五阶段处理:

阶段输出段本用例的要点
词法分析TOKENSTrue/False是 UpperIdent 标签而非关键字;字符串以三段式 token 呈现
语法分析PARSEe-match+branches结构;分支模式为p-tag
格式化FORMATTEDNO CHANGE,源码即规范格式
规范化CANONICALIZE未定义标识符转为ident_not_in_scope错误节点;模式包装为pattern (degenerate false) (p-applied-tag);字符串归一为e-literal
类型检查TYPES表达式整体类型为Str,两个字符串分支类型一致

这个用例同时是理解 Roc“布尔即标签”设计的绝佳切片:match与 tag union 模式是 Roc 语言的核心表达机制,而快照测试则为这套机制提供了可机械校验、逐层可见的权威档案。掌握快照文件的阅读方法后,整个test/snapshots/match_expr/目录(以及 docs/langref/pattern-matching.md 语言参考)都将成为你学习 Roc 语法与编译器内部原理的活教材。

【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询