解读FLEX代码之生成NFA

来源:互联网 发布:软件功能对比分析报告 编辑:程序博客网 时间:2024/06/05 01:08

1.   生成NFA

1.1. 基本数据结构

       state contextLex源文件中用%x%s定义的状态正文,一个state context包含多条规则。默认的state contextINITIAL,如果一个规则没有指定state context,则属于INITIAL%s表示共享,如果一个规则没有指定state context,则会加入每个共享的state context%x表示互斥,没有指定state context的规则不会加入互斥的state context。每个state context包含两个状态机,一个对应非顶行的规则集,另一个对应顶行的规则集。

       ruleLex中的每个正则表达式,每个规则对应一个NFA状态机。

       machineNFA的状态机,flex中没有明显的状态机对象,状态机隐含在状态的数据结构中。state context的状态机有一个起始状态,多个接受状态。rule的状态机有一个起始状态,一个接受状态。正在构造中的状态机,有一个头状态和一个尾状态,没有起始状态和接受状态。

       stateNAF状态机中的节点,属性如下:
       firstst
:该state所属machine的参数,该状态机的头状态,也是编号最小的状态。

       finalst:该state所属machine的参数,该状态机中编号最大的状态。

       lastst:该state所属machine的参数,该状态机的尾状态。

       transchar:从该状态出来的边上的字符,负数表示一个字符集合,正数表示一个字符,零表示ε。

       trans1:从该状态转移到的状态编号。

       trans2:从该状态转移到的状态编号,只有transchar为零时才有意义。

       accptnum:非接受状态该值为零,普通接受状态该值为rule的编号,向前看接受状态该值为rule的编号加YY_TRAILING_HEAD_MASK

       assoc_rule:该状态所属rule的编号。

       state_type:状态类型,区分普通状态和向前看状态。

 

       正在构造的状态机的特点:永远只有一个头状态、一个尾状态;状态机的头状态是编号最小的状态,状态机的编号是连续的,不跳过一个编号。

       状态的特点:transchar不为零时有且只有一条出边;transchar为零时,可以有一条、两条、或没有出边,接受状态没有出边。

1.2. 最终的状态机

起始状态

接受状态

复杂的状态变迁

一个ruleNFA

rule1

rule2为空

rule3

多个ruleNFA,红线为ε转移

ruleNFA

为处理方便增加的没用的状态

       flex在语法分析过程中生成NFA。每扫描一个字符生成一个NFA状态,遇到规则尾部时生成接受状态,扫描完规则后就已经生成对应的状态机了。

       规则的NFA:一个起始状态,一个接受状态,中间是复杂的状态变迁。空规则不识别任何字符,通常不会出现空规则,空规则只有一个接受状态,起始状态和接受状态重合。

       规则集的NFAflex增加一些没有用的状态,将多个ruleNFA组合成二叉树结构,或者称之为链表结构。

       起始状态的编号能唯一标识一个NFA

      

       处理词法源文件的过程如下:

1.         INITIAL state context生成两个NFA,每个NFA包含一个非接受状态,这个状态是没有用的,仅仅为了处理方便。第一个NFA对应非顶行的规则集,第二个NFA对应顶行的规则集,顶行就是^打头的规则。

2.         识别%s%x定义的所有state context,为每个state context生成两个NFA,同INITIAL

3.         逐条识别规则。

a)         识别该规则属于哪个state context,每写明属于哪个state context的为公有规则,公有规则属于所有共享state context

b)        为该规则生成一个NFA,用起始状态的编号标识该NFA

c)        如果是非顶行规则,则加入所属state context的第一个NFA;如果是顶行规则,则加入所属state context的第二个NFA

4.         扫描完所有规则后,生成一条缺省规则,将加入所有state context的第一个NFA。该规则接收任意字符,将其输出到屏幕上,用来处理不能识别的字符。flex的运行参数-s用于禁止生成该缺省规则,此时遇到不能识别的字符后会退出程序。

5.         将每个state context的第一个NFA加到第二个NFA上,结果做为第二个NFA。这个过程不是在扫描结束时做的,而是在讲NFA转换成DFA时做的。查看flex生成的词法分析程序,可知,在输入流行首时使用第二个NFA,不在行首时使用第一个NFA,准确地讲使用的是根据NFA生成的DFA。这段代码为yy_current_state = yy_start; yy_current_state += YY_AT_BOL()

