我该如何处理Flex词法分析器中的词法错误?

10

我目前正在尝试使用Flex+Bison编写一个小型编译器,但是在错误处理方面有些迷茫,特别是如何使所有东西协调一致。为了激发讨论,考虑以下我用于字符串字面量的词法分析器片段:

["]          { BEGIN(STRING_LITERAL); init_string_buffer(); }
<STRING_LITERAL>{
    \\\\    { add_char_to_buffer('\\'); }
    \\\"    { add_char_to_buffer('\"'); }
    \\.     { /*Invalid escape. How do I treat this error?*/ }
    ["]     { BEGIN(INITIAL); yylval = get_string_buffer(); return TK_STRING; }
}

我该如何处理无效转义字符的情况?目前我只是打印错误信息并调用exit,但如果可能的话,我希望能够继续运行并检测文件中的多个错误。

我的问题:

  • 我应该使用哪个函数来打印错误信息?是否应该使用后面 bison 需要的相同的 yyerror 函数?如果词法分析器和语法分析器在不同的文件中,我应该将 yyerror 函数的定义放在哪里?
  • 我应该从我的动作中返回哪个标记代码?对于“文件结束”应该返回 0 吗?还是一些特殊的 TK_INVALID_STRING 标记?
  • 如何确保语法分析器在出现词法错误(无效的文字、杂散的标点符号等)后能够继续解析?
3个回答

12

有很多选择,哪个是最好的可能只是个人观点问题。(请注意,SO 不喜欢那些答案是意见而非事实的问题。)

这在很大程度上取决于您如何处理应用程序中的错误消息。但这里有几个可能性:

  1. 直接从词法分析器打印错误消息。告诉你的错误检测系统编译不成功了:你可以使用全局错误计数(全局变量!),或者作为附加参数传递给 yylex 的共享数据结构。然后忽略该字符并继续进行词法分析。

  2. 返回类似于 TK_INVALID_STRING 的内容给语法分析器。语法分析器将需要相应的 error 产生式来适当地处理和恢复此错误,这需要更多的工作,但优点是将所有的错误处理都放入了语法分析器。然而,在字符串的特定情况下,您可能需要完成对关闭引号之前的字符串的词法分析;否则,继续解析将是徒劳的。

关于 yyerror:没有什么神奇的地方。该函数完全由您负责。 bison 做的唯一一件事就是使用指定的一组参数调用它。如果您发现在词法分析器中记录错误很有用(我认为可能是这样的),那么请继续使用它。您完全负责声明 yyerror,因此将其定义放在您在词法分析器和语法分析器中都包含的共享头文件中。或者调整 bison 的代码生成选项,以便在与 bison 创建的头文件一起创建时包含其定义。不管哪种方法更容易。一旦您弄清楚了如何声明 yyerror,就可以在任何地方定义它: 在词法分析器文件中、在 bison 文件中,或者(我倾向于)在一个支持函数的单独库中。

(顺便说一下,我已经尝试过选项2,但对我来说似乎太费力了;选项1对我来说效果很好。但是口味因人而异,仅供参考;我不会在这里为我的选择辩护,但我不介意承认它。)


如果您选择选项1,当词法分析器发现无效的字符串文字时,您会让它返回什么?您会假装它是有效的(返回TK_STRING),然后由高级代码负责调用解析器来检查全局错误变量吗? - hugomg
@missingno:没错。这是继续解析的最简单方法。在生成代码之前,您无需检查错误。从这个意义上说,它与超出范围的整数没有区别,例如:您希望确保编译失败,但从解析的角度来看,您应该能够继续以便检查其余的语法。 - rici
就我个人而言,我经常使用选项一,但会调用yyerror来发出错误消息,以便所有“语法错误”都能在一个地方处理。 - akim
@akim:请在我的回答中搜索“我认为很可能是”。 - rici

4
如果您正在使用Bison进行C++输出,另一个选项是抛出异常。
.   throw yy::parser::syntax_error("invalid character: " + std::string(yytext, yyleng);

如果您正在使用Bison 3.6或更高版本(包括C在内的所有目标语言),则还可以返回YYerrorspecial token。这类似于rici的建议return TK_INVALID_STRING,但是然后解析器会抱怨此未知TK_INVALID_STRING(因此有两个错误消息:一个来自您对yyerror的调用,另一个来自yyparse关于未知TK_INVALID_STRING)。YYerror没有这样的事情,但您确实正确进入了错误恢复。
换句话说,在C中,我建议(如果您的yyerror支持可变参数):
yyerror (yylloc, _("syntax error: invalid character: %c"), c);
return YYerror;

这是Bison发行版中“bistromathic”示例的摘录(在典型的发行版中可在/usr/local/share/doc/bison/examples找到,或者在SavannahGitHub上获取)。

这是一个合理的方法,但是你如何处理OP中的最后一个问题:“如何确保解析器在词法错误后能够继续解析?”想必,一旦你从其中longjmp出来,flex内部就会崩溃,不是吗? - rici
@rici:syntax_error 由生成的解析器捕获,然后触发常规的错误报告/恢复序列。至于 yylex 函数本身,我不知道会发生什么事情:它的控制流程被设计为可以通过 return;continue; 等中断。实际上,我从来没有遇到过任何问题。 - akim

2
最简单的方法就是只有一个最终规则。
. return yytext[0];

这涵盖了所有单个特殊字符和所有非法字符。在语法中直接使用特殊字符,如“:”、“;”等。然后,如果出现非法字符,则会调用解析器的错误处理程序,这有一些恢复的前景。如果在词法分析器中处理它们,您所能做的就是打印错误并忽略它们。
这还可以减少词法分析文件的大小。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接