2016-03-10 21 views
12

Ich habe eine ANTLR 4 Grammatik und baute daraus einen Lexer und Parser. Jetzt versuche ich, diesen Parser so zu instanziieren, dass er analysiert wird, bis er auf einen Fehler stößt. Wenn es zu einem Fehler kommt, sollte es nicht weiter analysieren, aber es sollte nützliche Informationen über das Problem liefern; idealerweise ein maschinenlesbarer Ort und eine von Menschen lesbare Nachricht. HierAbbruch auf Parse Fehler mit nützlichen Nachricht

ist das, was ich im Moment haben:

grammar Toy; 

@parser::members { 

    public static void main(String[] args) { 
     for (String arg: args) 
      System.out.println(arg + " => " + parse(arg)); 
    } 

    public static String parse(String code) { 
     ErrorListener errorListener = new ErrorListener(); 
     CharStream cstream = new ANTLRInputStream(code); 
     ToyLexer lexer = new ToyLexer(cstream); 
     lexer.removeErrorListeners(); 
     lexer.addErrorListener(errorListener); 
     TokenStream tstream = new CommonTokenStream(lexer); 
     ToyParser parser = new ToyParser(tstream); 
     parser.removeErrorListeners(); 
     parser.addErrorListener(errorListener); 
     parser.setErrorHandler(new BailErrorStrategy()); 
     try { 
      String res = parser.top().str; 
      if (errorListener.message != null) 
       return "Parsed, but " + errorListener.toString(); 
      return res; 
     } catch (ParseCancellationException e) { 
      if (errorListener.message != null) 
       return "Failed, because " + errorListener.toString(); 
      throw e; 
     } 
    } 

    static class ErrorListener extends BaseErrorListener { 

     String message = null; 
     int start = -2, stop = -2, line = -2; 

     @Override 
     public void syntaxError(Recognizer<?, ?> recognizer, 
           Object offendingSymbol, 
           int line, 
           int charPositionInLine, 
           String msg, 
           RecognitionException e) { 
      if (message != null) return; 
      if (offendingSymbol instanceof Token) { 
       Token t = (Token) offendingSymbol; 
       start = t.getStartIndex(); 
       stop = t.getStopIndex(); 
      } else if (recognizer instanceof ToyLexer) { 
       ToyLexer lexer = (ToyLexer)recognizer; 
       start = lexer._tokenStartCharIndex; 
       stop = lexer._input.index(); 
      } 
      this.line = line; 
      message = msg; 
     } 

     @Override public String toString() { 
      return start + "-" + stop + " l." + line + ": " + message; 
     } 
    } 

} 

top returns [String str]: e* EOF {$str = "All went well.";}; 
e: 'a' 'b' | 'a' 'c' e; 

speichern diese auf Toy.g, dann diese Befehle versuchen:

> java -jar antlr-4.5.2-complete.jar Toy.g 
> javac -cp antlr-4.5.2-complete.jar Toy*.java 
> java -cp .:tools/antlr-4.5.2-complete.jar ToyParser ab acab acc axb abc 
ab => All went well. 
acab => All went well. 
acc => Failed, because 2-2 l.1: no viable alternative at input 'c' 
axb => Parsed, but 1-1 l.1: token recognition error at: 'x' 
Exception in thread "main" org.antlr.v4.runtime.misc.ParseCancellationException 
    at org.antlr.v4.runtime.BailErrorStrategy.recoverInline(BailErrorStrategy.java:90) 
    at org.antlr.v4.runtime.Parser.match(Parser.java:229) 
    at ToyParser.top(ToyParser.java:187) 
    at ToyParser.parse(ToyParser.java:95) 
    at ToyParser.main(ToyParser.java:80) 
Caused by: org.antlr.v4.runtime.InputMismatchException 
    at org.antlr.v4.runtime.BailErrorStrategy.recoverInline(BailErrorStrategy.java:85) 
    ... 4 more 

