词法分析原理 Lexical Analysis
Regex, DFA, NFA 算法理論
算法1:根據(jù)Regex構(gòu)建NFA - McNaughton-Yamada-Thompson Construction
輸入:字母表∑上的一個(gè)正則表達(dá)式r。
輸出:一個(gè)接受L(r)的NFA N。
方法:首先對(duì)r進(jìn)行語(yǔ)法分析,分解出組成它的子表達(dá)式。構(gòu)建NFA的規(guī)則分為基本規(guī)則和歸納規(guī)則。
基本規(guī)則:處理不包含運(yùn)算符的子表達(dá)式。
對(duì)于表達(dá)式ε,構(gòu)造如下NFA:[ start ]---->[ i ]--(ε)-->[[ f ]]。其中i和f都是新?tīng)顟B(tài),分別為起始狀態(tài)和接受狀態(tài)。
對(duì)于表達(dá)式a,構(gòu)造如下NFA:[ start ]---->[ i ]--(a)-->[[ f ]]。其中i和f都是新?tīng)顟B(tài),分別為起始狀態(tài)和接受狀態(tài)。
對(duì)于每次ε或a作為r的子表達(dá)式出現(xiàn),都會(huì)用新?tīng)顟B(tài)構(gòu)建一個(gè)獨(dú)立的NFA。
歸納規(guī)則:根據(jù)給定表達(dá)式r的直接子表達(dá)式的NFA構(gòu)造總NFA。
假設(shè)正則表達(dá)式s和t的NFA分別為N(s)和N(t),可如圖根據(jù)三種情況構(gòu)建r的DFA。
算法2:根據(jù)NFA構(gòu)建等價(jià)DFA - 子集構(gòu)造算法 - The Subset Construction
輸入:一個(gè)NFA N。
輸出:一個(gè)等價(jià)的DFA D。
方法:DFA的每個(gè)狀態(tài)是一個(gè)NFA的狀態(tài)集合,構(gòu)造DFA轉(zhuǎn)換方程Dtran,使得DFA并行地模擬NFA遇到給定輸入串時(shí)可能執(zhí)行的所有動(dòng)作。首先定義如下操作,其中s表示NFA的單個(gè)狀態(tài),T表示NFA的一個(gè)狀態(tài)集合。
ε-closure(s): 能夠從NFA狀態(tài)s開(kāi)始,只通過(guò)ε轉(zhuǎn)換到達(dá)的NFA狀態(tài)集合。
ε-closure(T): 能夠從NFA的狀態(tài)集T中某個(gè)狀態(tài)s開(kāi)始,只通過(guò)ε轉(zhuǎn)換到達(dá)的NFA狀態(tài)集合。
move(T, a):能夠從NFA的狀態(tài)集T中某個(gè)狀態(tài)s開(kāi)始,通過(guò)標(biāo)記為a的轉(zhuǎn)換到達(dá)的NFA狀態(tài)集合。
算法:初始狀態(tài)可能是ε-closure(s0)中的任意狀態(tài),其中s0是NFA的起始狀態(tài)。讀入輸入符號(hào)a后,NFA可以立即移動(dòng)到move(T, a)中任何狀態(tài),同樣可以進(jìn)一步移動(dòng)到ε-closure(move(T, a))中的任何狀態(tài)。
子集構(gòu)造法:
一開(kāi)始,ε-closure(s0)是Dstates中的唯一狀態(tài),且未標(biāo)記; while (在Dstates中有一個(gè)為標(biāo)記狀態(tài)T) { 標(biāo)記T; for (每個(gè)字母表中符號(hào)a) { U = ε-closure(move(T, a)); if (U不在Dstates中) 將未標(biāo)記的U加入到Dstates中; Dtran[T, a] = U; } }
計(jì)算ε-closure(T):
將T的所有狀態(tài)壓入棧stack中, 將ε-closure(T)初始化為T(mén);
while (stack不為空) {
t = stack.pop;
for (每個(gè)滿(mǎn)足如下條件的u: 從t出發(fā)有一個(gè)標(biāo)號(hào)為的轉(zhuǎn)換到達(dá)狀態(tài)u)
if (u不在ε-closure(T)中) {
將u加入到ε-closure(T)中;
將u壓入棧中;
}
}
算法3:模擬NFA執(zhí)行過(guò)程
輸入:一個(gè)以eof結(jié)尾的輸入串x,一個(gè)NFA N,開(kāi)始狀態(tài)為s0,接受狀態(tài)集為F,轉(zhuǎn)換函數(shù)為move。
輸出:接受串x則返回yes,否則返回no。
方法:保存一個(gè)當(dāng)前狀態(tài)集合S,即可以從開(kāi)始狀態(tài)沿著標(biāo)號(hào)到當(dāng)前已讀入的輸入部分的路徑到達(dá)狀態(tài)的集合。如果c是函數(shù)nextChar()讀到的下一個(gè)輸入字符,那么就首先計(jì)算move(S, c),然后通過(guò)ε-closure()求閉包。
模擬NFA執(zhí)行過(guò)程:
S = ε-closure(s0);
c = nextChar();
while (c != eof) {
S = ε-closure(move(S, c));
c = nextChar();
}
if (S ∩ F != ?) return yes;
else return no;
算法4:直接通過(guò)Regex創(chuàng)建DFA
重要狀態(tài):如果一個(gè)NFA狀態(tài)有離開(kāi)轉(zhuǎn)換,且都不是基于ε的轉(zhuǎn)換,則該狀態(tài)為重要狀態(tài)。NFA重要狀態(tài)直接對(duì)應(yīng)于regex中符號(hào)的位置。
過(guò)程函數(shù):這些函數(shù)基于增廣正則表達(dá)式(r)#所構(gòu)成的抽象語(yǔ)法樹(shù)。
1) nullable(n),該節(jié)點(diǎn)所代表的子表達(dá)式對(duì)應(yīng)的語(yǔ)句包含ε語(yǔ)句,則為真。
2) firstpos(n),可以出現(xiàn)在該節(jié)點(diǎn)表達(dá)的語(yǔ)句的第一個(gè)位置的符號(hào)。
3) lastpos(n),可以出現(xiàn)在該節(jié)點(diǎn)表達(dá)的語(yǔ)句的最后一個(gè)位置的符號(hào)。
4) followpos(p),可以出現(xiàn)在p所代表的語(yǔ)句的后面的第一個(gè)字符。
a) 計(jì)算nullable,firstpos,lastpos
可以根據(jù)語(yǔ)法樹(shù),自底向上遞歸獲得。
b) 計(jì)算followpos
只有兩種情況會(huì)使得一個(gè)regex的某個(gè)位置跟在另一個(gè)位置之后:
1) 如果n是一個(gè)cat結(jié)點(diǎn),左右子節(jié)點(diǎn)為c1、c2,那么對(duì)于lastpos(c1)中的每個(gè)位置i,followpos(i) = firstpos(c2);
2) 如果n是star結(jié)點(diǎn),對(duì)于lastpos(n)中的每個(gè)位置i,followpos(i) = firstpos(n)。
輸入:一個(gè)regex r。
輸出:一個(gè)識(shí)別L(r)的DFA D。
方法:
1) 根據(jù)擴(kuò)展正則表達(dá)式(r)#構(gòu)造語(yǔ)法樹(shù)T。
2) 計(jì)算函數(shù)nullable(), firstpos(), lastpos()和followpos()。
3) 根據(jù)如下算法構(gòu)造:
從Regex構(gòu)造DFA:
初始化Dstates, 使其只包含未標(biāo)記狀態(tài)集firstpos(n0), 其中n0是T的根節(jié)點(diǎn);
while (Dstates存在未標(biāo)記狀態(tài)集S) {
標(biāo)記S;
for (每個(gè)輸入符號(hào)a) {
令U為S中和a對(duì)應(yīng)的所有位置p的followpos(p)的并集;
if (U不在Dstates中)
將未標(biāo)記的U加入Dstates;
Dtran[S, a] = U;
}
}
算法5:最小化DFA - Hopcroft's Algorithm
DFA的等價(jià)狀態(tài):對(duì)于任意輸入串產(chǎn)生同樣狀態(tài)。
方法:首先粗劃分為兩組p0 = Daccept,p1 = {D - Daccpet}。對(duì)于ps中的狀態(tài)di和dj,他們必須滿(mǎn)足?c∈Σ,δ(i, c) = x,δ(j, c) = y 且 dx, dy ∈ pt。為了劃分P,該算法檢查每一個(gè)p∈P以及每一個(gè)c∈Σ。如果c劃分p,該算法就將p劃分為兩個(gè)子集并添加至T中。
創(chuàng)建DFA:根據(jù)等價(jià)狀態(tài)分組,對(duì)于每一組狀態(tài)集p∈P,在DFA中對(duì)應(yīng)建立一個(gè)狀態(tài)。對(duì)于dj∈pl,dk∈pm,且δ(dj, c) = dk,我們就創(chuàng)建兩個(gè)狀態(tài)分別對(duì)應(yīng)pl和pm,且δ(pl, c) = pm。
DFA等價(jià)狀態(tài)劃分:
T = {Da, {D - Da}};
P = ?;
while (P != T) {
P = T;
T = ?;
for (each set p ∈ P)
T = T ∪ Split(p);
}
Split(S) {
for (each c ∈ Σ) {
if (c splits S into s1 and s2)
then return {s1, s2};
}
return S;
}
算法6:直接從NFA構(gòu)建最小化DFA- Brzozowski's Algorithm
應(yīng)當(dāng)注意到在子集構(gòu)建DFA時(shí),不會(huì)有相同前綴出現(xiàn)。該算法運(yùn)用了這一特性,直接從NFA構(gòu)建最小化DFA。
對(duì)于給定NFA N:
reverse(N): 將NFA倒轉(zhuǎn),將所有原接受狀態(tài)連接至新的起始狀態(tài)。
reachable(N): 所有從起始狀態(tài)可以到達(dá)的狀態(tài)。
subset(N): 子集構(gòu)建法獲得的DFA。
于是可得NFA N對(duì)應(yīng)的最小化DFA為:
reachable(subset(reverse( reachable(subset(reverse(n))) )))
其中,內(nèi)部的subset(reverse(n))精簡(jiǎn)了NFA的后綴,reachable()棄掉了所有無(wú)用的狀態(tài)。然后外部的三個(gè)函數(shù)再一次精簡(jiǎn)了NFA的前綴,并棄掉其余無(wú)用狀態(tài),最終構(gòu)建最小化NFA。
將DFA轉(zhuǎn)化為代碼實(shí)現(xiàn)
實(shí)現(xiàn)1:表驅(qū)動(dòng)分析器 - Table-Driben Scanner
方法:將轉(zhuǎn)換函數(shù)輸入到一個(gè)對(duì)應(yīng)的二維數(shù)組中,scan過(guò)程中通過(guò)查表來(lái)模擬DFA行為。還可以提供另外一個(gè)表用來(lái)將輸入字符分類(lèi),實(shí)現(xiàn)壓縮轉(zhuǎn)換表。
Table-Driven Scanner:
Nextword() { /* initalization */ state = S0; lexeme = ""; stack.clear(); stack.push(bad); /* scanning loop to model the DFA's behavior */ while (state != Serror) { char = Nextchar(); lexeme += char; if (state ∈ Saccept) stack.clear(); stack.push(state); category = CharCat[char]; // 表1: 字符分類(lèi)表, e.g. digit, letter, punc, space state = δ[state, cat]; // 表2: 轉(zhuǎn)換表, 包含每個(gè)狀態(tài)下對(duì)應(yīng)不同輸入的轉(zhuǎn)換狀態(tài) } /* roll back loop in case the DFA overshoots the end of the token */ while (state ? Saccept && state != bad) { stack.pop(); truncate lexeme; RollBack(); } /* interprets and reports the result */ if (state ∈ Saccept) return Type[state]; else return invalid; }
實(shí)現(xiàn)2:直接編碼分析器 - Direct-Coded Scanners
方法:直接將轉(zhuǎn)換表的行為編入代碼,節(jié)省查表時(shí)間。
Sinit: lexeme = "";
stack.clear();
stack.push(bad);
goto S0;
S0: Nextchar(char);
lexeme += char;
if (state ∈ Saccept)
stack.clear();
stack.push(state);
if (char == 'r')
goto S1;
else goto Sout;
S1: ...
S2: ...
Sout: while (state ? Saccept && state != bad) {
state = stack.pop();
truncate lexeme;
RollBack();
}
if (state ∈ Saccept)
then return Type[state];
return invalid;
實(shí)現(xiàn)3:手工構(gòu)建分析器 - hand-Coded Scanner
方法:利用雙重緩沖技術(shù),通過(guò)指針高效讀取輸入字符。
initialization:
Input = 0;
Fence = 0;
fill Buffer[0 : n];
implementing NextChar():
Char = Buffer[Input];
Input = (Input + 1) mod 2n;
if (Input mod n == 0) {
fill Buffer[Input : Input + n - 1];
Fence = (Input + n) mod 2n;
}
return Char;
implementing RollBack():
if (Input == Fence)
then signal roll back error;
Input = (Input - 1) mod 2n;
總結(jié)
以上是生活随笔為你收集整理的词法分析原理 Lexical Analysis的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 使用MetaMask实现转账交易时附带I
- 下一篇: 惠普M1005打印机无法自动进纸的问题