
Roc 语言字符串解析测试解析以 REPL 快照 num_from_str_i32_success 为例理解 I32.from_str【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc本篇文章以 Roc 仓库中 test/snapshots/repl/num_from_str_i32_success.md 这份 REPL 快照测试文档为主线系统讲解 Roc 语言中I32.from_str以及全部整数类型的from_str如何将字符串解析为整数、边界值如何判定、失败时如何返回Err(BadNumStr)并深入到底层 Zig 实现与类型系统层面的设计。读完本文你将能够读懂 Roc 的快照测试文件格式、掌握from_str系列 API 的完整行为语义并学会用边界值思维验证数值解析代码。一、先看懂 REPL 快照测试文件的结构Roc 仓库中的test/snapshots/repl/目录存放了大量 REPL 快照测试每个.md文件就是一个完整的测试用例采用统一的四段式结构。以 num_from_str_i32_success.md 为例区块作用# META元数据采用ini风格键值对description描述测试目的type声明测试种类此处为repl# SOURCE以»开头的 REPL 输入行等价于在交互式 REPL 中逐行敲入的表达式# OUTPUT与输入逐行对应的期望输出行与行之间用---分隔# PROBLEMS期望出现的编译/诊断问题列表NIL表示无问题该文件的 META 区明确定义了本测试的意图descriptionI32.from_str success cases typerepltyperepl表明这是 REPL 求值类快照每行»之后的表达式会被 Roc 编译器检查并求值求值结果Ok(...)/Err(...)/ 字面量等按顺序写入# OUTPUT。这种快照格式使得数值解析这类行为可以直接以“输入 → 输出”的断言形式被机器验证也让文档本身兼具测试与示例的双重价值。二、I32.from_str 的成功用例从字符串到Ok(i32)原文档 num_from_str_i32_success.md 的# SOURCE区块完整给出了 5 个成功解析用例» I32.from_str(0) » I32.from_str(42) » I32.from_str(-42) » I32.from_str(2147483647) » I32.from_str(-2147483648)对应的期望输出为Ok(0) --- Ok(42) --- Ok(-42) --- Ok(2147483647) --- Ok(-2147483648)这 5 个用例按语义可拆解为三组零值0解析为Ok(0)说明空字符串之外的“0”是合法输入常规正负整数42与-42分别解析为Ok(42)、Ok(-42)证明from_str支持可选的负号前缀类型边界极值2147483647与-2147483648恰好是I32的最大值与最小值解析结果均为Ok(...)说明边界值本身属于合法输入。这组用例的价值在于它把成功路径的“最小/最大边界”固定为回归断言一旦编译器或运行时对极值处理出现偏差快照对比就会失败从而第一时间暴露问题。三、失败路径不合法输入与越界输入统一返回Err(BadNumStr)要完整理解I32.from_str必须同时看它的姊妹快照 num_from_str_i32_failure.md» I32.from_str(2147483648) » I32.from_str(-2147483649) » I32.from_str(hello) » I32.from_str() » I32.from_str(12.5)期望输出全部为Err(BadNumStr)这 5 个失败用例分别覆盖了不同类型的非法输入输入失败原因2147483648超过I32最大值2147483647数值越界-2147483649低于I32最小值-2147483648数值越界hello非数字字符语法非法空字符串无内容可解析12.5含小数点不是整数表示值得注意的是越界、非法字符、空串、小数这四类截然不同的错误在I32.from_str的语义中被统一收敛为单一错误标签BadNumStr。这一点在 src/build/roc/Builtin.roc 中I32.from_str的官方文档注释里表述得非常明确Parse anI32from aStr. ReturnsErr(BadNumStr)if the string is not a valid integer, or if the parsed value does not fit in anI32(-2147483648to2147483647).即只要字符串不是一个合法的、且落在I32取值范围内的十进制整数就返回Err(BadNumStr)。这也是 Roc 在“解析失败”这一错误建模上的设计选择——调用方只需要处理一种失败标签模式匹配非常简洁。四、扩展到全整数类型一张边界值测试矩阵from_str并非I32独有Roc 内置的每一种整数类型都提供了同名 API。仓库中的 num_from_str_all_int_types.md 与 num_from_str_various_types.md 用一组快照覆盖了全部整数类型的边界验证» I8.from_str(127) # Ok(127) —— I8 最大值 » I8.from_str(-128) # Ok(-128) —— I8 最小值 » I8.from_str(128) # Err(BadNumStr) —— 越界 » U16.from_str(65535) # Ok(65535) —— U16 最大值 » U16.from_str(65536) # Err(BadNumStr) —— 越界 » I16.from_str(32767) # Ok(32767) —— I16 最大值 » I16.from_str(-32768) # Ok(-32768) —— I16 最小值 » I16.from_str(32768) # Err(BadNumStr) —— 越界 » U32.from_str(4294967295) # Ok(4294967295) —— U32 最大值 » U32.from_str(4294967296) # Err(BadNumStr) —— 越界 » U64.from_str(18446744073709551615) # Ok(...) —— U64 最大值 » U64.from_str(18446744073709551616) # Err(BadNumStr)从这些快照可以归纳出通用规律每个类型的最大值与最小值字符串都是合法输入解析返回Ok最大值1或最小值-1一律返回Err(BadNumStr)无符号类型U8/U16/U32/U64/U128对负数输入同样报错——num_from_str_unsigned_negative.md 即专门验证这一行为。from_str同样覆盖 128 位与浮点/十进制类型见 num_from_str_various_types.md» F32.from_str(3.14) # Ok(3.14) » F32.from_str(invalid) # Err(BadNumStr) » I64.from_str(-9223372036854775808) # Ok(-9223372036854775808) —— I64 最小值 » I64.from_str(9223372036854775808) # Err(BadNumStr) —— 越界 » U128.from_str(12345678901234567890) # Ok(12345678901234567890) » I128.from_str(-12345678901234567890) # Ok(-12345678901234567890)以及Dec十进制高精度类型的成功用例 num_from_str_dec_success.md» Dec.from_str(0) # Ok(0.0) » Dec.from_str(123.456) # Ok(123.456) » Dec.from_str(-99.99) # Ok(-99.99) » Dec.from_str(1000000) # Ok(1000000.0)由此可以看到Roc 的from_str是一个覆盖全部数值类型的统一解析协议F32/F64/Dec接受十进制小数表示各整数类型只接受整数表示失败时统一复用BadNumStr标签。五、API 签名与用法Try类型与expect示例在 src/build/roc/Builtin.roc 中I32.from_str的完整类型签名是from_str : Str - Try(I32, [BadNumStr, ..])Try是 Roc 中Result风格的内置类型Ok/Err返回值Ok(I32)表示解析成功Err(BadNumStr)表示解析失败。同一文件给出的官方expect示例直接演示了典型用法expect I32.from_str(42) Ok(42) expect I32.from_str(-1) Ok(-1) expect I32.from_str(3000000000) Err(BadNumStr)这里3000000000超过I32上限返回Err(BadNumStr)——与快照文件中的越界用例相互印证。在真实代码中解析用户输入后通常通过when/模式匹配解包when I32.from_str(user_input) is Ok(value) - value # 解析成功得到 I32 Err(BadNumStr) - 0 # 解析失败走兜底逻辑六、底层实现统一的整数解析包装与Ok/Err判别编码快照中的行为并非魔法其背后是 Zig 实现的运行时支撑。所有整数类型的from_str在底层共用同一个解析入口src/builtins/dev_wrappers.zig 中的roc_builtins_int_from_str。该函数根据int_width1/2/4/8/16 字节与is_signed两个参数把解析分发到对应的 Zig 原生整数类型if (is_signed) { switch (int_width) { 1 writeIntParseResult(i8, out, disc_offset, roc_str), 2 writeIntParseResult(i16, out, disc_offset, roc_str), 4 writeIntParseResult(i32, out, disc_offset, roc_str), // I32.from_str 走这里 8 writeIntParseResult(i64, out, disc_offset, roc_str), 16 writeIntParseResult(i128, out, disc_offset, roc_str), else unreachable, } } else { /* u8/u16/u32/u64/u128 同理 */ }writeIntParseResult的核心工作有两个调用num.parseIntFromStr(T, roc_str)完成真正的字符串解析与范围检查把解析结果直接写入Try标签联合体tag union的内存布局——值payload写在偏移 0 处判别位discriminant写在disc_offset处其中Err0、Ok1判别标签按字母序排列因此Err在前// Roc discriminants: Err0, Ok1 (alphabetically sorted) // parseIntFromStr errorcode: 0success, 1failure // So: Ok discriminant 1 - errorcode out[disc_offset] 1 - r.errorcode;这段注释揭示了一个精巧的设计底层解析函数只返回“成功/失败”这一位信息errorcode上层通过1 - errorcode直接映射为 Roc 的判别标签同时把原始字节拷贝进Try的 payload 区全程零分配、零装箱。对于Dec类型实现位于 src/builtins/dec.zig其fromStr同样遵循“解析失败即返回错误”的模式// All parse failures currently map to null; the Roc wrapper reports that as // BadNumStr. pub fn fromStr(roc_str: RocStr) ?RocDec { if (roc_str.isEmpty()) { return null; } return call(.always_inline, RocDec.fromNonemptySlice, .{roc_str.asSlice()}); }Dec以i128作为内部表示乘以 10 的幂缩放因此fromNonemptySlice最终调用decimal_parse.parseScaledI128完成解析空字符串在这里被提前拦截返回null与快照中I32.from_str()返回Err(BadNumStr)的行为保持一致null在 Roc 包装层统一映射为BadNumStr。这些 Zig 函数通过 src/builtins/main.zig 的导出注册如exportDecFn(dec.fromStr, from_str)暴露给编译器与运行时最终形成快照中看到的统一行为。七、回归测试佐证越界浮点输入的BadNumStrBadNumStr语义的正确性还由编译器的回归测试持续守护。src/eval/test/eval_regression_repros.zig 中记录了回归用例 B076{ .name regression B076: F64.from_str rejects out-of-range finite inputs, .source F64.from_str(\1e999\), .expected .{ .inspect_str Err(BadNumStr) }, },1e999在数学上有限但远超f64可表示范围F64.from_str同样返回Err(BadNumStr)与整数类型的越界处理保持一致。这说明“数值超出目标类型的可表示范围 →Err(BadNumStr)”是贯穿全部数值类型的统一约定也是快照测试所锚定的核心语义。八、快照与 REPL 会话的关系从测试机制看typerepl的快照由 REPL 会话执行层驱动。仓库中 src/cli/ReplSession.zig 是 REPL 会话的核心实现每一行输入经过evaluateExpression后产生StepResult其联合体包含output、diagnostic、runtime_crash、none等分支见该文件第 84-89 行快照的# OUTPUT区正是对output分支的逐行固化。会话还通过eval.Inspected系列 API 完成解析、规范化与求值并支持definitions.snapshot/restore第 369-390 行在出错时回滚会话状态——这保证了快照测试彼此独立、互不污染。九、如何在你的 Roc 代码中实践这套语义结合上述分析在实际项目中可以遵循以下实践永远不要直接信任用户输入任何来自 CLI 参数、表单或文件的字符串数字都应通过对应类型的from_str解析并对Err(BadNumStr)做显式兜底用边界值自测参考快照的用例组织方式对每个类型至少测试“最小值、最大值、最大值1、最小值-1、非法字符、空串”六类输入善用Try的传播在函数返回Try(I32, [BadNumStr, ..])的场景中可以直接用?或模式匹配把解析错误沿调用链传递避免静默吞错测试先行如果你想为某个数值解析逻辑新增保障直接按本文第一节的META/SOURCE/OUTPUT/PROBLEMS四段式补充 REPL 快照即可获得与仓库同款的可回归断言。小结I32.from_str表面上是“一行字符串转整数”的便捷 API实则承载了 Roc 一整套设计取舍——统一的BadNumStr错误标签、覆盖全数值类型的解析协议、严格贴合类型边界的成功/失败判定以及底层“判别位编码 直接写 tag union”的零分配实现。本文以 num_from_str_i32_success.md 为起点结合其失败用例、全类型边界矩阵与 Zig 源码完整还原了这条从文档到实现的链路。想要进一步验证任意类型的边界行为直接翻阅 test/snapshots/repl 目录下对应的快照文件即可。【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考