【问题标题】:How does the yacc/bison LALR(1) algorithm treat "empty" rules?yacc/bison LALR(1) 算法如何处理“空”规则?
【发布时间】:2011-11-23 12:53:05
【问题描述】:

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

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

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

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

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

如下输入将匹配此规则:

((((()))))

这意味着在前瞻标记中读取 '(' 并看到 ')' 时,解析器会选择:

  1. 移动 ')'(不可能)
  2. 根据其他规则减少输入(不可能)
  3. 别的东西...

不太适合“shift”或“reduce”的核心算法。解析器实际上需要什么都不移动到堆栈上,将“无”减少到expr,然后移动下一个标记')',给出'(' expr ')',当然这减少到expr,等等。

让我困惑的是“什么都不做”。解析表是如何传达这样一个概念的?还考虑到应该可以调用一些语义操作,在减少空值时将值返回到$$,因此只是从解析表中跳过它并说堆栈上的'(' 和@前瞻中的 987654330@ 应该简单地转换为移位,不会真正产生序列 '(' expr ')',而只会产生序列 '(' ')'

【问题讨论】:

  • 我确信dragon book 中有很长的部分是关于处理此类规则的。我不认为 Stack Overflow 是讨论它的合适场所——也许是程序员?
  • 感谢您对本书的建议...现在查看该链接。 Stackoverflow 对我来说似乎是正确的地方。这是关于算法的直接问题,而不是主观讨论。有人可以很好地搜索这个,如果有人知道答案,请快速解决。
  • 我想我实际上是在我意识到一个基本点时才意识到这一点的,而且它是如此的明显和直截了当。会回答问题,因为谷歌搜索什么都没有,这是一个相当自然的问题;)

标签: parsing yacc lalr


【解决方案1】:

尽管这几天一直在想这个问题,但在写下这个问题的时候和接下来的几分钟里,我一直在思考这个问题,但我还是觉得非常明显和简单。

所有规则的归约总是:从堆栈中弹出 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

它“正常工作”的原因是,为了通过空规则减少,您只需从堆栈中弹出零个项目。

【讨论】:

  • 事实证明,对我的解析器中的 DSL 稍作更改就可以完成这项工作......零规则组件和一个为空字符串的规则组件是两个非常不同的东西。
【解决方案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”项目,所以它想减少。为了验证减少的上下文,我们查看FOLLOW(S):我们希望减少的语法符号可以跟在前瞻中的内容吗?完全同意。 $ 始终在 FOLLOW(S) 中,因为根据定义,开始符号后面是输入的结尾。所以是的,我们可以减少。由于我们正在减少符号S,因此减少实际上是一个accept 动作:解析结束。我们用accept 操作填充表格条目。

类似地,我们可以使用前瞻id 重复下一个项目集。让我们跳到 S-deriving-empty 规则:

S -> .     , 'id'

S 后面可以跟id 吗?几乎不。所以这种减少是不合适的。我们不填充解析器表条目。

因此您可以看到空规则没有问题。它立即变成一个 dot final LR(0)LR(1) 项目(取决于解析器构造方法),并且在考虑前瞻和填充表格方面与任何其他 dot final 项目相同。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多