1. 程式人生 > >編譯原理-語法分析四

編譯原理-語法分析四


本文是語法分析的第四篇文章,第一篇文章介紹了基本的文法、推導和歸約的概念,第二篇文章介紹了自頂向下的語法分析和LL(1)方法,第三篇文章介紹了自底向上的語法分析和SLR方法。本文將承接第三篇文章,介紹比SLR更為強大的LR方法。

PS:閱讀本文需要掌握前三篇文章的知識,建議讀者先閱讀前三篇文章。

文法&約定
老規矩,先給出一個貫穿全文的文法G:

    S→L=R|R
    L→*R|id
    R→L
1
2
3
以及該文法的增廣形式:

    S'→S
    S→L=R|R
    L→*R|id
    R→L
1
2
3
4
併為產生式標號:

    (1) S→L=R    (2) S→R    (3) L→*R
    (4) L→id     (5) R→L
1
2
以下是下文使用的符號約定:

大寫字母:表示非終結符號,如A、B、C等;
小寫字母:表示終結符號,如a、b、c等;
希臘字母:表示由終結符號和非終結符號組成的串或者空串,如α、β、γ等;
開始符號:用S表示文法的開始符號;
結束符號:用$表示結束標記,如輸入結束、棧為空等。
SLR的不足
SLR雖然足夠簡單(不然怎麼會叫簡單LR),但是它的功能還不夠強大。考慮文法G,為它構造LR(0)自動機和LR語法分析表(如果不知道如何構造,請先閱讀第三篇文章):

在語法分析表中,si表示移入狀態i,rj表示按照第j個產生式歸約。注意到,狀態2在輸入符號“=”上有s6和r5兩個動作,這裡產生了一個移入/歸約衝突。但是,文法G不是二義性的,只能說SLR還不夠強大。

如果把狀態2在輸入符號“=”上的動作規定為r5,那麼對串id=id,它的語法分析過程為:

可以看到,語法分析器最後報告了一個錯誤。

如果把狀態2在輸入符號“=”上的動作規定為s6,那麼它的語法分析過程變為:

語法分析器最終接受了串id=id。

為了讓語法分析器對有衝突的選項選擇正確的動作,我們需要一種更強大的LR技術。

規範LR方法
規範LR方法和SLR方法類似,它也會構造一個自動機,並從這個自動機得到語法分析表。但是,它們用於構造自動機的項集族不同,SLR方法使用規範LR(0)項集族構造一個自動機,而規範LR方法使用規範LR(1)項集族構造一個自動機。

 

規範LR(1)項
形如[A→α·β, a]的項是一個規範LR(1)項,其中,第一個分量是一個規範LR(0)項,第二個分量是一個終結符號或$,它表示這個規範LR(1)項的向前看符號。在形如[A→α·β, a]且β≠ε的項中,向前看符號沒有任何作用,但是在形如[A→α·, a]的項中,只有在下一個輸入符號為a時才會按照A→α進行規約。

規範LR(1)自動機
構造LR(1)自動機也用到了CLOSURE和GOTO函式,只不過它們的規則有所改變。

計算一個規範LR(1)項集I的CLOSURE集合,按照如下規則進行:

將I中的所有項加入CLOSURE集合中;
對I中每個形如[A→α·Bβ, a]的項,首先找到產生式頭為B的每個產生式B→γ,然後找到FIRST(βa)中的每個終結符號b,最後將所有[B→γ, b]加入CLOSURE集合中;
重複上面的步驟,直到沒有新的項可以加入CLOSURE集合中為止。
計算一個規範LR(1)項集I的GOTO集合,按照如下規則進行:

對I中每個形如[A→α·Xβ, a]的項,將[A→αX·β, a]加入GOTO集合中;
把GOTO集合作為CLOSURE函式的引數,計算GOTO集合的閉包。
下面我們對文法G構造LR(1)自動機:

我們發現LR(1)自動機比LR(0)自動機多了5個狀態,通常情況下,LR(1)自動機的狀態數要比LR(0)自動機的狀態數多得多。

規範LR(1)語法分析表
規範LR(1)語法分析表也是由ACTION和GOTO函式組成的,但是,它的ACTION函式規則與LR(0)不同。

對一個規範LR(1)自動機,按照如下規則設定狀態i上的動作:

