FEATURED · 精选文章

Roc 语言 `%` 与 `//` 运算符优先级解析:从 issue 9712 回归测试看左结合规则与 REPL 快照验证

发布时间 / 2026/9/21 1:43:39
来源 / 创域科博编辑部
栏目 / 资讯中心
Roc 语言 `%` 与 `//` 运算符优先级解析:从 issue 9712 回归测试看左结合规则与 REPL 快照验证 Roc 语言%与//运算符优先级解析从 issue 9712 回归测试看左结合规则与 REPL 快照验证【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc1 % 10 // 100到底该先算哪个在 Roc 语言中这个看似简单的表达式背后隐藏着一个运算符优先级回归测试——repro_issue_9712_mod_floordiv_precedence.md。本文以该快照测试文件为主线深入 Roc 编译器的词法分析与绑定优先级binding power实现讲解%取模与//整除为何必须从左到右解析为(1 % 10) // 100以及 REPL 快照测试如何机械地验证这一行为。读完你将掌握 Roc 运算符优先级的底层机制、快照测试文件的格式规范与更新流程。背景issue 9712 到底修复了什么Roc 是一门fast, friendly, functional的语言其编译器即当前仓库在 src/parse/Parser.zig 中统一维护所有二元运算符的优先级。issue 9712 描述的缺陷是当取模运算符%与整除运算符//出现在同一表达式中时如果解析器将//的优先级错误地置于%之上表达式1 % 10 // 100会被解析为1 % (10 // 100)。按数学计算10 // 100整除结果为 0于是整个表达式退化为1 % 0——对零取模直接导致运行时崩溃。正确的语义要求这两个运算符属于同一优先级组并保持左结合left-associative使1 % 10 // 100解析为(1 % 10) // 100先得1 % 10 1再1 // 100 0安全得到结果 0全程不崩溃。逐段解析快照文件META / SOURCE / OUTPUT / PROBLEMS该回归测试位于 test/snapshots/repro_issue_9712_mod_floordiv_precedence.md全文仅四个段落却完整编码了一次 REPL 求值的全部期望# META ~~~ini descriptionrepro for https://github.com/roc-lang/roc/issues/9712—1 % 10 // 100 must parse left-to-right as (1 % 10) // 100 0, not crash typerepl ~~~ # SOURCE ~~~roc » 1 % 10 // 100 ~~~ # OUTPUT 0.0 # PROBLEMS NILMETA以ini代码块声明快照元数据。description用一句话记录该测试的动机——复现 issue 9712强调1 % 10 // 100必须按从左到右解析为(1 % 10) // 100 0而非崩溃typerepl声明这是 REPL 类型的快照详见 test/snapshots/README.md 中关于快照类型的说明与file、expr、snippet等类型区分开来。SOURCE»是 Roc REPL 的输入提示符其后是要在 REPL 会话中逐行求值的表达式。快照工具正是以»为分隔符切分输入见下文源码分析。OUTPUT记录 REPL 求值输出的期望值0.0。注意结果以浮点形式呈现——这是 REPL 表达式求值器对数值表达式的输出格式由解释器内省输出路径决定见 src/snapshot_tool/main.zig 的lirInterpreterInspectedStr调用。数值上(1 % 10) // 100 1 // 100 0与0.0等价。PROBLEMSNIL表示该表达式在编译全流程词法、语法、canonicalization、类型检查中没有产生任何诊断报告。若优先级解析出错导致崩溃或类型错误此段将不再是NIL从而让回归测试立即失败。词法层面//与%如何被切分为 Token优先级问题的第一层证据在词法分析器 src/parse/tokenize.zig 中。//与%分别被切分为独立的运算符 Token字符/的切分逻辑tokenize.zig当当前位置的下一个字符也是/时一次性消费两个字符并生成.OpDoubleSlash整除运算符//否则生成单个.OpSlash普通除法/。这说明//是单一 Token而不是两个/的拼接语法层由此才能对//单独赋优先级。字符%的切分逻辑tokenize.zig消费单个字符并生成.OpPercent取模运算符%。Token 定义本身也印证了这一点.OpSlash与.OpPercent同属于二元运算符 Token 枚举tokenize.zig而.OpDoubleSlash则单独存在。Token 化完成后的 Token 流即为解析器优先级表下节的输入。解析层面绑定优先级表与乘法组的左结合规则优先级决策的真正核心在解析器 src/parse/Parser.zig 中。Roc 使用 Pratt 解析器风格的绑定优先级binding power表每个运算符同时记录左右两侧的优先级数值pub const BinOpBp struct { left: u8, right: u8 };相关条目如下Parser.zig运算符 Tokenleftright说明OpStar*3233乘法OpSlash/3233除法OpDoubleSlash//3233整除OpPercent%3233取模OpPlus2223加法OpBinaryMinus-2223减法源码注释对此给出了精确的语义解释Parser.zig*、/、//、%构成同一个乘法优先级组组内彼此左结合——因为right left当解析器在右操作数位置遇到同组的下一个运算符时其left32无法满足left min_bp此时最小可接受优先级为 33于是循环终止不会把后续运算符吞进右操作数。具体到1 % 10 // 100解析1遇到%left32, right33进入右操作数解析min_bp设为 33解析10遇到//left32——但32 33不成立//不被吞入%的右操作数%的解析完成得到(1 % 10)随后//作为同一层级的下一运算符继续左结合解析最终得到(1 % 10) // 100。同理该组整体绑定比加法组22/23更紧即a b % c // d会先归组为a ((b % c) // d)。这一设计与常见 C 系语言的乘法/除法/取模同级左结合语义一致也是 Roc friendly 设计理念在运算符层面的体现。实操运行与更新这条 REPL 快照测试快照测试的驱动工具由 build.zig 暴露为zig build run-snapshot-tool步骤用法与说明详见 test/snapshots/README.md# 运行/重新生成全部快照 zig build run-snapshot-tool # 只处理当前这条优先级回归快照 zig build run-snapshot-tool -- test/snapshots/repro_issue_9712_mod_floordiv_precedence.md # 用当前编译器输出覆盖期望值OUTPUT 段 zig build run-snapshot-tool -- test/snapshots/repro_issue_9712_mod_floordiv_precedence.md --update-expected # 带解释器 trace 调试 REPL 快照仅限 typerepl 且单文件 zig build run-snapshot-tool -- test/snapshots/repro_issue_9712_mod_floordiv_precedence.md --trace-eval工作流程上.rules 亦有记载当修改编译器后运行zig build run-snapshot-tool通过 diff 审查快照变化是否为预期行为。若解析器优先级表被误改本快照的OUTPUT会从0.0变为崩溃错误文本或异常值测试随即暴露回归。快照工具的 CLI 校验逻辑对--trace-eval有强约束它只能用于typerepl快照且只能搭配单个文件使用src/snapshot_tool/main.zig与本文件类型完全匹配。源码视角REPL 快照的机械执行流程REPL 快照并非手写结果而是由工具按固定管线生成与校验的核心实现位于 src/snapshot_tool/main.zig分发processSnapshotFile检测content.meta.node_type .repl后转交processReplSnapshotmain.zig后者依次生成 HTML 包装、META、SOURCE、OUTPUT、PROBLEMS 各段main.zig。切分输入generateReplOutputSection以»为分隔符把 SOURCE 切成若干条 REPL 输入main.zig每条输入经snapshotReplStep分发为定义 / 表达式 / 语句表达式三种求值模式之一main.zig。求值输出表达式最终由 LIR 解释器求值并经lirInterpreterInspectedStr转成字符串main.zig多步输出之间以独立的---行分隔空输出如纯类型标注步骤也按位置保留main.zig。更新或校验在--update-expected模式下把实际输出写回快照文件在校验模式下与实际输出比对并报告差异。正是这套管线保证了语法语义正确性与渲染展示效果分离普通快照含本文件的PROBLEMS段记录的是诊断的规范化 S-expression见 test/snapshots/README.md不含任何盒式绘制字符、ANSI 转义等渲染细节——NIL即无任何报告的规范表示。本快照的PROBLEMS: NIL因此意味着该表达式在全部编译阶段均无错误与警告。小结一条快照背后的完整语义链条回到最初的问题1 % 10 // 100在 Roc 中之所以安全求值为0.0是因为从词法//作为独立 Token 切分到语法*、/、//、%共享 left32/right33 的同一乘法优先级组且组内左结合再到快照测试OUTPUT: 0.0、PROBLEMS: NIL形成了一条完整的、可机械验证的语义链条。对编译器开发者而言这条快照既是 issue 9712 的回归防线也是理解 Roc 运算符优先级设计的绝佳入口对语言使用者而言记住乘法组内一律从左到右这一规则即可安全混用%、//、*、/必要时用括号显式表达意图。进一步探索建议阅读 src/parse/tokenize.zig 中运算符 Token 的完整枚举与%、//的切分分支阅读 src/parse/Parser.zig 中BinOpBp定义、bin_op_bp_table全表及分组注释阅读 test/snapshots/README.md 了解普通快照与 reporting 快照的分工浏览 test/snapshots/binops.md 等同类快照对比其他二元运算符的解析期望。【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