简单正则表达式实现引擎

  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的下一个字符。递归的深度不会超过匹配模式的长度,而通常情况下匹配模式的长度都是很短的,因此不会出现耗尽内存空间的危险。

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