java error-handling antlr4

java - Manejo de errores en ANTLR4



error-handling (3)

Cuando utiliza DefaultErrorStrategy o BailErrorStrategy , el campo ParserRuleContext.exception se establece para cualquier nodo de árbol de análisis sintáctico en el árbol de análisis sintáctico resultante donde se produjo un error. La documentación para este campo se lee (para las personas que no desean hacer clic en un enlace adicional):

La excepción que forzó a esta regla a regresar. Si la regla se completó con éxito, esto es null .

Editar: si usa DefaultErrorStrategy , la excepción de contexto de análisis no se propagará hasta el código de llamada, por lo que podrá examinar directamente el campo de exception . Si usa BailErrorStrategy , la ParseCancellationException lanzada incluirá una RecognitionException si llama a getCause() .

if (pce.getCause() instanceof RecognitionException) { RecognitionException re = (RecognitionException)pce.getCause(); ParserRuleContext context = (ParserRuleContext)re.getCtx(); }

Editar 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 interfaz ANTLRErrorListener . Desea llamar a parser.removeErrorListeners() para eliminar el oyente predeterminado que escribe en la consola, y luego llamar a parser.addErrorListener(listener) para su propio oyente especial. A menudo uso 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.

lexer.removeErrorListeners(); lexer.addErrorListener(DescriptiveErrorListener.INSTANCE); parser.removeErrorListeners(); parser.addErrorListener(DescriptiveErrorListener.INSTANCE);

Un ejemplo mucho más complicado de un detector de errores que uso para identificar ambigüedades que hacen que una gramática no SLL sea la clase SummarizingDiagnosticErrorListener en TestPerformance .

El comportamiento predeterminado cuando el analizador no sabe qué hacer es imprimir mensajes a 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.

He intentado usar BailErrorStrategy , pero esto arroja una ParseCancellationException sin un mensaje (causada por una InputMismatchException , también sin un mensaje).

¿Hay alguna forma de que pueda informar los errores a través de excepciones mientras conserva la información útil en el mensaje?

Esto es lo que realmente busco: normalmente utilizo 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 llamada a dataspec() arroje una excepción (idealmente una marcada) cuando la entrada no puede ser analizada
  • 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 suba por la pila de llamadas donde sea más adecuado para presentar un mensaje útil al usuario, del mismo modo que manejaría una conexión de red descartada, leyendo un archivo corrupto, etc.

Vi que las acciones ahora se consideran "avanzadas" en ANTLR4, así que tal vez estoy haciendo cosas de una manera extraña, pero no he investigado cuál sería la forma "no avanzada" de hacerlo, ya que de esta manera ha estado funcionando bien para nuestras necesidades.


Lo que he encontrado hasta ahora se basa en extender DefaultErrorStrategy y reemplazar reportXXX métodos de reportXXX (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 pueden obtenerse del token offending o, si no se establece, del token current mediante ((Parser) re.getRecognizer()).getCurrentToken() en la RecognitionException .

Estoy bastante contento con la forma en que esto está funcionando, aunque tener seis métodos reportX para anular me hace pensar que hay una mejor manera.


Como he tenido algunas dificultades con las dos respuestas existentes, me gustaría compartir la solución con la que terminé.

Antes que nada, creé mi propia versión de un ErrorListener como sugería Sam Harwell :

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); } }

Tenga en cuenta el uso de una ParseCancellationException lugar de una RecognitionException ya que DefaultErrorStrategy atraparía a la última y nunca llegaría a su propio código.

No es necesario crear una estrategia de error completamente nueva como Brad Mace, ya que DefaultErrorStrategy produce mensajes de error bastante buenos por defecto.

Luego uso el ErrorListener personalizado en mi función de análisis:

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); }

(Para obtener más información sobre lo que MyParseRules hace, consulte aquí ).

Esto le dará los mismos mensajes de error que se imprimirían en la consola de forma predeterminada, solo en forma de excepciones adecuadas.