如今研究2.2)根据正则表达式规则rule生成NFA(非肯定有穷自动机)。正则表达式
从spec 文件(rule)区域读取/解析输入的函数为CLexGen.userRules(), 该函数中其实包含了
2.2)-2.6)的多步,为突出研究各个生成步骤,分解为几个小步骤分别看。实际userRules()
函数中解析(parse)正则表达式和生成NFA是在一块儿的,函数名为CMakeNfa.thompson()。算法
函数CMakeNfa.thompson(),顾名思义,使用的是thompson构造法来从regex中构造出NFA,
参见龙书算法3.3。函数
thompson()函数中调用machine()函数来解析rule,生成NFA,machine()是一个递归向下
正则表达式的解析器的实现,咱们以生成式的方式写出其对应的生成式以下:orm
(1) machine -> rule while(rule) 一部NFA状态机由多条rule构成,while(rule)表示
这是使用尾递归方式实现的rule*。一个spec能够没有任何规则。递归
(2) rule -> state expr accept 一条rule前面是lex state,后面是accept action。
实际上 state部分在machine产生式(函数)中处理的,写在这里是方便看。io
(3) expr -> cat_expr while(cat_expr) 一个expr是多个cat_expr OR 或构成的。
例如 a|b, 则cat_expr1=a, cat_expr2=bclass
(4) cat_expr -> factor while(factor) 一个cat_expr是多个factor CONCAT链接构成的。
例如 ab, 则factor1=a, factor2=b扩展
(5) factor -> term[*+?] 一个factor是一个term加上可选的Kleene算符*构成,+?是*
的变化形式。语法
(6) term -> normal_char 任意普通字符是一个term
| '.' '.' 符号匹配任意字符
| '[' char_class ']' 字符类匹配该类的任意字符
| '(' expr ')' 括号里面的expr是一个独立term,括号经常使用来改变算符优先级im
以上产生式的左侧名字也即CMakeNfa类的函数名,经过这组递归向下正则表达式解析
处理,正则表达式被按照thompson算法构造为一个内部的树结构。下面用龙书上的例子
来举例,生成正则表达式(a|b)*abb的内部表示。
NFA 的一个状态在JLex表示一个CNfa类的一个实例(instance),CNfa类定义以下:
class CNfa
m_edge -- 若是>=0表示是输入的值;也即当前状态在此输入下将转移(下述)
=CNfa.CCL=-1 表示是一组输入,使用m_set保存该组输入(如[0-9])
=CNfa.EMPTY=-2 表示此状态没有转移(无接受任何输入)
=CNfa.EPSILON(ε)=-3 表示是空串ε输入。只有此类型才可能有两个转移目标状态
m_set -- 若是输入边的类型为字符类(m_edge==CNfa.CCL), CCL=Character CLass时,使用
m_set保存是哪些输入,如[0-9]
m_next -- 输入以后的转移状态。EMPTY时为null
m_next2 -- 输入为 ε 时才可能有,若是没有则为 null.
m_accept -- 若是是终态,则保存用户给出的action代码。
m_anchor -- 和$^匹配行首行尾有关的标志。
m_label -- 此状态的编号。
重申一下,每一个CNfa的instance都表示一个NFA的状态,以及从该状态出发的全部边。
在CNfa中只有m_next, m_next2最多2个边,至关于用2叉树来表示更复杂的树,下面咱们会看到。
从和上面所述产生式相反的顺序咱们研究正则表达式是如何构造为NFA(CNfa的相互链接)的:
term() 函数咱们能够认为其返回值为CNfaPair{start, end}对,其中start表示开始节点,end表示
结束节点,参见龙书中途
(6.1) term -> normal_char
略去语法分析部分,一旦找到一个普通字符,例如'a',构造的NFA以下图示例:
解释一下该图,标号为1的CNfa 节点为start,2为end,start实例的m_edge='a'表示输入为字符'a',
在图上标记在边上,边上的箭头表示从1到2转移,start.m_next=end。start.m_next咱们画在
上面,若是有m_next2咱们画在下面。end没有任何出边,实际上其m_edge=EMPTY。
此图表示正则表达式a的状态NFA。
(6.2) term -> . 任意字符。
图相似于上面,只是start.m_edge=CCL(Character CLass),m_set为全部字符除了'\r\n'。
(6.3) term -> [ char_class ] 相似于 (6.2) m_set 为char_class包含的全部字符。
(6.4) term -> '(' expr ')' 根据 thompson算法,直接返回expr表示的NFA便可。
(5) factor -> term[*+?] 实如今函数 CMakeNfa.factor()
term返回{start, end} CNfa节点对,对于:
(5.1) term* 构造为类似于龙书图 3-42 的NFA。经过ε边实现*算符。以下图示:
(5.2) term+ 构造相似于*,少一条从i到f的ε转移边。表示1次到任意次。
(5.3) term? 构造相似于*,少内部term的end回到start的ε转移边。表示0-1次。
(5.4) term后面无算符,则返回原term pair{start,end}便可。
(5.5) 这里没有实现term{m,n}这样的正则语法扩展。
在*算符的实现中,start节点m_edge就是EPSILON(ε),而且有两条出的边。
(4) cat_expr -> factor while(factor)
两个子factor链接起来,设两个factor 为first, second,则将first.end和second.start合并便可。
参见书上的图。
(3) expr -> cat_expr while(cat_expr)
两个cat_expr以OR(|)算符链接,建立两个新的start,end,经过分支将二者并联在一块儿,构成或
关系,参见书上的图。(这种新的start有两个ε边)。
(2) rule -> state expr accept
将state,accept信息合并到expr的NFA中,accept做为终态的CNfa.m_accept.
state部分我研究的少,不曾详细看。
(1) machine -> rule while(rule)
一个NFA machine由多条rule 构成,经过CNfa.m_next2字段构成树结构。
生成的NFA状态转换图,画出来和龙书上图3-57很相似。不一样之处在于Jlex为其伪输入BOL,EOF
生成了一个空的NFA分支,以“吃掉”未处理的BOL,EOF的输入。
如下研究 2.3)简化2.2)中生成的NFA中的字符输入为字符类。