ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Mojo 表达式参考文档的测试矩阵:从 Expressions 参考到 tests.mojo 与 Bazel 构建的完整实践指南

Mojo 表达式参考文档的测试矩阵:从 Expressions 参考到 tests.mojo 与 Bazel 构建的完整实践指南 Mojo 表达式参考文档的测试矩阵从 Expressions 参考到 tests.mojo 与 Bazel 构建的完整实践指南【免费下载链接】mojoThe Modular Platform (includes MAX Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo表达式expression是 Mojo 中一切产生值的代码单元而本文围绕 Mojo/docs/site/code/reference/expressions/README.md 所描述的目录深入剖析 Mojo 官方如何为 Expressions reference 文档配套一套可编译、可运行、可回归的代码示例与测试体系。读完本文你将掌握 expressions 示例目录的组织方式、每个表达式语法点在tests.mojo中的验证写法、Bazel 构建与测试目标的生成规则以及 Mojo 编译器两阶段表达式解析背后的设计动机。目录定位文档驱动的示例与测试仓库Mojo/docs/site/code/reference/expressions/是 Mojo 官方文档仓库中文档与代码一一对应实践的代表。按照 README.md 的说明该目录的全部职责是为 Expressions reference即仓库中的 Mojo/docs/site/reference/expressions.mdx章节提供可直接运行的代码示例用 Bazel 将每个示例编译成独立二进制并为每个二进制注册一个运行测试确保文档中的语法示例始终能被当前编译器正确编译和执行。目录内部结构非常简单只有三个文件文件作用README.md目录说明描述文件组织与 Bazel 目标命名约定tests.mojo覆盖 Expressions 参考中绝大部分语法点的可运行测试约 401 行33 个测试函数BUILD.bazel为每个.mojo文件生成mojo_binary与modular_run_binary_test目标的构建脚本这种参考文档 可运行示例 回归测试三位一体的结构意味着文档中的任何语法声明都能在源码层面找到对应的可执行证据——这是 Mojo 文档体系保证准确性的一种工程手段也值得其他语言项目借鉴。构建与测试目标BUILD.bazel 的自动生成逻辑BUILD.bazel 只有 25 行却展示了 Bazel 的宏式生成风格。它加载了两个来自//bazel:api.bzl的规则load(//bazel:api.bzl, modular_run_binary_test, mojo_binary) MOJO_SRCS glob([*.mojo]) [ mojo_binary( name src.split(.)[0], srcs [src], deps [mojo//:std], ) for src in MOJO_SRCS ] [ modular_run_binary_test( name src.split(.)[0] _test, size small, binary src.split(.)[0], ) for src in MOJO_SRCS ]核心约定与 README 的说明一一对应目标命名每个.mojo文件生成一个与文件名同名去扩展名的mojo_binary目标例如tests.mojo→:tests测试命名每个二进制再对应一个带_test后缀的modular_run_binary_test目标例如:tests_test依赖所有二进制都依赖mojo//:std即 Mojo 标准库因此测试中可以使用List、Dict、Set、String、range等标准库能力规模标记size small表明这些是轻量级快速测试可见性package(default_visibility [//oss/modular/docs:__subpackages__])将可见性限定在文档相关子包范围内。采用glob 列表推导式生成目标意味着今后只要往该目录新增一个.mojo示例构建与测试目标会被自动补齐无需手工维护 BUILD 文件——这是该目录可扩展、低维护的设计关键。测试文件的组织方式按表达式类别分组的 33 个测试tests.mojo 是本文档目录的主体它用from std.testing import assert_equal对每个语法点做断言式验证并在文件头部的注释中明确列出故意不测试的清单Set/dict 混写错误{a: 1, 2}、{1, b: 2}这些是文档中用于展示报错信息的反例属于有意错误不适合放入可运行测试关键字参数后跟位置参数、重复关键字参数等报错场景同理为文档反例comptime不带括号有意错误input()海象运算符示例依赖交互式输入无法在非交互测试中运行函数类型表达式def() - Int等属于类型层面形式没有独立的运行时行为可断言type_of、conforms_to、origin_of返回编译器内部类型无法直接打印或断言只在测试语料库的其他where子句中间接使用标识符表达式小节裸名称不是独立代码。这种显式声明哪些内容不可测的做法与 expressions.mdx 文档正文的Sharp edge风格一脉相承帮助读者区分可以运行的语法与仅用于说明的语法。全部 33 个测试函数在main()中被逐一调用覆盖以下类别与 Expressions 参考的小节完全对应表达式类别测试函数核心验证点括号表达式test_paren_precedence、test_paren_multiline括号改变优先级括号内换行无需反斜杠元组test_tuple_by_comma等 4 个逗号创建元组解构赋值单元素元组(1,)索引访问集合显示test_list_display等 6 个列表/字典/集合字面显示尾随逗号表达式元素空集合类型注解初始化器列表test_initializer_list_call、test_initializer_list_keyword花括号语法作为构造调用糖关键字初始化成员访问test_member_access点运算符访问结构体字段调用test_call_positional等 3 个位置参数、关键字参数、默认参数混用下标与切片test_subscript_list等 6 个列表/字典下标start:stop、开放 stop、步长、负步长三元条件test_ternary_basic、test_ternary_chained_right_assoc基础 if-else 表达式右结合链式嵌套海象运算符test_walrus_in_if、test_walrus_in_while赋值即表达式的用法编译期表达式test_comptime_expressioncomptime(...)强制编译期求值推导式7 个测试列表/集合/字典推导式过滤条件多重 for/if 子句从测试反推语法要点关键表达式的源码级验证下面沿着测试代码逐类印证 expressions.mdx 文档中的语法声明让你既能看懂文档也能看懂测试为什么这样写。括号表达式与多行表达式var a 2 var b 3 var c 4 assert_equal((a b) * c, 20)文档声明括号可覆盖默认优先级测试用(a b) * c 20验证若不加括号a b * c应为 14。同时文档强调括号可让表达式跨越多行而无需反斜杠续行符测试中的test_paren_multiline正是把三个值的加法拆成三行写在括号内。元组逗号是关键括号只是分组Mojo 中创建元组的是逗号而非括号var a 2, 3 # 无括号元组 var b (2, 3) # 带括号的同一元组 var x, y b # 解构x 为 2y 为 3 var one_tup (1,) # 单元素元组尾随逗号不可或缺(1)只是括号里的整数 1而(1,)才是单元素元组——测试test_tuple_one_element同时验证了(1,)与三元素元组的索引访问。test_tuple_destructuring验证了解构赋值var x, y b的正确性。集合显示Displays字面量与显示的区别文档特别区分了字面量literal与显示display字面量不能包含表达式而显示可以。[1, 2, 3]是列表字面量[1, 11, 111]是列表显示后者由编译器在解析期展开为求值列表。测试test_list_display_with_expressions对此做了断言var values [1, 1 1, 1 1 1] assert_equal(values[0], 1) assert_equal(values[1], 2) assert_equal(values[2], 3)Mojo 允许所有集合元素含最后一个带尾随逗号测试的strings列表演示了多行 尾随逗号写法。Set 显示的Sharp edge语法内置类型需导入这是文档中最值得注意的坑集合显示{2, 3, 5, 7}是 Mojo 核心语法不需要任何导入但Set类型本身不在核心语法中使用Set[Int]()必须显式导入from std.collections import Set # 使用 Set 类型必须导入 var display_set {1, 2, 3} # 集合显示无需导入 var empty_set Set[Int]() # 命名 Set[Int] 需要导入 empty_set.add(4) empty_set.add(4) # 添加重复元素是空操作 assert_equal(len(empty_set), 1) # 集合不允许重复元素test_set_explicit_type完整复刻了这一场景tests.mojo顶部的from std.collections import Set正是为此服务。另外集合不是初始化器列表花括号语法同时兼任初始化器列表为推断出的类型构造实例在没有类型上下文时编译器无法区分两者这一歧义在类型检查阶段才被消解。文档给出的反例{a: 1, 2}与{1, b: 2}会产生明确的错误信息因此被测试文件明确排除在可运行测试之外。初始化器列表花括号即构造调用糖初始化器列表是初始化器调用的语法糖当类型T可从上下文推断时{1, hello}等价于T(1, hello)。测试用fieldwise_init结构体演示了两种形态fieldwise_init struct Pair(Copyable, Movable): var a: Int var b: String def process(p: Pair) - String: return p.b def test_initializer_list_call() raises: # {1, hello} 是 Pair(1, hello) 的语法糖类型由签名推断 assert_equal(process({1, hello}), hello) def test_initializer_list_keyword() raises: var p: Pair {a 7, b kw} assert_equal(p.a, 7) assert_equal(p.b, kw)关键词形式的初始化器列表{a 7, b kw}对应Pair(a7, bkw)类型由变量声明推断。注意fieldwise_init装饰器为结构体生成了按字段赋值的初始化逻辑这是初始化器列表能工作的前置条件。调用表达式位置参数、关键字参数与错误边界def greet(name: String, loud: Bool False) - String: return String(HELLO, ).upper() if loud else String(Hello, ) name assert_equal(greet(Alice), Hello, Alice) assert_equal(greet(Alice, loudTrue), HELLO, ) assert_equal(greet(nameBob), Hello, Bob)test_call_mixed_with_default同时验证了默认参数、位置参数与关键字参数混用。文档明确了两条调用规则位置参数不能出现在关键字参数之后greet(loudTrue, Alice)报错、关键字参数不能重复greet(nameAlice, nameBob)报重复关键字错误。这两类错误同样属于文档反例测试文件在头部注释中注明位置参数跟在关键字参数之后的调用错误与重复关键字调用错误均不纳入运行测试。切片start:stop:stride 三段的缺省语义文档声明切片三段全部可选start 默认开头、stop 默认结尾、stride 默认 1且stop 位置的元素不包含在结果中。测试用同一[0, 1, 2, 3, 4, 5]列表覆盖四种形态var first_three items[0:3] # [0, 1, 2]长度 3 var from_three items[3:] # [3, 4, 5]长度 3 var every_other items[::2] # [0, 2, 4]长度 3 var reversed items[::-1] # [5, 4, 3, 2, 1, 0]长度 6负步长[::-1]实现完整反转验证了 stride 支持负值。三元条件表达式右结合与链式var label even if x % 2 0 else odd文档强调三元表达式是右结合的因此可以链式嵌套。测试test_ternary_chained_right_assoc用一个尺寸分类函数验证了分组语义def size(n: Int) - String: return small if n 10 else large if n 100 else medium # 解析为small if n 10 else (large if n 100 else medium) assert_equal(size(5), small) assert_equal(size(50), medium) assert_equal(size(500), large)注意size被定义为测试函数内部的嵌套def这展示了 Mojo 支持函数内嵌函数的特性。海象运算符:赋值即表达式普通赋值是语句不产生值海象运算符:是赋值的表达式形态——它把值绑定到名字并同时求值为该值。文档给出的两个约束在测试中均有体现目标名字必须已声明测试先var n: Int再使用优先级最低不加括号会绑定比较结果而非列表项。var items [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] var captured: Int -1 var n: Int if (n : len(items)) 10: captured n assert_equal(captured, 11)test_walrus_in_while进一步展示了在while条件中一边赋值一边比较的循环模式。文档还提醒海象赋值不隐含所有权、引用或内存语义右值可能只存在于寄存器中。编译期表达式comptimecomptime强制表达式在编译期求值括号是必需的——comptime heavy_calculation()不带括号会报错。测试用一个百万次循环求和验证了编译期求值的结果def heavy_calculation() - Int: var sum 0 for i in range(1_000_000): sum i return sum def test_comptime_expression() raises: var x comptime (heavy_calculation()) assert_equal(x, 499_999_500_000)循环在编译期只运行一次运行时x是常量O(1) 访问。如果表达式无法在编译期求值编译器会直接报错。同小节的type_of、conforms_to、origin_of是编译期类型内省的类函数关键字返回编译器内部类型无法打印因此测试文件声明它们通过其他测试语料中的where子句间接覆盖。推导式列表、集合、字典与多子句推导式用单个表达式取代循环 append模式统一语法为expr for pattern in iterable if condition。测试覆盖了全部三种集合# 列表推导式带过滤 var squares [x * x for x in [0, 1, 2, 3, 4] if x % 2 0] # [0, 4, 16] # 集合推导式去重后元素可能少于迭代次数 var fibs {fib(x) for x in range(6)} # {1, 2, 3, 5, 8}6 次迭代 5 个元素 # 字典推导式 var dict_squares {x: x * x for x in range(3)} # {0: 0, 1: 1, 2: 4} var lengths: Dict[String, Int] { k: k.byte_length() for k in [one, two, three, four] }集合推导式的去重语义非常直观fib前 6 项是1, 1, 2, 3, 5, 8去重后只剩 5 个元素。多重for/if子句从左到右求值每层for引入新迭代变量每个if基于当前值过滤var products [ (x, y, x * y) for x in range(3) for y in range(3) if (x y) % 2 0 ] # 预期结果 5 项[(0, 0, 0), (0, 2, 0), (1, 1, 1), (2, 0, 0), (2, 2, 4)] assert_equal(len(products), 5)test_multi_clause_comprehension正是文档中子句从左到右求值声明的直接验证。文档中未纳入 tests.mojo 的内容Expressions 参考中还有三类内容在 tests.mojo 中没有对应测试理解它们有助于把握测试边界函数类型表达式def() - Int、def(Int, Int) - Int、def(var value: String) - None、def() raises - String、def(T) - T等描述函数签名的类型层面形式。它们没有独立运行时行为无法断言故测试文件注明type-level forms with no standalone runtime behavior to assert。Lambda 表达式var inc lambda (x: Int) {} - Int: x 1属于匿名单表达式函数其完整语法、语义与示例被独立拆分到 lambda expressions reference 及其配套测试目录Mojo/docs/site/code/reference/lambda-expressions/同样采用 README tests.mojo BUILD.bazel 的组织模式。标识符表达式裸名称score、Int、range不是独立代码片段无法单独编译。底层原理Mojo 的两阶段表达式解析为什么文档中的表达式语法如此丰富且需要这样一套测试体系答案藏在编译器前端。从 ParserExprs.cpp 的文件头注释可以看到Mojo 的表达式解析采用两阶段设计第一阶段把表达式解析为 AST 形式的中间表示由ExprParser : public ParserBase类实现第二阶段类型检查并生成操作或类型。注释明确列出了这种设计要解决的三类难题非词法变量引用如[x.strip().upper() for x in flags]只有在for x被类型检查并解析之后才能对整个表达式做类型检查——这解释了推导式为何需要模式 迭代器 过滤条件的专门语法节点奇怪的求值顺序如foo() if cond() else bar()条件与分支的求值时机由解析树结构决定赋值左侧的解析歧义x[foo()] bar()在没有看到等号之前无法确定x[foo()]是赋值目标还是下标访问表达式。两阶段设计使表达式解析器独立于主解析器工作先构建表达式树再统一做类型检查。这也解释了为什么 tests.mojo 中大量测试函数标注raises并以assert_equal断言——表达式求值可能涉及类型检查与运行期求值测试需要同时验证可编译与结果正确两个维度。如何在本地运行这些测试该目录是 Mojo 官方仓库Bazel 构建系统的一部分测试的运行依赖仓库的 Bazel 工作区配置根目录的 MODULE.bazel、REPO.bazel 与 bazelw 包装脚本。在已配置好 Bazel 与 Mojo 工具链的环境下可按如下方式操作# 构建 tests.mojo 对应的二进制 bazel build //Mojo/docs/site/code/reference/expressions:tests # 运行对应的回归测试modular_run_binary_test bazel test //Mojo/docs/site/code/reference/expressions:tests_test新增示例也很简单往该目录放入一个新的.mojo文件BUILD.bazel中的glob会自动为其生成同名二进制目标与_test运行测试目标无需手工编辑构建文件。单个.mojo文件本身就是独立的 Mojo 应用程序也可以在安装 Mojo 工具链后直接用mojo run方式本地执行验证。小结Mojo/docs/site/code/reference/expressions/目录的价值不仅在于提供示例更在于它为 Expressions reference 的每一个可运行语法点提供了可编译、可断言的验证证据BUILD.bazel用glob 列表推导式实现了目标自动生成新示例零成本接入构建与测试tests.mojo用 33 个测试函数覆盖括号、元组、集合显示、初始化器列表、成员访问、调用、下标切片、三元、海象、comptime 与推导式全部类别并在头部注释中诚实列出有意错误、类型层形式、交互式构造等不可测内容编译器前端 ParserExprs.cpp 的两阶段表达式解析设计为文档中推导式、三元、赋值歧义等复杂语法提供了底层机制解释。这份文档 — 示例 — 测试 — 编译器实现层层对应的工程组织既是学习 Mojo 表达式的完整路线图也是任何希望保证文档技术准确性的语言项目可以复用的模式。【免费下载链接】mojoThe Modular Platform (includes MAX Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表