研究 JLex(3)

现在研究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()是一个递归向下
正则表达式的解析器的实现,我们以生成式的方式写出其对应的生成式如下:

(1)   machine -> rule while(rule)      一部NFA状态机由多条rule构成,while(rule)表示
    这是使用尾递归方式实现的rule*。一个spec可以没有任何规则。

(2)   rule -> state expr accept            一条rule前面是lex state,后面是accept action。
    实际上 state部分在machine产生式(函数)中处理的,写在这里是方便看。

(3)   expr -> cat_expr while(cat_expr)    一个expr是多个cat_expr OR 或构成的。
     例如 a|b, 则cat_expr1=a, cat_expr2=b

(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,括号常用来改变算符优先级

以上产生式的左侧名字也即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中的字符输入为字符类。

转载于:https://my.oschina.net/u/232554/blog/40184

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值