詞法分析


詞法分析器的任務是按照一定模式從源程序中識別出記號(token).

我們使用正規式描述這一模式,並通過有限自動機進行識別.

正規式與正規集

語言是在有限字母表上有限長字符串的集合.

正規式又稱正則表達式, 是一種特殊的字符串用來描述一類的字符串的集合.

我們把可用正規式描述(其結構)的語言稱為正規語言或正規集.

在介紹正規式之前, 我們先定義幾個字符串集合中的概念:

  • \(\epsilon\): 空串, 沒有字符的串不是空格由空格組成的串

  • 並運算: $ L \cup M = {s| s \in L or s \in M }$

  • 交運算: $ L \cap M = {s| s \in L and s \in M}$

  • 連接運算: $ LM = {st | s \in L and t \in M } $ , 任意屬於L的字符串與任意屬於M的字符串按順序連接

  • 閉包運算: L* $ = L^0 \cup L \cup L^2 $ ..., 其中\(L^0 = {\epsilon}, L^2 = LL\)

  • 正閉包: $ L+ = L \cup L^2... $

正規式采用遞歸定義:

  • \(\epsilon\)是正規式, 表示集合\({\epsilon}\)

  • 任意字符a是正規式, 表示集合{a}

  • 若r和s是正規式, L(r)和L(s)是它們則:

    • r|s是正規式, 表示的集合為$ L(r) \cup L(s)$

    • rs是正規式, 表示的集合為$ L(r)L(s) $

    • r是正規式, 表示的集合為L(r)

    • r+是正規式, 表示的集合為$ L(r)+ $

上面同樣定義了正則表達式的基本運算, 運算均為左結合, 優先級從高到低為閉包, 連接, 交並運算,正規式運算可以使用括號改變順序.

兩個正規式描述的集合相同則稱它們是等價的, 也就是說正規式和集合之間是多對一的關系.

有限自動機

不確定的有限自動機(Nondeterministic Finite Automaton, NFA)是識別模式的方法, 我們用狀態圖來描述NFA.

下圖的NFA用於識別正規式= < | <= | <> | = | > | >=

NFA的初始狀態為0, 若其第一個字符為<則轉移到狀態1, 其它類推.

若下一個字符為=則轉移到狀態2並返回, 若下一個字符為>則轉移到狀態3並返回, 否則直接返回狀態1.這里的返回是指以當前狀態作為終態, 終止匹配.

上面的策略體現了最長匹配原則, 即達到狀態1時不立即返回而是繼續嘗試狀態2或狀態3.

NFA識別字符串就是反復試探所有路徑,直到到達終態后返回,或者到達不了終態后放棄.一般使用回溯法試探所有路徑.

我們使用五個要素描述NFA:

  • 狀態集S

  • 字母表

  • move(i, j)狀態轉移函數

  • S0初態

  • F終態集

狀態轉移函數接受兩個參數, 當前狀態和轉移條件, 返回新的狀態.

轉移條件是指后續字符串滿足該條件才會發生狀態轉移, 比如要求下一個字符為特定字符.

NFA的問題在於:

  • 只有嘗試了全部可能的路徑,才能確定一個輸入序列不被接受

  • 識別過程中需要進行大量回溯,時間復雜度很高

NFA中對狀態轉移函數幾乎沒有限制, 允許其出現一對多的狀態轉移和\(\epsilon\)狀態轉移.

所謂\(\epsilon\)狀態轉移是指轉移條件為空, 狀態轉移可以隨意發生, 這種狀態轉移經常在識別閉包時出現.

確定的有限自動機 (Deterministic Finite Automaton, DFA)是NFA的一個特例,其最大的特點是其狀態轉移函數都是一對一的且不允許\(\epsilon\)狀態轉移.

因為NFA對狀態轉移不加限制在實際應用中帶來很多問題, 通常我們將NFA轉換為等價的DFA. 這里所謂的自動機等價是指它們識別同樣的正規集.

以正規式(a|b)*abb為例, 其NFA可以表示為:

可以看到狀態為0, 下一個字符為a時出現一對多的問題.

DFA的狀態轉移復雜一些:

詞法分析器

構建詞法分析器一般需要幾個步驟:

  1. 用正規式描述記號的模式

  2. 為正規式設計NFA

  3. 將NFA轉換為等價的DFA, 這一步稱為確定化

  4. 優化DFA使其狀態數最少, 這一步稱為最小化

從正規式到NFA

Thompson算法可以用來為一個正規式構建NFA.

  • \(\epsilon\), 構造NFA:

  • 對字符a構造NFA:

  • r和s是正規式, 它們的NFA為N(r)和N(s):

    • r|s的NFA為:

    • rs的NFA為:

    • r*的NFA為:

使用Thompsonn算法構造正規式(a|b)*abb的NFA, 自下而上構建:

作出狀態轉移圖:

從NFA到DFA

基於NFA構造DFA的核心在於將一對多的狀態轉移確定化.

使用回溯法在發現無法匹配的路徑后返回是一種自然的思路, 不過我們可以采用並行的方法.

當發現一對多的情況時我們可以同時試探所有路徑, 當發現某條路徑不通時直接放棄該路徑不必回溯.

\(\epsilon\)閉包

為了消除\(\epsilon\)狀態轉移, 我們引入\(\epsilon\)閉包的概念:

