正则表达式的真正力量 (2012)
作为一个经常在 StackOverflow 上关注 PHP 标签的人,我经常看到关于如何使用正则表达式解析 HTML 某个特定方面的问题。对这样问题的一个常见回答是:你无法使用正则表达式解析 HTML,因为 HTML 不是正则的。请使用 XML 解析器。这个说法在提问的背景下有一点误导,甚至可以说绝对错误。本文将尝试展示现代正则表达式的真正强大。那么什么才是“正则”?在形式语言理论的上下文中,当某个语法的所有产生规则具有以下形式之一时,就称其为“正则”: B -> a B -> aC B -> ε 你可以把这些 -> 规则理解为“左侧可以被右侧替换”。所以第一个规则可以理解为“B 可以替换为 a”,第二个规则“B 可以替换为 aC”,第三个规则“B 可以替换为空字符串”(ε 是表示空字符串的符号)。那么 B、C 和 a 有什么含义呢?按照约定,大写字母表示所谓的“非终结符”——可以进一步分解的符号——而小写字母表示“终结符”——无法进一步分解的符号。听起来可能有些抽象,我们来看一个例子:定义自然数的语法。 N -> 0 N -> 1 N -> 2 N -> 3 N -> 4 N -> 5 N -> 6 N -> 7 N -> 8 N -> 9 N -> 0N N -> 1N N -> 2N N -> 3N N -> 4N N -> 5N N -> 6N N -> 7N N -> 8N N -> 9N 这个语法的含义是:一个自然数(N)是……其中一个数字 0 到 9 或者……一个数字 0 到 9 后面跟着另一个自然数(N)。在这个例子中,数字 0 到 9 是终结符(因为它们无法进一步分解),而 N 则是唯一的非终结符(因为它可以并且确实进一步分解)。如果你再看看这些规则并将它们与上面对正则语法的定义进行比较,你会发现它们符合条件:前十条规则的形式是 B -> a,后十条规则则遵循 B -> aC 的形式。因此,定义自然数的语法是正则的。你可能还会注意到,尽管上述语法定义了如此简单的东西,但它已经相当臃肿。如果我们能以更简洁的方式表达同样的概念,不是更好吗?这就是正则表达式的用武之地:上述语法等价于正则表达式 [0-9]+(简直简单得多)。而这种转换可以在任何正则语法上进行:每个正则语法都有一个对应的正则表达式来定义其所有有效字符串。正则表达式可以匹配什么?因此出现了这样的问题:正则表达式是否只能匹配正则语法,或者它们是否也可以匹配更多?对此的回答是既是也不是:在形式语法意义上,正则表达式(几乎按定义可知)只能解析正则语法,而无法解析更多。但是,当程序员谈论“正则表达式”时,他们并不是在谈论形式语法。他们谈论的是他们的语言实现的正则表达式派生物。而这些正则表达式的实现与原始的正则概念仅有微弱的关系。任何现代正则表达式的风格都可以匹配远远超过正则语言的东西。到底能匹配多少,这就是本文其余部分要讨论的内容。为了简单起见,我将在接下来的内容中专注于 PCRE 正则表达式实现,因为我对它最熟悉(因为它被 PHP 使用)。不过,大多数其他正则表达式实现也非常相似,因此大多数内容也适用于它们。语言层次 为了分析正则表达式可以和不能匹配的内容,我们首先需要看看还有哪些其他类型的语言。一个好的起点是乔姆斯基层次: 乔姆斯基层次: /------------------------------------------- | | | 可递归枚举语言 | 类型 0 | | | /-----------------------------------/ | | | 上下文相关语言 | 类型 1 | | | /---------------------------/ | | | 上下文无关语言 | 类型 2 | | | /-------------------/ | | | 正则语言 | 类型 3 | | | /-------------------/ | | | | | | | | -------------------------------------------/ 如你所见,乔姆斯基层次将形式语言分为四种类型:正则语言(类型 3)是功能最弱的,其次是上下文无关语言(类型 2)、上下文相关语言(类型 1),最后是全能的可递归枚举语言(类型 0)。乔姆斯基层次是包含层次,因此上面图片的小框完全包含在大框内。例如,每个正则语言也是上下文无关语言(但反之则不然!)。所以,让我们在这个层次上向上迈出一步:我们已经知道正则表达式可以...
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