Обработка ошибок в ANTLR4
поведение по умолчанию, когда синтаксический анализатор не знает, что делать, это печатать сообщения на терминал, как:
строка 1: 23 отсутствует десятичное число в'}'
это хорошее сообщение, но не в том месте. Я бы предпочел получить это как исключение.
Я пробовал использовать BailErrorStrategy
, но это бросает!--3--> без сообщения (вызвано InputMismatchException
, также без сообщения).
есть ли способ заставить его сообщать об ошибках через исключения, сохраняя полезную информацию в сообщении?
вот что мне действительно нужно-я обычно использую действия в правилах для создания объекта:
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); }
;
затем, когда я вызываю парсер, я делаю что-то вроде этого:
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;
}
все, что я действительно хочу-это
- на
dataspec()
вызов для создания исключения (в идеале проверенного), когда вход не может быть проанализирован - для этого исключения, чтобы иметь полезное сообщение и обеспечить доступ к номеру линии и позиции, где была обнаружена проблема
тогда я позволю этому исключению всплывать в стеке вызовов, где лучше всего подходит для представления полезного сообщения пользователю-так же, как я бы обрабатывал удаленное сетевое соединение, чтение поврежденного файла и т. д.
Я видел, что действия теперь считаются "продвинутыми" в ANTLR4, поэтому, возможно, я иду о вещах странным образом, но я не смотрел на то, что "не продвинутый" способ сделать это было бы так как этот способ хорошо работает для наших нужд.
3 ответа:
поскольку у меня была небольшая борьба с двумя существующими ответами, я хотел бы поделиться решением, с которым я закончил.
прежде всего, я создал свою собственную версию ErrorListener, как Сэм Харвелл предлагается:
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); } }
обратите внимание на использование
ParseCancellationException
вместоRecognitionException
поскольку DefaultErrorStrategy поймает последний, и он никогда не достигнет вашего собственного кода.создание совершенно новой ErrorStrategy, как бред Мейс предложение не требуется, так как DefaultErrorStrategy по умолчанию выдает довольно хорошие сообщения об ошибках.
затем я использую пользовательский 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); }
(для получения дополнительной информации о том, что
MyParseRules
не вижу здесь.)это даст вам те же сообщения об ошибках, которые будут напечатаны на консоли по умолчанию, только в виде соответствующих исключений.
при использовании
DefaultErrorStrategy
илиBailErrorStrategy
наParserRuleContext.exception
поле устанавливается для любого узла дерева синтаксического анализа в результирующем дереве синтаксического анализа, где произошла ошибка. Документация для этого поля гласит (для людей, которые не хотят нажимать дополнительную ссылку):исключение, которое заставило это правило вернуться. Если правило успешно завершено, это
null
.Edit: если вы используйте
DefaultErrorStrategy
, исключение контекста разбора не будет распространяться на весь путь до вызывающего кода, поэтому вы сможете изучитьexception
поле напрямую. Если вы используетеBailErrorStrategy
наParseCancellationException
брошенный им будет включать в себяRecognitionException
если вы называетеgetCause()
.if (pce.getCause() instanceof RecognitionException) { RecognitionException re = (RecognitionException)pce.getCause(); ParserRuleContext context = (ParserRuleContext)re.getCtx(); }
Edit 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); } }
С этим классом доступны, вы можете использовать следующее, чтобы использовать его.
lexer.removeErrorListeners(); lexer.addErrorListener(DescriptiveErrorListener.INSTANCE); parser.removeErrorListeners(); parser.addErrorListener(DescriptiveErrorListener.INSTANCE);
A много более сложный пример прослушивателя ошибок, который я использую для выявления двусмысленностей, которые делают грамматику не SLL, - это
SummarizingDiagnosticErrorListener
классTestPerformance
.
то, что я придумал до сих пор, основано на расширении
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()); } }
это вызывает исключения с полезными сообщениями, и линия и положение проблемы могут быть получены из любого
offending
маркер, или если это не установлено, отcurrent
маркер с помощью((Parser) re.getRecognizer()).getCurrentToken()
наRecognitionException
.Я довольно доволен тем, как это работает, хотя имея шесть
reportX
методы для переопределения заставляет меня думать, что есть лучший способ.