Parsing 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在概念上是如何处理空规则定义的。我的解析器无法处理它们,因

在LALR(1)解析器中,语法中的规则被转换成一个解析表,该解析表有效地表示“如果到目前为止您有这个输入,并且先行标记是X,那么就转换到状态Y,或者按规则R进行缩减”

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

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

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

expr:   /* empty */
      | '(' expr ')'
      ;
类似以下的输入将匹配此规则:

((((()))))
这意味着,在读取lookahead标记中的“(”并看到“)”时,解析器选择:

  • 将“')”移位(不可能)
  • 根据其他规则减少输入(不可能)
  • 还有别的
  • 不太适合“shift”或“reduce”的核心算法。解析器实际上需要在堆栈上不移动任何内容,将“nothing”减少到
    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
    操作填充表条目

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

    S -> .     , 'id'
    
    S
    后面能跟
    id
    吗?很难。所以这种减少是不合适的。我们不填充解析器表条目


    因此,您可以看到,空规则不会带来任何问题。它会立即变成圆点final
    LR(0)
    LR(1)
    项(取决于解析器构造方法),并被视为与任何其他dot最终项目一样,考虑前瞻性和填写表格。

    我确信关于处理的章节很长
    S -> .     , 'id'