Обработка ошибок в ANTLR4
Поведение по умолчанию, когда парсер не знает, что делать, – это печатать сообщения на терминале, например:
строка 1:23 отсутствует DECIMAL at ‘}’
Это хорошее сообщение, но не в том месте. Я предпочел бы получить это как исключение.
- Что случилось с переопределяемыми вызовами метода в конструкторах?
- Критерии Hibernate возвращают дочерние элементы несколько раз с помощью FetchType.EAGER
- Как получить первый день номера данной недели в Java
- Должен ли я использовать classы даты и времени Java или использовать стороннюю библиотеку, такую как Joda Time?
- Объяснение принципа get-put
Я попытался использовать BailErrorStrategy
, но это вызывает ParseCancellationException
без сообщения (вызванного 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 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 в настоящее время считается «продвинутым», так что, возможно, я странствую, но я не смотрел, что «непродвинутый» способ сделать это будет таким образом хорошо работает для наших нужд.
- Перечисления: Почему? Когда?
- Как реализовать карту с несколькими ключами?
- Почему заголовок JTable не отображается на изображении?
- NetBeans - развертывание всех в одном банке
- Как получить дату без времени в Java?
- Как разделять строку с любыми символами пробелов как разделителями?
- Приложение Java EE Enterprise: выполните некоторые действия по развертыванию / запуску
- Поиск библиотеки проверки орфографии Java
Поскольку у меня была небольшая борьба с двумя существующими ответами, я хотел бы поделиться решением, в котором я оказался.
Прежде всего, я создал свою собственную версию 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
вместо ParseCancellationException
RecognitionException
так как DefaultErrorStrategy поймает последнее, и он никогда не достигнет вашего собственного кода.
Создание целой новой ErrorStrategy, такой как Brad Mace, не требуется, поскольку 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
, см. Здесь .)
Это даст вам те же сообщения об ошибках, которые будут напечатаны на консоли по умолчанию, только в виде правильных исключений.
Когда вы используете BailErrorStrategy
или BailErrorStrategy
, поле ParserRuleContext.exception
устанавливается для любого дерева дерева parsingа в полученном дереве parsingа, где произошла ошибка. Документация для этого поля читается (для людей, которые не хотят нажимать дополнительную ссылку):
Исключение, которое вынудило это правило вернуться. Если правило успешно завершено, это значение равно
null
.
Изменить: если вы используете DefaultErrorStrategy
, исключение контекста синтаксиса не будет распространяться на весь код вызова, так что вы сможете напрямую изучить поле exception
. Если вы используете BailErrorStrategy
, ParseCancellationException
будет включать getCause()
RecognitionException
если вы вызываете getCause()
.
if (pce.getCause() instanceof RecognitionException) { RecognitionException re = (RecognitionException)pce.getCause(); ParserRuleContext context = (ParserRuleContext)re.getCtx(); }
Изменить 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); } }
Имея этот class, вы можете использовать его для его использования.
lexer.removeErrorListeners(); lexer.addErrorListener(DescriptiveErrorListener.INSTANCE); parser.removeErrorListeners(); parser.addErrorListener(DescriptiveErrorListener.INSTANCE);
Более сложный пример прослушивателя ошибок, который я использую для определения двусмысленностей, которые представляют собой грамматику non-SLL, представляет собой class 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
позволяет мне думать, что есть лучший способ.