在进行课程实践的时候遇到了这个问题。
对于空产生式E->$
,应当在LR计算闭包时,直接生成E->#
。
也就是说,当我们在对类似项目A->αEβ
计算其闭包时,若E可推导出空字符串,则直接生成E->#
这样可以直接归约的项目。
特别的,对于LR1文法,如果生成了这样的可以直接归约的项目,则当最新的token属于First(βa)
时,其中β和a分别为A->αEβ,a
中E的后续序列和A的前看符号,程序可以根据预测分析表判断出现在应该归约。
对于归约操作,程序应当在归约时检测项目是否形如E->#
,若符合,则表明当前进行的是空产生式的归约,则不须从符号栈中弹出任何符号,同样的,也不需要进行状态回溯,直接在符号栈中压入一个归约结果E即可。
示例
以LR1处理如下语法为例:
block -> { blockItem }
blockItem -> decl blockItem
blockItem -> $
对于以上的语法,初始项目集为:
// I0
block' -> #block, $
block -> #{ blockItem }, $
求GOTO(I0, block),则有:
// I1
block' -> block#, $
I1即为最终的结果,此处不是重点,只是顺带一提
而求GOTO(I0, {),则有:
// I2
block -> {# blockItem }, $
blockItem -> #decl blockItem, }
blockItem -> #$, }
此时,根据I0和’{‘,得到了block -> {# blockItem }, $
。
然后对其求闭包,可以看到有blockItem -> #decl blockItem, }
和blockItem -> #$, }
,第二个即为根据空产生式的产生的项目,表示如果当前输入缓存区前端的符号为’{',则直接凭空产生一个blockItem到栈中。
下面为具体文本的归约过程:
"{}"
// 移入
block -> {# blockItem }
// 归约
block -> { blockItem #}
// 移入
block -> {blockItem}#
// 归约
block' -> block#
如上,对于"{}",根据语法规则,我们知道其可以最终归约到block。
机器首先读取到’{‘符号,然后将其移入。此时项目为block -> {#blockItem}
,输入缓存区前端的符号为’{‘。
此时机器查预测分析表,找到I2项目集blockItem -> #$, }
的这条规则,发现blockItem可以直接凭空产生,同时要求前看符号为’}‘,而此时正好满足要求,于是机器不再移入,而是直接压入一个blockItem到栈中,不移入输入缓存区的符号,归约操作也不消耗栈内的符号。
上一段为拟人描述,实际中,预测分析表可以使用哈希表实现,机器根据当前的状态和前看符号查找到应当使用的规则,获取到该规则的归约结果blockItem
和归约条件$
,根据编程时的判断程序,$
表示不需要弹出任何栈内符号,于是机器直接push一个blockItem到栈中。
最后机器根据当前状态block->{ blockItem #}
和前看符号’}‘,将’}'也从输入缓存区取出,push到栈中,归约到block,最终完成归约过程。