Das Standardverhalten, wenn der Parser nicht weiß, was zu tun ist, besteht darin, Nachrichten wie folgt an das Terminal zu drucken:
Zeile 1:23 fehlt DECIMAL bei '}'
Dies ist eine gute Nachricht, aber am falschen Ort. Ich möchte dies lieber als Ausnahme erhalten.
Ich habe versucht, die zu verwenden BailErrorStrategy
, aber dies wirft eine ParseCancellationException
ohne Nachricht (verursacht durch eine InputMismatchException
, auch ohne Nachricht).
Gibt es eine Möglichkeit, Fehler über Ausnahmen zu melden, während die nützlichen Informationen in der Nachricht erhalten bleiben?
Folgendes ist mir wirklich wichtig: Normalerweise verwende ich Aktionen in Regeln, um ein Objekt aufzubauen:
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); }
;
Wenn ich dann den Parser aufrufe, mache ich so etwas:
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;
}
Alles was ich wirklich will ist
- Damit der
dataspec()
Aufruf eine Ausnahme auslöst (idealerweise eine aktivierte), wenn die Eingabe nicht analysiert werden kann - Damit diese Ausnahme eine nützliche Nachricht enthält und Zugriff auf die Zeilennummer und die Position bietet, an der das Problem gefunden wurde
Dann lasse ich diese Ausnahme den Callstack dahin sprudeln, wo es am besten geeignet ist, dem Benutzer eine nützliche Nachricht zu präsentieren - genauso wie ich mit einer unterbrochenen Netzwerkverbindung, dem Lesen einer beschädigten Datei usw. umgehen würde.
Ich habe gesehen, dass Aktionen in ANTLR4 jetzt als "fortgeschritten" angesehen werden, also gehe ich die Dinge vielleicht auf seltsame Weise an, aber ich habe nicht untersucht, wie die "nicht fortgeschrittene" Art, dies zu tun, seit dieser Weise aussehen würde hat gut für unsere Bedürfnisse gearbeitet.
quelle
ThrowingErrorListener
Klasse als Singleton zu verwenden?Wenn Sie das
DefaultErrorStrategy
oder das verwendenBailErrorStrategy
, wird dasParserRuleContext.exception
Feld für jeden Analysebaumknoten im resultierenden Analysebaum festgelegt, bei dem ein Fehler aufgetreten ist. Die Dokumentation für dieses Feld lautet (für Personen, die nicht auf einen zusätzlichen Link klicken möchten):Bearbeiten: Wenn Sie verwenden
DefaultErrorStrategy
, wird die Analysekontextausnahme nicht bis zum aufrufenden Code weitergegeben, sodass Sie dasexception
Feld direkt untersuchen können. Wenn Sie verwendenBailErrorStrategy
, enthält dasParseCancellationException
von ihm geworfene ein,RecognitionException
wenn Sie anrufengetCause()
.if (pce.getCause() instanceof RecognitionException) { RecognitionException re = (RecognitionException)pce.getCause(); ParserRuleContext context = (ParserRuleContext)re.getCtx(); }
Bearbeiten 2: Basierend auf Ihrer anderen Antwort scheint es, dass Sie eigentlich keine Ausnahme wollen, aber was Sie wollen, ist eine andere Art, die Fehler zu melden. In diesem Fall interessieren Sie sich mehr für die
ANTLRErrorListener
Benutzeroberfläche. Sie möchten aufrufenparser.removeErrorListeners()
, um den Standard-Listener zu entfernen, der in die Konsole schreibt, und dannparser.addErrorListener(listener)
Ihren eigenen speziellen Listener aufrufen . Ich verwende oft den folgenden Listener als Ausgangspunkt, da er den Namen der Quelldatei mit den Nachrichten enthält.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); } }
Wenn diese Klasse verfügbar ist, können Sie sie wie folgt verwenden.
Ein viel komplizierteres Beispiel für einen Fehler-Listener, mit dem ich Mehrdeutigkeiten identifiziere, die eine Grammatik nicht SLL machen, ist die
SummarizingDiagnosticErrorListener
Klasse inTestPerformance
.quelle
((InputMismatchException) pce.getCause()).getCtx().exception
, um an die nützliche Fehlermeldung zu gelangen?RecognitionException
. Die gewünschten Informationen sind in der Ausnahme verfügbar, die bereits ausgelöst wird.Was ich bisher entwickelt habe, basiert auf der Erweiterung
DefaultErrorStrategy
und Überschreibung derreportXXX
Methoden (obwohl es durchaus möglich ist, dass ich die Dinge komplizierter als nötig mache):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()); } }
Dies löst Ausnahmen mit nützlichen Nachrichten aus, und die Zeile und Position des Problems kann entweder vom
offending
Token oder, falls dies nicht festgelegt ist, vomcurrent
Token mithilfe von((Parser) re.getRecognizer()).getCurrentToken()
auf dem Token abgerufen werdenRecognitionException
.Ich bin ziemlich zufrieden mit der Funktionsweise, obwohl
reportX
ich denke, dass es einen besseren Weg gibt , wenn ich sechs Methoden zum Überschreiben habe.quelle