2015-04-23 13 views
12

Mam prostą gramatykę ANLTR i towarzyszący mu Gość. Wszystko działa świetnie, chyba że dane wejściowe są nieprawidłowe. Jeśli dane wejściowe są nieprawidłowe, błędy zostaną połknięte, a mój kalkulator zostanie wyświetlony z nieprawidłowym wynikiem.Czy można podać wyjątek, jeśli dane wejściowe nie są prawidłowe?

Próbowałem już zaimplementować detektor błędów, korzystając z metody lexera Recover i ... no ... jeszcze pół tuzina innych rzeczy. Czy ktoś może mi pokazać, jak po prostu rzucić błąd, zamiast połknąć złe "żetony"? (. Używam cudzysłowu, bo są nie znaki na wszystkie znaki są niezdefiniowane w moim gramatyki.)

Ważny Wejście:

1 + 2 * 3 - 4

Nieprawidłowy Wejście:

1 + 2 + 3 (4)

Chcę rzucić ArgumentException, jeśli parser/lexer natknie się na nawias (lub dowolną inną niezdefiniowaną postać). Obecnie wydaje się, że nieprawidłowe postacie znikają w eterze, a parser po prostu ślizga się, jakby nic nie było nie tak.

Jeśli uruchomię go w konsoli za pomocą komendy grun, otrzymam następujące dane wyjściowe, więc rozpoznaje nieprawidłowe tokeny na pewnym poziomie.

Linia 1: 9 błąd żeton na uznanie: '(' 1:11 błąd

linia znak uznania pod adresem: ')'

i ten wynikający drzewo parse.

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

Kalkulator:

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

Spójrz na tę odpowiedź od autora Antlr4cs: http://stackoverflow.com/a/18486405/2573395 – Alex

+0

Yup. Próbowałem tego @Alex. Odziedziczyłem po 'BaseErrorListener' i dołączałem go do mojego parsera, ale żadna z tych metod nigdy nie została wywołana. – RubberDuck

+0

Uwaga dla siebie, nad jazdą coś tutaj może pomóc. Wydaje się, że nie ma zbyt wiele czasu, aby zapewnić zakończenie analizowania, gdy potrzebuję go zatrzymać. https://github.com/antlr/antlr4/blob/master/runtime/Java/src/org/antlr/v4/runt/DefaultErrorStrategy.java – RubberDuck

Odpowiedz

5

@CoronA was right. The error happens in the lexer..Tak więc, chociaż wciąż myślę, że utworzenie ErrorStrategy byłoby lepsze, to jest to, co faktycznie zadziałało dla mnie i mojego celu rzucania wyjątku dla niezdefiniowanych danych wejściowych.

Najpierw tworzone klasę pochodnych, które dziedziczy BaseErrorListeneri narzędzi IAntlrErrorListener<T>. Druga część była moim problemem od samego początku. Ponieważ mój użytkownik odziedziczył po FooBarBaseVistor<int>, mój detektor błędów również musi być typu, aby zarejestrować go przy pomocy mojego lexera.

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

I zmieniłem Calculator klasę dołączyć mój zwyczaj błędzie słuchacza do lexer. Zauważ, że nie musisz usuwać ConsoleListener, tak jak robiłem to, aby błąd został faktycznie zgłoszony. Ponieważ tak naprawdę tego nie używam, uznałem, że najlepiej jest iść dalej i to zrobić.

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

I to wszystko. Wyjątek argumentu jest zgłaszany, a ten test przechodzi teraz.

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

Jedna ostatnia uwaga. Jeśli wrzucisz tutaj RecognitionException, zostanie ona ponownie połknięta. Polecenie ParseCancelationException jest zalecane, ponieważ nie pochodzi z RecognitionException, ale wybieram ArgumentException, ponieważ czułem, że ma to największy sens dla kodu C# klienta.

10

Właściwie każdy komunikat o błędzie jest spowodowany przez wyjątek. Ten wyjątek został przechwycony, a parser próbuje odzyskać. Drzewo analizy jest wynikiem odzyskiwania.

Ponieważ błąd występuje w lexer (lexer po prostu nie zna znaków ( lub )), obsługa błędów musi zostać dołączona do lexer. W Javie będzie to wyglądać następująco:

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

Składnia C# nie powinna być daleko od tego. Jednak zalecam, aby nie rzucać wyjątku. Lepiej zbieraj błędy na liście i zgłoś je po ukończeniu leksykonu i nie rozpoczynaj analizowania, jeśli lista błędów nie jest pusta.

+0

"BailErrorStrategy" również nie może podnieść żadnych wyjątków. Otrzymuję takie same wyniki, jak w przypadku 'DefaultErrorStrategy' – RubberDuck

+0

Myliłem się. Rzeczywiście parser i lexer są ściśle oddzielone w ANTLR, więc moje pierwsze rozwiązanie użycia ErrorStrategy na parserze nie działałoby. Mimo to dołączenie słuchacza do leksera zrobi to. Poprawiłem moją odpowiedź, aby opisać rozwiązanie – CoronA

+0

Rozwiązałem to dzięki twojemu popchnięciu we właściwym kierunku. Dziękuję Ci bardzo. – RubberDuck