Robust error reporting and recovery strategies for handling syntax errors and providing meaningful diagnostics during parsing. ANTLR4 provides comprehensive error handling through exception hierarchies, error strategies, and customizable error listeners.
Core exception types for recognition errors.
/**
* Parameters for creating RecognitionException
*/
interface ExceptionParams {
/** Error message */
message: string;
/** Recognizer that encountered the error (optional) */
recognizer?: Recognizer<never>;
/** Input stream where error occurred (optional) */
input?: CharStream | TokenStream;
/** Parser rule context where error occurred (optional) */
ctx?: ParserRuleContext;
}
/**
* Base exception class for all recognition errors
*/
class RecognitionException extends Error {
/** Rule context where error occurred */
ctx: RuleContext;
/** Token that caused the error (null for lexer errors) */
offendingToken: Token | null;
/**
* Create recognition exception
* @param params - Exception parameters
*/
constructor(params: ExceptionParams);
}Specialized exceptions for different types of parsing errors.
/**
* Exception thrown when no viable alternative exists
*/
class NoViableAltException extends RecognitionException {
/** ATN configuration set at decision point */
deadEndConfigs: ATNConfigSet;
/** Token where decision started */
startToken: Token;
/**
* Create no viable alternative exception
* @param recognizer - Parser that encountered the error
*/
constructor(recognizer: Recognizer<any>);
}
/**
* Exception thrown by lexer when no viable alternative exists
*/
class LexerNoViableAltException extends RecognitionException {
/** ATN configuration set at decision point */
deadEndConfigs: ATNConfigSet;
/** Character index where error started */
startIndex: number;
/**
* Create lexer no viable alternative exception
* @param recognizer - Lexer that encountered the error
*/
constructor(recognizer: Recognizer<any>);
}
/**
* Exception thrown when semantic predicate fails
*/
class FailedPredicateException extends RecognitionException {
/**
* Create failed predicate exception
* @param recognizer - Parser that encountered the error
* @param predicate - Predicate that failed (optional)
* @param message - Error message (optional)
*/
constructor(recognizer: Parser, predicate: string | undefined, message: string | undefined);
}
/**
* Exception thrown when input doesn't match expected token
*/
class InputMismatchException extends RecognitionException {
/**
* Create input mismatch exception
* @param recognizer - Parser that encountered the error
*/
constructor(recognizer: Parser);
}Interfaces for customizing error reporting behavior.
/**
* Interface for handling recognition errors and reporting
*/
abstract class ErrorListener<TSymbol> {
/**
* Report syntax error
* @param recognizer - Recognizer that encountered the error
* @param offendingSymbol - Symbol that caused the error
* @param line - Line number where error occurred
* @param column - Column position where error occurred
* @param msg - Error message
* @param e - Recognition exception (optional)
*/
syntaxError(recognizer: Recognizer<TSymbol>, offendingSymbol: TSymbol, line: number, column: number, msg: string, e: RecognitionException | undefined): void;
/**
* Report ambiguous parse (parser only)
* @param recognizer - Parser that found ambiguity
* @param dfa - DFA that found ambiguity
* @param startIndex - Start token index of ambiguous region
* @param stopIndex - Stop token index of ambiguous region
* @param exact - Whether ambiguity is exact
* @param ambigAlts - Set of ambiguous alternatives
* @param configs - ATN configurations
*/
reportAmbiguity(recognizer: Recognizer<TSymbol>, dfa: DFA, startIndex: number, stopIndex: number, exact: boolean, ambigAlts: BitSet, configs: ATNConfigSet): void;
/**
* Report attempt at full context parse (parser only)
* @param recognizer - Parser attempting full context
* @param dfa - DFA requiring full context
* @param startIndex - Start token index
* @param stopIndex - Stop token index
* @param conflictingAlts - Conflicting alternatives
* @param configs - ATN configurations
*/
reportAttemptingFullContext(recognizer: Recognizer<TSymbol>, dfa: DFA, startIndex: number, stopIndex: number, conflictingAlts: BitSet, configs: ATNConfigSet): void;
/**
* Report context sensitivity in parse (parser only)
* @param recognizer - Parser with context sensitivity
* @param dfa - DFA with context sensitivity
* @param startIndex - Start token index
* @param stopIndex - Stop token index
* @param prediction - Predicted alternative
* @param configs - ATN configurations
*/
reportContextSensitivity(recognizer: Recognizer<TSymbol>, dfa: DFA, startIndex: number, stopIndex: number, prediction: number, configs: ATNConfigSet): void;
}
/**
* Error listener that provides diagnostic information
*/
class DiagnosticErrorListener<TSymbol> extends ErrorListener<TSymbol> {
// Specialized error listener for detailed diagnostics
}Strategies for recovering from parsing errors.
/**
* Interface for error recovery strategies
*/
abstract class ErrorStrategy {
/**
* Reset error recovery state
* @param recognizer - Parser to reset
*/
reset(recognizer: Parser): void;
/**
* Synchronize parser after error
* @param recognizer - Parser to synchronize
*/
sync(recognizer: Parser): void;
/**
* Recover from recognition exception
* @param recognizer - Parser to recover
* @param e - Exception to recover from
*/
recover(recognizer: Parser, e: RecognitionException): void;
/**
* Recover inline by consuming tokens until match
* @param recognizer - Parser to recover
* @returns Recovery token
*/
recoverInline(recognizer: Parser): Token;
/**
* Report successful match
* @param recognizer - Parser that matched
*/
reportMatch(recognizer: Parser): void;
/**
* Report recognition error
* @param recognizer - Parser with error
* @param e - Recognition exception
*/
reportError(recognizer: Parser, e: RecognitionException): void;
}
/**
* Default implementation of error recovery strategy
*/
class DefaultErrorStrategy implements ErrorStrategy {
/**
* Recover from recognition exception using default strategy
* @param recognizer - Parser to recover
* @param e - Exception to recover from
*/
recover(recognizer: Parser, e: RecognitionException): void;
/**
* Recover inline by consuming and inserting tokens
* @param recognizer - Parser to recover
* @returns Recovery token
*/
recoverInline(recognizer: Parser): Token;
/**
* Report error to error listeners
* @param recognizer - Parser with error
* @param e - Recognition exception
*/
reportError(recognizer: Parser, e: RecognitionException): void;
/**
* Report successful match to listeners
* @param recognizer - Parser that matched
*/
reportMatch(recognizer: Parser): void;
/**
* Reset error recovery state
* @param recognizer - Parser to reset
*/
reset(recognizer: Parser): void;
/**
* Synchronize parser by consuming tokens
* @param recognizer - Parser to synchronize
*/
sync(recognizer: Parser): void;
/**
* Check if parser is in error recovery mode
* @param recognizer - Parser to check
* @returns True if in error recovery mode
*/
inErrorRecoveryMode(recognizer: Parser): boolean;
/**
* Begin error condition (enter recovery mode)
* @param recognizer - Parser entering error mode
*/
beginErrorCondition(recognizer: Parser): void;
/**
* Get a missing symbol for error recovery
* @param recognizer - Parser needing symbol
* @returns Missing symbol token
*/
getMissingSymbol(recognizer: Parser): Token;
}
/**
* Error strategy that bails out on first error (no recovery)
*/
class BailErrorStrategy extends DefaultErrorStrategy {
/**
* Create bail error strategy
*/
constructor();
}Creating a custom error listener:
import { ErrorListener, RecognitionException, BitSet, DFA, ATNConfigSet } from "antlr4";
class CustomErrorListener extends ErrorListener<Token> {
private errors: string[] = [];
syntaxError(recognizer: Recognizer<Token>, offendingSymbol: Token, line: number, column: number, msg: string, e: RecognitionException | undefined): void {
const error = `Syntax error at ${line}:${column} - ${msg}`;
this.errors.push(error);
console.error(error);
if (offendingSymbol) {
console.error(`Offending token: '${offendingSymbol.text}'`);
}
}
reportAmbiguity(recognizer: Recognizer<Token>, dfa: DFA, startIndex: number, stopIndex: number, exact: boolean, ambigAlts: BitSet, configs: ATNConfigSet): void {
const warning = `Ambiguity detected from token ${startIndex} to ${stopIndex}`;
console.warn(warning);
}
reportAttemptingFullContext(recognizer: Recognizer<Token>, dfa: DFA, startIndex: number, stopIndex: number, conflictingAlts: BitSet, configs: ATNConfigSet): void {
console.log(`Attempting full context from token ${startIndex} to ${stopIndex}`);
}
reportContextSensitivity(recognizer: Recognizer<Token>, dfa: DFA, startIndex: number, stopIndex: number, prediction: number, configs: ATNConfigSet): void {
console.log(`Context sensitivity at token ${startIndex} to ${stopIndex}, prediction: ${prediction}`);
}
getErrors(): string[] {
return this.errors.slice(); // Return copy
}
hasErrors(): boolean {
return this.errors.length > 0;
}
}Using custom error listeners:
import { Parser, CustomErrorListener } from "antlr4";
// Create parser
const parser = new MyParser(tokens);
// Remove default error listeners
parser.removeErrorListeners();
// Add custom error listener
const errorListener = new CustomErrorListener();
parser.addErrorListener(errorListener);
// Parse with custom error handling
try {
const tree = parser.expression();
if (errorListener.hasErrors()) {
console.error("Parsing completed with errors:");
errorListener.getErrors().forEach(error => console.error(error));
} else {
console.log("Parsing successful!");
}
} catch (e) {
console.error("Fatal parsing error:", e.message);
}Creating a custom error recovery strategy:
import { DefaultErrorStrategy, RecognitionException, Token, Parser } from "antlr4";
class CustomErrorStrategy extends DefaultErrorStrategy {
private maxErrors = 10;
private errorCount = 0;
recover(recognizer: Parser, e: RecognitionException): void {
this.errorCount++;
if (this.errorCount > this.maxErrors) {
throw new Error(`Too many parse errors (${this.errorCount}), giving up`);
}
// Use default recovery but log it
console.log(`Recovering from error: ${e.message}`);
super.recover(recognizer, e);
}
recoverInline(recognizer: Parser): Token {
this.errorCount++;
if (this.errorCount > this.maxErrors) {
throw new Error(`Too many parse errors (${this.errorCount}), giving up`);
}
console.log("Attempting inline recovery");
return super.recoverInline(recognizer);
}
reset(recognizer: Parser): void {
this.errorCount = 0;
super.reset(recognizer);
}
}
// Usage
const parser = new MyParser(tokens);
parser._errHandler = new CustomErrorStrategy();Using bail error strategy for fast failure:
import { BailErrorStrategy } from "antlr4";
const parser = new MyParser(tokens);
// Set bail error strategy (stops on first error)
parser._errHandler = new BailErrorStrategy();
try {
const tree = parser.expression();
console.log("Parsing successful!");
} catch (e) {
if (e instanceof RecognitionException) {
console.error(`Parse failed at line ${e.offendingToken?.line}: ${e.message}`);
} else {
console.error("Unexpected error:", e.message);
}
}Handling specific exception types:
import {
RecognitionException,
NoViableAltException,
InputMismatchException,
FailedPredicateException
} from "antlr4";
try {
const tree = parser.expression();
} catch (e) {
if (e instanceof NoViableAltException) {
console.error(`No viable alternative at token: ${e.startToken.text}`);
console.error(`Dead-end configurations: ${e.deadEndConfigs.configs.length}`);
} else if (e instanceof InputMismatchException) {
console.error(`Input mismatch: expected different token`);
console.error(`Current token: ${e.offendingToken?.text}`);
} else if (e instanceof FailedPredicateException) {
console.error(`Semantic predicate failed`);
} else if (e instanceof RecognitionException) {
console.error(`General parse error: ${e.message}`);
} else {
console.error(`Unexpected error: ${e.message}`);
}
}Creating an error-collecting listener for batch processing:
interface ParseError {
line: number;
column: number;
message: string;
token?: string;
}
class ErrorCollector extends ErrorListener<Token> {
private errors: ParseError[] = [];
syntaxError(recognizer: Recognizer<Token>, offendingSymbol: Token, line: number, column: number, msg: string, e: RecognitionException | undefined): void {
this.errors.push({
line,
column,
message: msg,
token: offendingSymbol?.text
});
}
// Implement other methods as needed
reportAmbiguity(): void { /* ignore for collection */ }
reportAttemptingFullContext(): void { /* ignore for collection */ }
reportContextSensitivity(): void { /* ignore for collection */ }
getErrors(): ParseError[] {
return this.errors.slice();
}
clear(): void {
this.errors = [];
}
hasErrors(): boolean {
return this.errors.length > 0;
}
getErrorSummary(): string {
if (!this.hasErrors()) {
return "No errors";
}
return `${this.errors.length} error(s) found:\n` +
this.errors.map(err =>
` Line ${err.line}:${err.column} - ${err.message}` +
(err.token ? ` (token: '${err.token}')` : "")
).join("\n");
}
}
// Usage for processing multiple inputs
const errorCollector = new ErrorCollector();
const inputs = ["input1", "input2", "input3"];
const results = [];
for (const input of inputs) {
errorCollector.clear();
const parser = createParser(input);
parser.removeErrorListeners();
parser.addErrorListener(errorCollector);
try {
const tree = parser.parse();
if (errorCollector.hasErrors()) {
results.push({
input,
success: false,
errors: errorCollector.getErrors()
});
} else {
results.push({
input,
success: true,
tree
});
}
} catch (e) {
results.push({
input,
success: false,
fatal: e.message
});
}
}
// Report results
results.forEach(result => {
console.log(`Input: ${result.input}`);
if (result.success) {
console.log(" Status: Success");
} else {
console.log(" Status: Failed");
if (result.fatal) {
console.log(` Fatal: ${result.fatal}`);
}
if (result.errors) {
result.errors.forEach(err => {
console.log(` Error: Line ${err.line}:${err.column} - ${err.message}`);
});
}
}
});