1. 程式人生 > >正則表示式的真正威力(0)

正則表示式的真正威力(0)

原文

我在瀏覽StackOverflow上關於PHP的問題時經常看到有人提問如何使用正則表示式解析HTML。對於該問題的回答通常是這樣的:

你不能使用正則表示式解析HTML,因為HTML不是正則的。使用XML解析器吧。

這種回答——在該問題的語境下——是有誤導性的甚至是完全錯誤的。接下來我會展示現代正則表示式是多麼強大。

“正則”到底是什麼意思

形式語言理論中,說一個東西是“正則(regular)”那麼其語法的產生式規則必須滿足如下形式之一:

B -> a
B -> aC
B -> ε

這些帶箭頭的規則可以這樣讀,“左邊可以被右邊替換”。所以第一條規則是“B可以用a替換”,第二條是“B可以用aC替換”,第三條是“B可以用空字串替換”(ε是代表空字串的符號)。

BCa又代表什麼呢?根據慣例,大寫字母表示“非終結符”——可以進一步分解的符號,小寫字母表示“終結符”——不能進一步分解的符號。

這麼說可能有點抽象,我們來看一個例子:自然數的語法定義如下。

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的任意一個數字後面再加上一個自然數

這個例子中數字0到9就是終結符(不能進一步分解)並且N是唯一的非終結符(可以進一步分解)。

如果將上面自然數的語法規則和正則語法的定義比較一下,你就會發現自然數語法規則滿足標準:前面十條規則符合形式B -> a並且接下來的十條規則符合形式B -> aC。因此定義自然數的語法是正則的。

你可能還注意到了,定義這樣一個簡單的東西的語法就顯得很臃腫。如何用更加簡潔的方式來表達相同的概念呢?

這就是正則表示式派上用場的時候了:上面的語法等同於正則表示式[0-9]+

(真是簡潔太多了)。對於每一種正則的語法都可以進行這種轉換:每一種正則的語法都有相對應的正則表示式來定義其所有合法的字串。

正則表示式可以匹配什麼

那麼問題就來了,正則表示式只可以匹配正則的語法嗎?答案是肯定也是否定的:

按照形式語法中的定義來看正則表示式就只是用於解析正則的語法。然而當程式設計師談論“正則表示式”時,不是說的形式語法,指的是他們所使用的程式語言實現的正則表示式變體。並且這些正則實現和原始的正則式概念沒多大關係。

任何一個現代正則表示式實現能匹配的語言遠超過正則語言。接下來就要討論正則的能力界限到底在哪兒。

為了方便討論,我將會專注於PCRE的正則實現,因為我對其瞭解最多(PHP使用該實現)。大部分其他正則實現是相似的,所以下面的討論也同樣適用。

語言層級

為了分析正則表示式能匹配什麼和不能匹配什麼,先了解一下除了正則語言還存在哪些其他型別的語言。下面是喬姆斯基譜系

Chomsky hierarchy:

/-------------------------------------------\
|                                           |
|     Recursively enumerable languages      | Type 0
|                                           |
|   /-----------------------------------\   |
|   |                                   |   |
|   |    Context-sensitive languages    |   | Type 1
|   |                                   |   |
|   |   /---------------------------\   |   |
|   |   |                           |   |   |
|   |   |  Context-free languages   |   |   | Type 2
|   |   |                           |   |   |
|   |   |   /-------------------\   |   |   |
|   |   |   | Regular languages |   |   |   | Type 3
|   |   |   \-------------------/   |   |   |
|   |   \---------------------------/   |   |
|   \-----------------------------------/   |
\-------------------------------------------/

喬姆斯基將語言劃分為四種類型:

正則語言(型別3)是最弱的,然後是上下文無關語言(型別2),上下文相關語言(型別1)以及最後無所不能的遞迴可列舉語言(型別0)。

喬姆斯基譜系是一個包含性的層級,上圖中小盒子完全包括在更大的盒子裡面。例如每一個正則語言同時也是一個上下文無關語言(反之則不成立)。

因此我們在層級中向上走一級:我們已經知道正則表示式可以匹配任意正則語言。然而也可以匹配上下文無關語言嗎?

(請注意:我在說“正則表示式”的時候是從程式設計師的視角來看的,而不是形式語言理論的視角。)