Skip to content

Instantly share code, notes, and snippets.

@KetothXupack
Last active August 31, 2016 18:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KetothXupack/bec735ad12016ea2b552a3503f80cd53 to your computer and use it in GitHub Desktop.
Save KetothXupack/bec735ad12016ea2b552a3503f80cd53 to your computer and use it in GitHub Desktop.
package org.nohope.antlr4;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.FailedPredicateException;
import org.antlr.v4.runtime.InputMismatchException;
import org.antlr.v4.runtime.NoViableAltException;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
/**
*/
public final class VerboseErrorMessagePolicy extends DefaultErrorStrategy {
private static final Logger LOG = LoggerFactory.getLogger(ProperErrorMessagePolicy.class);
private static final String UNKNOWN_RECOGNITION_ERROR_TYPE = "Unknown recognition error type: %s with message: %s";
private static final String ALREADY_REPORTED_ERROR_MESSAGE = "Got an already reported error: %s";
private static final String MISMATCHED_INPUT_ERROR_MESSAGE = "Parse error: mismatched input on line %s:%s. Got a %s but expected %s. Text: \"%s\"";
private static final String VIABLE_ALTERNATIVE_ERROR_MESSAGE = "Parse error: can't find viable alternative at %s(line %s:%s). Rule context: %s. Text: \"%s\"";
private static final String TOKEN_DISPLAY_FORMAT = "<%s>";
private static final int STRING_START = 0;
private final String originalExpression;
public VerboseErrorMessagePolicy(String expression) {
this.originalExpression = expression;
}
private static void setExceptionContext(Parser recognizer, RuntimeException e) {
for (ParserRuleContext context = recognizer.getContext(); context != null; context = context.getParent()) {
context.exception = (RecognitionException) e;
}
}
@Override
public void recover(Parser recognizer, RecognitionException error) {
setExceptionContext(recognizer, error);
LOG.debug("Get a parse exception: ", error);
throw new ParseCancellationException(error);
}
@Override
public void reportError(Parser recognizer, RecognitionException error) {
if (inErrorRecoveryMode(recognizer)) {
LOG.debug(String.format(ALREADY_REPORTED_ERROR_MESSAGE, error.getMessage()));
return; // don't report spurious errors
}
beginErrorCondition(recognizer);
if (error instanceof NoViableAltException) {
reportNoViableAlternative(recognizer, (NoViableAltException) error);
} else if (error instanceof InputMismatchException) {
LOG.debug("Input mismatch: ", error);
reportInputMismatch(recognizer, (InputMismatchException) error);
} else if (error instanceof FailedPredicateException) {
LOG.debug("Failed predicate exception: ", error);
reportFailedPredicate(recognizer, (FailedPredicateException) error);
} else {
LOG.error(String.format(UNKNOWN_RECOGNITION_ERROR_TYPE, error.getClass().getName(), error.getMessage()));
recognizer.notifyErrorListeners(error.getOffendingToken(), error.getMessage(), error);
}
}
@Override
protected void reportNoViableAlternative(@Nonnull Parser recognizer,
@Nonnull NoViableAltException e) {
String msg = formViableExceptionMessage(e, recognizer);
LOG.debug("Got a non-viable exception {}. Original exception: ", msg, e);
recognizer.notifyErrorListeners(e.getOffendingToken(), msg, e);
throw new ParseCancellationException(msg, e);
}
private boolean tokenIsEOF(Token token) {
return getSymbolType(token) == Token.EOF;
}
private static String getContext(Parser recognizer, TokenStream tokens) {
String context = tokens.getText(recognizer.getContext());
return context.isEmpty() ? "No context available" : context;
}
private String getPieceOfTextWhichContainsError(RecognitionException e) {
return originalExpression.substring(STRING_START, e.getOffendingToken().getStopIndex());
}
private String formViableExceptionMessage(NoViableAltException e, Parser recognizer) {
TokenStream tokens = recognizer.getInputStream();
String text = getPieceOfTextWhichContainsError(e);
String context = getContext(recognizer, tokens);
return String.format(VIABLE_ALTERNATIVE_ERROR_MESSAGE,
escapeWSAndQuote(getInputToken(e, tokens)),
e.getOffendingToken().getLine(),
e.getOffendingToken().getCharPositionInLine(),
context, text);
}
@Override
public Token recoverInline(Parser recognizer)
throws RecognitionException {
LOG.debug("Got an input mismatch exception");
InputMismatchException e = new InputMismatchException(recognizer);
setExceptionContext(recognizer, e);
reportError(recognizer, e);
throw e;
}
private static String getExpectedToken(Parser recognizer, InputMismatchException e) {
return e.getExpectedTokens().toString(recognizer.getTokenNames());
}
private static int getCharPositionInLine(InputMismatchException e) {
return e.getOffendingToken().getCharPositionInLine();
}
private static int getLine(InputMismatchException e) {
return e.getOffendingToken().getLine();
}
private String createErrorMessage(Parser recognizer, InputMismatchException e) {
return String.format(MISMATCHED_INPUT_ERROR_MESSAGE,
getLine(e),
getCharPositionInLine(e),
getTokenErrorDisplay(e.getOffendingToken()),
getExpectedToken(recognizer, e),
getPieceOfTextWhichContainsError(e));
}
@Override
protected void reportInputMismatch(@Nonnull Parser recognizer,
@Nonnull InputMismatchException e) {
String msg = createErrorMessage(recognizer, e);
recognizer.notifyErrorListeners(e.getOffendingToken(), msg, e);
throw new ParseCancellationException(msg, e);
}
@Override
protected String getTokenErrorDisplay(Token token) {
if (token == null) {
return "<no token>";
}
return escapeWSAndQuote(String.format(TOKEN_DISPLAY_FORMAT, getErrorSymbol(token)));
}
private static boolean noTextSymbol(String s) {
return s == null;
}
private String getErrorSymbol(Token token) {
String symbol = getSymbolText(token);
if (noTextSymbol(symbol)) {
if (tokenIsEOF(token)) {
symbol = "EOF";
} else {
symbol = String.valueOf(getSymbolType(token));
}
}
return symbol;
}
private String getInputToken(NoViableAltException e, TokenStream tokens) {
if (tokens != null) {
String input;
if (tokenIsEOF(e.getStartToken())) {
input = "EOF";
} else {
input = tokens.getText(e.getStartToken(), e.getOffendingToken());
}
return input;
}
return "unknown input";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment