我有一个ANTLR 4语法,并据此构建了一个词法分析和解析器。现在,我正在尝试实例化该解析器,使其将解析直到遇到错误。如果遇到错误,它不应该继续解析,但它应该提供有关问题的有用信息;理想情况下,机器可读的位置和人类可读的消息。
这是我目前所拥有的:
grammar Toy;
@parser::members {
public static void main(String[] args) {
for (String arg: args)
System.out.println(arg + " => " + parse(arg));
}
public static String parse(String code) {
ErrorListener errorListener = new ErrorListener();
CharStream cstream = new ANTLRInputStream(code);
ToyLexer lexer = new ToyLexer(cstream);
lexer.removeErrorListeners();
lexer.addErrorListener(errorListener);
TokenStream tstream = new CommonTokenStream(lexer);
ToyParser parser = new ToyParser(tstream);
parser.removeErrorListeners();
parser.addErrorListener(errorListener);
parser.setErrorHandler(new BailErrorStrategy());
try {
String res = parser.top().str;
if (errorListener.message != null)
return "Parsed, but " + errorListener.toString();
return res;
} catch (ParseCancellationException e) {
if (errorListener.message != null)
return "Failed, because " + errorListener.toString();
throw e;
}
}
static class ErrorListener extends BaseErrorListener {
String message = null;
int start = -2, stop = -2, line = -2;
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line,
int charPositionInLine,
String msg,
RecognitionException e) {
if (message != null) return;
if (offendingSymbol instanceof Token) {
Token t = (Token) offendingSymbol;
start = t.getStartIndex();
stop = t.getStopIndex();
} else if (recognizer instanceof ToyLexer) {
ToyLexer lexer = (ToyLexer)recognizer;
start = lexer._tokenStartCharIndex;
stop = lexer._input.index();
}
this.line = line;
message = msg;
}
@Override public String toString() {
return start + "-" + stop + " l." + line + ": " + message;
}
}
}
top returns [String str]: e* EOF {$str = "All went well.";};
e: 'a' 'b' | 'a' 'c' e;
将其保存到 Toy.g
,然后尝试以下命令:
> java -jar antlr-4.5.2-complete.jar Toy.g
> javac -cp antlr-4.5.2-complete.jar Toy*.java
> java -cp .:tools/antlr-4.5.2-complete.jar ToyParser ab acab acc axb abc
ab => All went well.
acab => All went well.
acc => Failed, because 2-2 l.1: no viable alternative at input 'c'
axb => Parsed, but 1-1 l.1: token recognition error at: 'x'
Exception in thread "main" org.antlr.v4.runtime.misc.ParseCancellationException
at org.antlr.v4.runtime.BailErrorStrategy.recoverInline(BailErrorStrategy.java:90)
at org.antlr.v4.runtime.Parser.match(Parser.java:229)
at ToyParser.top(ToyParser.java:187)
at ToyParser.parse(ToyParser.java:95)
at ToyParser.main(ToyParser.java:80)
Caused by: org.antlr.v4.runtime.InputMismatchException
at org.antlr.v4.runtime.BailErrorStrategy.recoverInline(BailErrorStrategy.java:85)
... 4 more
一方面,我觉得我已经做得太多了。看看我为应该是一个简单而常见的任务写了多少代码,我不禁怀疑我是否缺少一些更简单的解决方案。另一方面,即使这样似乎也不够,原因有两个。首先,虽然我设法报告了词法分析器错误,但它们仍然不会阻止解析器继续处理剩余的流。这是输入axb
的Parsed, but
字符串证明的。其次,我仍然留下了未报告给错误侦听器的错误,如堆栈跟踪所示。
如果我不安装BailErrorStrategy
,我会得到更有用的输出:
acc => Parsed, but 2-2 l.1: mismatched input 'c' expecting 'a'
axb => Parsed, but 1-1 l.1: token recognition error at: 'x'
abc => Parsed, but 2-2 l.1: extraneous input 'c' expecting {<EOF>, 'a'}
有没有办法获得这种错误消息,但仍然保释错误?我可以从消息来源中看到,extraneous input
消息确实是由DefaultErrorStrategy
生成的,显然是在它弄清楚如何解决问题之后。我应该让它这样做然后纾困,即编写我自己的BailErrorStrategy
变体,在投掷之前调用超级?
在同样的情况下,我最终扩展了DefaultErrorStrategy
并覆盖了report*
方法。这非常简单(您也可以使用ANTLRErrorStrategy
)。
在这里,您可以找到快速失败策略的示例。我认为在您的情况下,您可以以相同的方式收集所有错误并构建详细的报告。
一种方法可能是修改错误侦听器而不是错误策略。可以将默认策略与以下侦听器一起使用:
class ErrorListener extends BaseErrorListener {
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line,
int charPositionInLine,
String msg,
RecognitionException e) {
throw new ParseException(msg, e, line);
}
}
class ParseException extends RuntimeException {
int line;
public ParseException(String message, Throwable cause, int line) {
super(message, cause);
this.line = line;
}
}
这样,错误将按照输出格式进行格式化,但要报告的第一个错误将通过引发命名异常导致编译中止。由于这是一个未经检查的异常,您必须确保捕获它,因为如果您忘记这样做,编译器不会警告您。
关于机器可读的位置,如果除了行号之外,您还希望输入的违规部分的源文本偏移量,则这样的代码似乎可以在syntaxError
方法中工作:
int start = 0, stop = -1;
if (offendingSymbol instanceof Token) {
Token t = (Token) offendingSymbol;
start = t.getStartIndex();
stop = t.getStopIndex();
} else if (recognizer instanceof Lexer) {
Lexer lexer = (Lexer)recognizer;
start = lexer._tokenStartCharIndex;
stop = lexer._input.index();
}