自下而上解析是大多数编译器采用的方法,采用最右推导。
语法格式: LALR
LA表示look ahead, 第二个L表示自左向右读取,R表示从右向左推导。
自底向上解析流程
1.如果堆栈顶部若干元素可以推导成某个推导式的右边,将这几个元素出栈,将表达式左边的非终结符压栈,也就是做reduce操作。
2.将当前字符对应的token压栈,将输入指向下个字符,即shift操作。
3.reduce操作完成后,全局非终结符被压栈,其输入为空,那么解析结束,文本被语法解析接受。
句柄(handle)定义,堆栈上的若干元素能引发reduce操作,这几个元素称为handle。
解析算法流程
push_stack(0) //状态机初始化为0,编号为0的表达式
while (table[tos][input] !== accept) {
if (table[tos][input] == err) error(); break
else if(table[tos][input] == shift action X) {
push_stack(X);
lexer.advance() //读取下个字符
}
else if (table[tos][input] == reduce action X) {
int count = 要弹出的元素个数;
pop_stack(count);
left = 表达式左边的非终结符;
state = table[tos][left];
push(state);
}
}
状态机构建算法流程
1.初始化
初始状态0,它包含语法表达式中的起始表达式,但在所有表达式右边第一个位置加了一个“."
2. 对.右边的符号做闭包操作,如果.符号右边是一个非终结符,那么把所有表达式中,该终结符在左边的表达式添加进来。对于新加的表达式继续该检查,直到添加结束。
3.对引入的表达式进行分区,把.右边拥有相同非一个终结符的表达式划入一个分区。对于每一个分区,把分区中表达式中.向右移动一位,每个分区形成一个新的状态节点。
4.构建原有节点与新节点的跳转关系。-- 处于哪个状态节点,输入确定时,转换到哪个新节点。
4.重复上述算法,生成最后的状态机图。
LR状态机缺陷与改进
shift/reduce矛盾
一个节点中含有两个表达式,一个.处于最末尾,一个.处于中间,该shift还是reduce
reduce矛盾
节点中多个表达式.都处于末尾
改进
SLR(1)语法 如果当前输入字符属于状态节点中的某个表达式左边的follow set,就做reduce操作 LR(1)语法 当前输入字符既是某个推导表达式左边非终结符的follow set中,又是另一个表达式.右边的终结符,那就无法解决该矛盾。事实上我们只要考虑reduce操作后,当前输入字符能否合法跟在reduce后的非终结符的后面。 这种能合法跟在某个非终结符后面的符号集合,被称之为look ahead set,是follow set的子集。
look ahead 集合计算方法
假定表达式形式及其look ahead集合形式如下:
S -> A . X B, C
X是非终结符,其表达式如下: X -> .r 计算look ahead集合时,需要把b和c首尾相连后再计算first set集合,即first(b, c) 上面算法反复进行,直到没有新的表达式生成。
从有限状态机到跳转表
通过有限状态机我们已经得到了节点间的跳转关系,现在只需要添加处于某个节点时,是否要采取reduce操作。reduce信息的构建只需要遍历每个节点,查看节点包含的表达式,如果符号”."位于表达式的末尾,那么就可以输出reduce信息。
歧义性语法的处理
处理shift/reduce矛盾
1.给每一个终结符赋予优先级,表达式的优先级与他最右边的终结符的优先级一致,如果表达式不含有终结符,那表达式优先级为0.
2.shift/reduce矛盾发生时,如果优先级一样,默认做shift操作。如果当前输入终结符优先级高,做shift操作,否则做reduce操作。
reduce矛盾
一般情况下是语法定义出错了。