LALR(1)與減少LR(1)分析表的體積

我們知道LR(1)分析表在計算的時候所生成的規範簇集大小會原地昇天——一套簡約的程序語言,它動則生成六百多項甚至一千多項的規範簇,分析表的體積非常膨脹(好吧這着實讓人無法忍受...忍無可忍了)。爲了解決這種問題,我們可以想辦法精煉文法,或者使用另一些分析技術——LALR(1),Look - Ahead LR(1)分析法。

減少LR(1)分析表大小的一種重要方案是減少產生式的數目,即精簡文法。例如我們傳統的表達式文法:

Goal -> Expr
Expr -> Expr + Term
Expr -> Expr - Term
Expr -> Term
Term -> Term * Fact
Term -> Term / Fact
Term -> Fact
Fact -> num
Fact -> ( Expr )

此時我們爲它生成LR(1)分析表,r表示歸約(Reduce)動作,s表示移進(shift)動作:

經典表達式文法的動作表與跳轉表

可以看到這個表包含30個狀態——對應30個規範簇。我們嘗試精簡文法。

對於+和-的產生式非常相近,相同的例子還有*和/。考慮制導設計——針對於+和-的制導設計是大體相同的,同理對於*和/也是:它們都是從棧中彈出兩個操作數,而後入棧一個結果。這意味着我們可以嘗試合併+和-,以及合併*和/。

請謹慎合併它們,隨意合併可能會導致意想不到的問題。例如在做關於數組下標(buff[a][b][c])和函數調用(call(a, b, c))的時候,我嘗試合併這兩個,結果導致後面的制導翻譯需要記錄額外的信息,使得程序變得略微冗餘起來。個人總結了一條合併產生式規則的條件:合併的產生式規則不會導致語法混亂(如使得運算符優先級無法被體現),並且不會給制導翻譯帶來過多的冗餘部分

合併之後,我們得到了下面的文法:

Goal -> Expr
Expr -> Expr addsub Term
Expr -> Term
Term -> Term muldiv Fact
Term -> Fact
Fact -> num
Fact -> ( Expr )

爲它計算動作表和跳轉表:

合併後的動作表和跳轉表

可以看到狀態從30個減少到了22個。這便是合併規則帶來的。在考慮完合併相同的產生式規則之後,我們還可以考慮合併相同的狀0態(這和LALR(1)很相似),以及減少產生式規則的長度——LR(1)分析表構建中的Goto過程會爲推導的每個步驟生成一個狀態,這意味着,如果有幾條產生式規則很長,但是卻很少有不同,可能會導致一個很大的表。

除此之外,我們還可以嘗試稀疏矩陣的表示來存放Goto表——因爲這個表非常的稀疏,有效的元素很少。當然我們沒有必要過度的去引入複雜,編譯器的語法分析一定是非常高效的。

最後的方案,就是退而求其次,使用LALR(1)分析法——超前查看LR(1)。

它基於下面的方案:如果兩個規範簇中的每個元素,忽略每個LR(1)項的前瞻符號之後,其它都是相同的,則這兩個規範簇可以合併爲一個(這在某些地方被稱作“同心的”)。當然這可能會造成衝突,但是對於大多數的程序語言語法,它也是足夠的。

例如下面的這個文法(來自“虎書”上的一段):

Goal -> S
S -> V = E
S -> E
E -> V
V -> x
V -> * E

爲了關注到對應的集合,我們計算它的規範簇集:

規範簇集

由於我還沒有編寫LALR(1)的分析表生成器...所以下面的圖是魔改的...可能不太美觀。總共有14個規範簇。首先,我們先去除掉所有LR(1)項的前瞻符號,然後去掉各個集合內的重複項:

去掉前瞻符號

下面我們開始合併相同的集合——請不要關注集合內的元素,注意LR(1)分析的狀態是每個集合分別生成的。在這之後,相同的集合我使用相同顏色的矩形標註了(很顯然它們是同樣的集合):

重複標註

緊接着,我們爲合併完成的這個集合重新編號:

重新編號

 

然後恢復各個LR(1)項的前瞻符號。

接着,我們開始填表——和傳統的LR(1)填表算法相同,有差異的是Goto表的填寫和移進動作的填寫,它需要按照合併的情況進行些許修改,例如原本轉移到CC13的現在應該轉移到CC7——它們兩個合併了。這過程可能會出現衝突,語法分析器生成工具需要處理這個衝突,就像我們在LR(1)填表算法中做的那樣。

經過這個過程後,得到LALR(1)分析表:

分析表

這便是LALR(1)分析表的構建過程。將它送入LR(1)語法分析器,即可發揮功效。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章