2015-04-23 10 views
12

Ho una semplice grammatica ANLTR e un accompagnatore. Tutto funziona alla grande, a meno che l'input non sia valido. Se l'input non è valido, gli errori vengono ingeriti e la mia calcolatrice esce con l'output sbagliato.È possibile generare un'eccezione se l'input non è valido?

Ho provato a implementare un listener di errori, a cavallo del metodo Recover del lexer e ... beh ... una mezza dozzina di altre cose oggi. Qualcuno può mostrarmi come lanciare semplicemente un errore invece di inghiottire brutti "token"? (. Io uso le virgolette perché sono non gettoni a tutti i personaggi non sono definiti nella mia grammatica.)

ingresso valido:

1 + 2 * 3 - 4

Invalid Input:

1 + 2 + 3 (4)

Voglio lanciare un ArgumentException se il parser/lexer trova la parentesi (o qualsiasi altro carattere non definito). Attualmente, i personaggi non validi sembrano scomparire nell'etere e il parser si muove semplicemente come se nulla fosse sbagliato.

Se lo eseguo nella console con il comando grun, ottengo il seguente output, quindi riconosce i token non validi su qualche livello.

linea 1: errore di 9 Token riconoscimento a: '(' errore

linea di token 01:11 riconoscimento a: ')'

e questo albero sintattico risultante.

enter image description here

BasicMath.g4

grammar BasicMath; 

/* 
* Parser Rules 
*/ 

compileUnit : expression+ EOF; 

expression : 
    expression MULTIPLY expression #Multiplication 
    | expression DIVIDE expression #Division 
    | expression ADD expression #Addition 
    | expression SUBTRACT expression #Subtraction 
    | NUMBER #Number 
    ; 

/* 
* Lexer Rules 
*/ 

NUMBER : INT; //Leave room to extend what kind of math we can do. 

INT : ('0'..'9')+; 
MULTIPLY : '*'; 
DIVIDE : '/'; 
SUBTRACT : '-'; 
ADD : '+'; 

WS : [ \t\r\n] -> channel(HIDDEN); 

Calcolatrice:

public static class Calculator 
{ 
    public static int Evaluate(string expression) 
    { 
     var lexer = new BasicMathLexer(new AntlrInputStream(expression)); 
     var tokens = new CommonTokenStream(lexer); 
     var parser = new BasicMathParser(tokens); 

     var tree = parser.compileUnit(); 

     var visitor = new IntegerMathVisitor(); 

     return visitor.Visit(tree); 
    } 
} 
+0

Dai un'occhiata a questa risposta dell'autore Antlr4cs: http://stackoverflow.com/a/18486405/2573395 – Alex

+0

Yup. Ho provato che @Alex. Ho ereditato dal 'BaseErrorListener' e l'ho collegato al mio parser, ma nessuno di questi metodi è mai stato chiamato. – RubberDuck

+0

Nota per se stessi, cavalcare qualcosa qui dentro potrebbe aiutare. Sembra che siano andate le lunghe distanze per garantire che l'analisi sia completa quando ne ho bisogno. https://github.com/antlr/antlr4/blob/master/runtime/Java/src/org/antlr/v4/runtime/DefaultErrorStrategy.java – RubberDuck

risposta

5

@CoronA was right. The error happens in the lexer..Quindi, mentre continuo a pensare che la creazione di un errore di strategia sarebbe migliore, questo è ciò che ha effettivamente funzionato per me e il mio obiettivo di generare un'eccezione per l'input non definito.

In primo luogo, ho creato una classe derivata che eredita da BaseErrorListenere attrezzi IAntlrErrorListener<T>. La seconda parte è stata il mio problema da sempre. Poiché il mio visitatore ha ereditato da FooBarBaseVistor<int>, anche il mio listener di errori doveva essere di tipo per registrarlo con il mio lexer.

class ThrowExceptionErrorListener : BaseErrorListener, IAntlrErrorListener<int> 
{ 
    //BaseErrorListener implementation; not called in my test, but left it just in case 

    public override void SyntaxError(IRecognizer recognizer, IToken offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e) 
    { 
     throw new ArgumentException("Invalid Expression: {0}", msg, e); 
    } 

    //IAntlrErrorListener<int> implementation; this one actually gets called. 

    public void SyntaxError(IRecognizer recognizer, int offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e) 
    { 
     throw new ArgumentException("Invalid Expression: {0}", msg, e); 
    } 
} 

e ha cambiato la mia classe Calculator per attaccare il mio ascoltatore di errore personalizzato al lexer. Si noti che non è necessario rimuovere lo ConsoleListener come se l'errore fosse effettivamente stato generato. Dal momento che non lo sto davvero usando, ho pensato che fosse meglio andare avanti e farlo.

public static class Calculator 
{ 
    public static int Evaluate(string expression) 
    { 
     var lexer = new BasicMathLexer(new AntlrInputStream(expression)); 
     lexer.RemoveErrorListeners(); //removes the default console listener 
     lexer.AddErrorListener(new ThrowExceptionErrorListener()); 

     var tokens = new CommonTokenStream(lexer); 
     var parser = new BasicMathParser(tokens); 

     var tree = parser.compileUnit(); 

     var visitor = new IntegerMathVisitor(); 

     return visitor.Visit(tree); 
    } 
} 

E questo è tutto. Viene generata un'eccezione di argomento e ora questo test viene superato.

[TestMethod] 
    [ExpectedException(typeof(ArgumentException))] 
    public void BadInput() 
    { 
     var expr = "1 + 5 + 2(3)"; 
     int value = Calculator.Evaluate(expr); 
    } 

Un'ultima nota. Se lanci un RecognitionException qui, sarà di nuovo inghiottito. ParseCancelationException è consigliato, perché non deriva da RecognitionException, ma scelgo uno ArgumentException perché ho ritenuto che fosse più sensato per il codice C# del client.

10

In realtà ogni messaggio di errore è causato da un'eccezione. Questa eccezione viene catturata e il parser tenta di recuperare. L'albero di analisi è il risultato del recupero.

Poiché l'errore si verifica nel lexer (il lexer semplicemente non conosce i caratteri ( o )), la gestione degli errori deve essere allegata al lexer. In Java questo sarebbe:

lexer.addErrorListener(new BaseErrorListener() { 
     @Override 
     public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) { 
      throw new RuntimeException(e); 
     } 
    }); 

La sintassi C# non dovrebbe essere lontana da quello. Eppure raccomando di non fare un'eccezione. È meglio raccogliere gli errori in un elenco e segnalarli dopo che il lexer è terminato e non iniziare l'analisi se l'elenco degli errori non è vuoto.

+0

'BailErrorStrategy' non riesce a generare alcuna eccezione. Ottengo gli stessi risultati che ho con la 'DefaultErrorStrategy' – RubberDuck

+0

mi sono sbagliato. In realtà, parser e lexer sono rigorosamente separati in ANTLR, quindi la mia prima soluzione per utilizzare ErrorStrategy sul parser non funzionerebbe. Tuttavia associare un ascoltatore al lexer lo farà. Ho corretto la mia risposta per descrivere la soluzione – CoronA

+0

Risolto grazie alla tua spinta nella giusta direzione. Grazie mille. – RubberDuck

Problemi correlati