ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Wing 正则表达式完全指南:regex 类型与 8 个内置 API 的实战解析

Wing 正则表达式完全指南:regex 类型与 8 个内置 API 的实战解析
  • 编程语言
  • 编译器
  • 云原生
  • 语言运行时

【免费下载链接】wing

A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡

项目地址:https://gitcode.com/gh_mirrors/wi/wing
点击查看免费下载

导读

Wing 语言内置了对正则表达式(regular expressions)的一等支持,开发者无需引入任何外部库,即可通过全局regex命名空间完成模式编译、匹配检测、捕获组提取、索引定位与全局替换等常见任务。本文以 docs/by-example/22-regex.md 的官方示例为主体,结合 SDK 源码(packages/@winglang/sdk/src/std/regex.ts)与完整测试用例(tests/sdk_tests/std/regex.test.w),逐方法剖析Regex类型的行为细节与底层实现,帮助你掌握在 Wing 中编写、运行和验证正则表达式的完整实战能力。

Wing 内置正则支持概览

Wing 把正则表达式作为标准库中的一等类型提供,对应 SDK 中的Regex类,类型名称为regex。与 JavaScript 的RegExp类似,使用前需要先把模式字符串编译为Regex对象,之后即可反复调用匹配、查找、替换等实例方法。

在 SDK 中,Regex类被标记为@wingType regex,并实现了ILiftable接口(见 packages/@winglang/sdk/src/std/regex.ts),这意味着编译后的正则对象既可以用于 preflight 阶段,也可以通过 lift 机制进入 inflight 代码——_toInflight()会生成对应的运行时构造代码(new (require(...).Regex)(...)),保证模式在不同执行环境下行为一致。完整的 API 签名清单见标准库参考文档 docs/api/04-standard-library/std/regex.md。

Regex对外暴露的接口共 9 个:1 个静态编译方法compile,以及 8 个实例方法——test、find、findIndex、findAll、findAllIndex、findSubmatch、findSubmatchIndex、replaceAll。

官方示例:从编译到输出的完整演示

下面这段代码是官方 by-example 文档(docs/by-example/22-regex.md)中的完整示例。它编译了模式"p([a-z]+)ch"——即以p开头、中间为至少一个[a-z]小写字母捕获组、以ch结尾——然后依次演示全部 8 个实例方法:

let r = regex.compile("p([a-z]+)ch"); // Checks if the regular expression matches the provided text. log(r.test("peach")); // Finds the first occurrence of the pattern within the text. log(r.find("peach peach") ?? ""); // Finds all non-overlapping occurrences of the pattern within the text. log(Json.stringify(r.findAll("peach punch pinch"))); // Finds the start and end index of all matches within the text. log(Json.stringify(r.findAllIndex("peach punch pinch"))); // Finds the start and end index of the first match within the text. log(Json.stringify(r.findIndex("peach"))); // Finds the first match and its submatches. log(Json.stringify(r.findSubmatch("peach punch"))); // Finds the start and end index of the match and all submatches. log(Json.stringify(r.findSubmatchIndex("peach punch"))); // Replaces all occurrences of the match with a replacement string. log(r.replaceAll("a peach", "<fruit>"));

在 Wing 控制台中运行(wing it)后,输出如下:

# Run locally with wing console wing it true peach ["peach","punch","pinch"] [[0,5],[6,11],[12,17]] [0,5] ["peach","ea"] [[0,5],[1,3]] a

注意:find、findIndex、findSubmatch、findSubmatchIndex均可能没有匹配结果,返回值是可选类型(str?/MutArray<num>?/MutArray<str>?等),因此示例中用?? ""提供兜底值;而findAll、findAllIndex在没有匹配时返回空数组,不会返回nil。这一点在 API 参考文档 docs/api/04-standard-library/std/regex.md 中有明确标注。

该示例在仓库中被自动抽取为可执行的 Wing 文件 tests/doc_examples/valid/22-regex.md_example_1/main.w,属于 doc_examples 有效性测试的一部分,可以直接运行验证。

编译模式:regex.compile

所有正则使用都从编译开始:

let r = regex.compile("p([a-z]+)ch");

compile是Regex的静态方法,签名与说明如下(见 docs/api/04-standard-library/std/regex.md):

项目说明
签名regex.compile(pattern: str);
参数pattern要编译的正则模式字符串(必填)
返回值新的Regex对象

底层实现非常轻量:compile直接new Regex(pattern),构造器内部把模式交给 JavaScript 原生new RegExp(pattern)(见 packages/@winglang/sdk/src/std/regex.ts)。也就是说,Wing 的正则语法与 JavaScript 的RegExp语法一致,熟悉 JS 正则的开发者可以无缝迁移。

regex也是一个可用于类型注解的类型。在 tests/valid/std_type_annotation.test.w 中可以看到标准用法:

let decimal: regex = regex.compile("^[0-9]*$"); let testString = inflight (re: regex, s: str) => { return re.test(s); }; test "regex is valid type" { let binary: regex = regex.compile("^[0-1]*$"); expect.equal(testString(decimal, "24"), true); expect.equal(testString(decimal, "a23"), false); expect.equal(testString(binary, "120010"), false); }

