LL(1)分析法和遞歸下降分析法同屬于自頂向下分析法。相對于遞歸下降而言,LL通過顯示
地維護一個棧來進行語法分析,遞歸下降則是利用了函數(shù)調(diào)用棧。
LL分析法主要由分析棧、分析表和一個驅(qū)動算法組成。其實LL的分析算法還是很容易懂的,
主要就是一個匹配替換的過程。而要構(gòu)造這里的分析表,則還涉及計算first集和follow集
的算法。
個人覺得龍書在解釋這些算法和概念時都非常清楚細致,雖然也有人說它很晦澀。
first集和follow集的計算,拋開書上給的嚴密算法,用人的思維去理解(對于compiler
compiler則需要用程序去構(gòu)造這些集合,這是讓計算機去理解),其實很簡單:
1、對于某個非終結(jié)符A的first集(first(A)),簡單地說就是由A推導得到的串的首符號的
集合:A->aB,那么這里的a就屬于first(A),很形象。
2、follow(A),則是緊隨A的終結(jié)符號集合,例如B->Aa,這里的a就屬于follow(A),也很形
象。
當然,因為文法符號中有epsilon,所以在計算上面兩個集合時則會涉及到一種傳遞性。例
如,A->Bc, B->epsilon,B可以推導出epsilon,也就是基本等同于沒有,那么first(A)中
就會包含c符號。
在了解了first集和follow集的計算方法后,則可以通過另一些規(guī)則構(gòu)造出LL需要的分析表。
編譯原理里總有很多很多的理論和算法。但正是這些理論和算法,使得編譯器的實現(xiàn)變得簡
單,代碼易維護。
在某個特定的編程語言中,因為其文法一定,所以對于其LL(1)實現(xiàn)中的分析表就是確定的
。我們也不需要在程序里動態(tài)構(gòu)造first和follow集合。
那么,要實現(xiàn)一個LL(1)分析法,大致步驟就集中于:設(shè)計文法->建立該文法的分析表->編
碼。
LL分析法是不能處理左遞歸文法的,例如:expr->expr + term,因為左遞歸文法會讓對應
的分析表里某一項存在多個候選式。這里,又會涉及到消除左遞歸的方法。這個方法也很簡
單,只需要把文法推導式代入如下的公式即可:
A -> AB | C 等價于:A -> CX, X -> BX | epsilon
最后一個問題是,如何在LL分析過程中建立抽象語法樹呢?雖然這里的LL分析法可以檢查文
法對應的語言是否合法有效,但是似乎還不能做任何有意義的事情。這個問題歸結(jié)于語法制
導翻譯,一般在編譯原理教程中語法分析后的章節(jié)里。
LL分析法最大的悲劇在于將一棵在人看來清晰直白的語法樹分割了。在遞歸下降分析法中,
一個樹節(jié)點所需要的屬性(例如算術(shù)運算符所需要的操作數(shù))可以直接由其子節(jié)點得到。但
是,在為了消除左遞歸而改變了的文法式子中,一個節(jié)點所需要的屬性可能跑到其兄弟節(jié)點
或者父節(jié)點中去了。貌似這里可以參考“繼承屬性”概念。
不過,綜合而言,我們有很多業(yè)余的手段來處理這種問題,例如建立屬性堆棧。具體來說,
例如對于例子代碼中計算算術(shù)表達式,就可以把表達式中的數(shù)放到一個棧里。
例子中,通過在文法表達式中插入動作符號來標識一個操作。例如對于文法:
expr2->addop term expr2,則可以改為:expr2->addop term # expr2。當發(fā)現(xiàn)分析棧的棧
頂元素是'#'時,則在屬性堆棧里取出操作數(shù)做計算。例子中還將操作符壓入了堆棧。
下載例子,例子代碼最好對照arith_expr.txt中寫的文法和分析表來看。
PS,最近在云風博客中看到他給的一句評論,我覺得很有道理,并且延伸開來可以說明我們
周圍的很多現(xiàn)象:
”很多東西,意識不到問題比找不到解決方法要嚴重很多。比如one-pass 這個,覺得實現(xiàn)
麻煩不去實現(xiàn),和覺得實現(xiàn)沒有意義不去實現(xiàn)就是不同的。“
對于以下現(xiàn)象,這句話都可以指明問題:
1、認為造輪子沒有意義,從不考慮自己是否能造出;
2、常告訴別人某個技術(shù)復雜晦澀不利于團隊使用,卻并不懂這個技術(shù);
3、籠統(tǒng)來說,【覺得】太多東西沒有意義,雖然并不真正懂這個東西。