Chapter 2 語法分析
我們來練習一下這些東西吧!
(1)語法的左遞迴消除
假設終結符集合為{a,b,...,y,z}, 首字母大寫(非終結符),且具有相同字首的單詞可以推匯出更長的一個單詞,大寫字母(非終結符)可以推匯出對應的小寫字母(終結符),比如
Pine -> Pineapple
如何對這個語句進行左遞迴消除呢?
Nice!
Pine -> apple Pine'
Pine' -> apple Pine' | \epsilon
(看起來我理解錯了,左邊必須是一個非終結符)
(2)左公因子提取
假設我們有下面的規則:
A -> a
A -> aback
A -> abandon
A -> abcent
A -> accent
A -> ad
A -> advertisement
假設終結符集合為{a,b,...,y,z}, 大寫字母為非終結符,那麼上面的文法在用於LL(1)預測分析時會遇到什麼問題?如何解決這個問題?
A -> aA'
A' -> bB'
A' -> cC'
A' -> dD'
B' -> aA''
B' -> cC''
A'' -> ...
C' -> ...
D' -> ...
A'' -> ...
C'' -> ...
(3)計算First和Follow
注意FIRST和FOLLOW的定義
假設一個線性方程組可以表示為:
終結符:{a,b,x,+}
G -> E G'
G' -> E G' | ε
E -> a x E'
E' -> + b x E' | ε
S1:
FIRST(G): {E}
FIRST(G'): {E,ε}
FIRST(E): {a}
FIRST(E'): {+,ε}
S2:代入FIRST(E):
FIRST(G): {a}
FIRST(G'): {a,ε}
FIRST(E): {a}
FIRST(E'): {+,ε}
首先, FOLLOW(G) = {$}
FOLLOW(E) = (FIRST(G') - {ε}) + FOLLOW(G) #因為G'能推匯出空串 = {a,$}
FOLLOW(G') = FOLLOW(G) = {$} #由第一個產生式
FOLLOW(E') = FOLLOW(E) = {a,$}
gpt-4的解釋:
您的推理是正確的。讓我們重新計算FIRST和FOLLOW集合,並確保我們的推理是基於正確的定義和規則。
首先,我們計算每個非終結符的FIRST集合:
-
FIRST(G)
- 從
G -> E G'
得到 FIRST(E) - 因此,FIRST(G) = FIRST(E) =
- 從
-
FIRST(G')
- 從
G' -> E G'
得到 FIRST(E) = - 從
G' -> ε
得到 ε - 因此,FIRST(G') =
- 從
-
FIRST(E)
- 從
E -> a x E'
得到a
- 因此,FIRST(E) =
- 從
-
FIRST(E')
- 從
E' -> + b x E'
得到+
- 從
E' -> ε
得到 ε - 因此,FIRST(E') =
- 從
接下來,我們計算每個非終結符的FOLLOW集合:
-
FOLLOW(G)
- 由於
G -> E G'
,G' 可能為 ε,所以 FOLLOW(G) 包含 FOLLOW(G') - 通常,FOLLOW(G) 包含結束符號
$
,表示文法的結束 - 因此,FOLLOW(G) =
- 由於
-
FOLLOW(G')
- 由於
G -> E G'
,G' 後面可能跟著 FOLLOW(G) - 因此,FOLLOW(G') = FOLLOW(G) =
- 由於
-
FOLLOW(E)
- 由於
G -> E G'
,E 後面可能跟著 FIRST(G'),但因為 G' 可以推匯出 ε,所以 FOLLOW(E) 也包含 FOLLOW(G') - 因此,FOLLOW(E) = (FIRST(G') - {ε}) ∪ FOLLOW(G') = {a} ∪ {$} =
- 由於
-
FOLLOW(E')
- 由於
E -> a x E'
,E' 後面可能跟著 FOLLOW(E) - 因此,FOLLOW(E') = FOLLOW(E) =
- 由於
總結來說,我們計算了每個非終結符的FIRST和FOLLOW集合,並根據文法規則推匯出了它們。實際的FOLLOW集合需要根據文法的完整上下文來確定。您的推理是正確的,FOLLOW(G) 應該包含結束符號 $
,而 FOLLOW(E) 應該包含從 FIRST(G') 中去除 ε 後的集合以及 FOLLOW(G')。
(3)LL(1)預測分析表構造:
假設還是以上邊的“線性方程組”進行分析
G -> E G'
G' -> E G' | ε
E -> a x E'
E' -> + b x E' | ε
首先列表:
列 = 非終結符,行 = 終結符 + '$'
FIRST(G): {a}
FIRST(G'): {a,ε}
FIRST(E): {a}
FIRST(E'): {+,ε}
FOLLOW(G): {$}
FOLLOW(G'): {$}
FOLLOW(E): {a,$}
FOLLOW(E'): {a,$}
a | b | x | + | $ | |
---|---|---|---|---|---|
G | |||||
G' | |||||
E | |||||
E' |
然後,根據下一個終結符的可能情況,選擇適當的產生式填表
(1)填入FIRST中的非ε元素:
a | b | x | + | $ | |
---|---|---|---|---|---|
G | G->EG' | ||||
G' | G'->EG' | ||||
E | E->axE' | ||||
E' | E'->+bxE' |
(2)考慮ε元素,填入FOLLOW:
a | b | x | + | $ | |
---|---|---|---|---|---|
G | G->EG' | ||||
G' | G'->EG' | G'->ε | |||
E | E->axE' | ||||
E' | E'->ε | E'->+bxE' | E'->ε |
(3)在空白處填入error
a | b | x | + | $ | |
---|---|---|---|---|---|
G | G->EG' | err | err | err | |
G' | G'->EG' | err | err | err | G'->ε |
E | E->axE' | err | err | err | |
E' | E'->ε | err | err | E'->+bxE' | E'->ε |
如果我們用上面的分析表來解析這樣一個字串
axax+bx
那麼如何給出具體的分析過程呢?
已匹配 | 棧 | 輸入 | 動作 |
---|---|---|---|
G | axax+bx$ | ||
EG' | axax+bx$ | G->EG' | |
axE'G' | axax+bx$ | E->axE' | |
ax | E'G' | ax+bx¥ | 匹配ax |
ax | G' | ax+bx$ | E'->ε |
ax | EG' | ax+bx$ | G'->EG' |
ax | axE'G' | ax+bx$ | E->axE' |
axax | E'G' | +bx$ | 匹配ax |
axax | +bxE'G' | $ | E' -> +bxE' |
axax+bx | E'G' | $ | 匹配+bx |
axax+bx | G' | $ | E' -> ε |
axax+bx | $ | G' -> ε |
這樣就完成了全過程~
接下來就是LR文法啦!
(4)LR(0)項集族構建
由於文法中存在右遞迴,所以可能還要把原來的文法修改一下:
G -> E G'
G' -> E G' | ε
E -> a x E'
E' -> + b x E' | ε
這裡為了方便敘述,我們把a和b合併,統稱為a(即未知數x前的係數)
G -> G E | E
E -> E + a x | a x
那麼如何構建它的LR項集族呢?
首先,我們增廣文法,設定一個新的非終結符G'和產生式G' -> G
;
0:G' -> G
1:G -> G E
2:E -> E + a x
3:E -> a x
4:G -> E
I0:
G' -> •G
G -> •GE
G -> •E
E -> •E+ax
E -> •ax
I1:
G' -> G•
G -> G•E
E -> •E+ax
E -> •ax
I2:
G -> E•
E -> E•+ax
I3:
E -> a•x
I4:
G -> GE•
E -> E•+ax
I5:
E -> E+•ax
I6:
E -> ax•
I7:
E -> E+a•x
I8:
E -> E+ax•
(注意這裡我把閉包生成的產生式用反引號標註出來了,這樣便於區分)
為了填寫ACTION-GOTO表,我們需要根據每個狀態中的項來確定ACTION(移進或規約)和GOTO(轉移到的狀態)。以下是根據您提供的狀態構建的ACTION-GOTO表:
狀態 | ACTION | GOTO | ||||
---|---|---|---|---|---|---|
+ | a | x | $ | G | E | |
0 | s3 | 1 | 2 | |||
1 | s3 | acc | 4 | |||
2 | s5 | r4 | r4 | r4 | ||
3 | s6 | |||||
4 | s5 | r1 | r1 | r1 | ||
5 | s7 | |||||
6 | r3 | r3 | r3 | r3 | ||
7 | s8 | |||||
8 | r2 | r2 | r2 | r2 |
這裡的"s"表示移進(shift),"r"表示規約(reduce),"acc"表示接受(accept)。數字表示移進或規約後進入的狀態。例如,狀態0在遇到終結符"a"時移進到狀態3,在遇到終結符"x"時移進到狀態4,在遇到非終結符"G"時轉移到狀態1,在遇到非終結符"E"時轉移到狀態2。
請注意,這裡的規約動作是根據產生式進行的。例如,狀態2表示使用產生式E -> E + a x
進行規約,狀態6和狀態8表示使用產生式E -> a x
進行規約。狀態1表示接受,意味著分析棧為空,輸入緩衝區中只剩下結束符,分析成功。
(5)SLR
為了將其轉換為SLR文法,我們需要計算原來文法的FOLLOW集合。
G -> G E | E
E -> E + a x | a x
首先,FIRST(G) = FIRST(E)(由產生式1); FIRST(E) = {a}(由產生式2),所以FIRST(G) = {a}.
FOLLOW(G) = {$} + FIRST(E) (產生式1,第一條規則) + FOLLOW(E) (產生式1,第二條規則);
FOLLOW(E) = {$,a,+} (產生式2的第一條規則:FOLLOW(E)中含有{+},產生式1中第二條規則:FOLLOW(E)包含FOLLOW(G)).
FOLLOW(G) = {$,a,+}
然後,對於規約到G的所有操作(r1,r4),只有下一個字元屬於FOLLOW(G)才執行;對於規約到E的所有操作(r2,r3),只有下一個字元屬於FOLLOW(E)才執行。
狀態 | ACTION | GOTO | ||||
---|---|---|---|---|---|---|
+ | a | x | $ | G | E | |
0 | s3 | 1 | 2 | |||
1 | s3 | acc | 4 | |||
2 | r4 | r4 | r4 | |||
3 | s6 | |||||
4 | s5 | r1 | r1 | |||
5 | s7 | |||||
6 | r3 | r3 | r3 | |||
7 | s8 | |||||
8 | r2 | r2 | r2 |
(6)LR(1)
如果我們需要構造LR(1)文法的話,我們需要向前看一個符號,這也就導致之前的狀態會有所增加。(SLR:8個狀態,LR(1):?個狀態)
LR(1)項集族也叫做規範LR項集族
FIRST(G) = {a}
FIRST(E) = {a}
FOLLOW(G) = {$,a,+}
FOLLOW(E) = {$,a,+}
首先還是增廣文法:
0:G' -> G
1:G -> G E
2:E -> E + a x
3:E -> a x
4:G -> E
然後,從一開始的狀態開始,一步一步往下寫狀態:
注意LR(1)和LR(0)的不同點是,我們要對First(βa)的所有字元將閉包生成的集合放進去
關鍵:B是緊跟在•後邊的下一個非終結符, 向前看符號由上一步閉包的First(βa)確定
LR能正確處理左遞迴,但是左遞迴在計算閉包過程中會出現閉包產生自身的情況,如果被忽略的話很容易漏掉一部分向前看符號(比如說這裡就漏掉了a)
I0:
G' -> •G,$
`G -> •GE,$` #FIRST(\epsilon) = $(閉包的上一步是G' -> •G)
`G -> •E,$/a` #FIRST(\epsilon) = $,不過這個表示式也可以由`G -> •GE,$`產生,所以還要加上FIRST(E), 即{a}.
`E -> •E+ax,$/+/a` #由`G -> •E,$`產生,所以FIRST(\epsilon)仍然是\$.
`E -> •ax,$/+/a` #可以由`G -> •E,$`和`E -> •E+ax,$`產生,所以向前看符號可以是$,+
I1: (I0->G)
G' -> G•,$
G -> G•E,$
`E -> •E+ax,$/+` #FIRST(\epsilon) = {$},但是閉包也有可能推出自身,所以FIRST(+ax) = {+}也新增進去了
`E -> •ax,$/+` #FIRST(\epsilon) = {$}, FIRST(+ax) = {+}
I2: (I0->E)
G -> E•,$/a
E -> E•+ax,$/+/a
I3: (I0->a)
E -> a•x,$/+/a
I4: (I1->a) #新增狀態
E -> a•x,$/+
I5: (I1->E)
G -> GE•,\$
E -> E•+ax,$/+
I6: (I2->+),
E -> E+•ax,$/+/a
I7: (I3->x)
E -> ax•,$/+/a
I8: (I4->x)
E -> ax•,$/+
I9: (I5->+)
E -> E+•ax,$/+
I10: (I6->a)
E -> E+a•x,$/+/a
I11: (I9->a)
E -> E+a•x,$/+
I12: (I10->x)
E -> E+ax•,$/+/a
I13: (I11->x)
E -> E+ax•,$/+
GOTO和ACTION表構造
(構造時可以先填入狀態轉換部分,即移入部分;然後填寫規約和acc)
狀態 | ACTION | GOTO | ||||
---|---|---|---|---|---|---|
a | + | x | $ | G | E | |
0 | s3 | 1 | 2 | |||
1 | s4 | acc | 5 | |||
2 | r4 | s6 | r4 | |||
3 | s7 | |||||
4 | s8 | |||||
5 | s9 | r1 | ||||
6 | s10 | |||||
7 | r3 | r3 | r3 | |||
8 | r3 | r3 | ||||
9 | s11 | |||||
10 | s12 | |||||
11 | s13 | |||||
12 | r2 | r2 | r2 | |||
13 | r2 | r2 |
Another Example:
假設一個文法是這樣的(已經經過增廣文法步驟)
S' -> S
S -> L = R | R
L -> * R | id
R -> L
如何構造其LR(1)項集族的第一個狀態I0呢?
好的,讓我們構造這個文法的LR(1)項集族的第一個狀態 ( I_0 )。文法是:
S' -> S
S -> L = R | R
L -> * R | id
R -> L
增廣文法
我們從增廣文法開始,這裡已經給出了增廣文法:
0: S' -> S
1: S -> L = R
2: S -> R
3: L -> * R
4: L -> id
5: R -> L
構造項集 ( I_0 )
構造項集 ( I_0 ) 的步驟如下:
- 初始專案:將初始專案
S' -> •S, $
放入 ( I_0 )。 - 閉包計算:根據初始專案,逐步計算其閉包,加入所有可以由點後面非終結符推出的專案。
初始專案
I0:
S' -> •S, $
閉包計算
我們看到 S
後面是點,因此我們要加入所有 S
的產生式專案,並且需要新增對應的向前看符號。
-
處理
S
:S -> •L = R, $ S -> •R, $
- 對於
S -> •L = R, $
,由於=
是一個終結符,所以向前看符號是$
。 - 對於
S -> •R, $
,由於沒有更多的產生式,向前看符號是$
。
- 對於
-
處理
L
:L -> •* R, = L -> •id, =
- 對於
L -> •* R, =
,由於*
是一個終結符,所以向前看符號是=
。 - 對於
L -> •id, =
,由於id
是一個終結符,所以向前看符號是=
。
- 對於
-
處理
R
:R -> •L, $
- 對於
R -> •L, $
,由於沒有更多的產生式,向前看符號是$
。
- 對於
-
處理
L
(從R -> •L, $
推出):L -> •* R, $ L -> •id, $
- 對於
L -> •* R, $
,由於*
是一個終結符,所以向前看符號是$
。 - 對於
L -> •id, $
,由於id
是一個終結符,所以向前看符號是$
。
- 對於
合併專案
我們現在將所有這些專案合併到一起,形成 ( I_0 ):
I0:
S' -> •S, $
S -> •L = R, $
S -> •R, $
L -> •* R, =/$
L -> •id, =/$
R -> •L, $
解釋
- 專案
S' -> •S, $
表示起始專案。 - 專案
S -> •L = R, $
和S -> •R, $
來自於文法規則S
。 - 專案
L -> •* R, =/$
和L -> •id, =/$
來自於文法規則L
。 - 專案
R -> •L, $
來自於文法規則R
。
透過這些步驟,我們完成了LR(1)項集族的第一個狀態 ( I_0 ) 的構造。
(7)LALR(1)
需要將LR(1)中相同核心(或者核心)的項合併。
I0:
- `G -> •GE,$` #FIRST(\epsilon) = $(閉包的上一步是G' -> •G)
- `G -> •E,$/a` #FIRST(\epsilon) = $,不過這個表示式也可以由`G -> •GE,$`產生,所以還要加上FIRST(E), 即{a}.
- `E -> •E+ax,$/+/a` #由`G -> •E,$`產生,所以FIRST(\epsilon)仍然是\$.
- `E -> •ax,$/+/a` #可以由`G -> •E,$`和`E -> •E+ax,$`產生,所以向前看符號可以是\$,+
I1: (I0->G)
G' -> G•,\$
G -> G•E,\$
`E -> •E+ax,$/+` #FIRST(\epsilon) = {\$},但是閉包也有可能推出自身,所以FIRST(+ax) = {+}也新增進去了
`E -> •ax,$/+` #FIRST(\epsilon) = {$}, FIRST(+ax) = {+}
I2: (I0->E)
G -> E•,\$/a
E -> E•+ax,\$/+/a
I3: (I0->a)
E -> a•x,$/+/a
I4: (I1->a) #新增狀態
E -> a•x,$/+
I5: (I1->E)
G -> GE•,\$
E -> E•+ax,$/+
I6: (I2->+),
E -> E+•ax,$/+/a
I7: (I3->x)
E -> ax•,$/+/a
I8: (I4->x)
E -> ax•,$/+
I9: (I5->+)
E -> E+•ax,$/+
I10: (I6->a)
E -> E+a•x,$/+/a
I11: (I9->a)
E -> E+a•x,$/+
I12: (I10->x)
E -> E+ax•,$/+/a
I13: (I11->x)
E -> E+ax•,$/+
我們可以看到: I3
和I4
,I6
和I9
,I7
和I8
,I10
和I11
,I12
和I13
都是具有相同核心的項!那麼...
狀態 | ACTION | GOTO | ||||
---|---|---|---|---|---|---|
a | + | x | $ | G | E | |
0 | s34 | 1 | 2 | |||
1 | s34 | acc | 5 | |||
2 | r4 | s69 | r4 | |||
3,4 | s78 | |||||
5 | s69 | r1 | ||||
6,9 | s1011 | |||||
7,8 | r3 | r3 | r3 | |||
10,11 | s1213 | |||||
12,13 | r2 | r2 | r2 |
我們可以構造一個這樣的LALR分析表!
對比之前的SLR分析表,可以發現SLR和LALR(1)都是隻有9個狀態:
狀態 | ACTION | GOTO | ||||
---|---|---|---|---|---|---|
+ | a | x | $ | G | E | |
0 | s3 | 1 | 2 | |||
1 | s3 | acc | 4 | |||
2 | r4 | r4 | r4 | |||
3 | s6 | |||||
4 | s5 | r1 | r1 | |||
5 | s7 | |||||
6 | r3 | r3 | r3 | |||
7 | s8 | |||||
8 | r2 | r2 | r2 |
如何用上面的SLR和LALR(1)分析表分別給出它們解析axax+ax
的過程呢?
SLR的分析過程
狀態棧 | 符號 | 輸入 | 動作 |
---|---|---|---|
0 | axax+ax | s3 | |
0 3 | a | xax+ax | s6 |
0 3 6 | ax | ax+ax | r3 |
0 2 | E | ax+ax | r4 |
0 1 | G | ax+ax | s3 |
0 1 3 | Ga | x+ax | s6 |
0 1 3 6 | Gax | +ax | r3 |
0 1 4 | GE | +ax | s5 |
0 1 4 5 | GE+ | ax | s7 |
0 1 4 5 7 | GE+a | x | s8 |
0 1 4 5 7 8 | GE+ax | r2 | |
0 1 4 | GE | r1 | |
0 1 | G | acc |
LALR的分析過程
狀態棧 | 符號 | 輸入 | 動作 |
---|---|---|---|
0 | axax+ax | s34 | |
0 34 | a | xax+ax | s78 |
0 34 78 | ax | ax+ax | r3 |
0 2 | E | ax+ax | r4 |
0 1 | G | ax+ax | s34 |
0 1 34 | Ga | x+ax | s78 |
0 1 34 78 | Gax | +ax | r3 |
0 1 5 | GE | +ax | s69 |
0 1 5 69 | GE+ | ax | s1011 |
0 1 5 69 1011 | GE+a | x | s1213 |
0 1 5 69 1011 1213 | GE+ax | r2 | |
0 1 5 | GE | r1 | |
0 1 | G | acc |
發現它們的分析過程基本是一樣的!
GPT的解釋:
在這個特定的例子中,SLR和LALR分析表確實產生了相同的分析過程,這是因為文法在這個情況下沒有衝突,且狀態合併後沒有產生新的衝突。然而,這並不意味著SLR和LALR總是等效的。在更復雜的文法中,LALR分析表可能會比SLR分析表更小,因為它能夠合併一些狀態,從而減少表的大小和分析器的複雜性。
LALR(1)分析表的一個主要優勢是它通常比SLR(1)分析表更小,因為它能夠合併一些看起來相似的狀態,只要這些狀態的向前看符號集合是相同的。這種合併可以顯著減少分析表的大小,從而減少分析器的記憶體需求。然而,這種合併有時會導致新的移進-規約衝突或規約-規約衝突,這在SLR分析表中可能不會出現。
在實際應用中,LALR(1)分析器通常被認為是一個很好的折中方案,因為它既能夠處理比SLR(1)更廣泛的文法,又不會像LR(1)分析器那樣需要大量的狀態。因此,即使在這個例子中SLR和LALR分析過程相同,LALR(1)仍然是一個更通用的選擇,特別是在處理更復雜的程式語言文法時。
(8) 一些相關概念
8.1 句型、句子和控制代碼
在語法分析中,句型、句子和控制代碼是三個重要的概念,它們描述了文法產生式和分析過程中的不同階段和結構。
-
句型(Sentential Form):
句型是指在語法分析過程中,從文法的開始符號出發,透過一系列產生式推匯出的中間形式。它是一個字串,可能包含終結符、非終結符以及可能的語法分析器狀態資訊(如點記號.
在LR分析中的位置)。句型不一定是合法的句子,因為它可能還在推導的過程中,包含未展開的非終結符。 -
句子(Sentence):
句子是指從文法的開始符號出發,透過一系列產生式推匯出的僅包含終結符的字串。換句話說,句子是句型的一種特殊情況,其中所有的非終結符都已經被替換為終結符。句子是文法定義的語言中的合法成員,它代表了語法分析過程的最終結果。 -
控制代碼(Handle):
控制代碼是指在句型中的一個最左邊的產生式,它可以被規約以向文法的開始符號靠近。在自底向上的語法分析(如LR分析)中,控制代碼是當前句型中需要被規約的部分,以便繼續分析過程。控制代碼的確切位置取決於分析器的當前狀態和輸入。在LR分析中,控制代碼通常是由分析表指示的,當分析器處於某個特定狀態時,它會識別出當前句型中的控制代碼,並執行規約操作。
例如,考慮以下文法:
S -> E
E -> E + T
E -> T
T -> a
對於輸入a + a
,分析過程中的句型可能如下:
- 初始句型:
S
- 推導後的句型:
E
- 進一步推導:
E + T
- 規約後的句型:
E + a
- 最終句子:
a + a
在這個過程中,a
、T
、E + T
和E + a
都是句型,而a + a
是句子。在自底向上的分析中,a
、T
和E + T
都是控制代碼,因為它們是當前句型中最左邊的可規約部分。
8.2 最左推導和最右推導
最左推導:在每一步推導中,替換最左邊的非終結符
最右推導:在每一步推導中,替換最右邊的非終結符
如果一個文法定義了加法和取模操作:
M -> M mod M
M -> M + M
M -> (M)
M -> id
如何給出一個具有兩個最左推導的串來說明它的二義性?如何消除二義性?
為了說明這個文法的二義性,我們需要找到一個串,它可以透過兩種不同的最左推導方式得到,這意味著存在兩種不同的語法樹。
考慮串 id + id mod id
,我們可以透過兩種不同的最左推導方式得到它:
最左推導1:
M -> M + M -> id + M -> id + M mod M -> id + id mod id
最左推導2:
M -> M mod M -> M + M mod M -> id + id mod id
這兩種推導方式說明了文法的二義性,因為同一個串 id + id mod id
可以透過兩種不同的語法樹得到。
為了消除二義性,我們需要修改文法,引入優先順序和結合性規則。例如,我們可以定義取模操作的優先順序高於加法操作,並且所有操作都是左結合的。以下是一個消除二義性的文法示例:
E -> E + T
E -> T
T -> T mod F
T -> F
F -> (E)
F -> id
在這個文法中,E
表示表示式,T
表示項,F
表示因子。取模操作的優先順序高於加法操作,因為取模操作在項 T
的產生式中,而加法操作在表示式 E
的產生式中。這樣,id + id mod id
將只有一種最左推導方式,從而消除了二義性。
如果一個文法定義如下(字尾表示式):
S -> S S + | S S * | id
如何說明它是沒有二義性的呢?
要說明這個字尾表示式文法沒有二義性,我們需要證明對於任何給定的字尾表示式,只存在一種最左推導方式。字尾表示式的一個關鍵特性是運算子總是出現在它們的運算元之後,這意味著不存在運算子優先順序或結合性的問題。
考慮字尾表示式 id id + id *
,我們可以透過以下唯一的最左推導方式得到它:
- 從
S
開始,替換最左邊的非終結符S
為S S +
,得到S S +
。 - 再次替換最左邊的非終結符
S
為id
,得到id S +
。 - 然後替換最左邊的非終結符
S
為id
,得到id id +
。 - 最後替換最左邊的非終結符
S
為S S *
,得到id id + S S *
。 - 再次替換最左邊的非終結符
S
為id
,得到id id + id S *
。 - 最後替換最左邊的非終結符
S
為id
,得到id id + id *
。
這個推導過程表明,對於給定的字尾表示式,只存在一種最左推導方式。因此,這個文法是沒有二義性的。
字尾表示式的文法之所以沒有二義性,是因為它的結構保證了每個運算子都緊跟在其運算元之後,不會出現多種解釋的可能性。在解析字尾表示式時,我們可以從左到右掃描表示式,每當遇到一個運算子時,它前面的兩個運算元就是它的運算元,這種結構是唯一確定的。
8.3 語法分析器的錯誤恢復
(1)LL(1)分析表的恐慌模式
以上面的文法為例:
G -> E G'
G' -> E G' | ε
E -> a x E'
E' -> + b x E' | ε
我們構造的LL(1)分析表如下:
a | b | x | + | $ | |
---|---|---|---|---|---|
G | G->EG' | err | err | err | err |
G' | G'->EG' | err | err | err | G'->ε |
E | E->axE' | err | err | err | err |
E' | E'->ε | err | err | E'->+bxE' | E'->ε |
如果LL(1)分析錶帶上恐慌模式是什麼樣的呢?
將FOLLOW(A)所有符號放在A的同步集合中:
a | b | x | + | $ | |
---|---|---|---|---|---|
G | G->EG' | err | err | err | syn |
G' | G'->EG' | err | err | err | G'->ε |
E | E->axE' | err | err | err | syn |
E' | E'->ε | err | err | E'->+bxE' | E'->ε |