[編譯原理-詞法分析(三)] 詞法分析器

前言

前前後後大概用了半個月,從構思到實現,試過語法分析樹和抽象語法樹,寫起來不是特別順利,然後換了思路,以一種直觀的方式進行詞法分析(可能時間複雜度較高,沒計算過)。

項目地址

Github

參考鏈接

在實現的過程中也發掘到幾篇不錯的blog

構建抽象語法樹
構建正則引擎
正則表達式轉有限狀態自動機

前文

支持語法

表達式 匹配 例子
c 單個非運算字符c a, b, z
\c 字符c的字面值 \\
「s」 串s的字面值 ".\「
. 除換行符以外的所有字符 *.out
[s] 字符串s中的任意一個字符 [abc], [a-z], [0-9]
[^s] 不在字符串s中的任意一個字符 [^abc]
r1r2 r1後加上r2 abc
(r) 與r相同 (ab)
r{m, n} 最少m個,最多n個r重複出現 r{1, 5}
r1 | r2 r1或r2 a | b
r* 和r匹配的零個或多個串連接的串 a*, [abc]*
r+ 和r匹配的一個或多個串連接的串 a+, [0-9]+
r? 零個或一個r a?, (ab)?

註解1:c匹配的是非元標識字符, 即除(\ " . ^ …) 代表特定意義的字符。 \c 匹配的是元標識字符的字面值, 即 ‘.’ 匹配除換行符以外的字符, 加上 ‘\.’ 表示匹配一個小數點 ‘.’ 。

註解2: r*口語爲kleene閉包(克林閉包),r+口語爲正閉包,r?口語爲???(我也不知道)。

註解3:在上個版本中支持[^s],但由於在當前版本改動了規則的繼承層次,導致無法直接實現此功能(或許有方法,笑)。

註解4:構建 ‘.’ 元標識符時,會有bug(繪製出NFA就明白了)。

註解5:在當前版本中,沒有構建符號表,因爲算是測試版本,返回的Token的實例也很‘直接’。

註解6:在當前版本中,有正則表達式到不確定有窮自動機的轉換;不確定的有窮自動機 轉 確定的有窮自動機沒辦法實現,需要一點小改動纔可。子集構造法的三個函數有,轉換類也有(註解了),在正文部分會提及如何。

正文

語法分析樹,抽象語法樹規則與定式

什麼是有限狀態自動機?
|
有限狀態自動機是一個五元組
M = ( Q , Σ , δ , q 0 , F ) M=(Q, Σ, δ, q0, F)
Q Q 狀態的非空有窮集合, q Q ∀q∈Q q q 稱爲 M M 的一個狀態
Σ Σ 輸入字母表
δ δ 狀態轉移函數,有時又叫作狀態轉換函數 δ Q × Σ Q δ ( q , a ) = p δ:Q×Σ→Q,δ(q,a)=p
q 0 q0 M M 的開始狀態,也可叫作初始狀態或啓動狀態, q 0 Q q0∈Q
F F M M 的終止狀態集合, F F Q Q 包含,任給 q F q∈F q q 稱爲 M M 的終止狀態

簡要提及:
正則表達式(Regular Expression, RE)
不確定的有限狀態自動機(Nondeterministic Finite Automata,NFA)
確定的有限狀態自動機(Deterministic Finite Automata,DFA)

1. 規則

每個規則類都包含輸入字母表中的一個子集,即 R Σ R∈Σ
每個狀態通過匹配規則到達新的狀態,即 δ ( q 1 , R ) = q 2 δ(q_1,R)=q_2

說起來比較抽象,其實很容易理解:
圖一例如圖一,0號狀態可以通過單字符規則(CharacterRule)到達1號狀態
圖二
圖二中,0號狀態可以通過或規則(OrRule),即0~9中任意一個字符可以到達1號狀態。
圖三圖二圖三的簡化版(防止狀態數過多),雖然狀態數減少,但是在某些時候就不是特別適用了,例如需要轉換爲確定的有窮自動機時,我們知道子集構造法中move函數接受的是單字符(詳情見代碼 SubsetConstruction類),而簡化版本用的是ConjunctionSymRule(連詞符規則),導致不能夠精確匹配,也就不能轉換爲確定的有窮自動機。

2. 定式

爲了生成有限狀態自動機的方便,每個規則可以生成一個模板有限狀態自動機,稱之爲定式。像 r r* r + r+ r ? r? 這三種閉包都有固定的NFA模板,得到r直接生成即可,即:RE -> NFA

圖四
圖四的正則式爲: r > a r -> a ,構建一個單定式有限狀態自動機,0號狀態即Start State,1號狀態即Accept State。

圖五

圖四構建一個Kleene閉包,即 r * 。

由規則生成的有限狀態自動機,此自動機包含一個開始狀態和一個接受狀態,不用考慮在開始狀態和接受狀態之間有多少轉換。

3. 代碼

講完了在項目中最主要的兩項-規則與定式,其他不是那麼重要,包括輸入文件,雙緩衝區方案等等。

輸入文件: 正則定義文件 和 識別源文件

正則定義上面是此次正則定義中的一部分,每行都包含一個正則表達式以及該正則表達式的名稱,形式爲:
n a m e R E name → RE

如果某個正則表達式A需要使用其他正則表達式B,則B必須在A之前聲明(如digit和number的關係)

識別源識別源文件是正則定義所能表示的語言。

輸出形式:返回識別源中的每個詞素,即正則定義中每個正則表達式能匹配識別源文件中的的最小單位。

輸出中的一部分:
輸出(2)
輸出(1)

規則與定式類圖

規則的繼承層次BaseRule

  1. 所有類將繼承BaseRule,BaseRule有一個生成狀態圖(有限狀態自動機)的方法
  2. BaseRule有兩個子類,SimpleRule和ComplicatedRule;在這裏的意爲:ComplicatedRule第二次生成的自動機是第一次生成的自動機的拷貝(未實現…),SimpleRule每次生成的自動機都是重新實例的。
  3. 下面有七個實際規則:
    單字符規則(CharacterRule),通過字符C可由當前狀態到新的狀態;
    字符串(StringRule),一個單字符規則序列;
    連詞符規則(ConjunctionSymRule),連詞符是類似[a-z][0-9]中的-符號,表示a1,a2,a3…an,形成一個邏輯上連續的序列時,可以表示爲a1-an
    或規則(OrRule),包含一個規則序列,只要匹配其中任意一個即可到達新的狀態。
    計數規則 (CountingRule)正則式 r { m , n } r\{m, n\} 的規則實例。
    閉包規則 (ClosureRule)由一個基礎規則和一個閉包屬性組成。
    組合規則(CombinationRule)是一個規則序列,匹配時需要按照順序匹配。
    4.PointRule是CharacterRule的一個特化,不算實際規則。

在其中,字符串規則不是必要規則,只是爲了簡化狀態數所用規則。(在用連接定式時,兩個正則式之間需要插入一條空轉換函數,如果類似 double -> double 這條正則式,每個字符構成的單字符規則,在連接時需要插入五個空轉換函數,將多出五個無用狀態。)

!!!
在當前版本中,SimpleRule還包含一個match方法,即有限狀態機上的一個轉換函數,所有繼承SimpleRule的類纔可調用匹配方法。

定式繼承層次BaseStereotypeDiagram

  1. 所有定式都繼承自BaseStereotypeDiagram,每個定式狀態圖包含一個開始狀態和接受狀態,不用考慮在開始狀態和接受狀態之間有多少轉換。

  2. 還有一個比較特殊的抽象基類,BaseClosureStereotypeDiagram,所有閉包類都繼承此類。

  3. 一共三種基礎定式和三種閉包定式:
    SingleStereotypeState 構建 c , c , 0 9 c, \\c, 0-9 (圖二連詞符規則也是一條單定式)
    OrStereotypeState 構建 [ a z ] [a-z]
    ConnectStereotypeState 構建$ r1r2 r { m , n } r\{m, n\}

    KleeneClosureState 構建 r r*
    PositiveClosureState 構建 r + r+
    ZeroOneClosureState 構建 r ? r?

特殊類

在這裏插入圖片描述Production意爲產生式,即一條正則表達式和該正則表達式的名稱,產生式的叫法是在文法中稱呼的,即有:
h e a d b o d y head → body
head稱爲產生式頭部或產生式左部body稱爲產生式體或產生式右部。
TransitionGraph存放一條規則和一張轉換表,一行正則表達式對應一個TransitionGraph實例。

DeterministicFiniteAutomaton是指DFA,因爲未實現,所以註解了。

轉換表

傳入一個NFA 生成一張轉換表

雙緩衝區

雙緩衝區方案,比之另外一個早期實現的C++要稍好(笑)
雙緩衝區blog c++實現

後記

整個項目寫得挺一般的,代碼約2k,功能不算特別完備,bug也挺多(前文那裏列出了)。至於爲什麼寫,是在學編譯原理的時候想找份代碼瞧瞧詞法分析是怎麼回事(龍書有前端代碼demo),可是網上找不到呀!! 然後就寫了一份;可優化的地方還有幾處,如果以後有時間或許會更新,謝謝!

標籤

正則表達式,RE,有窮狀態自動機,DFA,NFA
正則表達式轉不確定的有限狀態自動機

詞法分析 JAVA實現