借助于一个栈,可以将这种方法简化为“移进—规约分析”:
从左向右逐个扫描待分析的单词符号串,并将串中单词一个一个地移入栈中,边移入边分析;每当栈顶符号串形成了所给文法的某个产生式右部时就进行一次归约,即用该产生式的左部非终结符去替换相应的右部符号串(即:出现在栈顶的这一串单词符号,称为可归约串)。重复这一过程,若能将所有的单词符号都归约到文法的开始符号,则该单词符号串是该文法的合法句子,否则不是。
关键问题
分析过程中的关键问题是如何确定栈顶已经形成了可规约串,即:
- 如何定义可归约串?
- 如何识别可归约串?
如果简单地将“可规约串”定义为:栈顶形成了某个产生式的右部,那么识别起来是比较容易的:只需要每移进一个符号之后,就拿栈中符号串的所有子串与所有产生式的右部逐一比对,这样做虽然效率很低,但实现不难。但实际上这里存在一个大问题。
为此,引入如下一组定义:
LR分析法
L表示从左至右扫描输入串,R表示构造一个最右推导的逆过程,即:LR分析法是规范规约。
LR分析法的基本思想:根据“历史资料”、“现实输入符号”以及对未来的“展望”等三个方面来确定栈顶的符号是否构成了相对于某一产生式的句柄。
LR分析器的核心是一张分析表。这张表包括两部分:“动作”(Action)和“状态转换”(Goto) ,它们都是二维数据结构。
Action[s,a]规定了状态s面临输入符号a时应该采取什么动作;Goto[s,X]则指出状态s面对文法符号X(终结符或非终结符)时下一状态是什么。实际上,Goto[s,X]定义了一个以文法符号为字母表的DFA。
例如下面文法:
注意到每个文法定义的前面都有一个带括号的数字,这是我们自己加上去的,为的是方便后面填分析表的时候说明归约到哪个文法定义中,如r1则表示归约到第一个文法定义S->aAcBe中。
则分析表为:
其中,
LR分析法的步骤为:
一个LR分析器有时需要“展望”未来的k个输入符号才能决定应采取什么样的“移进-归约”决策。于是又有如下定义:对于文法G,若能用一个每步顶多向前查看k个输入符号的LR分析器进行分析,则称之为**LR(k)**文法。
LR(0)文法
只概括“历史”资料而不包含推测性“展望”材料,仅由这种简单状态就能识别呈现在栈顶的某些句柄。
例如:
则项目有:
可以得到如下DFA:
进而得到如下分析表:
SLR分析
LR(0)分析要求条件很苛刻,即使是定义算术表达式这样的简单文法,其分析表也含有冲突表项。因此,有必要研究一种带一点“展望”材料的LR分析法,也就是要检查一下下一个输入符号。
可以发现SLR分析与LR(0)分析之间的区别在于,SLR带有一些展望,因为文法会出现得到一个输入之后,可能移进也可能规约的情况,因此就需要“展望一下未来”以确定究竟是移进还是规约,因此SLR分析中需要求所有非终结符的Follow集,并且Follow集之间不能有交集。
这里我们通过一个例子详细讲解一下:
则构造出来的DFA如下:
可以看到初始状态下接受一个E,可能移进也可能规约,于是就需要看一下S和E(文法定义的左部符号)的Follow集,然后由于Follow(S)和Follow(E)除了一个#相同以外,没有相同的了,因此说明没问题,否则就无法使用SLR分析。
分析表的构造方法:
出错处理
结合前面的分析表,我们可以发现只有一部分的空格填了跳转的位置,而剩下一部分则都是空白的,这些空白的即为出错部分,如果程序运行的时候到了这些空白部分,输入情况既不能移进栈顶,栈顶也不能规约,就意味着发现了语法错误,应该调用相应的出错处理子程序。