该测试同时验证了两点:regex是合法的注解类型;Regex对象可以通过inflight闭包被 lift 到 inflight 阶段使用(前文提到的ILiftable实现)。

逐个解析 8 个实例方法

下面按"功能—签名—语义—示例"的结构逐个展开。所有签名均以 docs/api/04-standard-library/std/regex.md 为准。

1.test:检查是否匹配

test(text: str): bool

返回布尔值,指示整个或部分文本是否命中模式。test是最常用的"存在性"判断,也常被用于条件分支。

log(r.test("peach")); // true log(r.test("apple")); // false

底层实现直接委托给 JS 的RegExp.prototype.test(packages/@winglang/sdk/src/std/regex.ts)。

2.find:查找第一个匹配

find(text: str): str?

返回文本中第一个匹配到的子串;若没有匹配则返回nil,因此调用方通常需要配合??处理:

log(r.find("peach peach") ?? ""); // peach

源码中通过text.match(this._regex)取result[0]实现(packages/@winglang/sdk/src/std/regex.ts)。

3.findIndex:第一个匹配的起止索引

findIndex(text: str): MutArray<num>?

返回[start, end]形式的零基索引数组,标识第一个匹配子串在原文中的位置范围;无匹配时返回nil。注意结束索引是开区间(end不包含在匹配内),切片语义为text[start..end)。

log(Json.stringify(r.findIndex("peach"))); // [0,5]

实现为[result.index, result.index + result[0].length](packages/@winglang/sdk/src/std/regex.ts)。

4.findAll:查找所有不重叠匹配

findAll(text: str): MutArray<str>

返回模式在文本中所有不重叠匹配组成的数组;无匹配时返回空数组(而非nil)。"不重叠"意味着一旦找到一个匹配,下一次搜索从它的结束位置继续。

log(Json.stringify(r.findAll("peach punch pinch"))); // ["peach","punch","pinch"]

源码中先把正则转换为全局(g标志)版本,再用String.prototype.matchAll收集结果(packages/@winglang/sdk/src/std/regex.ts)。

5.findAllIndex:所有匹配的起止索引

findAllIndex(text: str): MutArray<MutArray<num>>

返回所有匹配的[start, end]索引数组的数组,零基索引,顺序与findAll一一对应:

log(Json.stringify(r.findAllIndex("peach punch pinch"))); // [[0,5],[6,11],[12,17]]

示例中三个词依次占位[0,5]、[6,11]、[12,17],注意空格被跳过(索引基于原始文本)。

6.findSubmatch:第一个匹配及其捕获组

findSubmatch(text: str): MutArray<str>?

返回一个数组:第一项是整个匹配,随后依次是每个捕获组(括号分组)匹配到的内容。若整体无匹配则返回nil。

log(Json.stringify(r.findSubmatch("peach punch"))); // ["peach","ea"]

对模式"p([a-z]+)ch"而言,"peach"中"ea"正好落入([a-z]+)捕获组,所以第二项是"ea"。实现为[result[0], ...result.slice(1)],即 JSmatch返回数组的展开(packages/@winglang/sdk/src/std/regex.ts)。

7.findSubmatchIndex:匹配与捕获组的起止索引

findSubmatchIndex(text: str): MutArray<MutArray<num>>?

在findSubmatch基础上进一步给出每个部分(整体匹配 + 各捕获组)在原文中的[start, end]索引:

log(Json.stringify(r.findSubmatchIndex("peach punch"))); // [[0,5],[1,3]]

这里第一项[0,5]是整体匹配"peach"的范围,第二项[1,3]是捕获组"ea"的范围("peach"中第 1~2 个字符)。实现上先取exec结果的整体范围,再用text.indexOf(submatch, 上次起始位置)逐个定位各捕获组(packages/@winglang/sdk/src/std/regex.ts)。若某个捕获组未参与匹配,其索引定位结果需要结合实际文本判断。

8.replaceAll:全局替换

replaceAll(text: str, replacement: str): str

将文本中所有匹配替换为replacement字符串并返回新文本。replacement中可以使用$1、$2等引用捕获组(与 JSString.prototype.replace规则一致)。

log(r.replaceAll("a peach", "<fruit>")); // a <fruit>

注意replaceAll是"正则全局替换",内部先把正则转换为全局版本再调用text.replace(globalRegex, replacement)(packages/@winglang/sdk/src/std/regex.ts)。

源码深读:getGlobalRegex与"全局化"策略

细心的读者会发现:findAll、findAllIndex、replaceAll三个方法都调用了私有辅助方法getGlobalRegex():

private getGlobalRegex(): RegExp { return this._regex.global ? this._regex : new RegExp(this._regex, "g"); }

(见 packages/@winglang/sdk/src/std/regex.ts)

