根据文法画出语法树_编译工程5:语法分析(3)
接下來我們討論自頂向下文法分析方法中的非遞歸的預測分析。
自頂向下文法分析可以看做是為1. 輸入串構造語法分析樹的問題,它從語法分析樹的根開始,深度優先地按照先根順序創建語法分析樹的各個節點。2.也可以看做是尋找輸入串的最左推導的過程。
更通俗地講,自頂向下分析就是從起始符號開始,不斷的挑選出合適的產生式,將中間句子中的非終結符的展開,最終展開到給定的句型。因此,該方法的關鍵字在于如何挑選出合適的產生式。
我們來看兩個例子。
對于下面的文法
S -> aS | bS | c如何生成句型abac?
可以寫出來如下的推導:
Working-string Production S S -> aS aS S -> bS abS S -> aS abaS S -> c abac ACCEPT簡單分析一下,這個推導式如何產生的;首先,假設有一個 strcmp 函數來比較符號串 “S” 和 “abac” ,找到第一個不匹配的符號,也就是 “S” 和 “a” ,因此,此時必須將中間句子中的 “S” 展開,才能得到最終句子。將最終句子中不匹配的這個 “a” ,和 S 的三個產生式的右邊 aS 、 bS 和 c 相比,可以看出,只能選擇 S -> aS 展開,才可以和 “a” 匹配。剩下的部分類似分析。
分析中需要回溯的情況。
看一個例子:
S → rXd | rZd X → oa | ea Z → ai在接收到的輸入串是read的時候:
上述產生回溯的原因主要是?
S → rS' S'-> Xd | Zd X → oa | ea Z → ai再考慮文法
S–> AB A –> aA | ε B –> b | bB假設要分析的句子為: aaab ,首先從起始符號 S 開始。
第 1 步,起始符號只有一個產生式: S -> AB ,所以只能選擇這個產生式,用這個產生式的右邊代替 S ,于是得到一個中間句子 AB ,將選擇的產生式和得到中間句子(working-string)寫成列表的形式,如下:
Working-string Production S S –> AB AB第 2 步,從 AB 開始,首先展開 A , A 有兩個產生式: A -> aA, A -> ε ,我們對比一下最終句子 aaab 和 目前得到的中間句子 AB ,發現只能選擇 A -> aA ,否則將無法推導出 aaab 。因此選擇這個產生式,將其右邊替換掉中間句子 AB 中的 A ,于是得到中間句子 aAB :
Working-string Production S S –> AB AB A –> aA aAB繼續嘗試展開 aAB 中的 A ,再次對比發現,還是只能選擇產生式 A -> aA ,得到:
Working-string Production S S –> AB AB A –> aA aAB A –> aA aaAB再次應用產生式 A -> aA ,得到:
Working-string Production S S –> AB AB A –> aA aAB A –> aA aaAB A –> aA aaaAB到了這里,可以發現只能使用 A -> ε (否則無法得到 aaab ),應用此產生式后得到:
Working-string Production S S –> AB AB A –> aA aAB A –> aA aaAB A –> aA aaaAB A -> ε aaaB第 3 步,從 aaaB 開始,按上面同樣的原則嘗試展開 B ,最終得到:
Working-string Production S S –> AB AB A –> aA aAB A –> aA aaAB A –> aA aaaAB A -> ε aaaB B -> b aaab ACCEPT這個例子當中我們希望大家注意什么問題呢?就是空產生式。什么時候可以使用這個空產生式呢?
從上面的討論中,我們可以知道,在自頂向下的分析過程中,在產生式選擇時,
同時,我們也可以很容易想到,如果一個非終結符的兩個或多個產生式有相同的首字符,那么朝前看一個字符不能解決問題;如果我們希望只通過朝前看一個字符就能確定選擇哪個產生式,必須要求同一個非終結符的兩個或多個產生式不能有相同的首字符。【還有其他條件】
自頂向下的分析器的構造可以使用和文法相關的兩個函數FIRST和FOLLOW來實現。在分析過程中,FIRST和FOLLOW可以使得我們根據下一個輸入符號來選擇使用哪個產生式。
我們結合FIRST和FOLLOW討論一下。考慮兩個產生式
只要FIRST(
)和FIRST( )是不相交的集合,那么只需要查看下一個輸入符號就可以在這兩個產生式中進行選擇。FIRST(
)被定義為可以從 推導得到的串的首符號的集合。其中 是任意的文法符號串。如果 推導出 ,那么 也在FIRST集合中。如何在預測分析中求得FIRST集合呢?考慮產生式
A -> a那就很好理解,A的FIRST集就是{a}。
如果產生式的右部是非終結符呢?
S -> AB A -> a那么,
FIRST(A) = {a} FIRST(S) = {a}如果產生式的右部是非終結符,并且右部的第一個非終結符還有空產生式呢?
S -> AB A -> a | ε B -> b那么,
FIRST(A) = {a,ε} FIRST(S) = ?如果產生式的右部是非終結符,并且右部的所有非終結符都有空產生式呢?
S -> AB A -> a | ε B -> b | ε那么,
FIRST(A) = {a,ε} FIRST(S) = ?以上是直觀的解釋,可能比較好懂;如果把FIRST集的規則寫出來,可能有點難懂:
對于非終結符
,FOLLOW(A)被定義為可能在某些句型中緊跟在A右邊的終結符號的集合。譬如,下面的文法產生式那么,終結符
就在A的FOLLOW集中。我們首先看一下,FOLLOW集如何產生。
S -> AB A -> a | ε B -> b來看一下A的FOLLOW集。A后面能跟著什么呢?
這里我們要明確一點,在LL(1)分析方法中,FOLLOW集的作用就是幫忙確認啥時候該用空產生式。
譬如對上面的文法的一個句子 b。應該怎么展開呢?
看到b的時候,知道使用
S->AB接著要展開A,A看到的依然是b,這是就該使用
A->ε接下來看到
再看一個例子:
S -> AB A -> C|a B -> b C -> c | ε這里,對于句子 b,應該如何分析呢?所以,從這里我們能得出什么?
有一個產生式
A -> C那么,A的FOLLOW集也應該是C的FOLLOW集的子集。
另外,如果A是某些句型的最右符號,那么結束符號$也在A的FOLLOW集中。
對于FOLLOW集而言,
雖然看起來不怎么直觀,但是描述的相當精確,我們只需要嚴格按照這個規則,就可以生成FIRST集和FOLLOW集。
練習:
S -> aS | bS | c再一個練習:
S–> AB A –> aA | ε B –> b | bB再次使用下面的文法做例子
E->TE' E'->+TE'|? T->FT' T'->*FT'|? F->(E)|id計算出FIRST集和FOLLOW集。
FIRST(E) = FIRST(T) = FIRST(F) = { ( , id } FIRST(E') = {+, ?} FRIST(T') = {*, ?} FOLLOW(E) = FOLLOW(E') = { ), $} FOLLOW(T) = FOLLOW (T') = {+, ), $} FOLLOW(F) = {+, *, ), $}繼續做練習:
S->SS+|SS*|a這個文法,FIRST集,FOLLOW集是什么?
請對它進行提左公因子、消除左遞歸。
下面的文法,
S->aS’ S’->aS’AS’|? A->+|*FIRST集,FOLLOW集是什么?
答案:
第一個文法:
FIRST(S)={a} FOLLOW(S)={a,+,*,$}第二個文法:
First(S) = First(aS’)={a} First(S’)= First(aS’AS’) ∪ First(?)= {a} ∪{?}= {a, ?} First(A) = { +,*} Follow(S) ={$} Follow(S’)= {$, +,*} Follow(A)= {a, +,*,$}計算FIRST和FOLLOW有什么用呢?首先,我們可以用來判斷LL(1)文法。其次,可以用來構造自動進行LL文法分析的預測分析表。
如果文法中的任何兩個產生式
都滿足下列條件:– FIRST(
) FIRST( ) =–若
* ? ,那么FIRST( ) FOLLOW(A) = 【 類似】那么文法滿足LL(1)文法。
【LL(1)文法由一些明顯的特征,譬如,無二義性、無左遞歸以及沒有公共左因子。因此,明顯,有以上三個特征的文法,不是LL(1)文法。】
第一個條件很好理解;
第二個條件什么意思呢?
考慮下面的文法:
S->Aa A->B|C C->? B->a這個文法非常簡單,只能生成串a或者aa。這是兩個不同的句型,但這里的問題是,如果只朝前看一個字符a,那么在對A進行展開時,無法判斷應該選擇哪個產生式,因為如果選擇B,B可以產生a,而如果選擇C,A自己的后繼本身也是a,所以對于輸入a,有兩種推導方式。
以上對aa以及a的推導對于人而言,可能覺得非常簡單,甚至覺得不會出錯,但是對于機器而言,要進行判斷就不那么容易了。
練習:
下面的文法是否是LL(1)文法,為什么?
S ->AB|PQx A -> xy B -> bc P -> dP| ε Q -> aQ| ε接下來,我們來看一下,構造出FIRST和FOLLOW之后,怎么樣進行自動推導。
預測分析表
預測分析表M[A,a],是一個二維數組,其中,A是一個非終結符號,a是一個終結符號, 或者特殊符號$,也即輸入的結束符號。
在構造了預測分析表之后,只有當下一個輸入符號a在FIRST(
)中時才選擇產生式 。在 可以推導出空的情況下,如果當前輸入符號在FOLLOW(A)中,或者已經達到輸入中的$符且$在FOLLOW(A)中,那么仍應該選擇A-> 。構造預測分析表的算法如下:
對于文法:
E->TE' E'->+TE'|ε T->FT' T'->*FT'|ε F->(E)|id結合上面計算得出的FIRST集和FOLLOW集
FIRST(E) = FIRST(T) = FIRST(F) = { ( , id } FIRST(E') = {+, ?} FRIST(T') = {*, ?} FOLLOW(E) = FOLLOW(E') = { ), $} FOLLOW(T) = FOLLOW (T') = {+, ), $} FOLLOW(F) = {+, *, ), $}對應的預測分析表如下:
其中,
和 的產生式右部的FIRST集中有?,所以它們的FOLLOW集中的符號的位置也有產生式。至于為何有這樣的規則,我們可以考慮一下,下面的文法:
S->?對于這個文法,FIRST集為{?},FOLLOW集為{$}。那么預測分析表應該是什么呢?
另外可以再看前面的例子:
S –> AB A –> aA | ε B –> b | bB因為A的產生式中有ε,所以A的FOLLOW集,也即{b}也會加入到預測分析表中。
Working-string Production S S –> AB AB A –> aA aAB A –> aA aaAB A –> aA aaaAB A -> εaaaB B -> b aaab ACCEPT這里可以看到,當A遇上b的時候,就可以使用A->ε產生式。
表中的空白指示錯誤。
對于每個LL(1)文法,分析表中的每個條目都唯一地指定了一個產生式或者標明一個語法錯誤。當然,對于左遞歸或者二義性文法,M至少會包含一個多重定義的條目。
基于以上內容對文法
S->Aa A->B|C C->? B->a構造一下預測分析表。
可以發現,當輸入是a時,A->B 以及A->C 都可以被使用。
在構造出預測分析表之后,如何進行文法推導呢?
練習:
構造下面文法的LL(1)分析表。
D -> TL T -> int | real L -> id R R -> , idR | ε關于LL,我們最后看一段代碼,體會一下LL的工作過程。
#include <iostream> #include <map> #include <stack>/*grammar: S->F; S->(S+F); F->a; sentence:a,(a+a),((a+a)+a) */enum Symbols {// the symbols:// Terminal symbols:TS_L_PARENS, // (TS_R_PARENS, // )TS_A, // aTS_PLUS, // +TS_EOS, // $, in this case corresponds to '0'TS_INVALID, // invalid token// Non-terminal symbols:NTS_S, // SNTS_F // F };/* Converts a valid token to the corresponding terminal symbol */ Symbols lexer(char c) {switch(c){case '(': return TS_L_PARENS;case ')': return TS_R_PARENS;case 'a': return TS_A;case '+': return TS_PLUS;case '0': return TS_EOS; // end of stack: the $ terminal symboldefault: return TS_INVALID;} }int main(int argc, char **argv) {using namespace std;if (argc < 2){cout << "usage:ntll '(a+a)'" << endl;return 0;}// LL parser table, maps < non-terminal, terminal> pair to action map< Symbols, map<Symbols, int> > table;stack<Symbols> ss; // symbol stackchar *p; // input buffer// initialize the symbols stackss.push(TS_EOS); // terminal, $ss.push(NTS_S); // non-terminal, S// initialize the symbol stream cursorp = &argv[1][0];// set up the parsing table 預測分析表;table[NTS_S][TS_L_PARENS] = 2;table[NTS_S][TS_A] = 1;table[NTS_F][TS_A] = 3;while(ss.size() > 0){if(lexer(*p) == ss.top()){cout << "Matched symbols: " << lexer(*p) << endl;p++;ss.pop();}else{cout << "Rule " << table[ss.top()][lexer(*p)] << endl;switch(table[ss.top()][lexer(*p)]){case 1: // 1. S → Fss.pop();ss.push(NTS_F); // Fbreak;case 2: // 2. S → ( S + F ) //使用產生式的右部替換左部ss.pop();ss.push(TS_R_PARENS); // )ss.push(NTS_F); // Fss.push(TS_PLUS); // +ss.push(NTS_S); // Sss.push(TS_L_PARENS); // (break;case 3: // 3. F → ass.pop();ss.push(TS_A); // abreak;default:cout << "parsing table defaulted" << endl;return 0;break;}}}cout << "finished parsing" << endl;return 0; }以上的代碼體現了之前介紹的LL分析的思想。首先,構造預測分析表,描述了當非終結符遇到特定輸入時應該使用哪個產生式;其次,在使用產生式進行處理的,將產生式的右部替換左部即可。
參考:
總結
以上是生活随笔為你收集整理的根据文法画出语法树_编译工程5:语法分析(3)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据结构中缀表达式转后缀表达式与后缀表达
- 下一篇: ebs r12多少钱 实施oracle_