What's the difference between ANTLR4's errorListener and errorHandler?
Asked Answered
L

1

7

I want to get the specific error message of ANTLR4's parser. And I found that there are two way to handle error: errorListener and errorHandler.

// set error handler
parser.removeErrorListeners();
parser.addErrorListener(new QueryErrorListener());

parser.setErrorHandler(new BailErrorStrategy());

But I'm confused about the difference between them.

I found that, errorListener can get the specific error message, but it can only print it or log it, can't throw a exception.

The implemention of errorListener as bellow:

public class QueryErrorListener extends BaseErrorListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(QueryDispatcher.class);


    @Override
    public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol,
                            int line, int charPositionInLine, String msg,
                            RecognitionException e)
    {
        List<String> stack = ((Parser)recognizer).getRuleInvocationStack(); Collections.reverse(stack);
        String errorMessage = "line "+line+":"+charPositionInLine+" at "+
                offendingSymbol+": "+msg;
        LOGGER.error("rule stack: "+stack);
        LOGGER.error(errorMessage);
        QueryParseErrorStrategy queryParseErrorStrategy = new QueryParseErrorStrategy();

    }
}

At the same time, the errorHandler can only throw a exception ParseCancellationException without any specific message.

public class BailErrorStrategy extends DefaultErrorStrategy {
    /** Instead of recovering from exception {@code e}, re-throw it wrapped
     *  in a {@link ParseCancellationException} so it is not caught by the
     *  rule function catches.  Use {@link Exception#getCause()} to get the
     *  original {@link RecognitionException}.
     */
    @Override
    public void recover(Parser recognizer, RecognitionException e) {
        for (ParserRuleContext context = recognizer.getContext(); context != null; context = context.getParent()) {
            context.exception = e;
        }

        throw new ParseCancellationException(e);
    }

    /** Make sure we don't attempt to recover inline; if the parser
     *  successfully recovers, it won't throw an exception.
     */
    @Override
    public Token recoverInline(Parser recognizer)
        throws RecognitionException
    {
        InputMismatchException e = new InputMismatchException(recognizer);
        for (ParserRuleContext context = recognizer.getContext(); context != null; context = context.getParent()) {
            context.exception = e;
        }

        throw new ParseCancellationException(e);
    }

    /** Make sure we don't attempt to recover from problems in subrules. */
    @Override
    public void sync(Parser recognizer) { }
}

I've try to find a solution, add a transfer method to get detail message from ParseCancellationException, as bellow.

I found that I can get some message from a Token object of RecognitionException, but I can only find the line/charPositionInLine/offendingSymbol message, I don't know where is the detail message, like "missing 'xxx', expect 'yyy'"

public class ANTLRExceptionTransfer {

    public static SemanticException transfer(RecognitionException re) {
        String errorMsg = "";
        Recognizer<?, ?> recognizer = re.getRecognizer();
        Token offendingSymbol = re.getOffendingToken();
        int line = offendingSymbol.getLine();
        int charPositionInLine = offendingSymbol.getCharPositionInLine();
        // ????????
        String msg = "";

        List<String> stack = ((Parser)recognizer).getRuleInvocationStack();
        Collections.reverse(stack);

        String errorMessage = "rule stack: "+stack;
        errorMessage = "\nline "+line+":"+charPositionInLine+" at "+
                offendingSymbol+": "+msg;
        return new SemanticException(errorMessage);


    }
}

Is it the right way to use errorHandler? How can I get a exception with specific error message?

Leschen answered 31/7, 2017 at 2:57 Comment(1)
I can get the basic information from RecognitionException now, but still don't know how to build readable message like errorListener did [line 1:4632 at [@1289,4632:4632='}',<46>,1:4632]: extraneous input '}' expecting ','] . Is there any available method provided by ANTLR4?Leschen
S
10

I find the setErrorHandler name a bit confusing. It should be consistent with what you can set there. It's for setting an error strategy (which is of course also some kind of handling...).

Both error listener and error strategy are means for the application to deal with parse errors. The error listener gets called for each encountered error and allows the application to collect them (e.g. to show them in a GUI). You'll get a pre-generated error message or can create an own one from the passed in parameters.

The error strategy is a class that determines how to continue after an error was found. The default stategy is to try to sync to the input stream and continue with parsing. Sometimes however you want the parser to stop immediately and avoid lengthy operations after an error was found. This so-called bail-out strategy is another class in ANTLR4, usually used for SLL parsing. See one of my projects for how that's used.

The thrown ParseCancellationException in the bail-out error strategy is an exception without any additional info. It's not meant for error handling (in the sense of sending it to the application/user, you have the error handler for that), but instead to throw an exception that's not one of the usual parser exceptions, in order to bypass all error catching and find a way out of the ongoing parse run as quick as possible. You have to catch this exception in your own code or it will bubble up to the root context of your application (and might cause the application to quit, depending on the target language).

Selden answered 31/7, 2017 at 7:8 Comment(3)
Can I get specific diagnose information from ParseCancellationException? Or is it possible to send errorlistener's error message to errorHandler? And I found that, if I set errorHandler and errorListener at the same time, sometimes errorListener doesn't work, but sometimes it works.Do they have some conflicts ?Leschen
Well, since the error strategy can cancel the parse run at any time (e.g. the bail out strategy) it could happen that not all errors are reported via the error listener. The ParseCancellationException has no info and serves only one goal: what I explained in my answer (cancel the current parse run).Selden
The RecognitionException contains all the information errorListener can get. RecognitionException has a member 'recognizer', we can get error stack from it, and another member 'offendingToken', we can get line(error line) & charPositionInLine (error position) & itself (error msg). But we seems need to transfer the error msg ourselves, because the error message is like '[@1,1:12='"selectmust"',<72>,1:1]', we need tranfer it to a more readable format. Thank you for answering my question~Leschen

© 2022 - 2024 — McMap. All rights reserved.