什么是自頂向下分析法
在語法分析過程中一般有兩種語法分析方法,自頂向下和自底向上,遞歸下降分析和LL(1)都屬於是自頂向下的語法分析
自頂向下分析法的過程就像從第一個非終結符作為根節點開始根據產生式進行樹的構建
S -> AB
A -> Cb | c
B -> f
C -> de
對輸入字符串debf的分析過程
S -> CbB -> debf
S -> cf x
整個過程就是對通過非終結符的不斷替換,所以當我們從左往右匹配這個句子的時候,需要找到合適的產生式,所以在自頂向下語法分析過程中作出正確的推導有兩種方法,一是遞歸下降,二是表驅動的語法分析,也就是LL(1)
遞歸下降
對於遞歸下降分析,每個非終結符都有一個對應的函數,程序從開始符號的對應函數開始執行,如果程序成功掃描了整個輸入字符串,就代表語法分析成功
在調用非終結符對應的函數時就會遇見兩種情況:
- 遇到終結符,因為終結符本質上是token,所以直接把這個終結符和句子中對應位置的token進行比較,判斷是否符合即可;符合就繼續,不符合就返回
- 遇到非終結符,此時只需要調用這個非終結符對應的函數即可。在這里函數可能會遞歸的調用,這也是算法名稱的來源。
void A() {
X1,X2,...Xk = select a production
for(i = 1 to k) {
if(Xi is a non-terminal) {
Xi();
} else if(Is equal to the token) {
Read in the next character
} else {
/* hadnle error */
}
}
}
但是非終結符的產生式不一定只有一個,所以就產生了選擇問題,就需要回溯
一個想法:可以改變非終結符和對應的產生式的數據結構,比如用鏈表來存儲產生式,如果當前產生式匹配失敗就沿着鏈表進行下一個產生式的匹配
else {
backtrack()
}
LL(1)語法解析
LL(1)算法屬於自頂向下的分析算法,它的定義為:從左(L)向右讀入一個符號,最左(L)推導,采用一個1前看符號。LL(1)算法和自頂向下分析算法本質上是一致的,它們的區別就在於LL(1)算法使用了一種稱為分析表的工具來避免了回溯操作。
分析表的大概結構
/ | 輸入字符 | ||
---|---|---|---|
terminator | terminator | EOF | |
non-terminal | 0 | 2 | - |
non-terminal | 1 | 3 | 4 |
即根據當前的非終結符和輸入字符可以預測之后的產生式,以此來避免回溯
LL(1)的大概工作流程就是
- 將開始符號壓入棧中
- 根據輸入符號和分析表來選擇產生式
- 把產生式都壓入棧中
- 如果當前棧頂是終結符,就進行匹配
- 匹配失敗退出,成功則讀入,再回到第二個步驟
構建預測分析表
要構建預測分析表就需要根據產生式來生成三個集合,Firset set, Fllow Set, Select Set
先明白一個概念,如果一個非終結符,它可以推導出空集,那么這樣的非終結符我們稱之為nullable的非終結符
First Set的構建
對一個給定的非終結符,通過一系列語法推導后,能出現在推導表達式最左端的所有終結符的集合,統稱為該非終結符的FIRST SET。
-
如果A是一個終結符,那么FIRST(A)={A}
-
對於以下形式的語法推導:
s -> A a
s是非終結符,A是終結符,a 是零個或多個終結符或非終結符的組合,那么 A 屬於 FIRST(s). -
對於推導表達式:
s -> b a
s 和 b 是非終結符,而且b 不是nullable的,那么first(s) = first(b) -
對於推導表達式:
s -> a1 a2 … an b
如果a1, a2 … an 是nullable 的非終結符,b是非終結符但不是nullable的,或者b是終結符,那么
first(s) 是 first(a1)… first(an) 以及first(b)的集合。public void buildFirstSets() { while (runFirstSetPass) { runFirstSetPass = false; Iterator<Symbols> it = symbolArray.iterator(); while (it.hasNext()) { Symbols symbol = it.next(); addSymbolFirstSet(symbol); } } } private void addSymbolFirstSet(Symbols symbol) { if (isTerminalSymbol(symbol.value)) { return; } for (int i = 0; i < symbol.productions.size(); i++) { int[] rightSize = symbol.productions.get(i); if (isTerminalSymbol(rightSize[0]) && !symbol.firstSet.contains(rightSize[0])) { runFirstSetPass = true; symbol.firstSet.add(rightSize[0]); } else if (!isTerminalSymbol(rightSize[0])) { addAdjacentFirstSet(symbol, rightSize); } } } private void addAdjacentFirstSet(Symbols symbol, int[] rightSize) { int pos = 0; Symbols curSymbol; do { curSymbol = symbolMap.get(rightSize[pos]); if (!symbol.firstSet.containsAll(curSymbol.firstSet)) { runFirstSetPass = true; for (int j = 0; j < curSymbol.firstSet.size(); j++) { if (!symbol.firstSet.contains(curSymbol.firstSet.get(j))) { symbol.firstSet.add(curSymbol.firstSet.get(j)); } } } pos++; } while (pos < rightSize.length && curSymbol.isNullable); }
Follow Set的構建
對於某個非終結符通過一系列推導變換后,某個終結符出現在該非終結符的后面,那么我們稱該終結符屬於對應非終結符的FOLLOW SET
- 先計算每一個非終結符的first set,並把每個非終結符的follow set設置為空.
- 對於表達式 s -> …a b…, a 是一個非終結符,b 是終結符或非終結符, 那么FOLLOW(a) 就包含 FIRST(b).
- 對於表達式 s->…a a1 a2 a3… an b…, 其中a是非終結符,a1, a2 a3… an 是nullable的非終結符,b是終結符或非nullable的非終結符,那么FOLLOW(a) 包含FIRST(a1)… FIRST(an) FIRST(b)的集合。
- 對於表達式s -> … a 其中a是非終結符,而且a出現在右邊推導的最后面,那么FOLLOW(a) 包含 FOLLOW(s)
- 對於表達式 s -> a a1 a2…an ,其中a是非終結符而且不是nullable的,a1 a2…an 是nullable的非終結符,那么FOLLOW(a), FOLLOW(a1)…FOLLOW(an) 都包含FOLLOW(s).
public void buildFollowSets() {
buildFirstSets();
while (runFollowSetPass) {
runFollowSetPass = false;
Iterator<Symbols> it = symbolArray.iterator();
while (it.hasNext()) {
Symbols symbol = it.next();
addSymbolFollowSet(symbol);
}
printAllFollowSet();
System.out.println("***********************");
}
}
private void addSymbolFollowSet(Symbols symbol) {
if (isTerminalSymbol(symbol.value)) {
return;
}
for (int i = 0; i < symbol.productions.size(); i++) {
int[] rightSize = symbol.productions.get(i);
for (int j = 0; j < rightSize.length; j++) {
Symbols current = symbolMap.get(rightSize[j]);
if(isTerminalSymbol(current.value)) {
continue;
}
for (int k = j + 1; k < rightSize.length; k++) {
Symbols next = symbolMap.get(rightSize[k]);
addSetToFollowSet(current, next.firstSet);
if (!next.isNullable) {
break;
}
}
}
int pos = rightSize.length - 1;
while (pos >= 0) {
Symbols current = symbolMap.get(rightSize[pos]);
if (!isTerminalSymbol(current.value)) {
addSetToFollowSet(current, symbol.followSet);
}
if (isTerminalSymbol(current.value) && !current.isNullable) {
break;
}
pos--;
}
}
}
Selction Set的構建
對於標號為N的推導表達式s -> a, 以及當前輸入T, 那么SELECT(N)要包含T的話,必須是,當棧頂元素是s, 且輸入為T時,要使用推導表達式N來進行下一步推導。
- 計算所以非終結符的first set 和follow set.
- 對應非nullable的表達式 , s -> a b… 其中s是非終結符,a 是一個或多個nullable的非終結符,b是終結符或是非終結符但不是nallable的,b后面可以跟着一系列符號,假設其標號為N,那么該表達式的選擇集就是FIRST(a) 和 FIRST(b)的並集。如果a不存在,也就是b的前面沒有nullable的非終結符,那么SELECT(N) = FIRST(b).
- 對應nullable的表達式: s -> a, s是非終結符,a是零個或多個nullable非終結符的集合,a也可以是ε,假設該表達式標號為N,那么SELECT(N)就是 FIRST(a) 和 FOLLOW(s)的並集。由於a可以是0個非終結符,也就是s -> ε,從而s可以推導為空,如果s推導為空時,那么我們就需要看看當前輸入字符是不是FOLLOW(s),也就是跟在s推導后面的輸入字符,如果是的話,我們才可以采用s->ε,去解析當前輸入。
private void buildSelectionSet() {
buildFirstSets();
buildFollowSets();
Iterator<Symbols> it = symbolArray.iterator();
while (it.hasNext()) {
Symbols symbol = it.next();
addSymbolSelectionSet(symbol);
}
}
private void addSymbolSelectionSet(Symbols symbol) {
if (isTerminalSymbol(symbol.value)) {
return;
}
boolean isNullableProduction = true;
for (int i = 0; i < symbol.productions.size(); i++) {
int[] rightSize = symbol.productions.get(i);
ArrayList<Integer> selection = new ArrayList<Integer>();
for (int j = 0; j < rightSize.length; j++) {
Symbols next = symbolMap.get(rightSize[j]);
if (!next.isNullable) {
isNullableProduction = false;
addSetToSelectionSet(selection, next.firstSet);
break;
}
addSetToSelectionSet(selection, next.firstSet);
}
if (isNullableProduction) {
addSetToSelectionSet(selection, symbol.followSet);
}
symbol.selectionSet.add(selection);
isNullableProduction = true;
}
}
構建完整的預測分析表
- 將解析表所有元素初始化為-1
- for (每一個推導表達式 N) {
lhs = 推導表達式箭頭左邊的非終結符
for (對應每一個在SELECT(N)中的token) {
parse_table[lhs][token] = N
}
}
private void setParsetTable() {
Iterator it = symbolArray.iterator();
while (it.hasNext()) {
Symbols symbol = (Symbols) it.next();
if (isTerminalSymbol(symbol.value)) {
continue;
}
for (int i = 0; i < symbol.selectionSet.size(); i++) {
ArrayList<Integer> selection = symbol.selectionSet.get(i);
for (int j = 0; j < selection.size(); j++) {
parseTable[symbol.value - SymbolDefine.NO_TERMINAL_VALUE_BASE][selection.get(j)] = productionCount;
}
productionCount++;
}
}
}