Antlr4中的ErrorListener机制非常适合在解析期间记录和决定语法错误,但在解析完成后,它可以更好地处理批处理错误。解析完成后,您可能希望处理错误的原因有很多,包括:
最重要的是,如果我们知道发生错误的完整上下文(包括其他错误),我们可以更聪明地报告并要求用户修复语法错误。为此,我有以下三个目标:
我已编写代码来做#1和#2,我正在寻找#3的帮助。我还建议做一些小改动,让每个人都更容易#1和#2。
首先,为了完成#1(完整的错误集合),我创建了CollectionErrorListener,如下所示:
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 is null when the parser was able to recover in line without exiting the surrounding rule.
e = new InlineRecognitionException(msg, recognizer, ((Parser)recognizer).getInputStream(), ((Parser)recognizer).getContext(), (Token) offendingSymbol);
}
this.errors.add(new SyntaxError(msg, e));
}
}
这是我的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);
}
}
这是我的SyntaxError容器类:
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);
}
}
这与280Z28对Antlr error/exception handling的回答所引用的SyntaxErrorListener非常相似。我需要InlineRecognitionException和SyntaxError包装器,因为如何填充CollectionErrorListener.syntaxError的参数。
首先,RecognitionException参数&#34; e&#34;如果解析器从行中的异常中恢复(不离开规则),则为null。我们不能实例化一个新的RecognitionException,因为没有允许我们设置违规令牌的构造函数或方法。无论如何,能够区分在线恢复的错误(使用instanceof测试)是实现目标#3的有用信息,因此我们可以使用InlineRecognitionException类来指示在线恢复。
接下来,我们需要SyntaxError包装类,因为,即使在RecognitionException&#34; e&#34;不为空(例如,当恢复不在行中时),e.getMessage()的值为空(由于某种未知原因)。因此,我们需要将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的输出有点烦人。 The Definitive ANTLR 4 Reference书的第9章显示了如何显示质量错误消息意味着您需要拆分输入行,反转规则调用堆栈,并将违规令牌中的大量内容拼凑在一起以解释错误发生的位置。并且,如果您在解析完成后报告错误,则以下命令不起作用:
// The following doesn't work if you are not reporting during the parse because the
// parser context is lost from the RecognitionException "e" recognizer.
List<String> stack = ((Parser)e.getRecognizer()).getRuleInvocationStack();
问题是我们丢失了RuleContext,而getRuleInvocationStack则需要这样做。幸运的是,RecognitionException保留了我们上下文的副本,getRuleInvocationStack接受了一个参数,所以这里是我们在解析完成后得到规则调用堆栈的方法:
// Pass in the context from RecognitionException "e" to get the rule invocation stack
// after the parse is finished.
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;
}
// replace tabs with single space so that charPositionInLine gives us the
// column to start underlining.
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中的多行等),但我希望你明白这一点。 / p>
我对ANTLR未来版本的建议摘要:
所以,我正在寻找有关我的三个目标的反馈,特别是有关收集有关目标#3的更多信息的任何建议:每个错误的严重性和恢复信息。
答案 0 :(得分:1)
我将这些建议发布到Antlr4 GitHub问题列表并收到以下回复。我相信ANTLRErrorListener.syntaxError方法包含冗余/混淆参数,需要大量的API知识才能正确使用,但我理解这个决定。以下是问题的链接和文本回复的副本:
来自:https://github.com/antlr/antlr4/issues/396
关于你的建议:
除外,所有语法错误的RecognitionException都为非null 我们发现了不匹配的令牌错误,我们可以从线上恢复, 没有从周围规则返回(通过单个令牌 插入和删除机制)。
我现在正在关闭此问题,因为我没有看到任何需要从此列表中更改运行时的操作项。