yacc / bison LALR(1)算法如何处理“空”规则?

时间:2011-11-23 12:53:05

标签: parsing yacc lalr

在LALR(1)解析器中,语法中的规则被转换为一个解析表,有效地说“如果到目前为止你有这个输入,并且前瞻标记是X,那么转移到状态Y,或者减少规则R“。

我已经使用解释语言(ruby)成功构建了一个LALR(1)解析器,而不是使用生成器,而是在运行时计算解析表并使用该解析表评估输入。这种方法效果非常好,表格生成非常简单(让我感到有些惊讶),支持自我引用规则和左/右关联。

然而,我有一点难以理解的是,yacc / bison在概念上如何处理空规则定义。我的解析器无法处理它们,因为在生成表时它会递归地查看每个规则中的每个符号,而“空”不是来自词法分析器的东西,也不会被规则缩小。那么,LALR(1)解析器如何处理空规则呢?他们是专门对待它,还是一个有效算法应该使用的“自然”概念,甚至不需要特别了解这样的概念?

比方说,一条规则可以匹配任意数量的配对括号,中间没有任何内容:

expr:   /* empty */
      | '(' expr ')'
      ;

以下输入符合此规则:

((((()))))

这意味着在前瞻标记中读取'('和see')'时,解析器选择:

  1. 转移')'(不可能)
  2. 根据其他规则(不可能)减少输入
  3. 别的......
  4. 不太适合“shift”或“reduce”的核心算法。解析器实际上需要不将任何内容转移到堆栈上,将“空白”减少到expr,然后移动下一个令牌')',得到'(' expr ')',这当然会减少到expr等等。

    这让我感到困惑的是“没有任何转变”。解析表如何传达这样一个概念?还要考虑应该可以调用一些语义操作,在减少空值时返回值$$,所以只是从解析表中跳过它并说'(' on的相当简单的视图前瞻中的堆栈和')'应简单地转换为转移,不会真正生成序列'(' expr ')',而只会生成序列'(' ')'

2 个答案:

答案 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'

现在让我们看一下FIRSTFOLLOW集:

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>