从开始符号出发推出句子,判断句子是否和给定的目标句子相等,过程中会涉及回溯。可以通过前看符号来避免回溯
算法伪码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
tokens[];
i=0;
stack = [s] //s是开始符号
while (stack != []) {
if (stack[top] is a terminal t) { //如果是终结符
if (t == tokens[i++])
pop();
else backtrack(); //t不能匹配tokens输入流的当前符号,因此回溯,回溯涉及将产生包含t的产生式的所有右部出栈、左部压栈,游标前移
} else if (stack[top] is a nonterminal T) { //非终结符
pop();
push(the next right hand side of T); //将右部逆序压栈,这里如果没有下一个right hand side了怎么办————回溯
if (there is no rhs of T any more) backtrack(); //这里回溯掉产生包含T的的产生式。可能需要另外一个栈来记住产生式选择以方便回溯
}
}
算法比较昂贵,我们需要线性时间的算法:
每个非终结符对应一个分析函数。分析函数的结果是当前输入token能否被代表这个函数的产生式接受(吃掉)。在每个分析函数内部用当前的前看符号选择一个产生式,如果没得选择报错。接下来,对这个选择的产生式进行分析,假设产生式中即包含终结符也包括非终结符,那么对于终结符判断当前输入符号是否匹配,对于非终结符递归调用该非终结符的parse函数。
问题:怎么根据前看字符来选择产生式? 对算术表达式的递归下降分析
1
2
3
4
5
E -> E + T
| T
T -> T * F
| F
F -> num
可以看成: E -> T + T + T + …. + T, T -> F * F * … * F
1
2
3
4
5
6
7
8
parse_E() {
parse_T();
token = tokens[i++];
while (token == '+') {
parse_T();
token = tokens[i++];
}
}
1
2
3
4
5
6
7
8
parse_T() {
parse_F();
token = tokens[i++];
while (token == '*') {
parse_F();
token = tokens[i++];
}
}
从语法声明式规范自动生成语法分析器代码的工具:ANTLR、YACC、bison
行:非终结符 列:终结符 表中元素Xij代表:栈顶元素为i行时,遇到j列非终结符时,选择产生式Xij
定义非终结符的first集:
伪码:(不动点迭代算法)
1
2
3
4
5
6
7
8
9
10
11
12
13
foreach (nonterminal N) {
first (N) = {};
}
while (some set is changing) { //迭代的终止条件是没有集合发生变化
foreach (production p: N -> beta1 .. betan) {
if (beta1 == a) {
first(N) U= {a};
}
if (beta1 == M) {
first(N) U= first(M);
}
}
}
思考:怎么证明算法正确且能终止
定义任意串的first集:
1
2
3
first_s(beta1...betan) =
first(N), if beta1 == N;
{a}, if beta1 == a;
构造LL(1)分析表: 遍历每个production p,将分析表中行位p左部,列在p右部的first集中的元素填上p
定义NULLABLE集: 非终结符X属于集合NULLABLE,当且仅当:
NULLABLE集构造算法:
1
2
3
4
5
6
7
8
9
NULLABLE = {};
while (nullale is still changing) {
foreach (production p : X -> beta)
if (beta == '')
NULLABLE U= {X};
if (beta == Y1 ... Yn)
if (Y1 belong to NULLABLE && ... && Yn belong to NULLABLE)
NULLABLE U= {X};
}
现在重新redine下非终结符的first集的定义:
那么非终结符first集构造算法redine后如下:
1
2
3
4
5
6
7
8
9
10
11
12
foreach (nonterminal N)
first(N) = {};
while (some set is changing) {
foreach (production p : N -> beta1 ... betaN)
foreach (betai from beta1 to betan) { //相比原来多了一层对产生式右部的符号的迭代
if (betai == a...) {first(N) U= {a}; break;}
if (betai == M...) {
first(N) U= first(M);
if (M is not in NULLABLE) break;
}
}
}
思考:怎么证明算法正确,并且能终止?1)集合元素有限,因此算法肯定能终止 2)反证法证明当算法终止时,结果集合就是要求的目标集合
OK, 有了非终结符的first集完整定义和构造算法后,来分析串的first_s集:
原来的定义是:对于N -> beta1 … betan first_s(beta1…betan) = first(M), if beta1 == M; {a}, if beta1 == a;
现在beta1可能是NULLABLE,同样beta2、…、betan都可能是NULLABLE的,first_s除了可能包含betai的first集外,还可能包含串之外的非终结符,即follow(N)。因此引入follow集的概念
非终结符follow集:非终结符后可能跟随哪些终结符构成的集合
这里可以先定义每个产生式右部符号的temp集,对于产生式 N -> beta1 … betan而言:
基本情况: temp(betan) = follow(N)
归纳情况:
temp集是干嘛的呢?每个非终结符的follow集必定包含它所在的每个产生式中它的temp集。 因此,对于非终结符的follow集构造算法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
foreach (nonterminal N)
follow(N) = {};
while (some set is changing) {
foreach (prodcution p : N -> beta1 ... betan) {
temp = follow(N); //定义temp集,每轮循环开始前temp是可能跟在betai后的非终结符集合
foreach (betai from betan downto beta1) {
if (betai == a...) temp = {a};
if (betai == M...) {
follow(M) U= temp; //M的跟随集里增加新元素
if (M is not NULLBALE)
temp = first(M); //M不在NULLABLE内,因此betai-1的temp集只能是first(M)
else temp U= first(M); //M在NULLABLE内,betai-1的temp集是first(M) U temp(betai)
}
}
}
}
最后,给出串的first_s集算法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
foreach (production p)
first_s(p) = {};
calculate_first_s (production p : N -> beta1 ... betan) {
foreach (betai from beta1 to betan) {
if (betai == a ...) {
first_s(p) U= {a};
return;
}
if (betai == M ...) {
first_s(p) U= first(M);
if (M is not NULLABLE) return;
}
}
first_s(p) U= follow(N);//为什么需要并上follow(N),此时p可以推出空,结合下first_s的作用就知道了为什么了
}
现在,终于可以构造LL(1)分析表了,其实和之前完全一样。那么LL(1)分析器的算法框架如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
tokens[];
i=0;
stack = [s] //s是开始符号
while (stack != []) {
if (stack[top] is a terminal t) { //如果是终结符
if (t == tokens[i++])
pop();
else error(..) //如果不匹配则直接报错
} else if (stack[top] is a nonterminal T) { //非终结符
pop();
push(table[T, tokens[i]]); //将table[T, tokens[i]]号产生式的右部逆序压栈
}
}
改变文法以去掉冲突
自底向上 YACC、bison、CUP 最右推导的逆过程