语法分析方法
按照语法树的建立方法,可以粗略的把语法分析分为两类:
自顶而下语法分析法
递归下降法和LL(1)分析法
自底而上语法分析法
**自下而上分析中最一般的方法为LR(0)方法,
SLR(1)分析
LR(1)分析
LALR(1)分析
**
下面介绍这两大类的语法分析方法
自顶而下语法分析法
递归下降法和LL(1)分析法
定义:
自上而下的语法分析方法就是对任何输入串(由token串构成的源程序),试图用一切可能的办法,从文法开始符号(根结点)出发,自上而下的为输入串建立一棵语法树,或者说,为输入串寻找一个最左推导。
问题引入
在替换一个非终结符时,如果一个非终结符有多个候选式,选择哪个候选式是一个问题。自上而下分析的过程本质上是一种试探过程,是反复使用不同产生式谋求匹配输入串的过程。确定的自上而下的分析就是每一次的候选式都是确定的,然而在试探过程中可能会出现以下问题:
1)试探与回溯
试探与回溯是一种穷尽一切可能的办法,效率低,代价高,导致分析器不稳定。使用自上而下分析时,要设法消除回溯。
即当文法中存在形如A->αβ1|αβ2…的产生式,即某个非终结符存在多个候选式的前缀相同(也称为公共左因子),则可能造成虚假匹配(即当前的匹配可能是暂时的),即发现不能匹配后重新回溯重新选择一个候选式进行匹配,这样反复多次,使得在分析过程中可能需要大量的回溯。
2)左递归(直接和间接都算)
导致分析过程无限循环。假如文法中存在形如:A->Aa的产生式(称为左递归),分析过程中又使用左推导,则就会使得分析过程陷入无限循环中,因此,使用自上而下分析时,文法应该不包含左递归。
回溯的消除
回溯产生的根本原因在于某个非终结符的多个候选式存在公共左因子,如非终结符A的产生式如下:
A->αβ1|αβ2
如果输入串中待分析的字前缀也为α,此时选择A的那个候选式就会不确定,可能就导致回溯。因此,要想进行确定的分析,必须保证文法G的每个非终结符的多个候选式均不包含公共左因子。
1)改造方法:
改造的方法是提取公共左因子。使得文法的每个非终结符号的各个候选式的首终结符集两两不相交。
左递归的消除
左递归包括直接左递归和间接左递归
递归下降法
前提条件
分析流程:
终结符号:产生匹配调用命令
非终结符号:产生调用命令
语法图来辅助(以EBNF为基础)
Factor -> (exp) | number
A->{B}
A->[B]
以if 语句为例:
非终结符号函数调用
碰到多个语法规则可以选的时候
遇到U-> @空规则
终结符号匹配
在递归程序上加一些中间处理
1、增加计算功能
在匹配到运算符号的时候,加入计算结果
2、构建语法树
同理,也是在做计算的位置,进行构建树
符号位置,将节点进行连接
数字的位置生成节点
LL(1)文法
没有左递归和没有公共左因子并不能进行确定的自上而下的分析。
1、分析表建立
2、分析栈
加 “¥”做匹配
如果最后只剩下:
“¥” “¥ ”
则说明匹配成功
if(input[0] == signstack[signstack.length()-1])//匹配
第一种匹配,结束
if(g->table[signstack.right(1)][QString(input[0])] != “”)
第二种
查表中,发现有对应的规则
把对应的文法规则加入分析栈中,reverseString(next); 注意是逆序放
循环结束之后:判断剩余的符号情况。
if(input == “ " ∣ ∣ s i g n s t a c k ! = " " || signstack != " "∣∣signstack!="”)//如果遇到的是剩下的符号的first集合都有@,说明也是可以匹配成功的
if(input != “ " ∣ ∣ s i g n s t a c k ! = " " || signstack != " "∣∣signstack!="”)
//执行最左推导
input.append("$");//加入一个结束符号
QString signstack = "$" + g->vn[0];//获取结束符号和一个文法开始符号
int step = 1;
while(signstack != "$" && input != "$")
{
model->setItem(step-1, 0, new QStandardItem(QString::number(step)));
//qDebug()<<signstack<<" "<<input;
if(input[0] == signstack[signstack.length()-1])//匹配
{
//qDebug()<<signstack.mid(1)<<" "<<input.left(input.length()-1)<<" match";
model->setItem(step-1, 1, new QStandardItem(signstack.mid(1)));
model->setItem(step-1, 2, new QStandardItem(input.left(input.length()-1)));
model->setItem(step-1, 3, new QStandardItem("匹配"));
input = input.mid(1);
signstack.chop(1);
step++;
continue;
}
if(g->table[signstack.right(1)][QString(input[0])] != "")
{
QString next = g->table[signstack.right(1)][QString(input[0])];
//qDebug()<<signstack.mid(1)<<" "<<input.left(input.length()-1)<<" "<<next;
model->setItem(step-1, 1, new QStandardItem(signstack.mid(1)));
model->setItem(step-1, 2, new QStandardItem(input.left(input.length()-1)));
model->setItem(step-1, 3, new QStandardItem(signstack.right(1) + "->" + next));
signstack = signstack.left(signstack.length()-1) + reverseString(next);
step++;
}
else
{
qDebug()<<"no match";
break;
}
}
if(input == "$" || signstack != "$")//如果遇到的是剩下的符号的first集合都有@,说明也是可以匹配成功的
{
QString last = signstack.right(1);
while(g->first[last].contains("@"))
{
QString next = g->table[signstack.right(1)][QString(input[0])];
//qDebug()<<signstack.mid(1)<<" "<<input.left(input.length()-1)<<" "<<next;
model->setItem(step-1, 0, new QStandardItem(QString::number(step)));
model->setItem(step-1, 1, new QStandardItem(signstack.mid(1)));
model->setItem(step-1, 2, new QStandardItem(input.left(input.length()-1)));
model->setItem(step-1, 3, new QStandardItem(signstack.right(1) + "->" + next));
signstack = signstack.left(signstack.length()-1) + reverseString(next);
step++;
last = signstack.right(1);
}
}
if(input != "$" || signstack != "$")
{
//qDebug()<<signstack.mid(1)<<" "<<input.left(input.length()-1)<<"失败";
model->setItem(step-1, 0, new QStandardItem(QString::number(step)));
model->setItem(step-1, 1, new QStandardItem(signstack.mid(1)));
model->setItem(step-1, 2, new QStandardItem(input.left(input.length()-1)));
model->setItem(step-1, 3, new QStandardItem("失败"));
}
else
{
//qDebug()<<signstack.mid(1)<<" "<<input.left(input.length()-1)<<"成功";
model->setItem(step-1, 0, new QStandardItem(QString::number(step)));
model->setItem(step-1, 1, new QStandardItem(signstack.mid(1)));
model->setItem(step-1, 2, new QStandardItem(input.left(input.length()-1)));
model->setItem(step-1, 3, new QStandardItem("成功"));
}
自底而上分析
LR(0)分析
在规范归约的过程中,一方面要记住已移进和归约出的整个字符串,也就是说要记住历史;一方面能够根据所用的产生式的推测未来可能碰到的输入符号,也就是说能够对未来进行展望。
分成几个不同的项目
移进项目,形如 A→a•ab
待约项目,形如 A→a•Bb
归约项目,形如 A→abs•
接受项目,形如S’ →S•
1.遇到归约项目
整个项归约,A->abs•, 则回退 3步
则到达A位置,从原始位置,走一步A
归约的字符数量是右部字符数量的两倍。
这里归约到A
3号状态获取A 到达4号状态。
4号状态收到 ) ,到达5号状态
5号状态是归约项
因为(A)是3个字符。 所以从分析栈中取出 6(3*2)个符号
回到3号状态,6个字符已经归约为A (看5号的文法规则)
3号状态收到归约得到的A, 跑到4号状态
。。。。。。。。。
LR(0)存储结构
顶点和边的存储结构
状态号是点
输入的符号是边
LR(0)文法判定:
如果文法对应的自动机中不存在移进-归约冲突和归约-归约冲突则为 LR(0)文法。换句话说LR(0)文法分析不能解决这两种冲突,所以范围最小。移进-归约冲突就是在同一个项集族中同时出现了可以移进的产生式和可以归约的产生式。归约-归约冲突类似。
问题一、发现二义性
文法开始符号有两个规则可以选择。
解决方法如下:
拓广文法(EBNF)
需要拓广文法,即加上S’->S,S为开始符号。
如果开始符号有多条规则可以选择,则需要拓广。
问题二、归约冲突
1号状态可以归约到 E‘ 或者 E ,选择出现二义性
从I0出发,我们发现
1.当下一个读入字符为S时,I0–>I1,所以(0,S)==1(意思是,行坐标为0,列坐标为S,对于的那个框填写 1 )
2. 当下一个读入字符为B时,I0–>I2,所以(0,B)==2
3. 当下一个读入字符为a时,I0–>I3,所以(0,a)==s3【移进】
4. 当下一个读入字符为b时,I0–>I4,所以(0,b)==s4【移进】
r1 表示规则1,按文法规则的第一条。
一般会碰到的冲突: 移进-归约冲突和归约-归约冲突
SLR(1)归约-----------比LR(0)更具体
可以用 follow集解决则是 SLR文法
SLR文法分析过程可以解决归约-归约冲突,但是不一定能解决移进-归约冲突。用 follow集来处理即出现移进-归约冲突的两条产生式,如果其 follow集相交为空则为 SLR文法,反之不是。当然,如果以上两种冲突都不存在自然是了。
还是LR(0)的DFA图,求Follow集合元素,超前查看一个符号,进而确定选择哪条规则
表格还是按之前的建立就行。只是更加具体了
移进——归约冲突
移进归约冲突如下:
解法一、最长串匹配原则-----即只做移进不做归约
比如上面的状态5,选最长的,所以做的是移进的操作,而不是归约
Follow集合元素交集不为空,超前查看一个符号无效(SLR(1)无效了)
发现集合交集有“¥”符号
LR(1)解决上述问题(记录先行符号)
超前考虑一个符号。
做标记, 具体是从哪里的规则往下推导的。
这里求的Follow集合是根据同一状态里面的 规则来计算的。
比如V ->.id,:= , 这里看上一条规则,V后面跟着:=E 这样推导出来的。记录先行符号
LR(1)分析表
LR(1)存状态还是有重复
先行符号不同,文法规则一样也要分成两个状态。
LALR(1)方法: 减少状态数的方法:
合并同心项,相同文法规则的就可以做合并
先行符号的解决: 做并集,把先行符号做并集
LALR(1)分析问题是:不能及时发现错误。会延迟,但是LR(1)比较及时
分析a)