現代編譯原理--第二章(語法分析之LR(1))


  (轉載請表明出處  http://www.cnblogs.com/BlackWalnut/p/4472772.html

     前面已經介紹過LL(1),以及如何使用LL(1)文法。但是LL(K)文法要求在看到K個字母的情況下必須做出預測,這相比於LR(K)文法而言就遜色很多。

  LR(K)文法的定義是:從左至右分析,最右推導,超前查看K個單詞。先看一個例子,來對LR文法有個大致的印象。

  以上就是使用LR文法對源碼進行分析的例子。注意到在LR文法中只有三個動作:移進,規約和接受,這三個動作也是通過查表來得到的。任何時候如果都是唯一確定這三個動作中的一個,我們就能讓LR文法正確的運行。為了更好的理解LR(K)文法,我們先介紹以下最簡單的LR(0)文法。

  因為動作是根據表來確定,所以,表的構建依然是我們構建的重點,先來看看一個表的最終形式:

                                  

  首先要說明的是,構建這張表的時候,我們使用到了狀態機,行標就代表狀態。列標由兩部分組成,分別是終結符,和非終結符。s代表移進,r代表規約,g代表跳轉,a代表接受,他們后面跟着的數字,除了r以外,都是狀態的標號,只有r后面的數字指的時規約到第幾個產生式。所有空的地方都代表出現錯誤。可見在非終結符下只有跳轉。

  為了構建這個表,我們首先構建狀態機。我們從一個基本的文法開始,文法如下:

  

  我們向產生式中添加一個點,形成這種形式,稱為項。這個點的位置告訴我們當前在狀態是什么。點每移動一次,我們跳轉一個狀態。點前面的字符串表示我們已經讀取的歷史,點后面的字符串表示我們希望得到的。也就是這種表達方式,既可以展望未來,也可以回顧過去。上面這個起始項中,我們希望得下一次得到一個S非終結符,可以看出1和2產生式是S的等價形式,如果我們得到1和2產生式的右部,我們就相當於得到了非終結符S,所以,我們的起始狀態為:

                                                                                          

  我們稱第一個產生式為核心項,其他為普通項。這個狀態我們稱為狀態1,所有的狀態都是由這個狀態中每個項的點的移動得到的。例如,狀態1吃掉一個終結符x時,狀態1的第二個項中的點要向右移動一位。得到狀態2:

                                                                                        

  當然,狀態1也可以吃掉一個終結符(,得到狀態3:

                            

  狀態3中的第一個項就是核心項。上面就我們說的移進操作。

  如果狀態1吃掉的是一個非終結符S,那么我們稱狀態需要跳轉,起始和移進時相似的效果。那么得到如下狀態:

                            

  我們再來看狀態2,目前點的位置已經到了產生式的最后面,那么意味着這個產生式已經完全匹配了,那么就可以將其規約。具體操作根據r的下標,選擇產生式,將棧中的產生式的右部字符串全部彈出,將產生式的左部符號壓棧,然后跳轉到相應的狀態。這個規約還是不太好理解,那么我們對最上面那張圖的最后四個規約來舉例解釋一下。

  首先,要說明的是,在實際的使用過程中,在棧中的內容不包含任何的符號,只有狀態編號,第一張圖是為了方便大家理解,所以才將符號都放入棧中。那么,在規約彈出棧的時候,我們彈出的也都是狀態編號。

  那么,對於最后四個規約的第一個規約,棧頂符號以此是  +16   (8  S12  ,18  E21  )22這六個符號以及六個個狀態,只有最上面的四個滿足規約,此時如果用項來表示的話,可以表示為E->(S,E).也就是說在 . 之前我們已經得到一個完整的產生式的右部,可以對其規約。需要把右部所涉及到的所有符號全部彈出,但是我們實際彈出的是狀態,所以,原來的16 8 12 18 21 22 彈出狀態后,得到的棧為16,我們彈出的字符串規約成為了非終結符E,此時,可以將E看作是在輸入隊列中輸入端,得到  E💲,棧頂依然是16。然后將E壓棧,對應的16號狀態遇到E跳轉到17狀態,此時棧頂為16E17。然后以此往下進行。

  以上,就是對基本概念移進,規約,跳轉(可以理解為移進),接受的分析,他們都是基本的動作。根據剛才的推導過程,我們可以構建一個狀態機,根據狀態機我們能構建一張我們需要的表。表如果我們得到的分析表中的每一項都只有一個動作,那么我們就說這是一個無二義性的LR(0)文法。但是LR(0)文法還是可能出現二義性,我們稱為移進規約沖突。首先來看一張有沖突的表:

                         

  很顯然,+號位置有沖突。我們來分析一下沖突產生的原因。如果輸入隊列輸入端是+號,則狀態3可以將+進棧,稱為移進,到狀態4,也可以進行規約,規約到產生式2。那么能不能規約呢?如果規約的話,(此時+依然在輸入隊列中)則規約到的E會先放到輸入隊列輸入端,然后將E壓棧,接下來+號也一定會進棧。那么在棧頂前兩位中就出現了E+這樣的字符串,這表面+號必須要是E的follow符號集合中一個才行。通過分析,我們知道,+不可能是E的follow集合中終結符。所以,也就不存在規約這個選項。

  所以,我們在構建表的時候,可以對終結符進行follow測試,只有在這個集合中符號才能進行規約。偽代碼和最后結果如下:

                                           

  注意,第二個for中,處理的是 . 已經到產生式最末尾的項。最后得到的集合R是可以放產生式A->α的非終結符,在這些非終結符下,都可以規約到A->α。

  上面這種針對LR(0)沖突的解決方式稱為SLR(0)。這是一種比較簡單的解決方式,但是不代表能完美解決所有沖突。因為它使用follow集合並不是一種很精確的沖突預判集合。還是有可能出現沖突。

  那么,為什么follow集合不精確呢?我們假設有一個項包含這樣的字符串αF.β,我們知道,F的follow集合包含字符串β的first集合。因此我們在SLR(0)中使用的follow集合不一定能正確的預測出產生式。但是,如果使用first集合,就能精准的預測出產生式。所以,我們重新定義了項,它包含產生式,標記狀態的點,超前查看符號集合,這三個部分。使用以下的算法來計算一個狀態的中每項的具體內容:

                                     

  其中Closure(I),是根據狀態集合I中的核心項來產生其他的一些項,可以看出,使用到了first集合。goto就是移進操作。可見,如果當項中的 . 后有字符時,它直接開始移進,如果項中的 . 后沒有字符,且輸入端得字符在該項的超前查看符號集合,則規約。最后一個R集合就是規約可以進行A->α規約的非終結符集合。

      使用以上算法,我們得到的就是LR(1)文法,顯然,它的狀態機和其他的LR(0)以及SLR(0)的狀態機包含的項是不一樣的。這里我們給一個例子,是關於C語言的:

                                              

  可以看出,在狀態機中,有很多的狀態,他們除了預測符號集合不一樣以外,其他都是一樣的。我們可以把這些狀態合並,這樣可以簡化LR(1)分析表的大小。我們把合並后的狀態機所描述的文法稱為LALR(1)(LA:Look Ahead)文法。它需要用來存儲表的空間更小 ,但是它的缺點是,有可能出現 規約—規約 沖突,但是在實際過程中,這種沖突的影響很小。

  通過這幾次的討論,我們直達了,一個語法分析程序最重要的是分析表的建立 。

  對於LL(K)類文法,他們的分析表是終結符和非終結符組成的行列索引,在表中填的是產生式。對於LR(K),他們是狀態和終結符,非終結符組成的索引,表中填的是各種動作。

     從分析過程上來說,LL(K)文法是自頂向下分析(由第二個L決定了遇到非終結符就開始向下分析),而LR(K)文法則是自下向上分析(R決定的)。

  其實,造成LR的能力高於LL的原因是,LL必須每輸入一個字符就要決定所使用的產生式,它是關注於當下的。但是LR則會一直到獲得得信息足夠確定一個產生式后,才去確定,這都是在項中使用 .  帶來得好處(回顧過去,展望未來)。

  以下是各種文法能力大小得比較:

                                 


免責聲明!

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



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