Parsing yacc/bison LALR(1)算法如何处理;“空的”;规则?
在LALR(1)解析器中,语法中的规则被转换成一个解析表,该解析表有效地表示“如果到目前为止您有这个输入,并且先行标记是X,那么就转换到状态Y,或者按规则R进行缩减” 我已经成功地用解释语言(ruby)构建了一个LALR(1)解析器,没有使用生成器,而是在运行时计算一个解析表,并使用该解析表评估输入。这工作得出人意料地好,而且表生成非常简单(这让我有点吃惊),支持自引用规则和左/右关联 然而,有一件事我很难理解,那就是yacc/bison在概念上是如何处理空规则定义的。我的解析器无法处理它们,因为在生成表的过程中,它会递归地查看每个规则中的每个符号,“空”并不是来自词法分析器的东西,也不会被规则缩减。那么,LALR(1)解析器如何处理空规则呢?他们是特别对待它,还是一个有效算法应该只处理的“自然”概念,甚至不需要对这样一个概念有特别的认识 比方说,一条规则可以匹配任意数量的中间没有任何内容的成对括号:Parsing yacc/bison LALR(1)算法如何处理;“空的”;规则?,parsing,yacc,lalr,Parsing,Yacc,Lalr,在LALR(1)解析器中,语法中的规则被转换成一个解析表,该解析表有效地表示“如果到目前为止您有这个输入,并且先行标记是X,那么就转换到状态Y,或者按规则R进行缩减” 我已经成功地用解释语言(ruby)构建了一个LALR(1)解析器,没有使用生成器,而是在运行时计算一个解析表,并使用该解析表评估输入。这工作得出人意料地好,而且表生成非常简单(这让我有点吃惊),支持自引用规则和左/右关联 然而,有一件事我很难理解,那就是yacc/bison在概念上是如何处理空规则定义的。我的解析器无法处理它们,因
expr: /* empty */
| '(' expr ')'
;
类似以下的输入将匹配此规则:
((((()))))
这意味着,在读取lookahead标记中的“(”并看到“)”时,解析器选择:
expr
,然后移动下一个标记)”
,给出('expr')”
,这当然会减少到expr
,依此类推
让我困惑的是“什么都不换”。解析表如何表达这样的概念?还可以考虑调用一些语义动作,在减少空值时,返回一个值“<代码> $$/COD>”,这样一个比较简单的视图,只是跳过解析表,并说“<代码>”(在堆栈和<代码>中的'<代码> <代码> > <代码> >应该简单地转换为移位,不会真正产生序列
('expr')”
,但会简单地产生序列('')
尽管思考了好几天,因为在写问题时以及在接下来的几分钟里,我一直在思考这个问题,有件事让我觉得非常明显和简单
所有规则的归约总是:从堆栈中弹出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
它“只起作用”的原因是,为了减少空规则,您只需从堆栈中弹出零项。另一个视图可能会对d11wtq的伟大答案进行取整,如果可能的话: 在解析器构造过程中,在函数
FOLLOW(X)
和FIRST(X)
下计算一个可为空的规则(派生出ϵ的规则)。例如,如果您有A->bx
,并且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'
现在让我们看一下第一组
和第二组
集合:
S -> . X , '$'
这不是一个“点最终”项,因此这里要移位,但仅当集合FIRST(X)
包含我们的先行符号$
时才移位。这是false,因此我们不填充表条目
下一步:
这是一个“dot final”项,因此它想要reduce。要验证reduce的上下文,我们查看FOLLOW(S)
:我们希望reduce的语法符号后面是否可以跟在lookahead中的内容?的确,是的。$
始终在FOLLOW(S)中
因为开始符号的定义后跟输入的结束。所以是的,我们可以减少。因为我们减少符号S
,减少实际上是一个accept
操作:解析结束。我们用accept
操作填充表条目
类似地,我们可以使用lookaheadid
重复下一个项目集。让我们跳到s-deriving-empty规则:
S -> . , 'id'
S
后面能跟id
吗?很难。所以这种减少是不合适的。我们不填充解析器表条目
因此,您可以看到,空规则不会带来任何问题。它会立即变成圆点final
LR(0)
或LR(1)
项(取决于解析器构造方法),并被视为与任何其他dot最终项目一样,考虑前瞻性和填写表格。我确信关于处理的章节很长
S -> . , 'id'