Saltar al contenido

Manejo de errores en ANTLR4

Solución:

Como he tenido un poco de dificultad con las dos respuestas existentes, me gustaría compartir la solución con la que terminé.

En primer lugar, creé mi propia versión de ErrorListener como sugirió 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 un ParseCancellationException en lugar de un RecognitionException ya que DefaultErrorStrategy capturaría este último y nunca alcanzaría su propio código.

No es necesario crear una ErrorStrategy completamente nueva como sugirió Brad Mace, ya que DefaultErrorStrategy produce mensajes de error bastante buenos de forma predeterminada.

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 qué MyParseRules hace, ver 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.

Cuando usa el DefaultErrorStrategy o la BailErrorStrategy, los ParserRuleContext.exception El campo se establece para cualquier nodo del árbol de análisis sintáctico 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):

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

Editar: Si utiliza DefaultErrorStrategy, la excepción de contexto de análisis no se propagará hasta el código de llamada, por lo que podrá examinar el exception campo directamente. Si utiliza BailErrorStrategy, los ParseCancellationException arrojado por él incluirá un RecognitionException si llamas getCause().

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 el ANTLRErrorListener interfaz. Quieres llamar parser.removeErrorListeners() para eliminar el oyente predeterminado que escribe en la consola y luego llamar parser.addErrorListener(listener) para 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.

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

A mucho Un ejemplo más complicado de un oyente de errores que utilizo para identificar ambigüedades que hacen que una gramática no sea SLL es el SummarizingDiagnosticErrorListener clase en TestPerformance.

Lo que se me ocurrió hasta ahora se basa en extender DefaultErrorStrategy y anulando es reportXXX 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
        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, desde el current token usando ((Parser) re.getRecognizer()).getCurrentToken() sobre el RecognitionException.

Estoy bastante contento con cómo funciona esto, aunque tengo seis reportX métodos para anular me hace pensar que hay una mejor manera.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)


Tags :

Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *