Java 处理 ANTLR4 中的错误
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18132078/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Handling errors in ANTLR4
提问by Brad Mace
The default behavior when the parser doesn't know what to do is to print messages to the terminal like:
当解析器不知道该做什么时,默认行为是将消息打印到终端,如:
line 1:23 missing DECIMAL at '}'
第 1:23 行在 '}' 处缺少 DECIMAL
This is a good message, but in the wrong place. I'd rather receive this as an exception.
这是一个很好的信息,但在错误的地方。我宁愿将此作为例外。
I've tried using the BailErrorStrategy
, but this throws a ParseCancellationException
without a message (caused by a InputMismatchException
, also without a message).
我试过使用BailErrorStrategy
,但这会抛出ParseCancellationException
没有消息的 a (由 a 引起InputMismatchException
,也没有消息)。
Is there a way I can get it to report errors via exceptions while retaining the useful info in the message?
有没有办法让它通过异常报告错误,同时保留消息中的有用信息?
Here's what I'm really after--I typically use actions in rules to build up an object:
这就是我真正想要的——我通常在规则中使用操作来构建一个对象:
dataspec returns [DataExtractor extractor]
@init {
DataExtractorBuilder builder = new DataExtractorBuilder(layout);
}
@after {
$extractor = builder.create();
}
: first=expr { builder.addAll($first.values); } (COMMA next=expr { builder.addAll($next.values); })* EOF
;
expr returns [List<ValueExtractor> values]
: a=atom { $values = Arrays.asList($a.val); }
| fields=fieldrange { $values = values($fields.fields); }
| '%' { $values = null; }
| ASTERISK { $values = values(layout); }
;
Then when I invoke the parser I do something like this:
然后当我调用解析器时,我会做这样的事情:
public static DataExtractor create(String dataspec) {
CharStream stream = new ANTLRInputStream(dataspec);
DataSpecificationLexer lexer = new DataSpecificationLexer(stream);
CommonTokenStream tokens = new CommonTokenStream(lexer);
DataSpecificationParser parser = new DataSpecificationParser(tokens);
return parser.dataspec().extractor;
}
All I really want is
我真正想要的是
- for the
dataspec()
call to throw an exception (ideally a checked one) when the input can't be parsed - for that exception to have a useful message and provide access to the line number and position where the problem was found
- 用于在
dataspec()
无法解析输入时抛出异常(理想情况下是已检查的异常)的调用 - 为该异常提供有用的消息并提供对发现问题的行号和位置的访问
Then I'll let that exception bubble up the callstack to whereever is best suited to present a useful message to the user--the same way I'd handle a dropped network connection, reading a corrupt file, etc.
然后我会让这个异常在调用堆栈中冒泡到最适合向用户呈现有用消息的地方——就像我处理断开的网络连接、读取损坏的文件等一样。
I did see that actions are now considered "advanced" in ANTLR4, so maybe I'm going about things in a strange way, but I haven't looked into what the "non-advanced" way to do this would be since this way has been working well for our needs.
我确实看到现在 ANTLR4 中的操作被认为是“高级”的,所以也许我正在以一种奇怪的方式处理事情,但我还没有研究过这样做的“非高级”方式是什么,因为这样一直很好地满足我们的需求。
采纳答案by Mouagip
Since I've had a little bit of a struggle with the two existing answers, I'd like to share the solution I ended up with.
由于我对现有的两个答案有些挣扎,因此我想分享我最终得到的解决方案。
First of all I created my own version of an ErrorListener like Sam Harwellsuggested:
首先,我创建了自己的 ErrorListener 版本,就像Sam Harwell建议的那样:
public class ThrowingErrorListener extends BaseErrorListener {
public static final ThrowingErrorListener INSTANCE = new ThrowingErrorListener();
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e)
throws ParseCancellationException {
throw new ParseCancellationException("line " + line + ":" + charPositionInLine + " " + msg);
}
}
Note the use of a ParseCancellationException
instead of a RecognitionException
since the DefaultErrorStrategy would catch the latter and it would never reach your own code.
请注意使用 aParseCancellationException
而不是 aRecognitionException
因为 DefaultErrorStrategy 会捕获后者并且它永远不会到达您自己的代码。
Creating a whole new ErrorStrategy like Brad Macesuggested is not necessary since the DefaultErrorStrategy produces pretty good error messages by default.
没有必要像Brad Mace建议的那样创建一个全新的 ErrorStrategy,因为 DefaultErrorStrategy 在默认情况下会产生非常好的错误消息。
I then use the custom ErrorListener in my parsing function:
然后我在我的解析函数中使用自定义 ErrorListener :
public static String parse(String text) throws ParseCancellationException {
MyLexer lexer = new MyLexer(new ANTLRInputStream(text));
lexer.removeErrorListeners();
lexer.addErrorListener(ThrowingErrorListener.INSTANCE);
CommonTokenStream tokens = new CommonTokenStream(lexer);
MyParser parser = new MyParser(tokens);
parser.removeErrorListeners();
parser.addErrorListener(ThrowingErrorListener.INSTANCE);
ParserRuleContext tree = parser.expr();
MyParseRules extractor = new MyParseRules();
return extractor.visit(tree);
}
(For more information on what MyParseRules
does, see here.)
(有关其作用的更多信息MyParseRules
,请参见此处。)
This will give you the same error messages as would be printed to the console by default, only in the form of proper exceptions.
这将为您提供与默认情况下打印到控制台相同的错误消息,仅以适当的异常形式。
回答by Sam Harwell
When you use the DefaultErrorStrategy
or the BailErrorStrategy
, the ParserRuleContext.exception
field is set for any parse tree node in the resulting parse tree where an error occurred. The documentation for this field reads (for people that don't want to click an extra link):
当您使用DefaultErrorStrategy
或 时BailErrorStrategy
,ParserRuleContext.exception
将为结果分析树中发生错误的任何分析树节点设置该字段。该字段的文档如下(对于不想单击额外链接的人):
The exception which forced this rule to return. If the rule successfully completed, this is
null
.
强制此规则返回的异常。如果规则成功完成,这是
null
。
Edit:If you use DefaultErrorStrategy
, the parse context exception will not be propagated all the way out to the calling code, so you'll be able to examine the exception
field directly. If you use BailErrorStrategy
, the ParseCancellationException
thrown by it will include a RecognitionException
if you call getCause()
.
编辑:如果您使用DefaultErrorStrategy
,解析上下文异常将不会一直传播到调用代码,因此您将能够exception
直接检查该字段。如果你使用BailErrorStrategy
,它ParseCancellationException
抛出的将包括一个RecognitionException
if 你调用getCause()
。
if (pce.getCause() instanceof RecognitionException) {
RecognitionException re = (RecognitionException)pce.getCause();
ParserRuleContext context = (ParserRuleContext)re.getCtx();
}
Edit 2:Based on your other answer, it appears that you don't actually want an exception, but what you want is a different way to report the errors. In that case, you'll be more interested in the ANTLRErrorListener
interface. You want to call parser.removeErrorListeners()
to remove the default listener that writes to the console, and then call parser.addErrorListener(listener)
for your own special listener. I often use the following listener as a starting point, as it includes the name of the source file with the messages.
编辑 2:根据您的其他答案,您似乎并不真正想要异常,但您想要的是报告错误的不同方式。在这种情况下,您会对ANTLRErrorListener
界面更感兴趣。您想调用parser.removeErrorListeners()
以删除写入控制台的默认侦听器,然后调用parser.addErrorListener(listener)
您自己的特殊侦听器。我经常使用以下侦听器作为起点,因为它包含带有消息的源文件的名称。
public class DescriptiveErrorListener extends BaseErrorListener {
public static DescriptiveErrorListener INSTANCE = new DescriptiveErrorListener();
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol,
int line, int charPositionInLine,
String msg, RecognitionException e)
{
if (!REPORT_SYNTAX_ERRORS) {
return;
}
String sourceName = recognizer.getInputStream().getSourceName();
if (!sourceName.isEmpty()) {
sourceName = String.format("%s:%d:%d: ", sourceName, line, charPositionInLine);
}
System.err.println(sourceName+"line "+line+":"+charPositionInLine+" "+msg);
}
}
With this class available, you can use the following to use it.
有了这个类,您可以使用以下内容来使用它。
lexer.removeErrorListeners();
lexer.addErrorListener(DescriptiveErrorListener.INSTANCE);
parser.removeErrorListeners();
parser.addErrorListener(DescriptiveErrorListener.INSTANCE);
A muchmore complicated example of an error listener that I use to identify ambiguities which render a grammar non-SLL is the SummarizingDiagnosticErrorListener
class in TestPerformance
.
阿多的错误听者,我使用以识别呈现语法非SLL歧义的更复杂的例子是SummarizingDiagnosticErrorListener
在类TestPerformance
。
回答by Brad Mace
What I've come up with so far is based on extending DefaultErrorStrategy
and overriding it's reportXXX
methods (though it's entirely possible I'm making things more complicated than necessary):
到目前为止,我提出的是基于扩展DefaultErrorStrategy
和覆盖它的reportXXX
方法(尽管我完全有可能使事情变得比必要的更复杂):
public class ExceptionErrorStrategy extends DefaultErrorStrategy {
@Override
public void recover(Parser recognizer, RecognitionException e) {
throw e;
}
@Override
public void reportInputMismatch(Parser recognizer, InputMismatchException e) throws RecognitionException {
String msg = "mismatched input " + getTokenErrorDisplay(e.getOffendingToken());
msg += " expecting one of "+e.getExpectedTokens().toString(recognizer.getTokenNames());
RecognitionException ex = new RecognitionException(msg, recognizer, recognizer.getInputStream(), recognizer.getContext());
ex.initCause(e);
throw ex;
}
@Override
public void reportMissingToken(Parser recognizer) {
beginErrorCondition(recognizer);
Token t = recognizer.getCurrentToken();
IntervalSet expecting = getExpectedTokens(recognizer);
String msg = "missing "+expecting.toString(recognizer.getTokenNames()) + " at " + getTokenErrorDisplay(t);
throw new RecognitionException(msg, recognizer, recognizer.getInputStream(), recognizer.getContext());
}
}
This throws exceptions with useful messages, and the line and position of the problem can be gotten from either the offending
token, or if that's not set, from the current
token by using ((Parser) re.getRecognizer()).getCurrentToken()
on the RecognitionException
.
这会抛出带有有用消息的异常,并且可以从offending
令牌中获取问题的行和位置,或者如果未设置,则current
通过((Parser) re.getRecognizer()).getCurrentToken()
在RecognitionException
.
I'm fairly happy with how this is working, though having six reportX
methods to override makes me think there's a better way.
我对它的工作方式相当满意,尽管有六种reportX
方法可以覆盖让我认为有更好的方法。