【編譯原理複習Part_2】語法分析

A_cute_Hanaoka_Yuzu發表於2024-06-27

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集合:

  1. FIRST(G)

    • G -> E G' 得到 FIRST(E)
    • 因此,FIRST(G) = FIRST(E) =
  2. FIRST(G')

    • G' -> E G' 得到 FIRST(E) =
    • G' -> ε 得到 ε
    • 因此,FIRST(G') =
  3. FIRST(E)

    • E -> a x E' 得到 a
    • 因此,FIRST(E) =
  4. FIRST(E')

    • E' -> + b x E' 得到 +
    • E' -> ε 得到 ε
    • 因此,FIRST(E') =

接下來,我們計算每個非終結符的FOLLOW集合:

  1. FOLLOW(G)

    • 由於 G -> E G',G' 可能為 ε,所以 FOLLOW(G) 包含 FOLLOW(G')
    • 通常,FOLLOW(G) 包含結束符號 $,表示文法的結束
    • 因此,FOLLOW(G) =
  2. FOLLOW(G')

    • 由於 G -> E G',G' 後面可能跟著 FOLLOW(G)
    • 因此,FOLLOW(G') = FOLLOW(G) =
  3. FOLLOW(E)

    • 由於 G -> E G',E 後面可能跟著 FIRST(G'),但因為 G' 可以推匯出 ε,所以 FOLLOW(E) 也包含 FOLLOW(G')
    • 因此,FOLLOW(E) = (FIRST(G') - {ε}) ∪ FOLLOW(G') = {a} ∪ {$} =
  4. 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 ) 的步驟如下:

  1. 初始專案:將初始專案 S' -> •S, $ 放入 ( I_0 )。
  2. 閉包計算:根據初始專案,逐步計算其閉包,加入所有可以由點後面非終結符推出的專案。

初始專案

I0:
S' -> •S, $

閉包計算

我們看到 S 後面是點,因此我們要加入所有 S 的產生式專案,並且需要新增對應的向前看符號。

  1. 處理 S

    S -> •L = R, $
    S -> •R, $
    
    • 對於 S -> •L = R, $,由於 = 是一個終結符,所以向前看符號是 $
    • 對於 S -> •R, $,由於沒有更多的產生式,向前看符號是 $
  2. 處理 L

    L -> •* R, =
    L -> •id, =
    
    • 對於 L -> •* R, =,由於 * 是一個終結符,所以向前看符號是 =
    • 對於 L -> •id, =,由於 id 是一個終結符,所以向前看符號是 =
  3. 處理 R

    R -> •L, $
    
    • 對於 R -> •L, $,由於沒有更多的產生式,向前看符號是 $
  4. 處理 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•,$/+
我們可以看到: I3I4I6I9I7I8I10I11I12I13都是具有相同核心的項!那麼...

狀態 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 句型、句子和控制代碼

在語法分析中,句型、句子和控制代碼是三個重要的概念,它們描述了文法產生式和分析過程中的不同階段和結構。

  1. 句型(Sentential Form)
    句型是指在語法分析過程中,從文法的開始符號出發,透過一系列產生式推匯出的中間形式。它是一個字串,可能包含終結符、非終結符以及可能的語法分析器狀態資訊(如點記號.在LR分析中的位置)。句型不一定是合法的句子,因為它可能還在推導的過程中,包含未展開的非終結符。

  2. 句子(Sentence)
    句子是指從文法的開始符號出發,透過一系列產生式推匯出的僅包含終結符的字串。換句話說,句子是句型的一種特殊情況,其中所有的非終結符都已經被替換為終結符。句子是文法定義的語言中的合法成員,它代表了語法分析過程的最終結果。

  3. 控制代碼(Handle)
    控制代碼是指在句型中的一個最左邊的產生式,它可以被規約以向文法的開始符號靠近。在自底向上的語法分析(如LR分析)中,控制代碼是當前句型中需要被規約的部分,以便繼續分析過程。控制代碼的確切位置取決於分析器的當前狀態和輸入。在LR分析中,控制代碼通常是由分析表指示的,當分析器處於某個特定狀態時,它會識別出當前句型中的控制代碼,並執行規約操作。

例如,考慮以下文法:

S -> E
E -> E + T
E -> T
T -> a

對於輸入a + a,分析過程中的句型可能如下:

  • 初始句型:S
  • 推導後的句型:E
  • 進一步推導:E + T
  • 規約後的句型:E + a
  • 最終句子:a + a

在這個過程中,aTE + TE + a都是句型,而a + a是句子。在自底向上的分析中,aTE + 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 *,我們可以透過以下唯一的最左推導方式得到它:

  1. S 開始,替換最左邊的非終結符 SS S +,得到 S S +
  2. 再次替換最左邊的非終結符 Sid,得到 id S +
  3. 然後替換最左邊的非終結符 Sid,得到 id id +
  4. 最後替換最左邊的非終結符 SS S *,得到 id id + S S *
  5. 再次替換最左邊的非終結符 Sid,得到 id id + id S *
  6. 最後替換最左邊的非終結符 Sid,得到 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'->ε

相關文章