研究 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()是一個遞歸向下
正則表達式的解析器的實現,咱們以生成式的方式寫出其對應的生成式以下: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中的字符輸入爲字符類。

相關文章
相關標籤/搜索