递归下降和LL(1)语法分析

2023-04-22 18:43

什么是自顶向下分析法

在语法分析过程中一般有两种语法分析方法,自顶向下和自底向上,递归下降分析和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)算法使用了一种称为分析表的工具来避免了回溯操作。

递归下降和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。

如果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++; } } }