【问题标题】:Why are multi-line comments in flex/bison so evasive?为什么 flex/bison 中的多行注释如此回避?
【发布时间】:2011-05-07 22:14:08
【问题描述】:

我正在尝试在我的 flex (.l) 文件中解析 C 样式的多行 cmets:

%s ML_COMMENT
%%

...

<INITIAL>"/*"                   BEGIN(ML_COMMENT);
<ML_COMMENT>"*/"                BEGIN(INITIAL);  
<ML_COMMENT>[.\n]+              { }

我没有返回任何标记,而且我的语法 (.y) 没有以任何方式解决 cmets。

当我运行我的可执行文件时,我得到一个解析错误:

$ ./a.out
/*
abc 
def
Parse error: parse error
$ echo "/* foo */" | ./a.out
Parse error: parse error

(我的 yyerror 函数执行 printf("Parse error: %s\n"),这是冗余错误消息的前半部分的来源)。

我可以看到为什么第二个示例失败了,因为整个输入都是注释,并且由于语法忽略了 cmets,因此没有语句。因此输入不是一个有效的程序。但是第一部分在我完成评论之前就抛出了一个解析错误。

同样令人困惑:

$ ./a.out
/* foo */
a = b;
Parse error: parse error

在这种情况下,注释在实际有效输入之前关闭(没有注释,解析就好了)。失败实际上发生在解析“a”之后,而不是在尝试解析赋值“a = b;”之后。如果我在自己的行上输入“a”,它仍然会引发错误。

鉴于错误消息是解析器错误而不是扫描仪错误,我的 .y 文件中是否缺少一些重要的信息?还是我在传播到解析器端的扫描仪规则中做错了什么?

编辑:按照@Rudi的建议,我打开调试发现:

$ ./a.out
Starting parse
Entering state 0
Reading a token: /*
foo
Next token is 44 (IDENTIFER)
Shifting token 44 (IDENTIFER), Entering state 4
Reducing via rule 5 (line 130), IDENTIFER  -> identifier
state stack now 0
Entering state 5

我关闭了调试,发现/* foo */ = bar;确实和foo = bar;解析一样。我正在使用 flex 2.5.4;它没有给我任何关于我尝试使用的有状态规则的警告。

【问题讨论】:

  • 我将 flex 重新标记为 gnu-flex。您的扫描仪规则看起来不错。解析错误表明对解析器的令牌输入无效。您可能想发布一些相应的 Bison 规则。此外,将 printf() 语句放入您的 bison 规则中可能是一个好主意,这样您就可以看到解析器在扫描令牌期间正在尝试哪些规则。
  • 为您的扫描仪创建一个单独的测试工具也是一个好主意。这样,您就可以将扫描仪缺陷与解析器缺陷隔离开来。任何扫描仪-解析器系统都足够复杂,当您真正想要的是执行单元测试时,您不需要通过执行集成测试来注入额外的复杂性......
  • 我建议 - 有理由不这样做 - 只是编写一个 Perl 预处理器来咀嚼 cmets。

标签: c comments bison multiline flex-lexer


【解决方案1】:

以这种方式解析 cmets 可能会导致错误,因为:

  • 您需要为所有 lex 规则添加条件
  • 如果您还想处理 // cmets,它会变得更加复杂
  • 您仍然面临 yacc/bison 合并两个 cmets(包括其间的所有内容)的风险

在我的解析器中,我处理这样的 cmets。首先定义注释开头的 lex 规则,如下所示:

\/\*     {
         if (!SkipComment())
            return(-1);
         }

\/\/     {
         if (!SkipLine())
            return(-1);
         }

然后编写 SkipComment 和 SkipLine 函数。他们需要消耗所有输入,直到找到注释的结尾(这是相当旧的代码,所以请原谅我有些过时的结构):

bool SkipComment (void)
{
int Key;

Key=!EOF;
while (true)
   {
   if (Key==EOF)
      {
      /* yyerror("Unexpected EOF within comment."); */
      break;
      }
   switch ((char)Key)
      {
      case '*' :
         Key=input();
         if (char)Key=='/') return true;
         else               continue;
         break;
      case '\n' :
         ++LineNr;
         break;
      }
   Key=input();
   }

return false;
}

bool SkipLine (void)
{
int Key;

Key=!EOF;
while (true)
   {
   if (Key==EOF)
      return true;
   switch ((char)Key)
      {
      case '\n' :
         unput('\n');
         return true;
         break;
      }
   Key=input();
   }

return false;
}

【讨论】:

  • 如果注释开始/结束字符序列出现在引用的文本中,它是否会处理它? (例如foo = "this doesn't contain a /* comment */"
  • 我没有明确提到这一点,但是您必须以完全相同的方式解析字符串。如果你想像 C/C++ 那样支持转义反斜杠,你尤其需要这样做。
  • 这比仅仅正确使用弹性启动状态更复杂、更容易出错、更冗长且更难做到。它基本上只是你的词法分析器的手写部分——如果你不喜欢 flex,为什么不直接手写整个东西呢?
【解决方案2】:

我认为您需要将 ML_COMMENT 开始条件声明为独占开始条件,以便只有 ML_COMMENT 规则处于活动状态。 %x ML_COMMENT 而不是 %s ML_COMMENT

否则没有开始条件的规则也有效。

【讨论】:

  • 啊!这似乎成功了。我现在唯一的问题是:为什么我的多行 cmets 的内容回显了?当我在 STDIN 中输入 /* foo bar */ 时,我得到 foo bar 打印到 STDOUT。
  • [.\n] 没有做你认为它在做的事情。将其替换为 2 条规则之一。一个用于\n。 Flex 默认回显不匹配任何规则的输入。这就是为什么许多 lex 规则集以“.”结尾的原因。所以每个输入都匹配一些东西。
【解决方案3】:

除了%x%s 的问题之外,您还遇到[.\n] 中的. 匹配(仅)文字. 而不是“换行符以外的任何字符”的问题. 确实如此。你想要这样的规则

<ML_COMMENT>.|"\n"     { /* do nothing */ }

改为

【讨论】:

    【解决方案4】:

    我发现这个 C 语言语法(实际上只是词法分析器)的描述非常有用。我认为这与帕特里克的回答基本相同,但略有不同。

    http://www.lysator.liu.se/c/ANSI-C-grammar-l.html

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-18
      • 1970-01-01
      • 2013-10-05
      • 2011-01-25
      • 2011-10-01
      • 1970-01-01
      • 2019-08-28
      • 2010-10-12
      相关资源
      最近更新 更多