У меня есть грамматика 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
С одной стороны, я чувствую, что я уже слишком много делаю. Глядя на то, сколько кода я написал для того, что должно быть простой и общей задачей, я не могу не задаться вопросом, не хватает ли я более простого решения. С другой стороны, даже этого недостаточно, по двум причинам. Во-первых, хотя мне удалось получить сообщение об ошибке lexer, они все равно не мешают продолжению анализатора на оставшемся потоке. Это свидетельствует строка Parsed, but для ввода axb. Во-вторых, я все еще остаюсь с ошибками, которые не сообщаются в прослушиватель ошибок, о чем свидетельствует трассировка стека.
Если я не устанавливаю 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'}
Есть ли способ получить подобные сообщения об ошибках, но все же залог при ошибке? Я могу видеть из источников, что сообщение extraneous input действительно генерируется DefaultErrorStrategy, по-видимому, после того, как оно разработало вопрос об устранении проблемы. Должен ли я позволить этому сделать это, а затем выручить, то есть написать свой собственный вариант BailErrorStrategy, который вызывает супер перед бросанием?
