本文将介绍简易正则转nfa的一个python实现。   
      
前置知识 
简易正则转NFA实现 简介   NFA,即非确定性有限状态自动机。他将文本匹配转化为状态的转移,给予初态与终态。当一串文本随着输入,能成功从初态转换到终止状态,即为匹配成功。并且由于其为非确定型,相同的文本匹配模式,可能存在的NFA形态会很多。   正则表达式,即一串用于描述文本匹配规则的文本。通过特殊字符组合成规则字符串,用于过滤文本。   本项目将实现使用python,将简易正则,即包含连接运算,克林星号运算,或操作运算的正则表达式,转换到等价NFA的过程。  
NFA实现   状态机本身相当于图,包含边集与点集。由于我们不需要实际进行匹配,只需要转换,因此可以省略部分点,只保留起点与终点。但我们需要展示所有边,因而边不可以省略。   根据上述描述,我们来定义类:
1 2 3 4 5 6 class  NFA :    def  __init__ (self,ch ):         self.head = 1          self.tail = 2          self.lines = [[1 ,ch,2 ]] 
 
  初始化方法,起点与终点,初始化的边。我们便拥有了一个最简单的NFA。在此基础上,我们再来实现相应运算功能。  
连接操作    连接操作最为简单。我们只需要将第二个NFA起点连接到第一个NFA终点即可。注意结点序号问题。我们需要标识每个结点,这时候只需要将接在后面的NFA的每个结点的序号增加前NFA节点个数-1的数量即可。  
1 2 3 4 5 def  and_NFA (self,NFA_b ):    NFA_b.add_num(len (self)-1 )     for  line in  NFA_b.lines:         self.lines.append(line)     self.tail = NFA_b.tail 
 
或运算操作    或运算稍为复杂,但也不难。或运算相当于将两个NFA并联起来。我们只需要增加头节点,尾结点,再在中间并联两个NFA即可。同样,注意结点序号问题。  
1 2 3 4 5 6 7 8 9 10 11 def  or_NFA (self,NFA_b ):    self.add_num(1 )     NFA_b.add_num(len (self)+1 )     for  line in  NFA_b.lines:         self.lines.append(line)     self.lines.append([1 ,'~' ,self.head])     self.lines.append([1 ,'~' ,NFA_b.head])     self.lines.append([self.tail,'~' ,NFA_b.tail+1 ])     self.lines.append([NFA_b.tail,'~' ,NFA_b.tail+1 ])     self.tail = NFA_b.tail+1      self.head = 1  
 
克林星号运算    克林星号运算所表示的NFA并不复杂。它表示前面的字符可以匹配0次或者多次。这里我们需要有一个循环的思想。即起点可以直接跳到终点,终点也可以直接跳到起点。那么增加两条边即可。  
1 2 3 def  kleen_NFA (self ):    self.lines.append([self.head,'~' ,self.tail])     self.lines.append([self.tail,'~' ,self.head])   
 
  由于我们只实现简易正则,实现三个运算即可。在完成NFA方法构建后,我们来关注正则的解析问题。  
正则表达式解析   正则的解析如同计算器一样,需要符号栈与文本栈,并且要处理优先级问题。但我们只实现了三种运算,优先级问题并不困难。   对于克林运算,我们可以直接对文本栈顶进行运算。它的优先级最高。   对于连接运算,我们需要放入符号栈,等待遇到或运算清理栈时候来处理。   对于或运算,当检测到或时候,我们可以将栈顶的连接运算全部清空,直到遇到或运算或者左括号为止。再放入符号栈。   对于括号运算,左括号直接进入符号栈,右括号则清理文本栈,直到遇到左括号为止。   当算法结束后,若文本栈未规约完成,则继续弹出两个文本与一个符号,进行运算,直至规约完成。   对于上述规则,我们可以得到代码.此处连接使用点来代替。calculate函数代表将符号作用于文本栈:  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 def  parse (s ):    sign_stack = []     nfa_stack = []     for  ch in  s:         if  ch=='(' :             sign_stack.append(ch)                      elif  ch == ')' :             while  len (sign_stack) != 0  and  sign_stack[-1 ] != '(' :                 tmp = sign_stack[-1 ]                 sign_stack.pop()                 nfa_stack[-2 ] = calculate(tmp,nfa_stack[-2 ],nfa_stack[-1 ])                 nfa_stack.pop()         elif  ch == '*' :             nfa_stack[-1 ] = calculate(ch,nfa_stack[-1 ],None )         elif  ch == '|' :             while  len (sign_stack) != 0  and  sign_stack[-1 ] == '.' :                 sign_stack.pop()                 nfa_stack[-2 ] = calculate('.' ,nfa_stack[-2 ],nfa_stack[-1 ])                 nfa_stack.pop()             sign_stack.append(ch)         elif  ch == '.' :             sign_stack.append(ch)         else  :             nfa_stack.append(NFA(ch))     while  len (nfa_stack)>1 :         ch = sign_stack[-1 ]         sign_stack.pop()         nfa_stack[-2 ] = calculate(ch,nfa_stack[-2 ],nfa_stack[-1 ])         nfa_stack.pop()     return  nfa_stack[0 ] 
 
One more thing    最后一个问题。在默认的正则表达式中没有连接运算符。我们可以硬编码一下增加连接运算符的规则,然后将原来的表达式转换为可以解析的表达式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def  RegexToNFA (s ):    ret = s[0 ]     for  i in  range (1 ,len (s)):         pre = s[i-1 ]         now = s[i]         if  isCharacter(pre) and  isCharacter(now):             ret +='.'              ret += now         elif  isCharacter(pre) and  now == '(' :             ret +='.'              ret += now         elif  pre == ')'  and  isCharacter(now):             ret +='.'              ret += now         elif  pre == '*'  and  isCharacter(now):                ret +='.'              ret += now         elif  pre == '*'  and  now == '(' :             ret +='.'              ret += now         else  :             ret += now     print(ret)     return  parse(ret) 
 
  规则阐述不必多说。上述可能有遗漏,欢迎指正。  
总结   本文提供了一个简单正则转NFA的python实现。之所以使用python是因为写着简单,数据结构随意。在最开始我想拿scheme写,但api还是不熟悉。最后选用了python。   本文技术含量不高,纯粹为课堂记录。