Zum einen glaube ich, dass ich bin mach schon zu viel. Wenn ich mir anschaue, wie viel Code ich für eine einfache und häufige Aufgabe geschrieben habe, kann ich nicht anders, als mich zu fragen, ob mir eine einfachere Lösung fehlt. Auf der anderen Seite scheint das aus zwei Gründen nicht genug zu sein. Erstens, obwohl es mir gelungen ist, einen Lexer-Fehler zu melden, hindern sie den Parser nicht daran, den verbleibenden Stream fortzusetzen. Dies wird durch die Parsed, but Zeichenfolge für den Eingang axb belegt. Und zweitens sind immer noch Fehler vorhanden, die nicht an den Fehler-Listener gemeldet werden, wie der Stack-Trace zeigt.

Wenn ich die BailErrorStrategy nicht installieren, erhalte ich mehr Nutzleistung:

acc => Parsed, but 2-2 l.1: mismatched input 'c' expecting 'a' 
axb => Parsed, but 1-1 l.1: token recognition error at: 'x' 
abc => Parsed, but 2-2 l.1: extraneous input 'c' expecting {<EOF>, 'a'} 

Gibt es eine Möglichkeit, diese Art von Fehlermeldungen zu erhalten, aber immer noch auf Fehler bürgen? Ich kann see from the sources, dass die extraneous input Nachricht in der Tat von der DefaultErrorStrategy generiert wird, offenbar nachdem es ausgearbeitet hat, wie es über die Behebung des Problems gehen würde. Sollte ich es tun lassen und dann Kaution, d. H. Meine eigene Variante von BailErrorStrategy schreiben, die vor dem Werfen zu super ruft?

Antwort

7

In der gleichen Situation endete ich mit der Erweiterung DefaultErrorStrategy und überschreiben report* Methoden. Es ist ziemlich einfach (Sie können auch ANTLRErrorStrategy verwenden).

Here können Sie ein Beispiel für Fail-Fast-Strategie finden. Ich denke in Ihrem Fall können Sie alle Fehler auf die gleiche Weise sammeln und einen detaillierten Bericht erstellen.

+0

Danke! Das bedeutet, dass Sie die Formatierung der Fehlermeldungen selbst vornehmen müssen, oder? Schauen Sie sich die Quellen von 'DefaultErrorStrategy' an (https://github.com/antlr/antlr4/blob/4.5/runtime/Java/src/org/antlr/v4/runtime/DefaultErrorStrategy.Java) scheint es keine Möglichkeit zu geben, zwischen der Fehlerformatierung und dem Aufruf zur Benachrichtigung der Fehlerreporter zu gelangen. – MvG

+0

@MvG leider ja. aber das ist die einzige zuverlässige Lösung, auf die ich gestoßen bin – vsminkov

1

Ein Ansatz könnte sein, den Fehlerlistener anstelle der Fehlerstrategie zu ändern. Man könnte die Standardstrategie zusammen mit dem folgenden Hörer verwenden:

class ErrorListener extends BaseErrorListener { 
    @Override 
    public void syntaxError(Recognizer<?, ?> recognizer, 
          Object offendingSymbol, 
          int line, 
          int charPositionInLine, 
          String msg, 
          RecognitionException e) { 
     throw new ParseException(msg, e, line); 
    } 
} 

class ParseException extends RuntimeException { 
    int line; 
    public ParseException(String message, Throwable cause, int line) { 
     super(message, cause); 
     this.line = line; 
    } 
} 

diese Weise werden die Fehler formatiert erhalten, wie sie für die Ausgabe sind, aber den ersten Fehler gemeldet wird, um die Kompilierung führen zum Abbruch durch die genannte Ausnahme zu werfen. Da dies eine ungeprüfte Ausnahme ist, müssen Sie sicherstellen, dass Sie sie abfangen, da der Compiler Sie nicht warnt, wenn Sie dies vergessen.

Im Hinblick auf ein maschinenlesbares Lage, wenn zusätzlich zu der Zeilennummer Sie sehen auch diese Quelltext-Offsets für den beanstandeten Teil des Eingangs, Code wollen scheint innerhalb der syntaxError Methode zu arbeiten:

 int start = 0, stop = -1; 
     if (offendingSymbol instanceof Token) { 
      Token t = (Token) offendingSymbol; 
      start = t.getStartIndex(); 
      stop = t.getStopIndex(); 
     } else if (recognizer instanceof Lexer) { 
      Lexer lexer = (Lexer)recognizer; 
      start = lexer._tokenStartCharIndex; 
      stop = lexer._input.index(); 
     }