第 4 章 · 07 正则的边界:为什么不能匹配配对括号 本节摘要:这是第 4 章的收尾节,讲一个常被问的问题:「为什么正则不能匹配配对的括号/嵌套的 HTML 标签?」答案藏在第 01 节的理论里:有限自动机没有「栈」,记不住嵌套深度。本节要把这个边界讲透,并指出:这类问题需要「下推自动机」(带栈的自动机),也就是「上下文无关文法」——这正是第 9 章编译器的领地。所以这一节既是第 4 章的句号,也是通往第 9 章的桥。 内容来源:基于形式语言理论整理的导读。 学习目标 阅读完本节,你应当能够: 说清「正则不能匹配配对括号/嵌套结构」这个事实。 解释根因:有限自动机没有栈,无法记录嵌套深度。
本节摘要:这是第 4 章的收尾节,讲一个常被问的问题:「为什么正则不能匹配配对的括号/嵌套的 HTML 标签?」答案藏在第 01 节的理论里:有限自动机没有「栈」,记不住嵌套深度。本节要把这个边界讲透,并指出:这类问题需要「下推自动机」(带栈的自动机),也就是「上下文无关文法」——这正是第 9 章编译器的领地。所以这一节既是第 4 章的句号,也是通往第 9 章的桥。
内容来源:基于形式语言理论整理的导读。
阅读完本节,你应当能够:
无数新手尝试用正则解析 HTML、匹配嵌套括号、解析 JSON,结果写出脆弱的「看似能用」的正则,在边界情况上崩盘。根本原因:正则的理论能力不足以处理「嵌套结构」。理解这个边界,你才能:
考虑语言 L = { 所有的括号串,且括号配对 },比如 ()、(())、()()、((())) 都是,但 (、)(、(() 不是。
要用正则匹配,你需要记住「当前未闭合的左括号有几个」——可能 1 个、2 个、N 个。但有限自动机只有有限个状态,无法记住「任意多个」。所以,无论你怎么写正则(\([^)]*\) 之类的尝试),都无法正确匹配任意深度的配对括号——它要么漏匹配深层、要么错匹配不配对的。
第 01 节讲的有限自动机,只有「状态」这一个记忆手段,且状态数有限。匹配配对括号需要「记住嵌套深度」,深度任意——这要求无限种状态(每种深度一种),有限自动机做不到。
Noam Chomsky 把形式语言分成四级,能力递增:
| 级别 | 语言类型 | 对应机器 | 例子 |
|---|---|---|---|
| 3 | 正则语言 | 有限自动机 | ab*c、标识符 |
| 2 | 上下文无关语言 | 下推自动机(带栈) | 配对括号、算术表达式、JSON 结构 |
| 1 | 上下文有关语言 | 线性有界自动机 | a^n b^n c^n |
| 0 | 递归可枚举语言 | 图灵机 | 任意可计算 |
正则(第 3 级)是最弱的——它能匹配线性模式(一行扫过去),但不能匹配嵌套结构。配对括号、算术表达式、JSON 这类「需要数嵌套」的,属于第 2 级「上下文无关语言」,需要下推自动机(带栈的自动机)——栈用来记录嵌套层级,遇到左括号压栈、遇到右括号弹栈。
处理配对括号、HTML、JSON、代码语法,需要「带栈」的解析器——也就是编译器的「语法分析器」(第 9 章)。所以:
(()()) 这类配对括号,体会它做不到。( 压栈,遇 ) 弹栈,栈空=配对。这是第 9 章解析器的基础。至此第 4 章「造正则引擎」全部讲完。本章从理论(正则=自动机)出发,经 NFA/DFA、Thompson 构造、匹配策略、语法实现、捕获组,落到能力边界。你不仅造了一个正则引擎,更预演了第 9 章编译器的前端思想。下一章我们造文本编辑器——把「匹配文本」升级到「交互式编辑文本」。