[转载]编译中的常见分析方法
原文:https://blog.csdn.net/hczhiyue/article/details/20483209
LL(k) 分析
LL 分析又稱為自頂向下的分析(top-down parsing),也有叫遞歸下降分析(recursive-descent parsing)。也是最簡單的一種分析方式。它工作的方式類似于找出一個產生式可以從哪一個終結符開始。
當分析時,從起始符號開始,比較輸入中的第一個終結符和啟動集,看哪一個產生式規則被使用了。當然,兩個啟動集之間不能擁有同一個終結符。如果有的話,就沒有辦法決定選擇哪個產生式規則了。
LL文法通常用數字來分類,比如 LL(1),LL(0) 等。這個數字告訴你,在一個文法規則中的任何點可以允許一次察看的終結符的最大數量。LL(0) 就不需要看任何終結符,分析器總是可以選擇正確的產生式規則。它只適用于所有的非終結符都只有一個產生規則。只有一個產生規則意味著只有一個字符串。[不用看當前的終結符是什么就可以決定是哪一個產生規則,說明這個規則是為一個固定的字符串所寫的。] 這種文法是沒有什么意義的。
最常見也是比較有用的事 LL(1) 文法。它只需要看一個終結符,然后就可以決定使用哪一個產生規則。而 LL(2) 則可以查看兩個終結符,還有 LL(k) 文法等等。對于某個固定的 k 值,也存在著根本不是 LL(k) 的文法,而且還很普遍。
下面來分析一下本章開頭給出的例子。首先看下面這條規則:
D := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
上述規則有十個產生式,每個產生式的啟動集是一個數字終結符構成的集合 {'0'}、{'1'}、……、{'9'}。這是一個很好的 LL(1) 文法,因為我們只要看一個終結符,就可以選擇一個正確的產生式。例如,如果看到一個終結符,其內容是 3,那么就采用上面第四個產生式,即 D := '3'。
接下來分析 DL 規則。
DL := D | D DL
上述規則有兩個產生式,啟動集是 {D},{D}。很不幸,兩個產生式的啟動集相同。這就表示只看第一個輸入中的第一個終結符不能選擇正確的產生式。
然而可以通過欺騙來繞過這個問題:如果輸入中第二個終結符不是一個數字,那么就選擇第一個產生式,但如果兩者都是數字就必須選擇第二個產生式。換句話說,這意味著這是一條好的 LL(2) 文法規則。實際上這里有些東西被簡化了。
再分析下 FN 規則吧。它的情況更糟糕。
FN := DL | DL '.' DL
它有兩條產生式,而且啟動集相同,均為 {DL}。然而這次不像 DL 規則那么幸運了。咋一看,似乎通過 LL(2) 可以分辨應該使用哪一個產生式。但是很不幸,我們無法確定在讀到終結符 ('.') 之前,需要讀多少個數字才算是 DL 符號的最后一個數字。[想想吧,分析器這么工作著:讀入第一個終結符,一看是相同的 DL 符號,那么就讀第二個終結符吧;讀入第二個終結符,兩者合起來一看,還是一樣的 DL 符號;讀入第三個終結符,前三個終結符合起來看,仍然是相同的 DL 符號。但是 DL 符號表指示數字表示沒有長度限制的。]沒有任何一個給定的 k 值,這都不符合 LL(k)文法,因為數字表總能突破這個 k 的長度。
最后看看啟動符號規則。有點意外,它產生規則的選擇很簡單。
S := '-' FN | FN
它有兩個產生規則,兩者的啟動集是 {'-'} 和{FN}。因此,如果輸入中第一個終結符是'-',那么就選擇第一個產生式,否則選擇第二個產生式。所以這是一個 LL(1)文法。
從上述的 LL 分析看,只有 FN 和 DL 規則引起了問題。但是不必絕望。大部分的非 LL(k) 文法都可以容易地轉換為 LL(1) 文法。下面以當前的這個例子來看看如何轉換有問題的 FN 和 DL。
對于 FN 符號來說,它的兩個產生式都開始于 DL,但是第二個產生式其后續的是一個小數點終結符 ('.'),以及另外一個數字表。那么這很容易解決:可以將 FN 改變為一個產生式,其以 DL 開始,后跟一個 FP(fractional part)符號。而 FP 符號則定義成或者為空,或者為小數點后跟著一個數字表,如下所示:
FN := DL FP
FP := @ | '.' DL
上述 @符號表示為空。現在 FN 文法沒有任何問題了,因為它現在只有一個產生式。而 FP 也不會有問題,因為它的兩個產生式的啟動集是不同的:前者是輸入的尾端,后者是小數點終結符。
DL 符號就不是好啃的核桃了,原因在于其遞歸和至少需要一個 D 符號。解決方案就是,給 DL 一個產生式,由一個 D 后跟一個 DR(digit rest)構成;而 DR 則有兩個產生式,一個是 D DR(表示更多的數字),一個是 @(表示沒有更多的數字)。最后本章開頭的例子被轉換成下面的一個完全的 LL(1) 文法了:
S := '-' FN | FN
FN := DL FP
FP := @ | '.' DL
DL := D DR
DR := @ | D DR
D := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
LR 分析
Lr 分析也叫自底向上的分析(bottom-up parsing),或者叫移進 - 歸約分析(shift-reduce parsing),相比 LL 分析難度更大些。它的基本原理是,首先收集輸入,直到它發現可以據此利用一個符號對收集到的輸入序列進行歸約。可以與數學里面解方程式時的消元法進行類比。這聽起來似乎很難。下面還是以一個例子來澄清。例子中將分析字符串 3.14,看看是怎樣從文法產生出來的。
S := '-' FN | FN
FN := DL | DL '.' DL
DL := D | D DL
D := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
首先從輸入中讀入 3。
3
然后看看是否可以將其歸約為一個符號(Symbol,即非終結符)。實際上可以歸約,就是說用 D 符號的產生式可以得到當前讀入的字符串(這也是成為產生式的原因)。
很快發現,從 DL 符號可以產生符號 D,于是又可以歸約成 DL。(實際上還可以進一步地歸約成 FN,于是這里就產生了歧義,到底應該歸約成哪一個呢?這表明這個文法定義是二義性的,我們在這里就忽略這個問題,直接選擇 DL 作為歸約結果吧。)接著從輸入中讀入一個小數點,并試圖進行歸約:
D ==> 規約到 DL
DL ==> 讀入下一個字符
DL . ==> 規約到?
但是這次的歸約嘗試失敗了,因為沒有任何符號的定義可以產生這種形式的字符串。也就是說,這種形式不能規約到任何符號。
所以接著我們讀入下一個字符 1。這次可以將數字 1 歸約到 D 符號。接著再讀入一個字符 4。4 可以歸約到 D,繼續歸約到 DL。這兩次的讀入和規約形成了 D Dl 這個序列,而這個序列可以歸約到 DL。
DL . ==> 讀入下一個字符 1
DL . 1 ==> 1 歸約到 D
DL . D ==> 讀入下一個字符 4
DL . D 4 ==> 4 歸約到 D
DL . D D ==> 4 繼續歸約到 DL
DL . D DL ==> D DL 歸約到 DL
察看文法我們可以很快地注意到,FN 能產生 DL . Dl 這種形式的序列,所以可以做一個歸約。然后注意到 FN 可以從 S 符號產生,所以可以歸約到 S,然后停止,整個分析結束。
DL . DL ==> 歸約到 FN
FN ==> 規約到 S
S ==> 分析結束
可能你已經注意到,我們經常可以選擇是否現在就做歸約,還是等到讀入更多的符號后再作不同的歸約。移進 - 歸約分析算法有很多不同的變種,按照復雜度和能力遞增的順序是:LR(0), SLR, LALR 和 LR(1)。LR(1) 通常需要一個巨大的分析表,在實踐上不具有實用性,因此 LALR 是最常使用的算法。SLR 和 LR(0) 對于大部分的程序語言來說還不夠強大。
轉載于:https://www.cnblogs.com/jiading/p/10793348.html
總結
以上是生活随笔為你收集整理的[转载]编译中的常见分析方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python中硬要写抽象类和抽象方法
- 下一篇: 3. CMake 系列 - 分模块编译安