使用解析器重新同步处理PLY.yacc错误

时间:2018-12-31 18:17:40

标签: python-3.x error-handling yacc ply

我正在尝试在解析过程中实现用户友好的语法错误处理。根据我在官方PLY documentation中观察到的情况。一种方法是在第一个SyntaxError发生时引发异常并终止解析。但是,正如文档所建议的,我想做类似的事情来使用解析器重新同步技术。

文档说:

  

处理语法错误的最佳方式是编写包含错误标记的语法规则。例如,假设您的语言对这样的打印语句具有语法规则:

def p_statement_print(p):
  'statement : PRINT expr SEMI'
  ...
     

要考虑表达错误的可能性,您可以编写其他语法规则,如下所示:

def p_statement_print_error(p):
    'statement : PRINT error SEMI'
     print("Syntax error in print statement. Bad expression")

我有这样一个语法摘录:

def p_operation(self, p) -> None:
    '''
    operation : unaryOperation
              | binaryOperation
    '''

def p_unaryOperation(self, p) -> None:
    '''
    unaryOperation : unaryOperation L_SQUARE_BRACKET projection R_SQUARE_BRACKET
                   | RELATION_NAME
    '''

def p_projection(self, p) -> None:
    '''
    projection : multipleAttributes
               | attribute
    '''

def p_multipleAttributes(self, p) -> None:
    '''
    multipleAttributes : projection COMMA attribute
    '''

def p_attribute(self, p) -> None:
    '''
    attribute : ATTRIBUTE
    '''

我不确定如何定义这样的新规则,包括error令牌。我应该用error令牌替换每个非终结符吗?

期待您的答复!非常感谢您的帮助

1 个答案:

答案 0 :(得分:1)

您绝对不应该为每个非终端添加错误信息。

当有一些令牌通常可以将解析上下文重置为已知状态时,重新同步工作。在具有清晰的语句结尾标记的语言中(在您引用的示例中为分号),该标记可以很好地用作重新同步点。丢弃直到下一个分号的文本,然后再从那里进行解析不会在100%的时间内起作用,但是在许多情况下确实可以。

括号和括号也可以用作重新同步点,但是启发式方法不那么可靠,因为许多语法错误是括号或括号不匹配的结果。例如,扫描缺少的右方括号可能会丢弃整个输入。

在没有明确的语句定界符的语言中,重新同步更加复杂,包括Python之类的语言,其中的换行符仅在语句未嵌套在括号内时才终止语句。丢弃最多换行符可能会起作用,但是您可能必须处理扫描程序与解析器之间的反馈,该反馈确定何时将换行符作为令牌传输以及何时将其跳过为空白。

不一致的缩进可能是有用的重新同步触发器,但有几点警告。首先,您一定不能拒绝带有“误导”缩进的有效输入,因此触发器在重新同步过程中比在正常解析过程中需要更加敏感。其次,跟踪不一致的缩进肯定需要解析器->扫描器反向通道。因此,它比简单的紧急恢复要多得多,但可以有效。

最重要的是,对于良好的错误报告和恢复,几乎没有通用算法(如果有)。您需要根据语言的句法本质来制定策略。

理想情况下,您将希望通过检查解析器对常见错误的响应来优化代码,但这只有在进行实际部署并了解常见错误之后才能真正做到。因此,我能提供的最佳建议是从一个简单的恢复策略开始,看看它如何处理不同的语法错误,尤其是您偶然创建的语法错误(或您的朋友和合作者的语法错误)。保留遇到的各种语法错误的存档,可用于测试对诊断和恢复代码的改进。别指望它是完美的,因为这是一个棘手的问题,但是请尽一切可能使它更准确。