自下而上的语法分析
自下而上的语法分析方法:
就是从给定的字符串出发,逐步向上规约,直至文法的开始符S,看能否找到一个最左规约序列。
具体方式:
采用一个存放文法符号的栈,把输入字符串的符号逐个压栈,随时观察栈顶的情况,当栈顶形成某个产生式的一个候选式时,就把这一部分出栈,将该产生式的左端符号压栈,即规约。如此不断地“移进—规约”分析,直到输入符号串移进完,且栈里仅剩下文法开始符为止。
例如:
怎么判断栈里已经形成了可规约串?
- 算符优先分析法
如果栈顶符号串已经形成最左素短语,就判断为可规约串。 - LR分析法
如果栈顶符号串已经形成句柄,就判断为可规约串。
下面重点讨论LR分析法。
LR分析法
LR分析过程:
采用一个下推栈,用来存放已移进(的终结符)和规约(的非终结符)的符号串,该符号串看成“历史”;当前准备移进栈的符号看成“现在”;推测将来可能碰到的移进栈的符号串,将其看成“将来”。
LR分析器根据“历史”,“现在”和“将来”判断栈顶是否已经形成句柄,从而确定本次分析应该采取的动作是:规约,移进或语法出错。
然而,在某些状态下,会出现既可以移进也可以规约的情况,或有两个以上不同的规约的情况,称为“移进—规约”冲突,或“规约—规约”冲突。
例如:
LR(K)文法的定义:
一个文法,如果能用一个每步顶多向前检查k个输入符号的LR分析器进行分析,则这个文法就称为LR(K)文法。
通常只讨论K≤1的情况。
K≤1的LR分析器可分为:LR(0),SLR(1),LALR(1),LR(1)。
其中,
LR(0)分析能力最弱,只对无冲突的文法有效;
SLR(0)可采用简单的方法解决冲突;
LALR(1)分析能力较强;
LR(1)分析能力最强,可采用精确的方法解决冲突。
下面重点讨论LR(0)和SLR(1)。
LR分析器
首先解释一下LR分析器进行语法分析的详细过程,之后再讨论LR(0)和SLR(1)。
LR分析器的结构:
- 分析栈
包括状态栈和符号栈:
状态栈包括分析的状态信息,历史和展望;
符号栈包括分析过程中移进和规约的文法符号信息。 - 分析表
包括action和goto两个子表:
action[s, a] 为在状态 s下,当前输入符号为 a 时应采取的分析动作。
① 移进:把(s,a)的下一状态 s’ 和输入符号 a 推进栈,下一输入符号变成当前输入符号。
② 归约:去除栈顶 t 个项,使状态 sm-t 变成栈顶状态,然后把 (sm-t, A) 的下一状态 s’=goto[sm-t, A] 和文法符号 A 推进栈。
③ 接收:宣布分析成功,停止分析器工作。
④ 出错:语法出错,记录出错处理程序入口。
goto[s, X] 为状态及非终结符的二维矩阵在状态 s 下,归约后的符号 X 入栈的状态。 - 总控程序
根据当前状态栈栈顶符号 s 以及当前输入符号 a 查询分析表 action[s, a] ,根据情况作如下操作:
① 若 action[s, a]=sj,则将状态 j 推入状态栈顶,输入指针指向下一个输入符号。
② 若 action[s, a]=rj,则按照第 j 个产生式 A→β 规约,假设 |β|=t ,则将状态栈顶的 t 个状态出栈;再根据当前栈顶状态 si 以及规约后的非终结符 A ,查goto表,若 goto[si, A]=k,则将状态 k 推入状态栈顶。
③ 若 action[s, a]=acc,则分析成功,输入串被接受。
④ 若 action[s, a] 或 goto[si, A] 为空白,则转出错处理。
LR分析器的分析过程:
设文法G如下:
则可以得到LR分析表如下:
举例说明:i+i*i 的分析过程
很好理解,一开始状态为0,然后根据输入的字符在LR分析表里找到对应的动作进行操作,直到ACC说明语法分析成功。
由此可见,利用LR分析表就可以进行语法分析了,所以问题的关键在于如何构造LR分析表。
LR分析表
在开始构造LR分析表之前,需要了解一些概念做准备。
句柄的定义:
S是文法G的开始符号,假定αAδ是文法G的一个句型,如果有
则称β是句型αβδ关于非终结符A的短语。
特别地如果存在产生式A→β,则称β是句型αβδ关于非终结符A的直接短语。
一个句型的最左直接短语称为该句型的句柄。
个人理解:
简单来说,某个句型取一段符号串,如果该符号串能(根据文法)被规约,那么该符号串就是直接短语。
句柄就是在所有直接短语中选取的最左直接短语。
例如:
有句型abbcde,文法中有A→b,那么该句型的句柄就是b;
有句型aAbcde,文法中有A→Ab,那么该句型的句柄就是Ab;
活前缀的定义:
规范句型中不含句柄之后任何符号的一个前缀。
即:从第一个符号开始,到句柄结束,构成了一个符号串,这符号串的前缀叫做该规范句型的活前缀。
活前缀与句柄之间的三种关系:
- 活前缀不含句柄的任何符号,用A→•αβ表示。
此时期待从剩余输入串中识别由句柄αβ推导出的符号串。 - 活前缀只含句柄的真前缀,用A→α•β表示。
此时句柄αβ中α已识别,并出现在栈顶,期待从剩余输入串中识别由β推导出的符号串。 - 活前缀已含句柄αβ的全部符号,用A→αβ•表示。
此时句柄αβ已全部出现在栈顶,下一步应将αβ规约为A。
项目的定义:
A→•αβ,A→α•β,A→αβ•
圆点可以看作栈内外的分界线。
在文法G的产生式不同位置加圆点为文法G的一个LR(0)项目,简称项目。
项目的分类:
- 归约项目:形如A→αβ•
- 移进项目:形如A→α•β(•后是终结符)
- 待约项目:形如A→α•β(•后是非终结符)
- 接受项目:形如S→αβ•
拓广文法:
对文法G(S),增加产生式S’→S,把开始符号重新表示为S’,形成拓广文法G(S’)。
在该拓广文法中, 项目S’→S•是唯一的接受项目。
有效项目集:
对活前缀δα有效的项目的集合称为对δα的有效项目集。
如果从初态到达状态A→α•β时,δα已识别出,希望继续从输入串中识别由β推出的串,则称项目A→α•β对活前缀δα有效。
有效项目集闭包closure(I)的求法:
设I是文法G的一个项目集,通过以下步骤构造有效项目集闭包closure(I):
- 对 i∈I,都有 i∈closure(I);
- 若A→α•Bβ∈closure(I),且B→η为文法G的一个产生式,则B→•η∈closure(I);
- 重复2,直至closure(I)不再增大。
项目集规范族的定义:
一个文法G的所有有效项目集组成的集合,叫做该文法G的项目集规范族。
项目集规范族C的求法:
begin
C : = { closure ( { S’→•S } ) };
repeat
for( C中每一项目集I和符号X, X∈V* )
do { if ( go(I, X)∉C ) then 把go(I, X)加入C中 }
until C不再增大
end
举例说明:
设经过拓广后的文法G如下:
求它的LR(0)项目集规范族的过程:
其中:
每一个方框就是一个状态,对应一个项目集I;
每一个箭头对应状态转换的文法符号X。