簡單正則表達式實現引擎

  1. /* match: search for regexp anywhere in text */ 
  2. int match(char *regexp, char *text) {         
  3.     if (regexp[0] == '^')   
  4.         return matchhere(regexp+1, text);  
  5.         
  6.     do {   
  7.         /* must look even if string is empty */   
  8.         if (matchhere(regexp, text))    
  9.             return 1;        
  10.     } while (*text++ != '/0');   
  11.     
  12.     return 0;  
  13. }  
  14. /* matchhere: search for regexp at beginning of text */ 
  15. int matchhere(char *regexp, char *text) {   
  16.     if (regexp[0] == '/0')           
  17.         return 1;         
  18.     if (regexp[1] == '*')    
  19.         return matchstar(regexp[0], regexp+2, text);    
  20.     if (regexp[0] == '$' && regexp[1] == '/0')     
  21.         return *text == '/0';     
  22.     if (*text!='/0' && (regexp[0]=='.' || regexp[0]==*text))    
  23.         return matchhere(regexp+1, text+1);     
  24.     return 0;   
  25. }   
  26. /* matchstar: search for c*regexp at beginning of text */    
  27. int matchstar(int c, char *regexp, char *text) {   
  28.     do {
  29.         /* a * matches zero or more instances */   
  30.         if (matchhere(regexp, text))     
  31.             return 1;       
  32.     } while (*text != '/0' && (*text++ == c || c == '.'));  
  33.     
  34.     return 0;    
  35. }

這一小段代碼就是c語言實現的簡單的正則表達式引擎,雖然簡單,但是包含了大部分日常運用到的功能。

 

解釋:

函數match(regexp,text)用來判斷文本中是否出現正則表達式;如果找到了一個匹配的正則表達式則返回1,否則返回0。如果有多個匹配的正則表達式,那麼函數將找到文本中最左邊的並且最短的那個。

match函數中的基本操作簡單明瞭。如果正則表達式中的第一個字符是^(固定位置的匹配),那麼匹配就一定要出現在字符串的開頭。也就是說,如果正則表達式是^xyz,那麼僅當xyz出現在文本的開頭而不是中間的某個位置時纔會匹配成功。在代碼中通過把正則表達式的剩餘部分與文本的起始位置而不是其他地方進行匹配來判斷。如果第一個字符不是^,那麼正則表達式就可以在字符串中的任意位置上進行匹配。在代碼中通過把模式依次與文本中的每個字符位置進行匹配來判斷。如果存在多個匹配,那麼代碼只會識別第一個(最左邊的)匹配。也就是說,如果則在表達式是xyz,那麼將會匹配第一次出現的xyz,而且不考慮這個匹配出現在什麼位置上。

注意,對輸入字符串的推進操作是在一個do-while循環中進行的,這種結構在C程序中使用相對較少。在代碼中使用do-while而不是while通常會帶來疑問:爲什麼不在循環的起始處判斷循環條件,而是在循環末尾當執行完了某個操作之後才進行判斷呢?不過,這裏的判斷是正確的:由於*運算符允許零長度的匹配,因此我們首先需要判斷是否存在一個空的匹配。

大部分的匹配工作是在matchhere(regexp,text)函數中完成的,這個函數將判斷正則表達式與文本的開頭部分是否匹配。函數matchhere把正則表達式的第一個字符與文本的第一個字符進行匹配。如果匹配失敗,那麼在這個文本位置上就不存在匹配,因此matchhere將返回0。然而,如果匹配成功了,函數將推進到正則表達式的下一個字符和文本的下一個字符繼續進行匹配。這是通過遞歸地調用matchhere函數來實現的。

由於存在着一些特殊的情況,以及需要設置終止遞歸的條件。因此實際的處理過程要更爲複雜些最簡單的情況就是,當正則表達式推進到末尾時(regexp[0] == '/0'),所有前面的判斷都成功了,那麼這個正則表達式就與文本匹配。

如果正則表達式是一個字符後面跟着一個*,那麼將會調用matchstar來判斷閉包(closure)是否匹配。函數matchstar(c, regexp, text)將嘗試匹配重複的文本字符c,從零重複開始並且不斷累加,直到匹配text的剩餘字符,如果匹配失敗,那麼函數就認爲不存在匹配。這個算法將識別出一個“最短的匹配”,這對簡單的模式匹配來說是很好的,例如grep,這種情況下的主要問題是儘可能快地找到一個匹配。而對於文本編輯器來說,“最長的匹配”則是更爲直觀,且肯定是更好的,因爲通常需要對匹配的文本進行替換。在目前許多的正則表達式庫中同時提供了這兩種方法,在《The Practice of Programming》一書中給出了基於本例中matchstar函數的一種簡單變形,我們在後面將給出這種形式。

如果在正則表達式的末尾包含了一個$,那麼僅當text此時位於末尾時纔會匹配成功:

    if (regexp[0] == '$' && regexp[1] == '/0')

        return *text == '/0';

如果沒有包含$,並且如果當前不是處於text字符串的末尾(也就是說,*text!='/0')並且如果text字符串的第一個字符匹配正則表達式的第一個字符,那麼到現在爲止都是沒有問題的;我們將接着判斷正則表達式的下一個字符是否匹配text的下一個字符,這是通過遞歸調用matchhere函數來實現的。這個遞歸調用不僅是本算法的核心,也是這段代碼如此緊湊和整潔的原因。

如果所有這些匹配嘗試都失敗了,那麼正則表達式和text在這個位置上就不存在匹配,因此函數matchhere將返回0。

在這段代碼中大量地使用了C指針。在遞歸的每個階段,如果存在某個字符匹配,那麼在隨後的遞歸調用中將執行指針算法(例如,regexp+1 and text+1),這樣在隨後的函數調用中,參數就是正則表達式的下一個字符和text的下一個字符。遞歸的深度不會超過匹配模式的長度,而通常情況下匹配模式的長度都是很短的,因此不會出現耗盡內存空間的危險。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章