Antlr4's ErrorListener mechanism is great for logging and making decisions about syntax errors that occur during parsing, but packet processing can improve after processing the analysis. There are a number of reasons why you might want to handle errors after the parsing is complete, including:
- we need a clean way to programmatically check for errors during analysis and processing them after the fact,
- sometimes one syntax error causes several others (for example, if it is not restored in a line), so it may be useful to group or insert these errors according to the parent context when displaying the output to the user, and you cannot know all the errors until the law is completed,
- you can display errors differently for the user depending on how much and how serious they are, for example, one error that came out of a rule or several errors restored in a line may ask the user to correct these local areas - otherwise you there may be a user who will edit the entire input file, and for this it is necessary to have all the errors.
The bottom line is that we can smarterly communicate and ask users to correct syntax errors if we know the full context in which the errors occurred (including other errors). For this, I have the following three goals:
- a complete set of all errors from this analysis,
- contextual information for each error and
- severity and recovery information for each error.
I wrote code for # 1 and # 2, and I'm looking for help on # 3. I am also going to offer some small changes to make # 1 and # 2 easier for everyone.
First, to execute # 1 (a full set of errors), I created a CollectionErrorListener as follows:
public class CollectionErrorListener extends BaseErrorListener {
private final List<SyntaxError> errors = new ArrayList<SyntaxError>();
public List<SyntaxError> getErrors() {
return errors;
}
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
if (e == null) {
e = new InlineRecognitionException(msg, recognizer, ((Parser)recognizer).getInputStream(), ((Parser)recognizer).getContext(), (Token) offendingSymbol);
}
this.errors.add(new SyntaxError(msg, e));
}
}
And here is my class for InlineRecognitionException:
public class InlineRecognitionException extends RecognitionException {
public InlineRecognitionException(String message, Recognizer<?, ?> recognizer, IntStream input, ParserRuleContext ctx, Token offendingToken) {
super(message, recognizer, input, ctx);
this.setOffendingToken(offendingToken);
}
}
And here is my class for the SyntaxError container:
public class SyntaxError extends RecognitionException {
public SyntaxError(String message, RecognitionException e) {
super(message, e.getRecognizer(), e.getInputStream(), (ParserRuleContext) e.getCtx());
this.setOffendingToken(e.getOffendingToken());
this.initCause(e);
}
}
SyntaxErrorListener, 280Z28 / Antlr. InlineRecognitionException, SyntaxError - CollectionErrorListener.syntaxError.
, RecognitionException "e" null, ( ). RecognitionException, , . , , ( test), №3, InlineRecognitionException .
SyntaxError, RecognitionException "e" (, ), e.getMessage() null ( - ). msg CollectionErrorListener.syntaxError. RecognitionException setMessage(), RecognitionException ( , ), , , .
:
CollectionErrorListener collector = new CollectionErrorListener();
parser.addErrorListener(collector);
ParseTree tree = parser.prog();
// ... Later ...
for (SyntaxError e : collector.getErrors()) {
// RecognitionExceptionUtil is my custom class discussed next.
System.out.println(RecognitionExceptionUtil.formatVerbose(e));
}
. RecognitionException . 9 Definitive ANTLR 4 Reference , , , , , , . , :
List<String> stack = ((Parser)e.getRecognizer()).getRuleInvocationStack();
, RuleContext, getRuleInvocationStack. , RecognitionException , getRuleInvocationStack , :
List<String> stack = ((Parser)e.getRecognizer()).getRuleInvocationStack(e.getCtx());
, , RecognitionException, . , RecognitionException:
public class RecognitionExceptionUtil {
public static String formatVerbose(RecognitionException e) {
return String.format("ERROR on line %s:%s => %s%nrule stack: %s%noffending token %s => %s%n%s",
getLineNumberString(e),
getCharPositionInLineString(e),
e.getMessage(),
getRuleStackString(e),
getOffendingTokenString(e),
getOffendingTokenVerboseString(e),
getErrorLineStringUnderlined(e).replaceAll("(?m)^|$", "|"));
}
public static String getRuleStackString(RecognitionException e) {
if (e == null || e.getRecognizer() == null
|| e.getCtx() == null
|| e.getRecognizer().getRuleNames() == null) {
return "";
}
List<String> stack = ((Parser)e.getRecognizer()).getRuleInvocationStack(e.getCtx());
Collections.reverse(stack);
return stack.toString();
}
public static String getLineNumberString(RecognitionException e) {
if (e == null || e.getOffendingToken() == null) {
return "";
}
return String.format("%d", e.getOffendingToken().getLine());
}
public static String getCharPositionInLineString(RecognitionException e) {
if (e == null || e.getOffendingToken() == null) {
return "";
}
return String.format("%d", e.getOffendingToken().getCharPositionInLine());
}
public static String getOffendingTokenString(RecognitionException e) {
if (e == null || e.getOffendingToken() == null) {
return "";
}
return e.getOffendingToken().toString();
}
public static String getOffendingTokenVerboseString(RecognitionException e) {
if (e == null || e.getOffendingToken() == null) {
return "";
}
return String.format("at tokenStream[%d], inputString[%d..%d] = '%s', tokenType<%d> = %s, on line %d, character %d",
e.getOffendingToken().getTokenIndex(),
e.getOffendingToken().getStartIndex(),
e.getOffendingToken().getStopIndex(),
e.getOffendingToken().getText(),
e.getOffendingToken().getType(),
e.getRecognizer().getTokenNames()[e.getOffendingToken().getType()],
e.getOffendingToken().getLine(),
e.getOffendingToken().getCharPositionInLine());
}
public static String getErrorLineString(RecognitionException e) {
if (e == null || e.getRecognizer() == null
|| e.getRecognizer().getInputStream() == null
|| e.getOffendingToken() == null) {
return "";
}
CommonTokenStream tokens =
(CommonTokenStream)e.getRecognizer().getInputStream();
String input = tokens.getTokenSource().getInputStream().toString();
String[] lines = input.split(String.format("\r?\n"));
return lines[e.getOffendingToken().getLine() - 1];
}
public static String getErrorLineStringUnderlined(RecognitionException e) {
String errorLine = getErrorLineString(e);
if (errorLine.isEmpty()) {
return errorLine;
}
errorLine = errorLine.replaceAll("\t", " ");
StringBuilder underLine = new StringBuilder(String.format("%" + errorLine.length() + "s", ""));
int start = e.getOffendingToken().getStartIndex();
int stop = e.getOffendingToken().getStopIndex();
if ( start>=0 && stop>=0 ) {
for (int i=0; i<=(stop-start); i++) {
underLine.setCharAt(e.getOffendingToken().getCharPositionInLine() + i, '^');
}
}
return String.format("%s%n%s", errorLine, underLine);
}
}
RecognitionExceptionUtil ( , , Parser, getErrorLineString ..), , .
ANTLR:
- "RecognitionException e" ANTLRErrorListener.syntaxError( OffendingToken), . , , e.getMessage() , msg.
- RecognitionException, OffendingToken.
- ANTLRErrorListener.syntaxError, .
- RecognitionException , getCharPositionInLine, getLineNumber, getRuleStack RecognitionExceptionUtil, . , null, , Parser .
- ANTLRErrorListener.syntaxError , , ( getRuleInvocationStack).
- , RecognitionException. e.getCtx(): e.getContext(), Parser.getContext(), -, , RecognitionException ( , Parser).
- RecognitionException , . №3 . , . ? /?
, № 3: .