在解析错误时中止并显示有用的消息

Posted

技术标签:

【中文标题】在解析错误时中止并显示有用的消息【英文标题】:Abort on parse error with useful message 【发布时间】:2016-06-25 18:57:16 【问题描述】:

我有一个 ANTLR 4 语法,并以此构建了一个词法分析器和解析器。现在我试图实例化该解析器,使其解析直到遇到错误。如果遇到错误,则不应继续解析,但应提供有关问题的有用信息;理想情况下是机器可读的位置和人类可读的消息。

这是我目前拥有的:

grammar Toy;

@parser::members 

    public static void main(String[] args) 
        for (String arg: args)
            System.out.println(arg + " => " + parse(arg));
    

    public static String parse(String code) 
        ErrorListener errorListener = new ErrorListener();
        CharStream cstream = new ANTLRInputStream(code);
        ToyLexer lexer = new ToyLexer(cstream);
        lexer.removeErrorListeners();
        lexer.addErrorListener(errorListener);
        TokenStream tstream = new CommonTokenStream(lexer);
        ToyParser parser = new ToyParser(tstream);
        parser.removeErrorListeners();
        parser.addErrorListener(errorListener);
        parser.setErrorHandler(new BailErrorStrategy());
        try 
            String res = parser.top().str;
            if (errorListener.message != null)
                return "Parsed, but " + errorListener.toString();
            return res;
         catch (ParseCancellationException e) 
            if (errorListener.message != null)
                return "Failed, because " + errorListener.toString();
            throw e;
        
    

    static class ErrorListener extends BaseErrorListener 

        String message = null;
        int start = -2, stop = -2, line = -2;

        @Override
        public void syntaxError(Recognizer<?, ?> recognizer,
                                Object offendingSymbol,
                                int line,
                                int charPositionInLine,
                                String msg,
                                RecognitionException e) 
            if (message != null) return;
            if (offendingSymbol instanceof Token) 
                Token t = (Token) offendingSymbol;
                start = t.getStartIndex();
                stop = t.getStopIndex();
             else if (recognizer instanceof ToyLexer) 
                ToyLexer lexer = (ToyLexer)recognizer;
                start = lexer._tokenStartCharIndex;
                stop = lexer._input.index();
            
            this.line = line;
            message = msg;
        

        @Override public String toString() 
            return start + "-" + stop + " l." + line + ": " + message;
        
    



top returns [String str]: e* EOF $str = "All went well.";;
e: 'a' 'b' | 'a' 'c' e;

将此保存到Toy.g,然后尝试以下命令:

> java -jar antlr-4.5.2-complete.jar Toy.g
> javac -cp antlr-4.5.2-complete.jar Toy*.java
> java -cp .:tools/antlr-4.5.2-complete.jar ToyParser ab acab acc axb abc
ab => All went well.
acab => All went well.
acc => Failed, because 2-2 l.1: no viable alternative at input 'c'
axb => Parsed, but 1-1 l.1: token recognition error at: 'x'
Exception in thread "main" org.antlr.v4.runtime.misc.ParseCancellationException
    at org.antlr.v4.runtime.BailErrorStrategy.recoverInline(BailErrorStrategy.java:90)
    at org.antlr.v4.runtime.Parser.match(Parser.java:229)
    at ToyParser.top(ToyParser.java:187)
    at ToyParser.parse(ToyParser.java:95)
    at ToyParser.main(ToyParser.java:80)
Caused by: org.antlr.v4.runtime.InputMismatchException
    at org.antlr.v4.runtime.BailErrorStrategy.recoverInline(BailErrorStrategy.java:85)
    ... 4 more

一方面,我觉得我已经做得太多了。看看我为应该是一项简单而常见的任务编写了多少代码,我不禁想知道我是否缺少一些更简单的解决方案。另一方面,即使这样似乎还不够,有两个原因。首先,虽然我设法报告了词法分析器错误,但它们仍然不会阻止解析器继续处理剩余的流。这是输入axbParsed, but 字符串的证据。其次,我仍然有错误没有报告给错误侦听器,正如堆栈跟踪所证明的那样。

如果我不安装BailErrorStrategy,我会得到更多有用的输出:

acc => Parsed, but 2-2 l.1: mismatched input 'c' expecting 'a'
axb => Parsed, but 1-1 l.1: token recognition error at: 'x'
abc => Parsed, but 2-2 l.1: extraneous input 'c' expecting <EOF>, 'a'

有没有什么办法可以得到这种错误信息,但仍然会因错误而保释?我可以see from the sourcesextraneous input 消息确实是由DefaultErrorStrategy 生成的,显然是在它解决了如何解决问题之后。我是否应该让它这样做并然后退出,即编写我自己的 BailErrorStrategy 变体,在抛出之前调用 super ?

【问题讨论】:

【参考方案1】:

一种方法可能是修改错误侦听器而不是错误策略。可以将默认策略与以下侦听器一起使用:

class ErrorListener extends BaseErrorListener 
    @Override
    public void syntaxError(Recognizer<?, ?> recognizer,
                            Object offendingSymbol,
                            int line,
                            int charPositionInLine,
                            String msg,
                            RecognitionException e) 
        throw new ParseException(msg, e, line);
    


class ParseException extends RuntimeException 
    int line;
    public ParseException(String message, Throwable cause, int line) 
        super(message, cause);
        this.line = line;
    

通过这种方式,错误会按原样进行格式化以用于输出,但要报告的第一个错误将通过抛出指定的异常导致编译中止。由于这是一个未经检查的异常,因此您必须确保捕获它,因为如果您忘记这样做,编译器不会警告您。

关于机器可读的位置,如果除了行号之外,您还希望输入的违规部分的源文本偏移量,这样的代码似乎可以在 syntaxError 方法中工作:

        int start = 0, stop = -1;
        if (offendingSymbol instanceof Token) 
            Token t = (Token) offendingSymbol;
            start = t.getStartIndex();
            stop = t.getStopIndex();
         else if (recognizer instanceof Lexer) 
            Lexer lexer = (Lexer)recognizer;
            start = lexer._tokenStartCharIndex;
            stop = lexer._input.index();
        

【讨论】:

【参考方案2】:

在同样的情况下,我最终扩展了DefaultErrorStrategy 并覆盖了report* 方法。这很简单(你也可以使用ANTLRErrorStrategy

Here你可以找到一个快速失败策略的例子。我认为在您的情况下,您可以以相同的方式收集所有错误并构建详细报告。

【讨论】:

谢谢!这意味着您必须自己完成所有错误消息的格式设置,不是吗?看着the DefaultErrorStrategy sources,似乎没有办法在错误格式和通知错误报告者的调用之间取得进展。 @MvG 不幸的是。但这是我遇到的唯一可靠的解决方案

以上是关于在解析错误时中止并显示有用的消息的主要内容,如果未能解决你的问题,请参考以下文章

在不解析错误消息的情况下获取有关完整性约束违规错误的信息

解析java结果137

带有所需子解析器的 Argparse

c#当sql视图不存在时中断

如何解析 MySQL 错误

如何解析 MySQL 错误