如何阻止ANTLR抑制语法错误?

所以我用Java编写了一个使用ANTLR的编译器,我对它如何处理错误感到有些困惑。

默认行为似乎是打印错误消息,然后通过令牌插入等方式尝试从错误中恢复并继续解析。 我原则上喜欢这个; 这意味着(在最好的情况下)如果用户提交了多个语法错误,他们将在每个错误中获得一条消息,但它会提到所有错误,而不是强制它们重新编译以发现下一个错误。 默认错误消息可以用于我的目的。 当它读完所有令牌时就会出现问题。

当然,我使用ANTLR的树构造函数来构建抽象语法树。 虽然解析继续通过语法错误很好,所以用户可以看到所有错误,一旦完成解析我想得到一个exception或某种指示输入在语法上没有效果; 这样我就可以停止编译并告诉用户“抱歉,修复语法错误,然后重试”。 我不想要的是它根据它认为用户试图说的内容吐出一个不完整的AST,并继续编译的下一个阶段,没有任何迹象表明出现任何问题(除了错误信息之外)到控制台,我看不到)。 但默认情况下,确实如此。

最终的ANTLR参考提供了一种在检测到语法错误时立即停止解析的技术:覆盖mismatchrecoverFromMismatchedSet方法以抛出RecognitionException ,并添加@rulecatch操作来执行相同操作。 这似乎失去了从解析错误中恢复的好处,但更重要的是,它只能部分工作。 如果缺少必要的标记(例如,如果二元运算符只在其一侧有一个表达式),它会按预期抛出exception,但如果添加了一个无关的标记,ANTLR会插入它认为属于的标记并继续以其快乐的方式,生成一个AST,除了控制台消息外没有任何语法错误的迹象。 (更糟糕的是,它插入的令牌是EOF ,所以文件的其余部分甚至都没有被解析。)

我确定我可以解决这个问题,例如,向解析器添加类似isValid字段的内容并覆盖方法并添加操作,以便在解析结束时,如果出现任何错误,它会抛出exception。 但有更好的方法吗? 我无法想象我想要做的事情在ANTLR用户中是不寻常的。

… [O]它已完成解析我想得到一个exception或某种指示输入在语法上无效; 这样我可以停止编译……

解析后,您可以在词法分析器和解析器上调用getNumberOfSyntaxErrors ,以确定是否存在ANTLR隐蔽地容纳的错误。 这显然没有告诉你这些错误是什么,但我认为这些方法解决了“一旦完成解析…停止编译”问题的一部分。

最终的ANTLR参考提供了一种在检测到语法错误时立即停止解析的技术:覆盖不匹配和recoverFromMismatchedSet方法以抛出RecognitionExceptions,并添加@rulecatch操作来执行相同操作。

我不认为您提到了您正在使用的ANTLR版本,但是用于方法recoverFromMismatchedSet的ANTLR v3.4代码中的recoverFromMismatchedSet说它“当前未使用”并且Eclipse“全局使用”扫描没有找到调用者。 无论是在这里还是那里都没有你的主要问题,但我想提一下它的记录。 它可能是覆盖您的版本的正确方法。

如果缺少必要的令牌……,[被覆盖的代码]会像预期的那样抛出exception,但是如果添加了一个无关的令牌,ANTLR会插入它认为属于那里的令牌并继续它的快乐方式……

方法recoverFromMismatchedToken通过分别委派给mismatchIsMissingTokenmismatchIsUnwantedToken方法recoverFromMismatchedToken测试可恢复的缺失和无关的令牌。 如果适当的方法确定插入或删除将解决问题,则recoverFromMismatchedToken进行适当的更正。 如果确定没有操作解决了不匹配的令牌问题,则recoverFromMismatchedToken会抛出MismatchedTokenException

如果发生恢复操作,则调用reportError ,该调用displayRecognitionError带有详细信息调用displayRecognitionError

这适用于ANTLR v3.4以及可能的早期版本。

这为您提供了至少两个选项:

  • 覆盖recoverFromMismatchedToken并处理细粒度级别的错误。 从这里,您可以将调用委托给超级实现,滚动自己的恢复代码,或者通过exception进行挽救。 无论如何,您的代码都将被调用,因此将意识到发生了不匹配错误,可恢复或以其他方式。 此选项可能等同于覆盖recoverFromMismatchedSet

  • 覆盖displayRecognitionError并在课程级别处理错误。 方法reportError做了一些状态杂耍,所以除非覆盖实现调用超级实现,否则我不建议覆盖它。 方法displayRecognitionError似乎是displayRecognitionError -token调用链中的最后一个调用之一,因此它是一个合理的位置来确定是否继续。 我希望它有一个名字,表明这是一个合理的地方,但哦,好吧。 这是一个演示此选项的答案 。

我倾向于重写displayRecognitionError因为它足够容易地提供错误消息文本,因为我知道它只会在令牌恢复操作和所需的状态杂耍之后被调用 – 我的解析器不需要弄清楚如何为自己恢复。 这与getNumberOfSyntaxErrors相结合似乎为您提供了您正在寻找的选项,假设您正在使用相关版本的ANTLR并且我完全理解您的问题。