Domyślnym zachowaniem, gdy parser nie wie, co zrobić, jest drukowanie wiadomości na terminalu, takich jak:
w wierszu 1:23 brakuje DECIMAL w „}”
To dobra wiadomość, ale w niewłaściwym miejscu. Wolałbym przyjąć to jako wyjątek.
Próbowałem użyć BailErrorStrategy
, ale to rzuca ParseCancellationException
bez komunikatu (spowodowane przez InputMismatchException
, również bez komunikatu).
Czy istnieje sposób, aby zgłosić błędy za pośrednictwem wyjątków, zachowując przydatne informacje w wiadomości?
Oto, czego naprawdę szukam - zwykle używam akcji w regułach, aby zbudować obiekt:
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); }
;
Następnie, kiedy wywołuję parser, robię coś takiego:
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;
}
Wszystko, czego naprawdę chcę, to
- aby
dataspec()
wywołanie zgłosiło wyjątek (najlepiej zaznaczony), gdy nie można przeanalizować danych wejściowych - aby ten wyjątek miał użyteczną wiadomość i zapewniał dostęp do numeru linii i pozycji, w której znaleziono problem
Następnie pozwolę temu wyjątkowi wypłynąć w górę stosu wywołań, gdzie najlepiej nadaje się do przedstawienia użytkownikowi użytecznej wiadomości - w ten sam sposób, w jaki poradziłbym sobie z zerwaniem połączenia sieciowego, odczytaniem uszkodzonego pliku itp.
Widziałem, że akcje są teraz uważane za "zaawansowane" w ANTLR4, więc może zajmuję się rzeczami w dziwny sposób, ale nie sprawdziłem, jaki byłby "niezaawansowany" sposób, aby to zrobić od tego czasu działa dobrze na nasze potrzeby.
źródło
ThrowingErrorListener
klasy jako singletona?W przypadku użycia
DefaultErrorStrategy
lubBailErrorStrategy
,ParserRuleContext.exception
pole jest ustawiane dla dowolnego węzła drzewa analizy w wynikowym drzewie analizy, w którym wystąpił błąd. Dokumentacja dla tego pola brzmi (dla osób, które nie chcą klikać dodatkowego łącza):Edycja: jeśli używasz
DefaultErrorStrategy
, wyjątek kontekstu analizy nie będzie propagowany aż do kodu wywołującego, więc będziesz mógłexception
bezpośrednio zbadać pole. Jeśli użyjeszBailErrorStrategy
,ParseCancellationException
rzucony przez niego będzie zawierałRecognitionException
jeśli zadzwoniszgetCause()
.if (pce.getCause() instanceof RecognitionException) { RecognitionException re = (RecognitionException)pce.getCause(); ParserRuleContext context = (ParserRuleContext)re.getCtx(); }
Edycja 2: Na podstawie innej odpowiedzi wydaje się, że tak naprawdę nie chcesz wyjątku, ale chcesz innego sposobu zgłaszania błędów. W takim przypadku
ANTLRErrorListener
interfejs będzie bardziej zainteresowany . Chcesz wywołać,parser.removeErrorListeners()
aby usunąć domyślny odbiornik, który zapisuje na konsoli, a następnie wywołaćparser.addErrorListener(listener)
własny specjalny odbiornik. Często jako punkt wyjścia używam następującego nasłuchiwania, ponieważ zawiera on nazwę pliku źródłowego z wiadomościami.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); } }
Mając dostępną tę klasę, możesz użyć następujących elementów.
O wiele bardziej skomplikowanym przykładem detektora błędów, którego używam do identyfikowania niejednoznaczności, które powodują, że gramatyka nie jest SLL, jest
SummarizingDiagnosticErrorListener
klasa wTestPerformance
.źródło
((InputMismatchException) pce.getCause()).getCtx().exception
aby uzyskać przydatny komunikat o błędzie?RecognitionException
. Żądane informacje są dostępne w wyjątku, który już został zgłoszony.To, co do tej pory wymyśliłem, opiera się na rozszerzaniu
DefaultErrorStrategy
i zastępowaniu jejreportXXX
metod (chociaż jest całkiem możliwe, że robię rzeczy bardziej skomplikowane niż to konieczne):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()); } }
Spowoduje to zgłoszenie wyjątków z przydatnymi komunikatami, a wiersz i położenie problemu można uzyskać z
offending
tokenu lub, jeśli nie jest ustawiony, zcurrent
tokenu za pomocą polecenia((Parser) re.getRecognizer()).getCurrentToken()
naRecognitionException
.Jestem dość zadowolony z tego, jak to działa, chociaż mając sześć
reportX
metod zastąpienia, myślę, że istnieje lepszy sposób.źródło
Dla wszystkich zainteresowanych, oto odpowiednik odpowiedzi Sama Harwella w języku 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; }
źródło