斯坦福编译原理笔记-8 自底向上解析

366 阅读4分钟

自下而上解析是大多数编译器采用的方法,采用最右推导。
语法格式: 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矛盾
一般情况下是语法定义出错了。