從狀態集T出發,不經任何字符可達到的狀態的集合稱為T的\(\epsilon\)閉包, 記作\(\epsilon(T)\)

建立一個集合V, 將T添加到V中, 遍歷V中的每一個狀態s, 將s可以通過\(\epsilon\)狀態轉移到達的狀態添加到V中, 最終得到的V即為T的\(\epsilon\)閉包.

{s2}的\(\epsilon\)閉包為{s2, s4, s5}

為了便於敘述, 我們將從狀態集S出發通過條件a可以到達的下一狀態全體記作smove(S, a).則$ smove(\epsilon(T), \epsilon) \subset \epsilon(T) $

我們可以把\(\epsilon\)閉包當做一個狀態來看待:

(a|b)*abb的NFA上識別輸入序列abb:

  1. 計算初態集: $ \epsilon({0}) = {0, 1, 2, 4, 7} = A $

  2. 由A出發經條件a到達: $ \epsilon(smove(A,a)) = {1, 2, 3, 4, 6, 7, 8} = B $

  3. 由B出發經條件b到達: $ \epsilon(smove(B, b)) = {1, 2, 4, 5, 6, 7, 9} = C $

  4. 由C出發經條件b到達: $ \epsilon(smove(C, b)) = {1, 2, 4, 5, 6, 7, 10} = D $

  5. 10為終態,接受

識別abab:

  1. 計算初態集: $ \epsilon({0}) = {0, 1, 2, 4, 7} = A $

  2. 由A出發經條件a到達: $ \epsilon(smove(A,a)) = {1, 2, 3, 4, 6, 7, 8} = B $

  3. 由B出發經條件b到達: $ \epsilon(smove(B, b)) = {1, 2, 4, 5, 6, 7, 9} = C $

  4. 由C出發經條件a到達: $ \epsilon(smove(C, a)) = {1, 2, 3, 4, 6, 7, 8} = B $

  5. 由B出發經條件b到達: $ \epsilon(smove(B, b)) = {1, 2, 4, 5, 6, 7, 9} = C $

未到達終態, 不接受

子集構造法

算法流程:

  1. 初始化數據結構: DFA自動機D, 狀態集的集合DS, 狀態轉義關系集DT
    將epsilon({0})加入到DS中, 所有狀態置為未標記
    當DS中仍有未標記的狀態集T時執行循環:
    標記T
    遍歷每一個字符a: // 只有可以從T中轉移出去的字符才有意義
    令 S = epsilon(smove(T, a))
    若S非空:
    令DT(T, a) = S
    若S不在DS中:
    將S作為未標記的狀態集加入DS

示例, 由(a|b)*abb的NFA構造DFA:

  1. 初始化:$ A = \epsilon({0}) = {0, 1, 2, 4, 7}; DS.append(A); $

  2. $ B = \epsilon(smove(A, a)) = {1, 2, 3, 4, 6, 7, 8}; DS.append(B); DT(A, a) = B $

  3. $ C = \epsilon(smove(A, b)) = {1, 2, 4, 5, 6, 7}; DS.append(C); DT(A, b) = C $

  4. $ S = \epsilon(smove(B, a)) = {1, 2, 3, 4, 6, 7, 8}; S == B; DT(B, a) = B $

  5. $ D = \epsilon(smove(B, b)) = {1, 2, 3, 4, 6, 7, 8}; DS.append(D); DT(B, b) = D $

  6. $ S = \epsilon(smove(C, a)) = {1, 2, 3, 4, 6, 7, 8}; S == B; DT(C, a) = B $

  7. $ S = \epsilon(smove(C, b)) = {1, 2, 4, 5, 6, 7}; S == C; DT(C, b) = C $

  8. $ S = \epsilon(smove(D, a)) = {1, 2, 3, 4, 6, 7, 8}; S == B; DT(D, a) = B $

  9. $ E = \epsilon(smove(D, b)) = {1, 2, 4, 5, 6, 7, 10}; DS.append(E); DT(D, b) = E $

  10. $ S = \epsilon(smove(E, a)) = {1, 2, 3, 4, 6, 7, 8}; S == B; DT(E, a) = B $

  11. $ S = \epsilon(smove(E, b)) = {1, 2, 4, 5, 6, 7}; S == C; DT(E, b) = C $\

根據DS和DT繪制狀態轉移圖:

最小化DFA

首先引入可區分的概念:對於DFA中任意兩個狀態s和t, 接受輸入字符串w, 若s和t轉移到不同狀態則稱w對於s和t是可區分的.

最小化DFA的目的是使DFA的狀態數最少, 定義一個DFA自動機的狀態集為S, 終態集為F, 算法流程:

初始化U = {S-F, F}
遍歷U中每一個狀態集T:
	初始化N = U
	遍歷T中任意狀態的組合(s,t, ..):
		若對於任意字符a, move(s,a)與move(t,a)均屬於U中同一個狀態集G:
			將s,t划分入同一組, 使用新划分的組代替N中的G
	若N == U退出, 以N作為最終划分
	令U=N
遍歷U中沒一個狀態集T:
	從T中選擇一個狀態s, 令T中出發的狀態轉移改為從s出發, 到T的狀態轉移改為轉移到s
清除所有死狀態(只能轉移到自身且不是終態)和不可達狀態.

我們用該算法簡化上面的DFA:

  1. U = {ABCD, E}

  2. U = {ABC, D, E}

  3. U = {AC, B, D, E}

AC可合並為一個狀態:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM