Last active
August 31, 2016 18:48
-
-
Save KetothXupack/bec735ad12016ea2b552a3503f80cd53 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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