Поведение по умолчанию, когда парсер не знает, что делать, - это выводить на терминал сообщения, например:
строка 1:23 отсутствует DECIMAL в '}'
Это хорошее сообщение, но не в том месте. Я бы предпочел получить это как исключение.
Я пробовал использовать 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<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, поэтому, возможно, я делаю что-то странным образом, но я не изучал, каким будет "не продвинутый" способ сделать это, поскольку этот способ хорошо работает для наших нужд.
источник
ThrowingErrorListener
класс как синглтон?Когда вы используете
DefaultErrorStrategy
илиBailErrorStrategy
,ParserRuleContext.exception
поле устанавливается для любого узла дерева синтаксического анализа в результирующем дереве синтаксического анализа, где произошла ошибка. Документация для этого поля гласит (для людей, которые не хотят нажимать лишнюю ссылку):Изменить: если вы используете
DefaultErrorStrategy
, исключение контекста синтаксического анализа не будет распространяться полностью на вызывающий код, поэтому вы сможетеexception
напрямую проверить поле. Если вы используетеBailErrorStrategy
,ParseCancellationException
брошенный им будет включать,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); } }
Имея этот класс, вы можете использовать его следующим образом.
Гораздо более сложный пример слушателя ошибки , которые я использую , чтобы определить двусмысленности , которые делают грамматику , не SLL является
SummarizingDiagnosticErrorListener
классTestPerformance
.источник
((InputMismatchException) pce.getCause()).getCtx().exception
полезного сообщения об ошибке?RecognitionException
. Необходимая информация доступна в уже созданном исключении.То, что я придумал до сих пор, основано на расширении
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
методов для переопределения заставляет меня думать, что есть способ лучше.источник
Для всех, кто интересуется, вот ANTLR4 C # эквивалент ответа Сэма Харвелла:
using System; using System.IO; using Antlr4.Runtime; public class DescriptiveErrorListener : BaseErrorListener, IAntlrErrorListener<int> { public static DescriptiveErrorListener Instance { get; } = new DescriptiveErrorListener(); public void SyntaxError(TextWriter output, IRecognizer recognizer, int offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e) { if (!REPORT_SYNTAX_ERRORS) return; string sourceName = recognizer.InputStream.SourceName; // never ""; might be "<unknown>" == IntStreamConstants.UnknownSourceName sourceName = $"{sourceName}:{line}:{charPositionInLine}"; Console.Error.WriteLine($"{sourceName}: line {line}:{charPositionInLine} {msg}"); } public override void SyntaxError(TextWriter output, IRecognizer recognizer, Token offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e) { this.SyntaxError(output, recognizer, 0, line, charPositionInLine, msg, e); } static readonly bool REPORT_SYNTAX_ERRORS = true; }
источник