在LALR(1)解析器中,语法中的规则被转换为一个解析表,有效地说“如果到目前为止你有这个输入,并且前瞻标记是X,那么转移到状态Y,或者减少规则R“。
我已经使用解释语言(ruby)成功构建了一个LALR(1)解析器,而不是使用生成器,而是在运行时计算解析表并使用该解析表评估输入。这种方法效果非常好,表格生成非常简单(让我感到有些惊讶),支持自我引用规则和左/右关联。
然而,我有一点难以理解的是,yacc / bison在概念上如何处理空规则定义。我的解析器无法处理它们,因为在生成表时它会递归地查看每个规则中的每个符号,而“空”不是来自词法分析器的东西,也不会被规则缩小。那么,LALR(1)解析器如何处理空规则呢?他们是专门对待它,还是一个有效算法应该使用的“自然”概念,甚至不需要特别了解这样的概念?
比方说,一条规则可以匹配任意数量的配对括号,中间没有任何内容:
expr: /* empty */
| '(' expr ')'
;
以下输入符合此规则:
((((()))))
这意味着在前瞻标记中读取'('和see')'时,解析器选择:
不太适合“shift”或“reduce”的核心算法。解析器实际上需要不将任何内容转移到堆栈上,将“空白”减少到expr
,然后移动下一个令牌')'
,得到'(' expr ')'
,这当然会减少到expr
等等。
这让我感到困惑的是“没有任何转变”。解析表如何传达这样一个概念?还要考虑应该可以调用一些语义操作,在减少空值时返回值$$
,所以只是从解析表中跳过它并说'('
on的相当简单的视图前瞻中的堆栈和')'
应简单地转换为转移,不会真正生成序列'(' expr ')'
,而只会生成序列'(' ')'
。
答案 0 :(得分:7)
尽管多年来一直在思考这个问题,但是在编写问题时考虑到了这一点,并且在随后的会议记录中,有些事情让我感到非常明显和简单。
所有规则的减少始终是:从堆栈中弹出X输入,其中X是规则中的组件数,然后将结果移回堆栈并转到该减少后表中给出的任何状态。
在空规则的情况下,您不需要考虑“空”甚至是一个概念。解析表只需要包含一个转换,该转换在堆栈上显示“给定'('
”和“在前瞻中不是'('
的任何内容”,通过“空”规则减少“。现在因为空规则的大小为零,所以从堆栈中弹出零意味着堆栈不会改变,然后当减少任何内容的结果移动到堆栈上时,你会看到确实出现在堆栈中的东西。语法,一切都变得清晰。
Stack Lookahead Remaining Input Action
--------------------------------------------------------------
$ ( ())$ Shift '('
$( ( ))$ Shift '('
$(( ) )$ Reduce by /* empty */
$((expr ) )$ Shift ')'
$((expr) ) $ Reduce by '(' expr ')'
$(expr ) $ Shift ')'
$(expr) $ Reduce by '(' expr ')'
$expr Accept
它“正常工作”的原因是因为为了减少空规则,你只需要从堆栈中弹出零项。
答案 1 :(得分:2)
如果有可能的话,另一种观点可能是d11wtq的最佳答案:
在函数FOLLOW(X)
和FIRST(X)
下的解析器构造期间会计算可为空的规则(派生ε的规则)。例如,如果您有A -> B x
,而B可以派生ε,那么我们必须在x
计算的集合中包含FIRST(A)
。还有集FOLLOW(B)
。
此外,空规则很容易在规范LR(1)
项集中表示。
一个有用的事情是想象有一个额外的非终结符号$
代表文件的结尾。
我们来看看语法:
S -> X | ϵ
X -> id
对于第一个规范LR(1)
项集,我们可以设置第一个LR(0)
项目集,并使用符号'$'添加前瞻:
S -> . X , '$'
S -> . , '$'
X -> . id , '$'
然后我们有一个前瞻是id
:
S -> . X , 'id'
S -> . , 'id
X -> . id , 'id'
现在让我们看一下FIRST
和FOLLOW
集:
S -> . X , '$'
这不是“点最终”项目,因此此处想要转移,但仅当集合FIRST(X)
包含我们的前瞻符号$
时。这是假的,所以我们不填写表格。
下一步:
S -> . , '$'
这是一个“dot final”项目,因此它希望减少。为了验证reduce的上下文,我们看一下FOLLOW(S)
:我们希望减少的语法符号可以跟在前瞻中吗?完全同意。 $
始终位于FOLLOW(S)
中,因为起始符号按定义后跟输入结束。所以是的,我们可以减少。由于我们正在减少符号S
,因此reduce实际上是一个accept
动作:解析结束。我们使用accept
操作填写表格条目。
同样,我们可以使用前瞻id
重复下一个项目集。让我们跳到S-derived-empty规则:
S -> . , 'id'
S
可以跟id
吗?几乎不。所以这种减少是不合适的。我们没有填写解析器表条目。
所以你可以看到一个空规则没有问题。它会立即变为点最终LR(0)
或LR(1)
项(取决于解析器构造方法),并且在考虑前瞻和填充表时,其处理方式与任何其他点最终项相同。< / p>