ANTLR Error Handle

In this article, I will show you how ANTLR handles errors by default and how to write your own ANTLR error listener, and all the code involved is in this repo.

ANTLR Default Error Behavior

ANTLR has great error reporting and error recovery abilities. By default, it would only print parse error messages to console, not exceptions, and it would recover from execution. Let's use code to understand what this means.

Based on the example from ANTLR Quick Start, we omit the ")" in expression 9 - 2 * (1 + 2), and see what happens:

@Test
void errorTest1() {
    String expression = "9 - 2 * (1 + 2";
    Integer value = evaluate(expression);
    assertEquals(3, value);
}

private static Integer evaluate(String expression) {
    CharStream charStream = CharStreams.fromString(expression);
    ExpressionLexer lexer = new ExpressionLexer(charStream);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    ExpressionParser parser = new ExpressionParser(tokens);
    EvalVisitor visitor = new EvalVisitor();
    return visitor.visit(parser.expr());
}

Output:
antlr-error-msg

So ANTLR only print error message, but the calculation continues, it even gets the right result: 3.

Add Custom Error Listener

By implementing the interface ANTLRErrorListener, we can define our own way to handle errors;

public class ErrorListener extends BaseErrorListener {
    public static final ErrorListener INSTANCE = new ErrorListener();

    public ErrorListener() {
        super();
    }

    @Override
    public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
        String errorMsg = String.format("line %s:%s at %s: %s", line, charPositionInLine, offendingSymbol, msg);
        throw new RuntimeException(errorMsg, e);
    }
}

Now let's test if it is working:

    @Test
    void errorTest2() {
        String expression = "9 - 2 * (1 + 2";
        CharStream charStream = CharStreams.fromString(expression);
        ExpressionLexer lexer = new ExpressionLexer(charStream);
        lexer.removeErrorListeners();
        lexer.addErrorListener(ErrorListener.INSTANCE);

        CommonTokenStream tokens = new CommonTokenStream(lexer);
        ExpressionParser parser = new ExpressionParser(tokens);
        parser.removeErrorListeners();
        parser.addErrorListener(ErrorListener.INSTANCE);

        EvalVisitor visitor = new EvalVisitor();
        assertThrows(RuntimeException.class, () -> visitor.visit(parser.expr()));
    }

The custom error listening is working, now ANTLR would recover from errors, it throws exception.