如果[A→α·aβ, b]在Ii中,並且GOTO(Ii, a)=Ij,那麼將ACTION[i, a]設定為“移入j”,這裡的符號a必須是一個終結符;
如果[A→α·, a]在Ii中且A≠S’,那麼將ACTION[i, a]設定為“按照A→α歸約”;
如果[S'→S·, $]在Ii中,那麼將ACTION[i, $]設定為“接受”;
其它所有空白的ACTION條目都設定為“報錯”。
對一個規範LR(1)自動機,按照如下規則設定狀態i上的轉移:

如果GOTO(Ii, A)=Ij,那麼GOTO[i, A]=j,這裡的第一個GOTO是規範LR(1)自動機的GOTO集合;
其它所有空白的GOTO條目都設定為“報錯”。
下面我們從文法G的LR(1)自動機構造規範LR(1)語法分析表:

現在再用這張表對輸入串id=id做語法分析:

可以發現規範LR(1)語法分析器最終接受了串id=id。

 

規範LR方法的問題
規範LR方法可以說是最通用的為文法構造LR語法分析表的技術,但是,它的一個問題是會產生大量的狀態。那麼,有沒有什麼方法能夠減少規範LR語法分析表的狀態數呢?

我們看一看上面文法G的LR(1)自動機,發現狀態4和狀態11非常相似,如果忽略向前看符號,它們就是相同的狀態,狀態5和狀態12,狀態7和13,狀態8和14,也有相同的特點。

如果把具有這樣的特點的所有項集合併成一個,那麼狀態數就能夠大大減少。現在的問題是,我們要如何表述這個特點。直接地說,如果兩個規範LR(1)項集,忽略其中每個項的向前看符號後,這兩個項集中包含相同的項,那麼這兩個項可以合併成一個。進一步的,我們可以比較這兩個項集的“核心項”的集合,從而判斷這兩個項集是否能夠合併。

判斷一個規範LR(0)項是否是核心項,使用如下兩條規則:

如果這個項是初始項S’→·S,那麼這個項是核心項;
如果這個項中的“·”不在最左端,那麼這個項是核心項。
換句話說,對一個項集I,假設I的核心項集為K,那麼CLOSURE(K)=I。這裡的項是規範LR(0)項,對規範LR(1)項來說,忽略向前看符號後就是一個規範LR(0)項。

現在我們可以正式定義這個特點了。對一個規範LR(1)語法分析表(器)中的所有狀態,按照如下規則進行合併:

把每個項集Ii替換為其核心項集Ki,如果兩個項集Km和Kn忽略向前看符號後包含相同的規範LR(0)項,那麼Km和Kn是相似的;
如果兩個核心項集Km和Kn相似,那麼將Km和Kn合併成一個新項集Kmn。對Km中的每個規範LR(1)項[A→α·β, a],其在Kn中對應於項[A→α·β, b],將[A→α·β, a/b]放入Kmn中;
重複上面的步驟,直到沒有任意兩個核心項集可以合併為止;
計算每個核心項集的CLOSURE集合。
現在我們嘗試對文法G的規範LR(1)自動機進行化簡,首先找出其中每個項集的核心項集:

其中,白色背景的項集是核心項集,灰色背景的項集是非核心項集。我們發現,I4與I11、I5與I12、I7與I13、I8與I14是相似的,將它們合併後得到:

合併後的自動機只有11個狀態,比合並前少了4個狀態。下面是根據合併後的LR(1)自動機得到的語法分析表:

這裡用編號在前的狀態表示合併的狀態,例如狀態4表示狀態4&11。用這張表對串id=id進行語法分析:

可以發現,化簡前後的規範LR(1)語法分析器都接受了輸入串id=id,但是化簡後的規範LR(1)語法分析器需要維護的狀態變少了。實際上,化簡後的規範LR(1)語法分析器是一個LALR語法分析器。

LALR方法
“向前看LR”方法,即LALR方法,它基於LR(0)項集族,通過向LR(0)項中引入向前看符號構造LALR語法分析表。使用LALR方法可以比SLR文法處理更多的文法,同時構造得到的語法分析表不比SLR分析表大。在很多情況下,LALR方法是最合適的選擇。

我們可以對上一節介紹的合併項集的方法進行修改,使得在建立LALR語法分析表的過程中不需要構造出完整的規範LR(1)項集族:

只使用核心項來表示任意的LR(0)或LR(1)項集,即[S'→·S]或[S'→·S, $]以及“·”不在產生式體最左端的項;
使用一個“傳播和自發生成”的過程來生成向前看符號,根據LR(0)項的核心生成LALR(1)項的核心;
使用CLOSURE函式對每個LALR(1)核心項集求閉包,得到規範LR(1)項集族,用該項集族就能構造LALR(1)語法分析表。
PS:在LALR方法中使用的CLOSURE和GOTO函式與規範LR方法中的相同。

生成向前看符號可以分為兩種情況,一種是自發生成的,一種是傳播的。對一個規範LR(1)核心項集I,假設它包含項[A1→α1·β1, a1]、[A2→α2·β2, a2]、…、[An→αn·βn, an],對文法符號X計算J=GOTO(CLOSURE(I), X),假設J中包含一個項[B→γ·δ, b],則有:

如果b≠ai(1<=i<=n),那麼說向前看符號b對於B→γ·δ是自發生成的;
如果b=ai(1<=i<=n),那麼說向前看符號b從I的核心中的Ai→αi·βi傳播到了J的核心中的B→γ·δ上。
對一個LR(0)項集I的核心項集K以及一個文法符號X,使用如下方法確定向前看符號:

現在可以把向前看符號附加在LR(0)項集的核心上,從而得到LALR(1)項集。首先,我們知道$是初始LR(0)項集中的S’→·S的向前看符號,使用上面的方法可以得到所有自發生成的向前看符號;接著,將所有這些向前看符號列出後,我們必須讓它們不斷傳播,直到不能繼續傳播為止。下面給出了一個傳播方法:

構造LR(0)項集族的核心;
對每個LR(0)項集的核心K和每個文法符號X,計算GOTO(CLOSURE(K), X),確定GOTO(CLOSURE(K), X)中各個項的哪些向前看符號是自發生成的,並確定傳播的向前看符號從I中的哪個項被傳播到GOTO(CLOSURE(K), X)中的哪個項上的;
初始化一個表格,表中給出了每個項集中的每個核心項相關的向前看符號。最初,每個項的向前看符號只包括那些在步驟(2)中確定為自發生成的符號;
不斷掃描所有項集的核心項。當我們訪問一個項i時,使用步驟(2)中得到的資訊,確定i將它的向前看符號傳播到了哪些核心項中。項i的當前向前看符號集合被加到和這些被傳播的核心項相關聯的向前看符號集合中。繼續在核心項上進行掃描,直到沒有新的向前看符號被傳播為止。
我們還是用例子來說明如何使用LALR方法。對文法G使用LALR方法。

第一步,構造它的LR(0)項集族的核心,這裡我們用一個LR(0)自動機表示:

白色背景是LR(0)核心項集,灰色背景不是LR(0)核心項集,並且這個自動機中已經給出了每個項集的GOTO函式。初始時,I0對應的LR(1)核心項集為{ [S'→·S, $] };

第二步,計算CLOSURE({ [S'→·S, #] })得到:

    S'→·S, #
    S→·L=R, #
    S→·R, #
    L→·*R, =
    L→·id, =
    R→·L, #
1
2
3
4
5
6
因此,“”是傳播的向前看符號,“=”是自發生成的向前看符號。我們有,“”是傳播的向前看符號,“=”是自發生成的向前看符號。我們有,“”從I0的[S'→·S, $],傳播給I1的[S'→S·, $]、I2的[S→L·=R, $]、I2的[R→L·, $]和I3的[S→R·, $];“=”在I4的[L→*·R, =]和I5的[L→id·, =]上是自發生成的。現在我們初始化表格,由於這個表格一開始只包含自發生成的向前看符號,因此表格初始化為:

第三步,開始進行第一趟傳播,由於在上一步中已經知道了“$”的傳播,因此我們只計算“=”的傳播。計算CLOSURE({ [L→*·R, #] })得到:

    L→*·R, #
    R→·L, #
    L→·*R, #
    L→·id, #
1
2
3
4
我們有,“=”從I4的[L→*·R, =]傳播給I4的[L→*·R, =]、I5的[L→id·, =]、I7的[L→*R·, =]和I8的[R→L·, =]。另外,由於I5的[L→id·, =]已經沒有計算閉包的必要,因此經過第一趟傳播後表格變為:

第四步,開始進行第二趟傳播,我們發現,只有I2的[S→L·=R, $]需要計算閉包。計算CLOSURE({ [S→L·=R, #] })得到:

    S→L·=R, #
1
我們有,“$”從I2的[S→L·=R, $]傳播給I6的[S→L=·R, $]。另外,由於I4多了一個[L→*·R, $],因此經過第二趟傳播後表格變為:

第五步,開始進行第三趟傳播,我們發現,只有I6的[S→L=·R, $]需要計算閉包。計算CLOSURE({ [S→L=·R, #] })得到:

    S→L=·R, #
    R→·L, #
    L→·*R, #
    L→·id, #
1
2
3
4
我們有,“$”從I6的[S→L=·R, $]傳播給I4的[L→*·R, $]、I5的[L→id·, $]、I8的[R→L·, $]和I9的[S→L=R·, $]。因此經過第三趟傳播後表格變為:

第六步,我們發現傳播不能繼續進行了,現在我們把表中的向前看符號加入到LR(0)自動機中,得到LALR(1)自動機:

第七步,根據LALR(1)自動機,可以構造LALR語法分析表。這裡構造分析表的規則與構造規範LR語法分析表的規則一樣,得到文法G的LALR語法分析表如下:

可以看到,相比於SLR語法分析表,LALR語法分析表中的狀態2對符號“=”沒有移入/規約衝突,而且兩張表的狀態數相同。
--------------------- 
作者:jzyhywxz 
來源:CSDN 
原文:https://blog.csdn.net/jzyhywxz/article/details/78443192 
版權宣告:本文為博主原創文章,轉載請附上博文連結!