前言
目錄 |
---|
01 文法和語言、詞法分析復習 |
02 自頂向下、自底向上的LR分析復習 |
03 語法制導翻譯和中間代碼生成復習 |
04 符號表、運行時存儲組織和代碼優化復習 |
05 用C++手撕PL/0 |
第4章 自頂向下的語法分析方法
確定的自頂向下分析思想
開始符號集或首符號集:設\(G=(V_T,V_N,P,S)\)是上下文無關文法。
\(FIRST(\alpha)=\{a \mid\alpha \stackrel{*}{\Rightarrow} a\beta, a\in V_T, \alpha,\beta\in V^*\}\)
若\(\alpha \stackrel{*}{\Rightarrow} \varepsilon\),則規定\(\varepsilon \in FIRST(\alpha)\),稱\(\varepsilon \in FIRST(\alpha)\)為\(\alpha\)的開始符號集或首符號集
簡單來說,就是查看該句型推導出的所有句子的首字母集合。
例如文法\(G[S]:\)
\(S\rightarrow Ap\)
\(S\rightarrow Bq\)
\(A\rightarrow aA\)
\(A\rightarrow \varepsilon\)
\(B\rightarrow Bb\)
\(B\rightarrow b\)
那么\(FIRST(S)=\{a,b,p\}\),\(FIRST(A)=\{a,\varepsilon\}\),\(FIRST(B)=\{b\}\)
FOLLOW集:設\(G=(V_T,V_N,P,S)\)是上下文無關文法,\(A\in V_N\),S是開始符號
\(FOLLOW(A)=\{a\mid S\stackrel{*}{\Rightarrow}\mu A\beta且a\in V_T,a\in FIRST(\beta), \mu\in {V_T}^*, \beta\in V^+\)
若\(S \stackrel{*}{\Rightarrow}\mu A\beta\),且\(\beta \stackrel{*}{\Rightarrow}\varepsilon\),則 \(\# \in FOLLOW(A)\)
簡單來說,就是查看該句型在被推導前后面跟隨的所有可能的第一個字母的集合。
例如之前的文法\(G[S]\),\(FOLLOW(S)=\{\#\}\),\(FOLLOW(A)=\{p\}\),\(FOLLOW(B)=\{b,q\}\)
選擇符號集:一個產生式的選擇符號集SELECT。給定上下文無關文法的產生式\(A\rightarrow \alpha, A\in V_N, \alpha\in V^*\),若\(\alpha\stackrel{*}{\nRightarrow}\varepsilon\),則\(SELECT(A\rightarrow\alpha)=FIRST(\alpha)\)
而如果\(\alpha\stackrel{*}{\Rightarrow}\varepsilon\),則\(SELECT(A\rightarrow\alpha)=(FIRST(\alpha)-\{\epsilon\})\cup FOLLOW(A)\)。
求出選擇符號集,是為了找到哪些符號應該使用該推導。那么,如果\(A\rightarrow \alpha\)不能推出空串,顯然 從該推導得到的所有句子的首字母構成的集合 來反向看出 哪些字母應該使用該推導。而如果\(A\rightarrow \alpha\)能推出空串,則還要考慮該非終結符的后跟字符。
一個上下文無關文法是LL(1)文法的充要條件,是對每個非終結符A的兩個不同產生式,\(A\rightarrow\alpha, A\rightarrow\beta\),滿足
例如文法\(G[S]:\)
\(S\rightarrow aA\)
\(S\rightarrow d\)
\(A\rightarrow bAS\)
\(A\rightarrow \varepsilon\)
有:
\(SELECT(S\rightarrow aA)=\{a\}\)
\(SELECT(S\rightarrow d)=\{d\}\)
\(SELECT(A\rightarrow bAS)=\{b\}\)
\(SELECT(A\rightarrow \varepsilon)=\{a,d,\#\}\)
所以:
\(SELECT(S\rightarrow aA)\cap SELECT(S\rightarrow d)=\{a\}\cap\{d\}=\emptyset\)
\(SELECT(A\rightarrow bAS)\cap SELECT(A\rightarrow \varepsilon)=\{b\}\cap\{a,d,\#\}=\emptyset\)
LL(1)文法的判別
LL(1)的含義:第1個L 表明 從左向右掃描輸入串,第2個L 表明 分析過程中將使用最左推導,1表明只需向右看一個符號就知道該選擇哪個產生式推導。
- 找出能推出\(\varepsilon\)的非終結符
- 計算FIRST集
- 計算FOLLOR集
- 計算SELECT集
- 進行判斷
某些非LL(1)文法到LL(1)文法的等價變換
LL(1)文法的充分條件為不含左公共因子
提取左公共因子
例如\(A\rightarrow \alpha\beta\mid \alpha\gamma\),可以寫成:
\(A\rightarrow \alpha B\)
\(B\rightarrow \beta\)
\(B\rightarrow \gamma\)
一般情況如\(A\rightarrow \alpha_1\alpha_2...\alpha_n(\beta_1\mid\beta_2\mid...\mid\beta_n)\),可以寫成:
\(A\rightarrow\alpha_1 A_1\)
\(A_1\rightarrow\alpha_2 A_2\)
\(...\)
\(A_{n-1}\rightarrow\alpha_n B\)
\(B\rightarrow\beta_1\)
\(B\rightarrow\beta_2\)
\(...\)
\(B\rightarrow\beta_n\)
此外,還需要檢查文法是否含有隱式的左公共因子,如:
\((1)A\rightarrow ad\)
\((2)A\rightarrow Bc\)
\((3)B\rightarrow aA\)
將(3)帶入(2),可暴露出左公共因子:
\((1)A\rightarrow ad\)
\((2)A\rightarrow aAc\)
\((3)B\rightarrow aA\)
可以看到此時(3)為多余規則,可以刪去,最后整理得到:
\((1)A\rightarrow aB\)
\((2)B\rightarrow Ac\)
\((2)B\rightarrow d\)
改寫后的文法不含空產生式,且無左遞歸時,則改寫后的文法是LL(1)文法
若還有空產生式,則還需要用LL(1)文法的判別方式進行判斷
消除左遞歸
對於包含直接左遞歸的文法,如\(G[A]:A\rightarrow Ab,A\rightarrow c, A\rightarrow d\)
可以直接改寫成右遞歸:\(A\rightarrow cA', A\rightarrow dA', A'\rightarrow bA',A'\rightarrow \varepsilon\)
一般情況下,如\(A\rightarrow A(\beta_1\mid\beta_2\mid...\mid\beta_n), A\rightarrow \alpha_1\mid\alpha_2\mid...\mid\alpha_n\),分為了包含左遞歸和不含左遞歸的兩部分,可以變形為:
\(A\rightarrow \alpha_1 A'\)
\(A\rightarrow \alpha_2 A'\)
\(...\)
\(A\rightarrow \alpha_n A'\)
\(A'\rightarrow \beta_1 A'\)
\(A'\rightarrow \beta_2 A'\)
\(...\)
\(A'\rightarrow \beta_n A'\)
對於包含間接左遞歸的文法,如\(G[S]:\)
\((1)A\rightarrow aB\)
\((2)A\rightarrow Bb\)
\((3)B\rightarrow Ac\)
\((4)B\rightarrow d\)
可以考慮把(1)和(2)帶入(3):
\((1)B\rightarrow aBc\)
\((2)B\rightarrow Bbc\)
\((3)B\rightarrow d\)
不會引起左遞歸的式子為(1)和(3),故可以寫成:
\((1)B\rightarrow aBcB'\)
\((2)B\rightarrow dB'\)
\((3)B'\rightarrow bcB'\)
\((4)B'\rightarrow \varepsilon\)
消除一切左遞歸
LL(1)文法的實現
程序實現
- 求出文法G[S]各個產生式的SELECT集合
- 然后根據集合內的符號來選擇所屬產生式
寫程序時,用getsym來讀入下一個符號,如果有不合法的符號,應當有錯誤處理。
如文法\(L(G[A]): A\rightarrow aBd \mid b, B\rightarrow\varepsilon\mid c\)
\(SELECT(A\rightarrow aBd)=\{a\}\)
\(SELECT(A\rightarrow b)=\{b\}\)
\(SELECT(B\rightarrow \varepsilon)=\{d\}\)
\(SELECT(B\rightarrow c)=\{c\}\)
void PraseA()
{
if (sym == 'a')
{
getsym();
PraseB();
if (sym == 'd')
getsym();
}
else if (sym == 'b')
{
getsym();
}
else
{
error();
}
}
void PraseB()
{
if (sym == 'c')
{
getsym();
}
else if (sym == 'd')
{
}
else
{
error();
}
}
預測分析表
求出所有規則的SELECT集后,根據集合填入預測分析表。如上面的文法:
a | b | c | d | # | |
---|---|---|---|---|---|
A | \(\rightarrow aBd\) | \(\rightarrow b\) | |||
B | \(\rightarrow c\) | \(\rightarrow \varepsilon\) |
下表是對對\(acd\)的分析過程:
步驟 | 分析棧 | 剩余輸入串 | 推導所用產生式或匹配 |
---|---|---|---|
1 | #A | acd# | A→aBd |
2 | #dBa | acd# | a匹配 |
3 | #dB | cd# | B→c |
4 | #dc | cd# | c匹配 |
5 | #d | d# | d匹配 |
6 | # | # | 接受 |
一開始把起始非終結符放入分析棧,然后會有2種情況:
- 若分析棧棧頂為非終結符,根據剩余輸入串的首字符以及預測分析表來選擇產生式。如果找到合適的產生式,此時需要把分析棧棧頂的非終結符彈出,然后根據產生式右邊的句型從右往左依次入棧。如果沒找到,則出現異常。
- 若分析棧棧頂為終結符,此時如果和剩余輸入串的首字符相匹配,則彈出分析棧棧頂終結符,並查看輸入串的下一個字符。如果不匹配,則出現異常。
這一章的可能考點
- 判別文法是否為LL(1)
- 已知LL(1)文法,構造預測分析表
- 給定LL(1)文法和輸入串,寫出分析過程表
第5章 自底向上的移進-歸約分析
自底向上的移進-規約分析要求對輸入符號串自左向右掃描,按句柄進行歸約。
移進:將輸入串的下一個字符移入符號棧
歸約:符號棧中的頂部幾個符號如果能匹配某條推導式的右邊,則用該推導式的左邊替換
自底向上的移進-歸約法是每次對最左邊的內容進行歸約,它的逆過程為自頂向下的規范(最右)推導。
設文法 \(G[S]\) 為
\(S\rightarrow aAcBe\)
\(A\rightarrow b\)
\(A\rightarrow Ab\)
\(B\rightarrow d\)
對輸入串\(abbcde\)使用自頂向下的最右推導:
對應的,我們可以得到它的逆過程,即規約過程。
步驟 | 符號棧 | 輸入符號串 | ----------動作---------- |
---|---|---|---|
(1) | # | abbcde# | 移進 |
(2) | #a | bbcde# | 移進 |
(3) | #ab | bcde# | 歸約\((A\rightarrow b)\) |
(4) | #aA | bcde# | 移進 |
(5) | #aAb | cde# | 歸約\((A\rightarrow Ab)\) |
(6) | #aA | cde# | 移進 |
(7) | #aAc | de# | 移進 |
(8) | #aAcd | e# | 歸約\((B\rightarrow d)\) |
(9) | #aAcB | e# | 移進 |
(10) | #aAcBe | # | 歸約\((S\rightarrow aAcBe)\) |
(11) | #S | # | 接受(acc) |
第6章 LR分析
LR(K)分析使用自底向上分析法,從左到右掃描符號,只需要根據分析棧中的符號棧和向右順序查看輸入串的K(K>=0)個符號來確定分析器接下來是移進還是規約,因而也能唯一地確定句柄。
LR分析器
總控程序負責LR分析過程
分析棧分為狀態棧和文法符號棧。它們均是后進先出。
分析表分為動作(ACTION)表和狀態轉換(GOTO)表兩個部分。
SP為棧指針,指向狀態棧和文法符號棧,即狀態棧和符號棧元素數目始終保持一致。
狀態轉換表內容按關系\(GOTO[S_i, X]=S_j\)確定,即當棧頂狀態為\(S_i\)遇到棧頂符號\(X\)時應當轉向狀態\(S_j\)
動作表按\(ACTION[S_i,a]\)確定了棧頂狀態為\(S_i\)時遇到輸入符號\(a\)應執行的動作。
動作按優先程度排列:
規約:如果棧頂形成了句柄\(\beta\)(它的長度為\(r\)),且有原來的推導\(A\rightarrow\beta\)來進行規約,則從狀態棧和文法符號棧中自頂向下去掉\(r\)個符號,即對SP減去\(r\)。接着把A移入文法符號棧內,再根據此時修改SP后的棧頂狀態,把滿足\(S_j=GOTO[S_i, A]\)的狀態移進狀態棧。
移進:如果棧頂沒有形成句柄,且\(S_j=GOTO[S_i,a]\)成立,則把\(S_j\)移入到狀態棧,把\(a\)移入到文法符號棧。其中\(i\)和\(j\)表示狀態號。
接受acc:當歸約到文法符號棧中只剩下文法的開始符號S,並且輸入符號串只剩下#(表示已經結束),則為分析成功。
報錯:如果狀態棧頂的當前狀態遇到了不該出現的文法符號時則報錯,說明輸入串不是該文法能接受的句子。
LR(0)分析
已知文法\(G[S]:\)
\((1)S\rightarrow aAcBe\)
\((2)A\rightarrow b\)
\((3)A\rightarrow Ab\)
\((4)B\rightarrow d\)
對輸入串\(abbcde\#\)用自底向上歸約的方法來分析,由於到第5步棧中的符號串為\(\#aAb\),此時狀態棧的棧頂狀態\(0236\)決定了應該用(3)式而不是(2)式來歸約。
分析表和分析過程如下:
可歸前綴和活前綴
為了更清楚地表示最右推導與最左歸約的關系,可以在推導過程中加入一些附加信息。對上面的文法用\([i]\)編號:
\(S\rightarrow aAcBe[1]\)
\(A\rightarrow b[2]\)
\(A\rightarrow Ab[3]\)
\(B\rightarrow d[4]\)
對輸入串abbcde進行最右推導,把序號也帶入:
\(S\Rightarrow aAcBe[1]\Rightarrow aAcd[4]e[1]\Rightarrow aAb[3]cd[4]e[1]\Rightarrow ab[2]b[3]cd[4]e[1]\)
對應的逆過程——最左歸約(規范歸約,即從左到右歸約)為:
\(ab[2]b[3]cd[4]e[1]\) 用產生式(2)歸約
\(\Leftarrow aAb[3]cd[4]e[1]\) 用產生式(3)歸約
\(\Leftarrow aAcd[4]e[1]\) 用產生式(4)歸約
\(\Leftarrow aAcBe[1]\) 用產生式(1)歸約
\(S\)
這里用\(\Leftarrow\)表示歸約。
每次歸約前,句型的前部依次為:
\(ab[2]\),它的前綴為\(\varepsilon, a, ab\)
\(aAb[3]\),它的前綴為\(\varepsilon, a, aA, aAb\)
\(aAcd[4]\),它的前綴為\(\varepsilon, a, aA, aAc, aAcd\)
\(aAcBe[1]\),它的前綴為\(\varepsilon, a, aA, aAc, aAcB, aAcBe\)
這些規范句型的前部我們稱之為可歸前綴。而a, aA, aAc等這些出現在一個或多個可歸前綴的部分,可稱之為活前綴,它的長度不能超過當前句型句柄的末端。
拓廣文法是指對原文法G增加產生式\(S'\rightarrow S\),其中\(S\)為原文法G的開始符號。這樣確保新的開始符號\(S'\)只會在推導式的左邊出現(而\(S\)不一定)
使用拓廣文法可以將上面的文法表示成:
\(S'\rightarrow S[0]\)
\(S\rightarrow aAcBe[1]\)
\(A\rightarrow b[2]\)
\(A\rightarrow Ab[3]\)
\(B\rightarrow d[4]\)
對句子\(abbcde\)列出可歸前綴:
\(S[0]\)
\(ab[2]\)
\(aAb[3]\)
\(aAcd[4]\)
\(aAcBe[1]\)
活前綴及可歸前綴的一般計算方法
重要!!!這部分可以不需要看,因為我們可以直接通過構造識別活前綴的DFA來反向求出活前綴以及可歸前綴。
設\(G=(V_N, V_T, P, S)\)是一個上下文無關文法,對於\(A\in V_N\),有
其中S'是G的拓廣文法G'的開始符號
\(LC(A)\)表明了在規范推導(最右推導)中在非終結符A左邊出現的符號串集合。
推論:若文法G中有產生式\(B\rightarrow \gamma A\delta\),則有
因為對任一形為\(\alpha B\omega\)的句型,必然有規范推導:
因此對任一\(\alpha \in LC(B)\),必有\(\alpha \gamma \in LC(A)\),即\(LC(B) \cdot\{\gamma\} \subseteq LC(A)\)
對於文法\(G[S]\):
\(S'\rightarrow S\)
\(S\rightarrow aAcBe\)
\(A\rightarrow b\)
\(A\rightarrow Ab\)
\(B\rightarrow d\)
需列出方程組求解(此處為正規式):
用正規式表示求解結果:
規定\(LR(0)C(A\rightarrow\beta)=LC(A)\cdot\beta\),這樣包含句柄的活前綴有:
包含句柄的活前綴也就是可歸前綴,將它們展開也就得到了所有的活前綴
對於遞歸型:
一直展開可以看到:
\(LC(A)\)
\(= a\mid LC(A)\cdot c\)
\(=a\mid (a\mid LC(A)\cdot c)\cdot c\)
\(=a\mid ac\mid LC(A)\cdot cc\)
\(=a\mid ac\mid acc\mid LC(A)\cdot ccc\)
\(=...\)
故\(LC(A)=ac^*\)
LR(0)項目規范族的構造
1. LR(0)項目
在文法G'中為每個產生式的右部的適當位置添加一個圓點構成項目
例如\(A\rightarrow Ab\)有3個項目:
\([0] A\rightarrow \cdot Ab\)
\([1] A\rightarrow A\cdot b\)
\([2] A\rightarrow Ab\cdot\)
而空產生式\(A\rightarrow\varepsilon\)只有一個項目\(A\rightarrow\cdot\)。
\(\cdot\)左邊的符號表示已經被掃描過的部分,右邊如果還有符號,則它的第一個符號則是下一個將會被掃描的符號。
2. 構造識別活前綴的NFA
列出所有項目后,這些項目標上編號用於構造NFA,確定\(S'\rightarrow \cdot S\)為初態,有2種情況:
- 即將從\(S'\rightarrow \cdot S\)過渡到\(S\rightarrow \cdot aA\)。這兩個項目用\(\rightarrow\)連接,並在上面標記\(\varepsilon\)
- 即將從\(S\rightarrow\cdot aA\)過渡到\(S\rightarrow a\cdot A\)。這兩個項目用\(\rightarrow\)連接,並在上面標記\(a\)
根據圓點所在位置和圓點后的符號狀況,可以分為4類:
- 移進項目,形如\(A\rightarrow a\cdot aB\),圓點后面是終結符
- 待約項目,形如\(A\rightarrow aa\cdot B\),圓點后面是非終結符,需要移進完非終結符內的所有符號才能歸約符號B
- 歸約項目,形如\(A\rightarrow aaB\cdot\),圓點后面沒有符號,此時可以進行歸約
- 接受項目,形如\(S'\rightarrow A\),S'是所有產生式唯一的左部
3. LR(0)項目集規范族的構造
從NFA構造成DFA的關鍵點僅在於,使用閉包函數,將當前項目(它前面不是由\(\varepsilon\)推出)以及后面用\(\varepsilon\)弧連接的所有項目,構成一個新的項目集。
但一個項目集中不能同時存在:
- 移進項目和歸約項目,形如\(A\rightarrow a\cdot a\beta\)和\(B\rightarrow \gamma\cdot\)
- 歸約項目和歸約項目,形如和\(A\rightarrow \beta\cdot\)和\(B\rightarrow \gamma\cdot\)
重點來了!從\(I_0\)項目集到最終要進行歸約的式子(即圓點在式子最后)的路徑掃過的符號順序就是這條推導式的活前綴!
比如從上圖就可以看出:
\(E\rightarrow bB\)的可歸前綴為\(bB\)
\(A\rightarrow cA\)的可歸前綴為\(acc^*A\)
4. LR(0)分析表的構造
現有LR(0)項目集規范族:
\(I_k\)為項目集的名,k為狀態名,令包含\(S'\rightarrow \cdot S\)項目的集合\(I_k\)的下標k作為分析器的初始狀態。分析表的ACTION表和GOTO表構造步驟如下:
- 若項目\(A\rightarrow \alpha\cdot a\beta\)屬於\(I_k\),且\(I_k\stackrel{a}{\rightarrow}I_j\),則置\(ACTION[k,a]=S_j\)
- 若項目\(A\rightarrow \alpha\cdot\)屬於\(I_k\),則對任何終結符和#號都置\(ACTION[k,*]=r_j\)
- 若\(I_k\stackrel{A}{\rightarrow}I_j\),則置\(GOTO[k,A]=j\)
- 若項目\(S'\rightarrow S\)屬於\(I_k\),則置\(ACTION[k,\#]=acc\),表示接受
SLR(1)分析
假定一個LR(0)規范族中含有如下的項目集(舉例):
也就是該項目集中出現了移進—歸約沖突和歸約—歸約沖突。
根據LR(0)分析,此時無法確定是移進,還是歸約,即產生了沖突。但我們可以向前查看一個符號(即查看當前剩余輸入串最前面一個符號),來確定接下來的動作,這就是SLR(1)分析。
對於上面的例子,要求三個項目中 \(·\) 后面的符號各不相同,即要求它們的FOLLOR集互不相交:
\(FOLLOW(A)\cap\{b\}=\emptyset\)
\(FOLLOW(B)\cap\{b\}=\emptyset\)
\(FOLLOW(A)\cap FOLLOW(B)=\emptyset\)
即說明在狀態5時,如果面臨某輸入符號為\(a\),則可以按以下規定決策:
- 若\(a=b\),則移進
- 若\(a\in FOLLOW(A)\),則用產生式\(A\rightarrow \gamma\)歸約
- 若\(b\in FOLLOW(B)\),則用產生式\(B\rightarrow \delta\)歸約
- 此外,則報錯
接下來是分析表的修改,比如說產生式2為\(A\rightarrow \gamma\),產生式3為\(B\rightarrow \delta\),\(FOLLOW(A)=c\),\(FOLLOW(B)=d\),原本LR(0)時候分析表對應:
狀態 | --- a --- | --- b --- | --- c --- | --- d --- | --- # --- |
---|---|---|---|---|---|
5 | \(r_2,r_3\) | \(r_2,r_3,S_6\) | \(r_2,r_3\) | \(r_2,r_3\) | \(r_2,r_3\) |
可見沖突非常嚴重,經過修改后,變成了:
狀態 | a | b | c | d | # |
---|---|---|---|---|---|
5 | \(S_6\) | \(r_2\) | \(r_3\) |
此時的分析表滿足SLR(1)分析。
LR(1)分析
現有一文法\(G'\):
\((1)S'\rightarrow S\)
\((2)S\rightarrow aAd\)
\((3)S\rightarrow bAc\)
\((4)S\rightarrow aec\)
\((5)S\rightarrow bed\)
\((6)A\rightarrow e\)
它的識別活前綴的DFA如下:
留意到\(I_5\)中存在移進—歸約沖突,\(FOLLOW(A)\cap\{c\}=\{c,d\}\cap\{c\}=\{c\}\)
以及\(I_7\)中存在移進—歸約沖突,\(FOLLOW(A)\cap\{d\}=\{c,d\}\cap\{d\}=\{d\}\)
它們的交集都不為空,不滿足SLR(1)文法,需要考慮使用LR(1)文法。
在\(I_5\)中,由於
\(S'\mathop{\Rightarrow}\limits_{R}S\mathop{\Rightarrow}\limits_{R}aAd\mathop{\Rightarrow}\limits_{R}aed\)
\(S'\mathop{\Rightarrow}\limits_{R}S\mathop{\Rightarrow}\limits_{R}aec\)
對於活前綴\(ae\)來說,當面臨符號\(c\)時應該移進,面臨符號\(d\)時應用產生式\(A\rightarrow e\)歸約。因為\(S'\mathop{\Rightarrow}\limits_{R}S\mathop{\nRightarrow}\limits_{R}aAc\),故\(aAc\)不是該文法的規范句型。
若\([A\rightarrow a\cdot B\beta]\in I_n\),則\([B\rightarrow\cdot\gamma]\in I_n\)。可以考慮把\(FIRST(\beta)\)作為用產生式\(B\rightarrow\gamma\)歸約的搜索符,成為向前搜索符,這樣在要決定是歸約還是移進的時候看看輸入串下一個符號是否屬於\(FIRST(\beta)\)集。向前搜索符通常也放在相應項目的后面。
LR(1)項目集族的構造
首先以 \(S'\rightarrow\cdot S,\#\) 作為初始項目集,\(\#\)為向前搜索符(表示活前綴\(\gamma\)要規約成\(S\)時,必須面臨輸入符\(\#\)才行)
和LR(0)相比,區別僅在於:如果項目\((A\rightarrow\alpha\cdot B\beta, a)\)屬於該項目集,\(B\rightarrow\gamma\)是文法中的產生式,\(\beta\in V^*\),\(b\in FIRST(\beta a)\),則\((B\rightarrow \cdot\gamma, b)\)屬於該項目集,並且沿着該項目集開始往后\(B\rightarrow \gamma\)的所有項目都用同一個向前搜索符\(b\)。
注意:向前搜索符有可能不止一個!
例1
現在目光來到上圖的項目集\(I_2\)
\(S\rightarrow a\cdot Ad\)
\(S\rightarrow a\cdot ec\)
\(A\rightarrow \cdot e\)
首先\(aAd\)被歸約成\(S\)時,根據唯一推導\(S\)的式子\(S'\rightarrow S\),可以確定\(S\)后面沒有符號,故\(S\rightarrow a\cdot Ad\)的向前搜索符為\(\#\)。
同理,\(S\rightarrow a\cdot ec\)的向前搜索符也為\(\#\)
現在,由於項目中有\((S\rightarrow a\cdot Ad,\#)\)和\((A\rightarrow\cdot e,?)\)(這里用?表示還不知道向前搜索符),在對\(A\rightarrow e\)這條式子歸約之前,可以看到,顯然歸約是對\(·\)后面的\(A\)進行的,且\(A\)的后面是\(d\),又有整個推導式\(aAd\)的后面是\(\#\),因此\(FIRST(d\#)=d\)。故在歸約前需要判斷當前輸入串的第一個符號是不是為\(d\)。因此\(A\rightarrow\cdot e\)向前搜索符為\(d\)
\(S\rightarrow a\cdot Ad, \#\)
\(S\rightarrow a\cdot ec, \#\)
\(A\rightarrow \cdot e, d\)
例2
再舉一個復雜點的例子,現在有\(G[S']:\)
\(S'\rightarrow S\)
\(S\rightarrow BB\)
\(B\rightarrow aB\)
\(B\rightarrow b\)
已知它的初始項目集\(I_0\)為:
\((1)S'\rightarrow\cdot S\)
\((2)S\rightarrow\cdot BB\)
\((3)B\rightarrow\cdot aB\)
\((4)B\rightarrow\cdot b\)
顯然,(1)和(2)的向前搜索符都為#
現在關鍵在推導式3和4,因為他們推導時用的都是\(S\rightarrow\cdot BB\)中的第一個\(B\),在歸約的時候要確定它后面構成的串\(B\#\)的\(FIRST\)集,在這里\(FIRST(B\#)=\{a,b\}\),故(3)和(4)的向前搜索符應該都為\(a,b\)
\((1)S'\rightarrow\cdot S,\#\)
\((2)S\rightarrow\cdot BB,\#\)
\((3)B\rightarrow\cdot aB,a/b\)
\((4)B\rightarrow\cdot b,a/b\)
這一章可能的考點
- 已知文法,構造LR(0)的活前綴DFA,可能會要構造分析表,又或者判別是否為LR(0)
- LR(1)補全DFA圖,或判別
- SLR(1)判別