【问题标题】:LL(1) Parsing -- First(A) with Recursive First AlternativesLL(1) Parsing -- First(A) with Recursive First Alternatives
【发布时间】:2013-03-18 11:20:30
【问题描述】:

我如何将 FIRST() 规则应用于生产,例如:

A -> AAb |抗体 | s

其中 A 是非终结符,b,s 是终结符。

备选方案 1 和 2 的 FIRST(A) 将再次成为 A,但由于我需要终端来获取 FIRST 集,因此这将导致 FIRST 的无限应用?

【问题讨论】:

    标签: parsing compiler-construction grammar context-free-grammar ll


    【解决方案1】:

    要计算 FIRST 集,您通常执行定点迭代。也就是说,您从一小组值开始,然后迭代地重新计算 FIRST 集,直到这些集收敛。

    在这种情况下,您首先要注意产生式 A → s 意味着 FIRST(A) 必须包含 {s}。所以最初你设置 FIRST(A) = {s}。

    现在,您遍历 A 的每个产生式,并根据您迄今为止计算的 FIRST 集的知识更新 FIRST。比如规则

    A → AAb

    意味着您应该更新 FIRST(A) 以包含 FIRST(AAb) 的所有元素。这不会导致 FIRST(A) 发生变化。然后你访问

    A → Ab

    您再次更新 FIRST(A) 以包括 FIRST(Ab),这又是一个无操作。最后,您访问

    A → s

    而且由于 FIRST(A) 已经包含 s,这不会导致任何变化。

    由于本次迭代没有任何改变,您最终会得到 FIRST(A) = {s},这确实是正确的,因为从 A 开始的任何推导最终都会产生一个 s 作为其第一个字符。

    有关更多信息,您可能会发现these lecture slides 很有用(这里是part two)。他们详细描述了自顶向下解析的工作原理以及如何迭代计算 FIRST 集。

    希望这会有所帮助!

    【讨论】:

    • @Apalala- 你确定吗?符号b 绝对不在 FIRST(A) 中。我所知道的用于计算 FIRST 集的算法的工作原理是为 FIRST 集播种所有从终端开始的产品,然后从那里开始。
    • @Apalala- 或者,如果您不以这种方式播种所有内容,则不会通过查看非终结符的 FIRST 集来计算 FIRST 集,除非该非终结符可以产生 epsilon。这意味着更新规则会将 FIRST(A) 添加到 FIRST(A),这会将空集添加到自身。
    • 您可以使用终端为A -> b 形式的规则播种 FIRST,但这还不够通用。但我投反对票的原因是,您建议在从A -> AAb 计算 FIRST 时,只需要查看第一个 A,这是错误的,因为虽然不是在这种情况下,但可能是 A =*=> ε,这将使FIRST(A) 包含b。请参阅我对 FIRST 的一般解决方案的回答。
    • @Apalala- 两次教授编译器课程后,我相当有信心您在进行 FIRST 集计算时确实查看非终结符,除非您明确发现该非终结符的第一个集合包含 epsilon。你不会乐观地看过去的非终结符。这有意义吗?
    • 您是对的,对于 FIRST[1],但对于 FIRST[k] 则不然。我编辑了您的答案以使其具有普遍性和正确性。希望你不要介意。
    【解决方案2】:

    我的teaching notes 是西班牙语,但算法是英语。这是计算FIRST的一种方法:

    foreach a ∈ Σ do
         F(a) := {a}
    for each A ∈ N do
         if A→ε ∈ P then
              F(A) := {ε}
         else
              F(A) := ∅
    repeat
         for each A ∈ N do
              F'(A) := F(A)
         for each A → X1X2...Xn ∈ P do
              if n > 0 then
                   F(A) := F(A) ∪ F'(X1) ⋅k F'(X2) ⋅k ... ⋅k F'(Xn)
    until F(A) = F'(A) forall A ∈ N
    FIRSTk(X) := F(X) forall X ∈ (Σ ∪ N)
    

    Σ 是字母表(终端),N 是非终端的集合,P 是产生式(规则)的集合,ε 是空字符串,⋅k 是连接修剪到k 的地方。请注意∅ ⋅k x = ∅,连接两个集合会产生笛卡尔积中元素的连接。

    手动计算FIRST组的最简单方法是每次算法迭代使用一个表。

    F(A) = ∅
    
    F'(A) = F(A) ⋅1 F(A) .1 F(b) U F(A) .1 F(b) U F(s)
    F'(A) = ∅  ⋅1 ∅ ⋅1 {b} U ∅  ⋅1 {b} U {s}
    F'(A) = ∅ U ∅ U {s} 
    F'(A) = {s}
    
    F''(A) = F'(A) ⋅1 F'(A) .1 F'(b) U F'(A) .1 F'(b) U F'(s)  
    F''(A) = {s} ⋅1 {s} ⋅1 {b} U {s} ⋅1 {b} U {s}
    F''(A) = {s} U {s} U {s}
    F''(A) = {s}
    

    我们已经完成了,因为 F' = F''FIRST = F''FIRST(A) = {s}

    【讨论】:

      【解决方案3】:

      您的语法规则有left recursion,正如您已经意识到的那样,LL 解析器有are not able to parse grammars with left recursion

      所以你需要先摆脱左递归,然后你应该能够计算出规则的第一组。

      【讨论】:

      • 虽然这是真的,但我认为 OP 的问题是关于计算 FIRST 集,即使语法是左递归的,这也是可能的。
      • 问题是关于计算 FIRST 集,这可以用于左递归语法。
      猜你喜欢
      • 2015-05-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-09
      • 2013-07-03
      • 2011-07-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多