ANTLR is a powerful parser generator for reading, processing, executing, or translating structured text or binary files
ANTLR4 provides a comprehensive error handling system with customizable recovery strategies, detailed error reporting, and multiple approaches to handling syntax errors during parsing.
Interface for receiving and handling syntax errors.
/**
* Interface for handling syntax errors during parsing
*/
public interface ANTLRErrorListener {
/**
* Called when parser encounters a syntax error
* @param recognizer the parser or lexer that encountered the error
* @param offendingSymbol the problematic token or character
* @param line the line number where error occurred
* @param charPositionInLine the character position in line
* @param msg error message
* @param e recognition exception (may be null)
*/
void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line,
int charPositionInLine,
String msg,
RecognitionException e);
}
/**
* Base error listener providing empty implementations
*/
public class BaseErrorListener implements ANTLRErrorListener {
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line,
int charPositionInLine,
String msg,
RecognitionException e) {
// Empty implementation - override in subclasses
}
}
/**
* Console error listener that prints errors to stderr
*/
public class ConsoleErrorListener extends BaseErrorListener {
/** Singleton instance */
public static final ConsoleErrorListener INSTANCE = new ConsoleErrorListener();
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line,
int charPositionInLine,
String msg,
RecognitionException e) {
System.err.println("line " + line + ":" + charPositionInLine + " " + msg);
}
}Usage Example:
import org.antlr.v4.runtime.*;
// Custom error listener
class MyErrorListener extends BaseErrorListener {
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line,
int charPositionInLine,
String msg,
RecognitionException e) {
System.err.println("Syntax error at " + line + ":" + charPositionInLine);
System.err.println("Message: " + msg);
if (offendingSymbol instanceof Token) {
Token token = (Token) offendingSymbol;
System.err.println("Problematic token: '" + token.getText() + "'");
}
}
}
// Add custom error listener to parser
MyParser parser = new MyParser(tokens);
parser.removeErrorListeners(); // Remove default console listener
parser.addErrorListener(new MyErrorListener());Interfaces and implementations for error recovery during parsing.
/**
* Interface for error recovery strategies
*/
public interface ANTLRErrorStrategy {
/** Reset strategy state */
void reset(Parser recognizer);
/** Recover from token mismatch */
Token recoverInline(Parser recognizer) throws RecognitionException;
/** Recover from recognition exception */
void recover(Parser recognizer, RecognitionException e) throws RecognitionException;
/** Synchronize parser after error */
void sync(Parser recognizer) throws RecognitionException;
/** Check if in error recovery mode */
boolean inErrorRecoveryMode(Parser recognizer);
/** Report error match */
void reportMatch(Parser recognizer);
/** Report error */
void reportError(Parser recognizer, RecognitionException e);
}
/**
* Default error recovery strategy with sync and single token insertion/deletion
*/
public class DefaultErrorStrategy implements ANTLRErrorStrategy {
/** Whether in error recovery mode */
protected boolean errorRecoveryMode = false;
/** Last error index to avoid infinite loops */
protected int lastErrorIndex = -1;
/** IntervalSet of tokens for synchronization */
protected IntervalSet lastErrorStates;
@Override
public void reset(Parser recognizer) {
endErrorCondition(recognizer);
}
@Override
public Token recoverInline(Parser recognizer) throws RecognitionException {
// Try single token deletion
Token matchedSymbol = singleTokenDeletion(recognizer);
if (matchedSymbol != null) {
recognizer.consume();
return matchedSymbol;
}
// Try single token insertion
if (singleTokenInsertion(recognizer)) {
return getMissingSymbol(recognizer);
}
// Fall back to more complex recovery
throw new InputMismatchException(recognizer);
}
@Override
public void recover(Parser recognizer, RecognitionException e) throws RecognitionException {
if (lastErrorIndex == recognizer.getInputStream().index() &&
lastErrorStates != null &&
lastErrorStates.contains(recognizer.getState())) {
// Consume at least one token to avoid infinite loops
recognizer.consume();
}
lastErrorIndex = recognizer.getInputStream().index();
if (lastErrorStates == null) {
lastErrorStates = new IntervalSet();
}
lastErrorStates.add(recognizer.getState());
IntervalSet followSet = getErrorRecoverySet(recognizer);
consumeUntil(recognizer, followSet);
}
@Override
public void sync(Parser recognizer) throws RecognitionException {
ATNState s = recognizer.getInterpreter().atn.states.get(recognizer.getState());
if (inErrorRecoveryMode(recognizer)) {
return;
}
TokenStream tokens = recognizer.getInputStream();
int la = tokens.LA(1);
if (recognizer.getATN().nextTokens(s).contains(la)) {
return;
}
switch (s.getStateType()) {
case ATNState.BLOCK_START:
case ATNState.STAR_BLOCK_START:
case ATNState.PLUS_BLOCK_START:
case ATNState.STAR_LOOP_ENTRY:
if (singleTokenDeletion(recognizer) != null) {
return;
}
throw new InputMismatchException(recognizer);
default:
// Nothing to sync on
break;
}
}
/** End error condition and exit recovery mode */
protected void endErrorCondition(Parser recognizer) {
errorRecoveryMode = false;
lastErrorStates = null;
lastErrorIndex = -1;
}
/** Enter error recovery mode */
protected void beginErrorCondition(Parser recognizer) {
errorRecoveryMode = true;
}
/** Get missing symbol for recovery */
protected Token getMissingSymbol(Parser recognizer) {
Token currentSymbol = recognizer.getCurrentToken();
IntervalSet expecting = getExpectedTokens(recognizer);
int expectedTokenType = expecting.getMinElement();
String tokenText;
if (expectedTokenType == Token.EOF) {
tokenText = "<missing EOF>";
} else {
tokenText = "<missing " + recognizer.getVocabulary().getDisplayName(expectedTokenType) + ">";
}
Token current = currentSymbol;
Token lookback = recognizer.getInputStream().LT(-1);
if (current.getType() == Token.EOF && lookback != null) {
current = lookback;
}
return recognizer.getTokenFactory().create(
new Pair<>(current.getTokenSource(), current.getInputStream()),
expectedTokenType, tokenText,
Token.DEFAULT_CHANNEL,
-1, -1,
current.getLine(), current.getCharPositionInLine()
);
}
}
/**
* Error strategy that bails out on first error (fail-fast)
*/
public class BailErrorStrategy extends DefaultErrorStrategy {
@Override
public void recover(Parser recognizer, RecognitionException e) throws RecognitionException {
// Don't recover; rethrow exception
for (ParserRuleContext context = recognizer.getContext();
context != null;
context = context.getParent()) {
context.exception = e;
}
throw new ParseCancellationException(e);
}
@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);
}
@Override
public void sync(Parser recognizer) throws RecognitionException {
// Don't sync; let exceptions bubble up
}
}Usage Example:
import org.antlr.v4.runtime.*;
// Use default error recovery
MyParser parser = new MyParser(tokens);
// Default strategy is already set
// Use bail error strategy (fail-fast)
MyParser parser2 = new MyParser(tokens);
parser2.setErrorHandler(new BailErrorStrategy());
try {
ParseTree tree = parser2.expr();
} catch (ParseCancellationException e) {
System.err.println("Parse failed: " + e.getCause().getMessage());
}Exception hierarchy for different types of parsing errors.
/**
* Base class for all recognition exceptions
*/
public class RecognitionException extends RuntimeException {
/** Parser or lexer that threw the exception */
private final Recognizer<?, ?> recognizer;
/** Rule context where exception occurred */
private final RuleContext ctx;
/** Input stream where exception occurred */
private final IntStream input;
/** Offending token or character */
private final Object offendingToken;
/** Position information */
private final int offendingState;
public RecognitionException(Recognizer<?, ?> recognizer,
IntStream input,
ParserRuleContext ctx) {
this.recognizer = recognizer;
this.input = input;
this.ctx = ctx;
if (recognizer != null) {
this.offendingState = recognizer.getState();
} else {
this.offendingState = -1;
}
}
/** Get expected tokens at point of error */
public IntervalSet getExpectedTokens() {
if (recognizer != null) {
return recognizer.getATN().getExpectedTokens(offendingState, ctx);
}
return IntervalSet.EMPTY_SET;
}
/** Get rule context */
public RuleContext getCtx() {
return ctx;
}
/** Get input stream */
public IntStream getInputStream() {
return input;
}
/** Get offending token */
public Object getOffendingToken() {
return offendingToken;
}
/** Get recognizer */
public Recognizer<?, ?> getRecognizer() {
return recognizer;
}
}
/**
* Exception for token mismatch errors
*/
public class InputMismatchException extends RecognitionException {
public InputMismatchException(Parser recognizer) {
super(recognizer, recognizer.getInputStream(), recognizer.getContext());
setOffendingToken(recognizer.getCurrentToken());
}
public InputMismatchException(Parser recognizer, int state, ParserRuleContext ctx) {
super(recognizer, recognizer.getInputStream(), ctx);
setOffendingState(state);
setOffendingToken(recognizer.getCurrentToken());
}
}
/**
* Exception when lexer cannot match input
*/
public class LexerNoViableAltException extends RecognitionException {
/** Start index of problematic text */
private final int startIndex;
/** Dead-end configurations */
private final ATNConfigSet deadEndConfigs;
public LexerNoViableAltException(Lexer lexer,
CharStream input,
int startIndex,
ATNConfigSet deadEndConfigs) {
super(lexer, input, null);
this.startIndex = startIndex;
this.deadEndConfigs = deadEndConfigs;
}
public int getStartIndex() {
return startIndex;
}
public ATNConfigSet getDeadEndConfigs() {
return deadEndConfigs;
}
}
/**
* Exception when parser cannot choose alternative
*/
public class NoViableAltException extends RecognitionException {
/** Dead-end configurations */
private final ATNConfigSet deadEndConfigs;
/** Start token */
private final Token startToken;
public NoViableAltException(Parser recognizer) {
this(recognizer, recognizer.getInputStream(), recognizer.getCurrentToken(),
recognizer.getCurrentToken(), null, recognizer.getContext());
}
public NoViableAltException(Parser recognizer,
TokenStream input,
Token startToken,
Token offendingToken,
ATNConfigSet deadEndConfigs,
ParserRuleContext ctx) {
super(recognizer, input, ctx);
this.deadEndConfigs = deadEndConfigs;
this.startToken = startToken;
setOffendingToken(offendingToken);
}
public Token getStartToken() {
return startToken;
}
public ATNConfigSet getDeadEndConfigs() {
return deadEndConfigs;
}
}
/**
* Exception when semantic predicate fails
*/
public class FailedPredicateException extends RecognitionException {
/** Rule index */
private final int ruleIndex;
/** Predicate index */
private final int predicateIndex;
/** Predicate text */
private final String predicate;
public FailedPredicateException(Parser recognizer) {
this(recognizer, null);
}
public FailedPredicateException(Parser recognizer, String predicate) {
this(recognizer, predicate, null);
}
public FailedPredicateException(Parser recognizer, String predicate, String message) {
super(formatMessage(predicate, message), recognizer,
recognizer.getInputStream(), recognizer.getContext());
ATNState s = recognizer.getInterpreter().atn.states.get(recognizer.getState());
AbstractPredicateTransition trans = (AbstractPredicateTransition) s.transition(0);
if (trans instanceof PredicateTransition) {
this.ruleIndex = ((PredicateTransition) trans).ruleIndex;
this.predicateIndex = ((PredicateTransition) trans).predIndex;
} else {
this.ruleIndex = 0;
this.predicateIndex = 0;
}
this.predicate = predicate;
setOffendingToken(recognizer.getCurrentToken());
}
public int getRuleIndex() {
return ruleIndex;
}
public int getPredicateIndex() {
return predicateIndex;
}
public String getPredicate() {
return predicate;
}
}Advanced error listener with detailed diagnostic information.
/**
* Error listener that provides detailed diagnostic information
*/
public class DiagnosticErrorListener extends BaseErrorListener {
/** Whether to report ambiguities */
protected final boolean exactOnly;
public DiagnosticErrorListener() {
this(true);
}
public DiagnosticErrorListener(boolean exactOnly) {
this.exactOnly = exactOnly;
}
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line,
int charPositionInLine,
String msg,
RecognitionException e) {
System.err.println("Syntax error at " + line + ":" + charPositionInLine + ": " + msg);
if (e != null) {
System.err.println("Exception type: " + e.getClass().getSimpleName());
if (e instanceof InputMismatchException) {
IntervalSet expected = e.getExpectedTokens();
System.err.println("Expected: " + expected.toString(recognizer.getVocabulary()));
} else if (e instanceof NoViableAltException) {
NoViableAltException nvae = (NoViableAltException) e;
System.err.println("No viable alternative");
System.err.println("Start token: " + nvae.getStartToken().getText());
} else if (e instanceof FailedPredicateException) {
FailedPredicateException fpe = (FailedPredicateException) e;
System.err.println("Failed predicate: " + fpe.getPredicate());
}
}
}
/** Report ambiguity in parse */
public void reportAmbiguity(Parser recognizer,
DFA dfa,
int startIndex,
int stopIndex,
boolean exact,
BitSet ambigAlts,
ATNConfigSet configs) {
if (exactOnly && !exact) {
return;
}
System.err.println("Ambiguity from " + startIndex + " to " + stopIndex);
System.err.println("Alternatives: " + ambigAlts);
}
/** Report attempt to use full context */
public void reportAttemptingFullContext(Parser recognizer,
DFA dfa,
int startIndex,
int stopIndex,
BitSet conflictingAlts,
ATNConfigSet configs) {
System.err.println("Attempting full context from " + startIndex + " to " + stopIndex);
}
/** Report context sensitivity */
public void reportContextSensitivity(Parser recognizer,
DFA dfa,
int startIndex,
int stopIndex,
int prediction,
ATNConfigSet configs) {
System.err.println("Context sensitivity from " + startIndex + " to " + stopIndex);
}
}import org.antlr.v4.runtime.*;
// Simple error collection
List<String> errors = new ArrayList<>();
MyParser parser = new MyParser(tokens);
parser.removeErrorListeners();
parser.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line,
int charPositionInLine,
String msg,
RecognitionException e) {
errors.add("Line " + line + ":" + charPositionInLine + " - " + msg);
}
});
ParseTree tree = parser.expr();
if (!errors.isEmpty()) {
System.err.println("Parse errors:");
for (String error : errors) {
System.err.println(" " + error);
}
}import org.antlr.v4.runtime.*;
// Custom error strategy with specific recovery
class MyErrorStrategy extends DefaultErrorStrategy {
@Override
protected void reportNoViableAlternative(Parser recognizer, NoViableAltException e) {
TokenStream tokens = recognizer.getInputStream();
String input;
if (tokens != null) {
if (e.getStartToken().getType() == Token.EOF) {
input = "<EOF>";
} else {
input = tokens.getText(e.getStartToken(), e.getOffendingToken());
}
} else {
input = "<unknown input>";
}
String msg = "no viable alternative at input " + escapeWSAndQuote(input);
recognizer.notifyErrorListeners(e.getOffendingToken(), msg, e);
}
@Override
protected void reportInputMismatch(Parser recognizer, InputMismatchException e) {
String msg = "mismatched input " + getTokenErrorDisplay(e.getOffendingToken()) +
" expecting " + e.getExpectedTokens().toString(recognizer.getVocabulary());
recognizer.notifyErrorListeners(e.getOffendingToken(), msg, e);
}
}
// Use custom strategy
MyParser parser = new MyParser(tokens);
parser.setErrorHandler(new MyErrorStrategy());import org.antlr.v4.runtime.*;
// Comprehensive error information
class DetailedErrorListener extends BaseErrorListener {
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line,
int charPositionInLine,
String msg,
RecognitionException e) {
System.err.println("=== Syntax Error ===");
System.err.println("Location: " + line + ":" + charPositionInLine);
System.err.println("Message: " + msg);
if (offendingSymbol instanceof Token) {
Token token = (Token) offendingSymbol;
System.err.println("Offending token: '" + token.getText() +
"' (type: " + token.getType() + ")");
}
if (e != null) {
System.err.println("Exception: " + e.getClass().getSimpleName());
if (recognizer instanceof Parser) {
Parser parser = (Parser) recognizer;
System.err.println("Rule stack: " + parser.getRuleInvocationStack());
if (e.getExpectedTokens() != null) {
System.err.println("Expected tokens: " +
e.getExpectedTokens().toString(parser.getVocabulary()));
}
}
}
System.err.println("==================");
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-antlr--antlr4-master