2014-04-15 3 views
14

Ich bin ein kompletter ANTLR4 Neuling, also bitte vergib meine Unwissenheit. Ich stieß auf this presentation, wo eine sehr einfache arithmetische Ausdruckgrammatik definiert ist. Es sieht aus wie:ANTLR4 Besucher Muster auf einfache Rechenbeispiel

grammar Expressions; 

start : expr ; 

expr : left=expr op=('*'|'/') right=expr #opExpr 
     | left=expr op=('+'|'-') right=expr #opExpr 
     | atom=INT #atomExpr 
     ; 

INT : ('0'..'9')+ ; 

WS : [ \t\r\n]+ -> skip ; 

Das ist sehr gut, weil es einen sehr einfachen binären Baum generieren, die mit dem Besuchermuster durchlaufen werden können, wie in den Folien erläuterten, zum Beispiel, hier ist die Funktion, die die expr besucht:

Das nächste, was ich hinzufügen möchte, ist die Unterstützung für Klammern. So modifizierte ich die expr wie folgt:

expr : '(' expr ')'      #opExpr 
     | left=expr op=('*'|'/') right=expr #opExpr 
     | left=expr op=('+'|'-') right=expr #opExpr 
     | atom=INT #atomExpr 
     ; 

Leider oberhalb der Code schlägt fehl, da bei der Begegnung Klammern die drei Attribute op, left und right null sind (nicht mit NPE).

Ich denke, dass ich das umgehen könnte, indem ich ein neues Attribut definiere, z. B. parenthesized='(' expr ')', und gehe dann mit dem im Besuchercode um. Es erscheint mir jedoch übertrieben, einen ganzen zusätzlichen Knotentyp zu haben, um einen Ausdruck in Klammern darzustellen. Eine einfachere, aber hässliche Lösung ist, die folgende Codezeile am Anfang der visitOpExpr Methode hinzufügen:

if (ctx.op == null) return visit(ctx.getChild(1)); // 0 and 2 are the parentheses! 

ich gar nicht die oben mag, weil es sehr instabil ist und stark abhängig von der Grammatik-Struktur.

Ich frage mich, ob es eine Möglichkeit gibt, ANTLR zu sagen, nur die Klammern zu "essen" und den Ausdruck wie ein Kind zu behandeln. Ist da? Gibt es einen besseren Weg, dies zu tun?

Hinweis: Mein Endziel ist das Beispiel zu erweitern Boolesche Ausdrücke enthalten, die sich um arithmetische Ausdrücke enthalten kann, beispielsweise (2+4*3)/10 >= 11, das heißt, eine Beziehung (<,>, ==, ~ = usw.) zwischen arithmetischen Ausdrücken kann einen atomaren booleschen Ausdruck definieren. Dies ist gerade nach vorne und ich habe bereits die Grammatik skizziert, aber ich habe das gleiche Problem mit Klammern, also muss ich der Lage sein, Sachen zu schreiben, wie (ich werde auch Unterstützung für Variablen hinzufügen):

((2+4*x)/10 >= 11) | (x>1 & x<3) 

BEARBEITEN: Die Präzedenz des Ausdrucks in Klammern behoben, Klammern haben immer höhere Priorität.

Antwort

10

Sicher, nur anders beschriften. Schließlich ist die Alternative '(' expr ')' kein #opExpr:

expr : left=expr op=('*'|'/') right=expr #opExpr 
     | left=expr op=('+'|'-') right=expr #opExpr 
     | '(' expr ')'      #parenExpr 
     | atom=INT       #atomExpr 
     ; 

Und in Ihrem Besucher, Sie so etwas tun würde:

public class EvalVisitor extends ExpressionsBaseVisitor<Integer> { 

    @Override 
    public Integer visitOpExpr(@NotNull ExpressionsParser.OpExprContext ctx) { 
     int left = visit(ctx.left); 
     int right = visit(ctx.right); 
     String op = ctx.op.getText(); 
     switch (op.charAt(0)) { 
      case '*': return left * right; 
      case '/': return left/right; 
      case '+': return left + right; 
      case '-': return left - right; 
      default: throw new IllegalArgumentException("Unknown operator " + op); 
     } 
    } 

    @Override 
    public Integer visitStart(@NotNull ExpressionsParser.StartContext ctx) { 
     return this.visit(ctx.expr()); 
    } 

    @Override 
    public Integer visitAtomExpr(@NotNull ExpressionsParser.AtomExprContext ctx) { 
     return Integer.valueOf(ctx.getText()); 
    } 

    @Override 
    public Integer visitParenExpr(@NotNull ExpressionsParser.ParenExprContext ctx) { 
     return this.visit(ctx.expr()); 
    } 

    public static void main(String[] args) { 
     String expression = "2 * (3 + 4)"; 
     ExpressionsLexer lexer = new ExpressionsLexer(new ANTLRInputStream(expression)); 
     ExpressionsParser parser = new ExpressionsParser(new CommonTokenStream(lexer)); 
     ParseTree tree = parser.start(); 
     Integer answer = new EvalVisitor().visit(tree); 
     System.out.printf("%s = %s\n", expression, answer); 
    } 
} 

Wenn Sie die Klasse oben laufen, dann würden Sie sehen der folgende Ausgang:

2 * (3 + 4) = 14
+1

Ich sehe. Also ich denke, es gibt keine Möglichkeit, nur ANTLR den Zwischenknoten loszuwerden? Prinzipiell geht es hier auch um Raumeffizienz, nicht wahr? –

+0

Nicht ganz sicher, ob ich weiß, was du meinst, aber es gibt keinen Weg um die geklammerte Alternative zu behandeln: ANTLR ist nur zum Parsen da, alle anderen Logik/Auswertung muss von dir selbst [in einer Besucher/Zuhörer Klasse] behandelt werden. –

+0

Ich meine, wenn Sie dies schreiben: '(((((2 + 3)))))))))', statt '2 + 3', die Syntax ist offensichtlich gültig, aber die Der Platz, den der Baum einnimmt, ist wegen aller Klammerknoten viel größer. Ich bin nur überrascht, dass es keinen Weg gibt, einen Kurzschluss zu definieren, so dass der erste Ausdruck in den letzten umgewandelt wird. –