处理ANTLR4中的错误

Bra*_*ace 70 java error-handling antlr4

解析器不知道该怎么做的默认行为是将消息打印到终端,如:

第1:23行丢失DECIMAL at'}'

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

我已经尝试过使用了BailErrorStrategy,但这会抛出ParseCancellationException没有消息(由a引起InputMismatchException,也没有消息).

有没有办法让我通过异常报告错误,同时保留邮件中的有用信息?


这就是我真正想要的 - 我通常在规则中使用动作来构建对象:

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); }
    ;
Run Code Online (Sandbox Code Playgroud)

然后当我调用解析器时,我做了类似这样的事情:

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;
}
Run Code Online (Sandbox Code Playgroud)

我真正想要的只是

  • 用于在dataspec()无法解析输入时调用异常(理想情况下是已检查的异常)的调用
  • 为该异常提供有用的消息,并提供对发现问题的行号和位置的访问权限

然后我会让这个异常泡出callstack,最适合向用户提供有用的消息 - 就像我处理丢弃的网络连接,读取损坏的文件等一样.

我确实看到ANTLR4中的行为现在被认为是"高级"的,所以也许我会以一种奇怪的方式处理事情,但我还没有研究过这样做的"非高级"方法是什么一直很好地满足我们的需求.

Mou*_*gip 80

由于我对现有的两个答案进行了一些争论,我想分享我最终得到的解决方案.

首先,我创建了自己的ErrorListener版本,如Sam Harwell建议:

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);
      }
}
Run Code Online (Sandbox Code Playgroud)

注意使用a ParseCancellationException而不是a,RecognitionException因为DefaultErrorStrategy会捕获后者,它永远不会到达你自己的代码.

创建像Brad Mace建议的全新ErrorStrategy 是没有必要的,因为默认情况下DefaultErrorStrategy会产生非常好的错误消息.

然后我在解析函数中使用自定义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);
}
Run Code Online (Sandbox Code Playgroud)

(有关具体功能的更多信息MyParseRules,请参见此处.)

这将为您提供与默认情况下打印到控制台的错误消息相同的错误消息,仅以正确的异常形式提供.

  • 我试过这个,我确认它运作良好.我认为这是3个提议的解决方案中最简单的. (3认同)

Sam*_*ell 46

使用DefaultErrorStrategy或时BailErrorStrategy,将ParserRuleContext.exception在生成错误的结果解析树中为任何解析树节点设置字段.该字段的文档为(对于不想单击额外链接的人):

强制此规则返回的异常.如果规则成功完成,则为null.

编辑:如果使用DefaultErrorStrategy,解析上下文异常将不会一直传播到调用代码,因此您将能够exception直接检查该字段.如果您使用BailErrorStrategy,ParseCancellationException它会引发RecognitionException如果你打电话getCause().

if (pce.getCause() instanceof RecognitionException) {
    RecognitionException re = (RecognitionException)pce.getCause();
    ParserRuleContext context = (ParserRuleContext)re.getCtx();
}
Run Code Online (Sandbox Code Playgroud)

编辑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);
    }
}
Run Code Online (Sandbox Code Playgroud)

使用此类,您可以使用以下内容来使用它.

lexer.removeErrorListeners();
lexer.addErrorListener(DescriptiveErrorListener.INSTANCE);
parser.removeErrorListeners();
parser.addErrorListener(DescriptiveErrorListener.INSTANCE);
Run Code Online (Sandbox Code Playgroud)

,我使用以识别呈现语法非SLL歧义错误听者的更复杂的例子是SummarizingDiagnosticErrorListener在类TestPerformance.


Bra*_*ace 9

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

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

这引发了有用的信息异常,而问题的路线和位置可以从任一来得到offending令牌,或者如果没有设置,从current使用的令牌((Parser) re.getRecognizer()).getCurrentToken()RecognitionException.

我对这是如何工作非常满意,虽然有六种reportX方法可以覆盖让我觉得有更好的方法.