当Ply(Yacc)出现错误时,如何获得语法生成



yacc.py文件中,我定义了一个语法的输出和一个错误,如下所示:

def p_error(p):
if p:
print("Error when trying to read the symbol '%s' (Token type: %s)" % (p.value, p.type))
else:
print("Syntax error at EOF")
exit()

除了这个错误消息,我还想打印错误发生时生产读取的内容,比如:

print("Error in production: ifstat -> IF LPAREN expression RPAREN statement elsestat")

我该怎么做?

真的,你做不到。您尤其不能使用像Ply生成的自下而上的解析器,但即使使用自上而下的解析器";生产读取的时间";不是一个定义明确的概念。

例如,考虑错误代码:

if (x < y return 42;

其中错误是缺少括号。至少,我会这样描述这个错误。但右括号并不是0后面唯一可以包含的内容。例如,一个正确的程序可能包括以下任何一项:

if (x < y) return 42;
if (x < y + 10) return 42;
if (x < y && give_up_early) return 42;

以及更多。

那么,当解析器看到令牌return时,它试图完成哪个生成呢?显然,它仍在尝试完成expression(它实际上可能有一个不同表达式类型的层次结构,或者可能依赖于优先级声明作为单个非终端,或者两者的某种组合。(但这并不能真正帮助将错误识别为缺少的右括号。

在自上而下的解析器中,可以遍历解析器堆栈,以包含顺序获得部分完成的产品的列表。(至少,如果解析器维护自己的堆栈,这是可能的。如果它是递归下降解析器,检查堆栈会更复杂。(

但在自下而上的解析器中,解析器状态更为复杂。自下而上的解析器比自上而下的解析器更灵活,正是因为它们实际上可以同时考虑多个产品。因此,通常没有一个单独的局部生产;解析器将通过逐渐消除所有不起作用的可能性来决定它所关注的产品。

这种描述听起来像是自下而上的解析器在做很多工作,这是有误导性的。解析器生成器已经完成了这项工作,它编译了一个简单的状态转换表来指导解析。在实践中,这意味着解析器知道如何在解析的每个时刻处理每个可能正确的令牌。因此,例如,当它看到if (x < y后面有一个)时,它立即知道必须完成expression的生成并继续执行if语句的其余部分。

Bison是yacc的一个C实现,它有一个可选特性,允许它在遇到错误时列出可能的正确标记。这并不像听起来那么简单,正确实现它会在解析时间上产生明显的开销,但有时它很有用。(不过,这通常没有用处,因为可能的令牌列表可能很长。以我使用的错误为例,该列表将包括每一个算术运算符,以及那些可以启动后缀运算符的令牌。bison扩展错误处理程序在达到第六个可能的令牌时停止尝试,这意味着它很少会生成扩展的er如果解析处于表达式的中间,则返回错误消息。(无论如何,Ply没有这样的功能。

Ply和bison一样,确实通过error伪令牌实现了错误恢复。错误恢复算法在具有明确的重新同步点的语言中效果最好,例如在具有明确语句终止符的语言中(与类C语言不同,在类C语言中,许多语句不以;结尾(。但是,您可以使用error产品来强制解析器将其堆栈弹出回某些包含产品,以便生成更好的错误消息。根据我的经验,需要进行大量的实验才能使这一策略正确。

简而言之,生成有意义的错误消息是很困难的。我的建议是首先集中精力让解析器处理正确的输入。

相关内容

  • 没有找到相关文章

最新更新