处理ANTLR4中的错误

parsing器不知道该怎么做的默认行为是将消息打印到terminal,如:

行1:23在'}'缺lessDECIMAL

这是一个好消息,但在错误的地方。 我宁愿接受这个例外。

我试过使用BailErrorStrategy ,但是这会抛出一个ParseCancellationException没有消息(由InputMismatchException引起,也没有消息)。

有没有办法让我能通过例外来报告错误,同时保留消息中的有用信息?


这就是我真正的追求 – 我通常使用规则中的动作来build立一个对象:

 dataspec returns [DataExtractor extractor] @init { DataExtractorBuilder builder = new DataExtractorBuilder(layout); } @after { $extractor = builder.create(); } : first=expr { builder.addAll($first.values); } (COMMA next=expr { builder.addAll($next.values); })* EOF ; expr returns [List<ValueExtractor> values] : a=atom { $values = Arrays.asList($a.val); } | fields=fieldrange { $values = values($fields.fields); } | '%' { $values = null; } | ASTERISK { $values = values(layout); } ; 

然后当我调用parsing器时,我做了这样的事情:

 public static DataExtractor create(String dataspec) { CharStream stream = new ANTLRInputStream(dataspec); DataSpecificationLexer lexer = new DataSpecificationLexer(stream); CommonTokenStream tokens = new CommonTokenStream(lexer); DataSpecificationParser parser = new DataSpecificationParser(tokens); return parser.dataspec().extractor; } 

我真正想要的是

  • dataspec()调用抛出一个exception(理想情况下是一个检查一个),当input不能被parsing
  • 为了这个例外,有一个有用的信息,并提供访问行号和发现问题的位置

然后,我会让这个exception向上调用callstack,以便向用户展示一个有用的消息 – 就像我处理丢弃的networking连接,阅读损坏的文件等一样。

在ANTLR4中,我确实已经看到这些行为被认为是“先进的”,所以也许我正在以一种奇怪的方式来讨论事情,但是我没有看到这样做的“非高级”方法是什么,一直在为我们的需求工作。

由于我已经与现有的两个答案进行了一些斗争,所以我想分享一下我最终的解决scheme。

首先我创build了我自己的一个像Sam Harwell的ErrorListener版本,build议:

 public class ThrowingErrorListener extends BaseErrorListener { public static final ThrowingErrorListener INSTANCE = new ThrowingErrorListener(); @Override public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) throws ParseCancellationException { throw new ParseCancellationException("line " + line + ":" + charPositionInLine + " " + msg); } } 

请注意使用ParseCancellationException而不是RecognitionException因为DefaultErrorStrategy会捕获后者,并且永远不会到达您自己的代码。

创build像Brad Mace这样的全新的ErrorStrategy是没有必要的,因为DefaultErrorStrategy默认会产生相当不错的错误信息。

然后我在parsing函数中使用自定义ErrorListener:

 public static String parse(String text) throws ParseCancellationException { MyLexer lexer = new MyLexer(new ANTLRInputStream(text)); lexer.removeErrorListeners(); lexer.addErrorListener(ThrowingErrorListener.INSTANCE); CommonTokenStream tokens = new CommonTokenStream(lexer); MyParser parser = new MyParser(tokens); parser.removeErrorListeners(); parser.addErrorListener(ThrowingErrorListener.INSTANCE); ParserRuleContext tree = parser.expr(); MyParseRules extractor = new MyParseRules(); return extractor.visit(tree); } 

(有关MyParseRules更多信息,请参阅此处 。)

这会给你相同的错误信息,默认打印到控制台,只有适当的例外。

当您使用DefaultErrorStrategyBailErrorStrategyParserRuleContext.exception字段将针对发生错误的结果分析树中的任何分析树节点进行设置。 此字段的文档(针对不想单击额外链接的人员):

强制此规则返回的exception。 如果规则成功完成,则为null

编辑:如果您使用DefaultErrorStrategy ,则分析上下文exception将不会传播到调用代码,因此您将能够直接检查exception字段。 如果使用BailErrorStrategy ,则抛出的ParseCancellationException将包含一个RecognitionException如果调用getCause()

 if (pce.getCause() instanceof RecognitionException) { RecognitionException re = (RecognitionException)pce.getCause(); ParserRuleContext context = (ParserRuleContext)re.getCtx(); } 

编辑2:根据你的其他答案,看起来你实际上并不需要一个例外,但你想要的是一个不同的方式来报告错误。 在这种情况下,你会对ANTLRErrorListener接口更感兴趣。 您想调用parser.removeErrorListeners()来删除写入控制台的默认侦听器,然后为您自己的特殊侦听器调用parser.addErrorListener(listener) 。 我经常使用下面的监听器作为起点,因为它包含了带有消息的源文件的名称。

 public class DescriptiveErrorListener extends BaseErrorListener { public static DescriptiveErrorListener INSTANCE = new DescriptiveErrorListener(); @Override public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) { if (!REPORT_SYNTAX_ERRORS) { return; } String sourceName = recognizer.getInputStream().getSourceName(); if (!sourceName.isEmpty()) { sourceName = String.format("%s:%d:%d: ", sourceName, line, charPositionInLine); } System.err.println(sourceName+"line "+line+":"+charPositionInLine+" "+msg); } } 

有了这个类,你可以使用以下来使用它。

 lexer.removeErrorListeners(); lexer.addErrorListener(DescriptiveErrorListener.INSTANCE); parser.removeErrorListeners(); parser.addErrorListener(DescriptiveErrorListener.INSTANCE); 

一个更复杂的错误侦听器例子,我用它来识别模糊语言非SLL是TestPerformanceSummarizingDiagnosticErrorListener类 。

到目前为止我所提出的基于扩展DefaultErrorStrategy并覆盖它的reportXXX方法(尽pipe完全有可能使事情比必要的更复杂):

 public class ExceptionErrorStrategy extends DefaultErrorStrategy { @Override public void recover(Parser recognizer, RecognitionException e) { throw e; } @Override public void reportInputMismatch(Parser recognizer, InputMismatchException e) throws RecognitionException { String msg = "mismatched input " + getTokenErrorDisplay(e.getOffendingToken()); msg += " expecting one of "+e.getExpectedTokens().toString(recognizer.getTokenNames()); RecognitionException ex = new RecognitionException(msg, recognizer, recognizer.getInputStream(), recognizer.getContext()); ex.initCause(e); throw ex; } @Override public void reportMissingToken(Parser recognizer) { beginErrorCondition(recognizer); Token t = recognizer.getCurrentToken(); IntervalSet expecting = getExpectedTokens(recognizer); String msg = "missing "+expecting.toString(recognizer.getTokenNames()) + " at " + getTokenErrorDisplay(t); throw new RecognitionException(msg, recognizer, recognizer.getInputStream(), recognizer.getContext()); } } 

这会抛出有用消息的exception,并且通过使用((Parser) re.getRecognizer()).getCurrentToken()可以从有问题的令牌或者如果没有设置,从current令牌获得问题的线和位置((Parser) re.getRecognizer()).getCurrentToken()关于RecognitionException

我很满意这是如何工作的,尽pipe有六种reportX方法可以覆盖,这让我觉得还有更好的办法。