其逻辑是:如果原始模式本身带有g标志(编译时传入,例如regex.compile("p([a-z]+)chg")这种写法在 JS 语法中等价于加g标志的写法),直接复用;否则基于原模式派生一个带g标志的新RegExp。这样既保证了"匹配所有"类方法的全局遍历语义,又不修改用户编译的原始对象。同时,test与find等单次匹配方法使用原始_regex,不受g标志状态影响(JS 中带g的exec/test有 lastIndex 副作用,Wing 通过这一分离设计规避了状态污染问题)。

在测试与断言中使用正则

Regex不仅可以直接在业务逻辑中使用,还内嵌于 Wing 的 expect 库。SDK 的expect模块提供了expect.match(actual, expected)与expect.doesNotMatch(actual, expected)两个断言,内部正是调用Regex.compile(expected).test(actual)实现的(见 packages/@winglang/sdk/src/expect/expect.ts):

bring expect; test "regex" { let r = regex.compile("p([a-z]+)ch"); expect.equal(r.test("peach"), true); expect.equal(r.find("peach peach"), "peach"); expect.equal(r.findAll("peach punch pinch"), ["peach", "punch", "pinch"]); expect.equal(r.findIndex("peach punch"), [0, 5]); expect.equal(r.findAllIndex("peach punch pinch"), [[0, 5], [6, 11], [12, 17]]); expect.equal(r.findSubmatch("peach punch"), ["peach", "ea"]); expect.equal(r.findSubmatchIndex("peach punch"), [[0, 5], [1, 3]]); expect.equal(r.replaceAll("a peach", "<fruit>"), "a <fruit>"); }

上面是官方 SDK 测试 tests/sdk_tests/std/regex.test.w 的完整内容,它把本文介绍的全部 8 个方法逐一用expect.equal断言,是验证各方法返回值类型与取值的最佳参考用例(覆盖了顶层代码与test块两种使用位置)。

与字符串方法replace/replaceAll的区分

Wing 的字符串类型也自带replace与replaceAll方法(packages/@winglang/sdk/src/std/string.ts),但二者与Regex.replaceAll有本质区别:

  • str.replace(search, replace)/str.replaceAll(search, replace):把search当作普通子串做首处 / 全局替换(SDK 中以宏方式编译到 JS 的同名方法);
  • Regex.replaceAll(text, replacement):把text当作被搜索文本,按正则模式做全局替换。

选择规则很简单:替换目标含通配符、捕获组等正则语法时用regex.compile(...).replaceAll(...);纯字面子串替换用str.replaceAll(...)。同时字符串侧没有"按正则替换"的便捷方法,这也是Regex类型存在的重要价值之一。

实战场景:从仓库测试看典型用法

在仓库的真实测试中,regex被广泛用于错误信息匹配、格式校验等场景,可作为实战参考:

  • 错误消息断言:tests/sdk_tests/fs/系列测试用regex.compile("^EEXIST: file already exists")、regex.compile("^ENOENT: no such file or directory")、regex.compile("^EISDIR: illegal operation on a directory")校验文件系统操作的报错前缀(见 tests/sdk_tests/fs/directory.test.w、tests/sdk_tests/fs/options.test.w);tests/sdk_tests/fs/json.test.w用regex.compile("^Unexpected token")校验 JSON 解析错误。
  • 权限位校验:tests/sdk_tests/fs/stat.test.w用regex.compile("[0-7]\{3\}")校验 Unix 权限位是否为三位八进制数字(注意 Wing 中\{的转义写法)。
  • 复杂 URL 模式:tests/valid/phase_independent_method_on_string.test.w 演示了编译一个完整的 URL 匹配正则,验证长模式字符串的书写方式(转义\/与\{1,256\}等)。

这些用例共同说明:Wing 的regex集成覆盖了"编译—匹配—断言"的完整链路,既可直接服务于业务逻辑,也能支撑测试断言,是一个开箱即用的生产级工具。

快速上手小结

需求方法返回类型无匹配时
是否存在匹配testboolfalse
第一个匹配findstr?nil
第一个匹配索引findIndexMutArray<num>?nil
所有匹配findAllMutArray<str>空数组
所有匹配索引findAllIndexMutArray<MutArray<num>>空数组
匹配 + 捕获组findSubmatchMutArray<str>?nil
匹配 + 捕获组索引findSubmatchIndexMutArray<MutArray<num>>?nil
全局替换replaceAllstr返回原文本

把main.w写入项目后执行wing it即可在 Wing 控制台看到与官方示例一致的输出;需要自动化断言时,可直接复用 tests/sdk_tests/std/regex.test.w 的写法。更多细节与完整签名可查阅 docs/api/04-standard-library/std/regex.md。

  • 编程语言
  • 编译器
  • 云原生
  • 语言运行时

【免费下载链接】wing

A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡

项目地址:https://gitcode.com/gh_mirrors/wi/wing
点击查看免费下载

相关推荐

上一篇:kubernetes Python客户端服务网格集成:Istio与Linkerd
下一篇:戴森球计划终极工厂蓝图指南:8000+优化布局快速提升生产效率

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进