【问题标题】:Use of Goto within lexer/parser在词法分析器/解析器中使用 Goto
【发布时间】:2011-06-30 08:57:53
【问题描述】:

我有一个词法分析器/解析器对(几年前我抄袭了别人)。我将添加一些功能,并认为我会首先标准化包含多个 if/else if/else 的 while(true) 的使用与使用 goto 跳回到切换之前的开关。

(在火焰开始之前,我通常不使用 goto 作为它的邪恶等)

while(true) 和嵌套 switch 的问题是,break 只能跳出 switch,不能超出 while。

我在这里做了一些搜索,并看到了使用从开关内部返回的建议。虽然这在某些情况下会起作用,但在其他情况下,会在一段时间之后但在返回之前进行一些处理。在多个地方复制此代码并没有真正的吸引力。

我还可以引入一个布尔标志并在 while 语句中使用它来决定是否中断 while 但这也不吸引人,因为它会给代码增加噪音。

解析器中当前使用 if/else if/else 代替内部开关的方法可行,但如果可能的话,我确实偏爱开关。

一般来说,词法分析器代码似乎通过删除 while(true) 并在开关开始之前放置一个标签并使用 goto 继续循环来解决这个问题。这使得 break 意味着停止循环,老实说,这似乎是最干净的方式,但确实涉及可怕的 goto。

回到while(true),我还可以看到第三种方式。在 while(true) 之后使用标签,并让开关代码在循环结束时使用 goto 到达它。然后,Break 意味着退出开关但继续循环。

那么小组对此有何看法? goto 使用起来太可恶了吗?或者当只有一个标签可以跳转并减少缩进并产生其他清晰的代码时是否可以?解析器/词法分析器是否应该获得使用 goto 的特殊许可?

如果有帮助,我可以提供一些示例代码。

【问题讨论】:

  • Goto 并不邪恶。如果您正在编写状态机之类的代码,那么它的使用是完全合理的。等一下……解析器很可能一个状态机。
  • 确实,看看 Linux Kernel 源代码,goto 的位置非常整洁,代码很棒!
  • Linux 内核不同,因为它是用 C 编写的,除了“goto cleanup”之外,没有更简单/清晰/优雅的方式来处理错误和释放资源。此外,出于性能考虑,我们可能会使用一个长函数而不是将其拆分。这些都不适用于 C#,除了自动生成的代码外,没有理由在那里使用 goto。
  • GOTO still considered harmful? 的可能重复项
  • @Simon:如果你自己回答了一个问题,请把你自己的答案标记为正确。

标签: c# parsing goto lexer


【解决方案1】:

以规范的方式使用 GOTO 是可以的。不允许突破任意嵌套块结构的语言导致这个问题被反复提出,因为 1970 年代人们将“语言应该具有什么样的控制流结构”的问题打死。 (注意:这种抱怨对于词法分析器/解析器来说并不特殊)。

你不想要带有布尔值的方案;它只是增加了循环检查的额外开销并使代码混乱。

我认为你有这个问题:

   <if/while/loop head> {
       <if/while/loop head> {
             ...
                 if <cond>  <want to break out all blocks>
             ...
                            }
                       }

良好语言的正确治疗方法是:

  blocks_label:
  <if/while/loop head> {
       <if/while/loop head> {
             ...
                 if <cond>  exit blocks_label;
             ...
                            }
                       }

如果 exit 结构存在于您的语言中,则退出 由命名标签标记的块。 (没有任何借口 现代语言没有这个,但是,我没有 设计它们)。

作为一个穷人的替代品,写得非常令人满意:

   <if/while/loop head> {
       <if/while/loop head> {
             ...
                 if <cond>  goto exit_these_blocks;
             ...
                            }
                       }
   exit_these_blocks:  // my language doesn't have decent block exits

有时你会发现一种语言可以提供

break <exp>

其中exp 通常是一个常数整数,意思是“打破exp 嵌套块”。这是一个非常愚蠢的想法,因为一些糟糕的维护人员可能会在堆栈中的某个地方插入另一个块,现在代码会做一些疯狂的事情。 (事实上​​,大约 20 年前,电信交换机中的这个确切错误导致整个东海岸电话系统瘫痪)。如果您在自己的语言中看到这种结构,请改用穷人的替代品。

【讨论】:

  • 在你的例子中,为什么 goto 被认为是你的 exit 构造的穷人替代品?鉴于这两个,我认为 goto 实际上更具可读性。我绝对同意 break 是废话。
  • @Simon Hewitt:因为穷人的版本可能会被破坏,一些维护者在标签前添加了一条语句。如果在退出案例的标签之后添加这样的语句,编译器将看到该标签不是块上的标签,并抱怨。它在概念上也更好:你命名了语句的嵌套,并说退出嵌套;这比 goto 更受目的限制,通常可以去任何地方。所以你已经更清楚地向读者说明了你的目的。
【解决方案2】:

在解析器中使用 GOTO 是完全合理的。当你降到一个基本级别时,循环和条件等都被实现为 goto,因为这是处理器可以做的——“从这里获取要执行的下一条指令”。

goto 的唯一问题,以及它们经常被妖魔化的原因,是它们可能是非结构化代码的一个标志,来自非结构化思维。在现代高级语言中,不需要 goto,因为所有工具都可以很好地结构化代码,而良好结构化的代码至少意味着一些结构化思维。

因此,如果需要,请使用 goto。不要仅仅因为你懒得好好思考问题就使用它们。

【讨论】:

  • 为什么只在解析器中而不是其他代码?如果 GOTO 是手动编码而不是自动生成的,您认为在解析器中使用 GOTO 是否仍然合理?据我所知,Goto 从来都不是真正需要的,总有替代方案。
  • 实际上在任何代码中它们都是可行的。它们从来都不是必需的,但在某些情况下,它们可以为问题提供最合适的解决方案。我认为解析器是它们可能适合的情况之一。就个人而言,我已经几十年没有使用过,所以我同意它们永远不需要。只是有时它们是有效的,因为替代方案更令人困惑。
  • 代码应该在问题域允许的范围内结构化,但如果问题域本质上是非结构化的,试图将结构强加到应该解决它的代码上可能会降低可读性。例如,如果问题由状态机定义,则在某些情况下使用goto 标签来表示状态可能比使用“状态”变量和switch 语句更清晰、更清晰。虽然执行“goto”会立即改变形状,但只有在退出并重新进入 switch 语句时,更改状态变量才会有效地改变状态。
猜你喜欢
  • 2013-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-07
  • 2013-04-29
  • 2020-01-17
相关资源
最近更新 更多