【问题标题】:lexer/parser ambiguity词法分析器/解析器歧义
【发布时间】:2010-04-13 23:30:48
【问题描述】:

词法分析器如何解决这种歧义?

/*/*/

为什么它不只是说,哦,是的,那是多行注释的开头,然后是另一个多行注释。

贪婪的词法分析器不会只返回以下标记吗?

  • /*
  • /*
  • /

我正在为 CSS 编写一个 shift-reduce 解析器,但是这个简单的注释事情妨碍了我。如果您不想了解更多背景信息,可以阅读this question

更新

很抱歉一开始就忽略了这一点。我计划以/* @ func ( args, ... ) */ 的形式向 CSS 语言添加扩展,但我不想混淆理解 CSS 但不理解我的扩展注释的编辑器。这就是为什么词法分析器不能忽略 cmets。

【问题讨论】:

  • 正如您在“this question”回复中所指出的,词法分析器应进入“评论中”状态并丢弃输入,直到它看到将其移出该状态的词素。解析器永远不会看到 cmets,词法分析器不应该看到 cmets 的内容,除非确定它们何时结束。
  • @msw:当然,让解析器永远不会看到 cmets 并不是一个硬性规定。您可以通过将 cmets 视为标记并将它们提供给解析器来做一些非常酷的事情 - 只需查看 Python 文档字符串。
  • 确实,我专门指的是 C 风格的 cmets 及其与语法的词汇关系。我本可以更清楚地指出 OP 的评论,即他不应该混淆词汇和句法解释。还同意 Python 文档字符串很有用(和 javadoc 等)。我没有看过 Python 语法,但我打赌有一个 的产生式。
  • 问题是我真的想将 cmets 提供给解析器。

标签: lexer


【解决方案1】:

一种方法是让词法分析器在遇到第一个/* 时进入不同的内部状态。例如,flex 调用这些 "start conditions"(匹配 C 风格的 cmets 是该页面上的示例之一)。

【讨论】:

  • 我相信这对我来说是正确的。因为我遗漏了我真正想要解析 cmets 的部分......
  • @John Leidegren:确保您了解包容性和排他性条件。它们确实非常方便。
  • @leppie - 在 lex 中?我现在反对使用工具。稍后我可能会考虑使用词法分析器生成器,但我真的很想接触编写词法分析和解析器的裸机。
  • @John Leidegren:我知道他们在 Flex 中。但是,如果您正在编写自己的代码,那么实现起来就相当简单(通过堆栈和集合)。
【解决方案2】:

最简单的方法可能是将评论作为一个单独的标记来使用——也就是说,不要发出“START COMMENT”标记,而是继续读取输入,直到您可以发出一个“COMMENT BLOCK”标记,其中包括整个/*(anything)*/ 位。

由于 cmets 与可执行代码的实际解析无关,因此它们基本上可以被词法分析器剥离(或者至少聚集成单个标记)。您不关心评论中的标记匹配。

【讨论】:

  • 问题是我将以/* @ func ( args, ... ) */ 的形式添加扩展,这就是为什么我不能扔掉cmets。 CSS2 规范还说 / * 都是单独的分隔符。
  • 一旦识别出评论标记,您可以递归地解析它们,如果您这样选择 - 根据您的扩​​展的复杂程度,它们可能能够从平面评论字符串中进行正则表达式,而不是需要被lexed。
  • 用正则表达式匹配评论听起来很实用,但感觉不是很正式。我现在想避免这种情况。现在我想专注于理解词法分析的基础。
【解决方案3】:

在大多数语言中,这并不是模棱两可的:第一个斜杠和星号被使用来生成“多行注释的开始”标记。后面是一个斜杠,它是评论中的纯“内容”,最后两个字符是“多行评论结束”标记。

由于前 2 个字符已被消耗,第一个星号也不能用于生成注释结束标记。我刚刚注意到它可能会产生第二个“评论开始”标记...哎呀,这可能是个问题,具体取决于解析器可用的上下文数量。

我在这里说的是令牌,假设对 cme​​ts 进行了解析器级别的处理。但同样适用于词法分析器,其基本规则是从'/*' 开始,然后直到找到'*/' 才停止。实际上,整个评论的词法分析器级别处理不会被第二个“评论开始”混淆。

【讨论】:

  • 我认为 OP 关注的歧义不是第一个 /*/,而是第二个 /*/ - 他们担心他们的词法分析器会发出两个“开始评论”标记和然后消耗了 5 个字符中的 4 个,只剩下一个 / 字符(因此没有“结束评论”标记)。我认为我的回答更明确地说明了如何在词法分析器级别避免这种情况。
  • @Dav:你是对的;我在编辑时发现了第二个“开始评论”标记的问题,也得出结论,对整个评论进行词法分析器级别的处理是最简单的。但是,某些应用程序需要解析 cmets 中的内容(例如:文档构建应用程序等),并且对于它们,解析器级别需要获取一些上下文(以某种形式或其他形式)才能摆脱这种泡菜。
【解决方案4】:

使用正则表达式的算法,从字符串的开头搜索到当前位置。

if (chars[currentLocation] == '/' and chars[currentLocation - 1] == '*') {
  for (int i = currentLocation - 2; i >= 0; i --) {
    if (chars[i] == '/' && chars[i + 1] == '*') {
      // .......
    }
  }
}

这就像应用正则表达式 /\*([^\*]|\*[^\/])\*/ 贪心和自下而上。

【讨论】:

    【解决方案5】:

    解决此问题的一种方法是让您的词法分析器返回:

    /
    *
    /
    *
    /
    

    然后让你的解析器从那里处理它。 对于大多数编程语言,我可能会这样做,因为 /'s 和 *'s 也可以用于乘法和其他类似的事情,这对于词法分析器来说太复杂了.词法分析器实际上应该只是返回 基本符号

    如果令牌开始过多地依赖于上下文,那么您正在寻找的很可能是一个更简单的令牌。

    话虽如此,CSS 不是一种编程语言,因此 /'s 和 *'s 不能重载。真的 afaik 它们不能用于除了 cmets 之外的任何其他东西。所以我很想把整个事情作为评论令牌传递,除非你有充分的理由不这样做:/\*.*\*/

    【讨论】:

      【解决方案6】:

      由于 CSS 不支持嵌套 cmets,您的示例通常会解析为单个标记,COMMENT。 也就是说,词法分析器会将/* 视为开始注释标记,然后使用直到并包括*/ 序列的所有内容。

      【讨论】:

      • 您可以使用封闭的反引号 (`) 仅将一段文本标记为代码,而不是整行,如下所示:/*
      猜你喜欢
      • 2014-03-02
      • 2013-01-31
      • 1970-01-01
      • 2012-05-03
      • 1970-01-01
      • 2011-04-07
      • 2013-04-29
      • 2020-01-17
      • 1970-01-01
      相关资源
      最近更新 更多