如今研究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中的字符輸入爲字符類。