真实管线:parseGlob → AST → regex 源串 → 引擎执行
glob 不自带匹配算法。主流实现(JS 的 minimatch、Python 的 glob 模块)走同一条路:把 glob 翻译成一条正则,再交给正则引擎执行。本页直接拆开本仓库的真实实现 @vega/parsing/glob——前面 01–04 页的全部 demo 都由它驱动:
四个阶段各居一个源文件:parser.ts(字符级递归下降 → AST)、ast.ts(节点类型)、translate.ts(AST → regex 源串)、matcher.ts(调 @vega/parsing/regex 的 compile 拿 matcher)。glob
这边不实现任何匹配算法——回溯、字符类、锚定全由 regex 引擎负责,这正是「编译到已有 IR」的取舍。本页假设已熟悉 01–04 页的语法语义;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)。相邻的两个
[^/]* 之间只隔一个字面字符就足以爆炸:*a*a*a*a*a*a*a*a*b 译出的正则喂 a 重复串,实测每加两个字符耗时翻倍——n=24 时 9.9ms、n=28 时 44ms、n=32 时 159ms(Chrome 与 Node 同源的 V8,核对于 2026-08)。minimatch 历史上的 ReDoS 通告正出自这一类 pattern。上面的 verdict
还把同一条源串喂给浏览器原生 RegExp 复核——产物就是一条普通正则。
2 · 另一条实现路线:段内 * 的双指针回溯
不经正则也能实现 glob:POSIX 的 fnmatch(3) 直接在 pattern 与字符串上匹配(注意 Python 标准库的 fnmatch 不是这一路:它转成 .* 形式的正则、* 跨 /、也不遵守 dot 规则,路径感知的活由 glob 与 pathlib 承担)。只有
* 和 ? 时,有个不用递归的经典算法——两个指针 + 一对寄存器:
扫 pattern、s 扫 str;遇到 * 时先假设它消费 0 个字符,并用 star / mark 记下「失配时回到此处,让它多消费一个」。下面单步看它怎么走——这也是上面 AST 那条 regex 里 [^/]* 在引擎内部回溯的微观图景。
这条双指针算法本身没有指数爆炸。 mark 单调递增、s 重置后只会更靠后,所以总步数有
的上界。要留意的是它只覆盖 * 与 ?,且与本页走正则的实现是两条路——上一节实测的回溯爆炸正发生在正则那一路。它和 字符串匹配里的朴素扫描同源,只是多了一个 * 的「记忆点」。