El comportamiento predeterminado cuando el analizador no sabe qué hacer es imprimir mensajes en la terminal como:
línea 1:23 falta DECIMAL en '}'
Este es un buen mensaje, pero en el lugar equivocado. Prefiero recibir esto como una excepción.
Intenté usar el BailErrorStrategy
, pero esto arroja un ParseCancellationException
sin un mensaje (causado por un InputMismatchException
, también sin un mensaje).
¿Hay alguna manera de que pueda informar errores a través de excepciones mientras conserve la información útil en el mensaje?
Esto es lo que realmente busco: normalmente uso acciones en reglas para construir un objeto:
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); }
;
Luego, cuando invoco el analizador, hago algo como esto:
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;
}
Todo lo que realmente quiero es
- para que la
dataspec()
llamada arroje una excepción (idealmente una marcada) cuando la entrada no se puede analizar - para que esa excepción tenga un mensaje útil y proporcione acceso al número de línea y la posición donde se encontró el problema
Luego dejaré que esa excepción burbujee en la pila de llamadas donde sea más adecuado para presentar un mensaje útil al usuario, de la misma manera que manejaría una conexión de red caída, leer un archivo corrupto, etc.
Vi que las acciones ahora se consideran "avanzadas" en ANTLR4, así que tal vez estoy haciendo las cosas de una manera extraña, pero no he investigado cuál sería la forma "no avanzada" de hacer esto desde esta manera. ha funcionado bien para nuestras necesidades.
fuente
ThrowingErrorListener
clase como Singleton?Cuando usa
DefaultErrorStrategy
o elBailErrorStrategy
, elParserRuleContext.exception
campo se establece para cualquier nodo del árbol de análisis en el árbol de análisis resultante donde se produjo un error. La documentación de este campo dice (para las personas que no quieren hacer clic en un enlace adicional):Editar: si lo usa
DefaultErrorStrategy
, la excepción de contexto de análisis no se propagará hasta el código de llamada, por lo que podrá examinar elexception
campo directamente. Si lo usaBailErrorStrategy
, elParseCancellationException
arrojado por él incluirá unRecognitionException
if you callgetCause()
.if (pce.getCause() instanceof RecognitionException) { RecognitionException re = (RecognitionException)pce.getCause(); ParserRuleContext context = (ParserRuleContext)re.getCtx(); }
Edición 2: según su otra respuesta, parece que en realidad no desea una excepción, pero lo que desea es una forma diferente de informar los errores. En ese caso, estará más interesado en la
ANTLRErrorListener
interfaz. Desea llamarparser.removeErrorListeners()
para eliminar el oyente predeterminado que escribe en la consola y luego llamarparser.addErrorListener(listener)
a su propio oyente especial. A menudo utilizo el siguiente oyente como punto de partida, ya que incluye el nombre del archivo fuente con los mensajes.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); } }
Con esta clase disponible, puede usar lo siguiente para usarla.
Un ejemplo mucho más complicado de un detector de errores que utilizo para identificar ambigüedades que hacen que una gramática no sea SLL es la
SummarizingDiagnosticErrorListener
clase enTestPerformance
.fuente
((InputMismatchException) pce.getCause()).getCtx().exception
para llegar al útil mensaje de error?RecognitionException
. La información que desea está disponible en la excepción que ya se lanzó.Lo que se me ocurrió hasta ahora se basa en extender
DefaultErrorStrategy
y anular susreportXXX
métodos (aunque es muy posible que esté haciendo las cosas más complicadas de lo necesario):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()); } }
Esto arroja excepciones con mensajes útiles, y la línea y la posición del problema se pueden obtener del
offending
token o, si no está configurado, delcurrent
token mediante el uso((Parser) re.getRecognizer()).getCurrentToken()
deRecognitionException
.Estoy bastante contento con cómo funciona esto, aunque tener seis
reportX
métodos para anular me hace pensar que hay una mejor manera.fuente
Para cualquiera que esté interesado, aquí está el equivalente ANTLR4 C # de la respuesta de Sam Harwell:
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; }
fuente