← glob · 文件路径的迷你匹配语言 / 真实管线:parseGlob → AST → regex 源串 → 引擎执行 待审核 5 / 6
05 · 引擎

真实管线:parseGlob → AST → regex 源串 → 引擎执行

glob 不自带匹配算法。主流实现(JS 的 minimatch、Python 的 fnmatch)走同一条路:把 glob 翻译成一条正则,再交给正则引擎执行。本页直接拆开本仓库的真实实现 @vega/parsing/glob——前面 0104 页的全部 demo 都由它驱动:

四个阶段各居一个源文件:parser.ts(字符级递归下降 → AST)、ast.ts(节点类型)、translate.ts(AST → regex 源串)、matcher.ts(调 @vega/parsing/regexcompile 拿 matcher)。glob 这边不实现任何匹配算法——回溯、字符类、锚定全由 regex 引擎负责,这正是「编译到已有 IR」的取舍。本页假设已熟悉 0104 页的语法语义;regex 侧的语法与引擎见 regex 系列

1 · 实时拆解:AST 与转译产物

翻译规则不长:字面量做 regex 元字符转义;*[^/]*(段内任意,刻意排除 /)、?[^/][!a-z][^/a-z](取反类额外排除 /,防止越段); {a,b} → 分支 (?:a|b);独占整段的 ** 与相邻的 / 合并,生成 (?:seg/)* 一类「零或多段」结构;dot 规则就是在通配符起头的段前插一枚 否定先行断言 (?!\.);最后两头加 ^…$ 锚定(glob 是整体匹配,不是含子串)。解析失败时返回 Result 形态的 { ok:false, error:{ message, pos } }——试试预设里两个非法 pattern。

glob 是「刻意收窄的正则子集」。 注意译文里通配全用 [^/]* 而非正则的 .*——/ 被刻意排除在外,这正是「不跨段」的来源。glob 也没有反向引用、没有无界嵌套量词,所以不会有 灾难性回溯 (ReDoS)。它用表达力换来了可预测的性能。上面的 verdict 还把同一条源串喂给浏览器原生 RegExp 复核——产物就是一条普通正则。

2 · 另一条实现路线:段内 * 的双指针回溯

不经正则也能实现 glob:fnmatch 风格的实现直接在 pattern 与字符串上匹配。只有 *? 时,有个不用递归的经典算法——两个指针 + 一对寄存器:p 扫 pattern、s 扫 str;遇到 *先假设它消费 0 个字符,并用 star / mark 记下「失配时回到这里,让它多消费一个」。下面单步看它怎么走——这也是上面 AST 那条 regex 里 [^/]* 在引擎内部回溯的微观图景。

这就是 glob 不怕 ReDoS 的微观原因。 mark 单调递增、s 重置后只会更靠后,所以总步数有 O(pat×str)O(|pat| \times |str|) 的上界——不会像正则的嵌套量词那样指数爆炸。它和 字符串匹配里的朴素扫描同源,只是多了一个 * 的「记忆点」。