1.3. 临时的状态机

头状态

尾状态

临时状态机

出边

a

单个字符a生成的临时状态机

rNFA

sNFA

r·s运算

rNFA

sNFA

r | s运算

rNFA

r?运算

rNFA

r*运算

rNFA

r+运算

说明:红线表示ε转移

rNFA

rNFA

出边不为ε时增加一个状态做为接收状态

出边为ε时将最后一个状态设为接收状态

      

    扫描规则的过程中会生成临时状态机。每扫描一个字符会生成只有一个状态的状态机,遇到 * | ? + 等符号时对状态机做运算。

       临时状态机的模型如上图所示,一个头状态,一个尾状态,一条出边。出边的transchar属性是有值的,但没有填转移到哪个状态;有两种类型的出边,transchar为ε或具体的字符。

       临时状态机的运算有:r·sr | sr*r+r?、增加接收状态。上图描述了这些操作的结果。

       r{2,4}r{2,}r{4}这种记号表示将r重复多次,例如,a{2,4}表示两个、或三个、或四个连续的字符aflexr{2,4}拆成r·r·r?·r?,将r{2,}拆成r·r·r*。此时需要将r的状态机复制很多份。复制过程比想像的要简单很多,用到临时状态机的特点:临时状态机中的状态编号是连续的,每个状态的trans1trans2字段永远指向该状态机内部的某个状态,不会指向状态机外部的状态。

1.4. 向前看问题

       向前看规则的形式为r/sflex期望rs能接收定长的字符串,例如a*/bcdabc/d*,这样处理起来很简单,生成的词法分析器也很简单。如果rs接收的字符串都不是定长的,例如a*/b*,处理起来很复杂。

       对于规则a*/bcd,生成的状态机同abcd,只是在识别出该模式后,先回退三个字符,再执行自定义动作。

       对于规则abc/d*,生成的状态机同abcd,只是在识别出该模式后,回退足够多的字符知道剩下三个字符,再执行自定义动作。

       a*/b*c为例,描述一下处理方法。

 

3

4

5

6

7

8

9

10

11

12

13

14

15

16

a

b

c

all

规则1

规则1

NFA:红线表示ε转移,灰色为接收状态,虚线为标记为TRAILING的状态

规则2,容错

1

6

2

3

4

5

7

8

other

DFA

蓝线表示字符a

红线表示字符b

黑线表示字符c

DFA中,各状态接收的规则如下

       state 1:规则1(trailing_head)

       state 2:规则1(trailing_head),规则2

       state 3:规则2

       state 4:规则1(trailing),规则2

       state 5:规则2

       state 6:规则1(trailing_head)

       state 7:不是接收状态

       state 8:规则1(trailing)

 

       生成的词法分析器在扫描输入串时,将遇到的每个DAF状态存在栈中,直到不能匹配更长的字符串。

       然后遍历状态栈中每个状态s,遍历状态s能接受的每个规则r。如果最先看到的是普通的规则x,即没有标记trailing_headtrailing,按规则x处理。如果最先看到的规则y标记了trailing,则继续找标记为trailing_head的规则y,按规则y处理。如果最先看到的规则标记了trailing_head,则跳过它,当它不存在。

      

       再来看生成的代码。

       系统有很多状态,每个状态能够接收零个或多个规则,系统将其压缩在数组yy_acceptyy_acclist中,yy_acclist[yy_accept[s]]yy_acclist[yy_accept[s+1]-1]存储状态s能接受的规则。了每个状态能够接受的规则的编号,有些加了YY_TRAILING_MASK标记,有些加了YY_TRAILING_HEAD_MASK标记。

       yy_state_buf存储了扫描输入串时遇到的DFA状态,yy_state_ptr指向栈顶。

       find_rule标号后面的代码,就是遍历状态栈yy_state_buf中的每个状态s,遍历s能接受的每个规则r

      

       总之,为了处理向前看问题,在NFA的接受状态上加上特殊的标记,转换为DFA后,DFA的状态也带有特殊标记。

原创粉丝点击