造轮子工程 · 第 4 章 造正则引擎 章节摘要:正则表达式看起来像「玄学咒语」——一串 看不出它是怎么工作的。但当你亲手造一个正则引擎,会发现它背后的原理出奇地优雅:把正则表达式翻译成一个「状态机」(有限自动机),然后用这个状态机去匹配文本。本章导读正则引擎的核心:从理论出发,讲清 NFA(非确定有限自动机)与 DFA(确定有限自动机)的差别、Thompson 构造法(把正则编译成 NFA)、子集构造法(NFA 转 DFA)、回溯式匹配(为什么很多引擎用回溯而非 DFA)、字符类与捕获组的实现。
章节摘要:正则表达式看起来像「玄学咒语」——一串
^[A-Z][a-z]+\d{2,3}$看不出它是怎么工作的。但当你亲手造一个正则引擎,会发现它背后的原理出奇地优雅:把正则表达式翻译成一个「状态机」(有限自动机),然后用这个状态机去匹配文本。本章导读正则引擎的核心:从理论出发,讲清 NFA(非确定有限自动机)与 DFA(确定有限自动机)的差别、Thompson 构造法(把正则编译成 NFA)、子集构造法(NFA 转 DFA)、回溯式匹配(为什么很多引擎用回溯而非 DFA)、字符类与捕获组的实现。造完之后,你会获得两层收益:一是彻底理解grep/sed/awk背后的机制(呼应《Linux 命令》第 3 章),二是理解「编译器前端」的核心思想——把一种语言(正则)翻译成另一种可执行的形式(状态机),这正是第 9 章(造编译器)的微缩预演。
阅读完本章,你应当能够:
[a-z])、量词(* + ? {m,n})、锚点(^ $)、分组与选择((ab|cd))。(...) 匹配到的文本),理解它为什么让引擎从 DFA 倒退回回溯式。正则引擎的本质是「编译 + 执行」:先把正则编译成状态机(这是「编译器前端」的微缩版),再用状态机跑文本。理解这条链路,既治好了正则玄学恐惧症,也为第 9 章的编译器打下了第一根桩。
正则表达式在数学上等价于「正则语言」,而正则语言等价于「有限自动机能识别的语言」。讲清这个理论对应——正则是「描述」,自动机是「执行」,两者等价。这一节是全章的认知地基。
NFA(非确定):一个状态对同一输入可有多条出边,需要「并行/猜测」;DFA(确定):一个状态一个输入一个去向,匹配快但状态可能爆炸。讲清两者在「构建成本」与「匹配速度」上的权衡,以及为什么没有「绝对更好」的一方。
肯·汤普森(Ken Thompson)1968 年提出的经典算法:对正则的每个构造(连接、选择、闭包)给出对应的 NFA 片段,递归组合。这是把「正则」翻译成「可执行形式」的核心一步,也是全章最难也最美的部分。
两条匹配路线:DFA 式(把 NFA 转成 DFA,匹配快但不支持捕获组/反向引用)、回溯式(深搜尝试,支持丰富特性但最坏情况慢)。多数生产引擎(PCRE、Python re)选回溯式——支持反向引用是关键原因。
动手实现:[a-z](字符类)、* + ? {m,n}(量词)、^ $(锚点)、.(任意字符)。这一节从「会写正则」上升到「会让计算机理解正则」,呼应《Linux 命令》第 3 章 grep 的 -E 扩展正则。
(ab|cd)(分组与选择)、(...)(捕获组,记住匹配文本以便回引)、\1(反向引用)。重点讲清捕获组为什么让引擎从 DFA 倒退回回溯——这是「正则从理论走向工程」的关键转折。
正则不能匹配「配对的括号」「嵌套的 HTML 标签」这类结构,理论根因是「有限自动机没有栈」。讲清这个边界,以及为什么这类问题需要上下文无关文法(第 9 章编译器的领地)。
本章遵循「理论 → 翻译 → 匹配 → 实现 → 边界」的递进路径,前两节是理论,中间三节是实现,最后是边界:
正则=自动机 (01) ── 认知地基 │ ▼ NFA vs DFA (02) ── 两种自动机的取舍 │ ▼ Thompson 构造 (03) ── 把正则编译成 NFA(全章难点与美感) │ ▼ DFA式 vs 回溯式 (04) ── 两条匹配路线 │ ▼ 基础语法实现 (05) ── 字符类/量词/锚点 │ ▼ 分组与捕获 (06) ── 捕获组让引擎倒退回回溯 │ ▼ 正则的边界 (07) ── 为什么配对括号匹配不了 │ ▼ 第 5 章:从"匹配文本"上升到"编辑文本"——造文本编辑器
这是一条「从理论到实现再到边界」的完整路线:01-02 是认知地基(不学这个,后续都是黑箱);03 是全章核心算法;04 是路线选择;05-06 是动手实现;07 则把视角拉到「正则的能力边界」,自然引出第 9 章的编译器——那里用「带栈的自动机(下推自动机)」突破了正则的限制。
前置知识:
本章为后续章节奠定的基础: