2014-04-15 15 views
14

Jestem kompletnym nowicjuszem ANTLR4, więc proszę wybaczyć moją niewiedzę. Wpadłem na this presentation, gdzie zdefiniowano bardzo prostą gramatykę wyrażenia arytmetycznego. Wygląda to tak:Wzór gościa ANTLR4 na prostym przykładzie arytmetycznym

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 ; 

Który jest wielki, ponieważ będzie generować bardzo prosty binarne drzewo, które można przejść za pomocą odwiedzający jak wyjaśniono w slajdach, na przykład tutaj jest funkcja, która odwiedza expr:

public Integer visitOpExpr(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("Unkown opeator " + op); 
    } 
} 

Następną rzeczą, którą chciałbym dodać, jest obsługa nawiasów. Więc zmodyfikował expr następująco:

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

Niestety, powyższy kod nie działa, ponieważ kiedy napotykają nawiasach trzy atrybuty op, left i right są nieważne (nie z NPE).

Myślę, że mógłbym obejść to poprzez zdefiniowanie nowego atrybutu, np. parenthesized='(' expr ')', a następnie poradzić sobie z tym w kodzie odwiedzającym. Jednak wydaje mi się przesadne, że mam cały dodatkowy typ węzła, który reprezentuje wyrażenie w nawiasach. Prostszy ale brzydsze rozwiązaniem jest dodanie następującej linii kodu na początku metody visitOpExpr:

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

nie lubię powyżej w ogóle, ponieważ jest bardzo kruchy i bardzo zależy od struktury gramatyczne.

Zastanawiam się, czy istnieje sposób, aby powiedzieć ANTLR, aby po prostu "zjeść" nawiasy i traktować wyrażenie jak dziecko. Jest tu? Czy jest lepszy sposób to zrobić?

Uwaga: Mój koniec celem jest rozszerzenie przykład zawierać wyrażeń logicznych, które same mogą zawierać wyrażeń arytmetycznych, np (2+4*3)/10 >= 11, czyli relację (<,>, ==, ~ =, itp.) pomiędzy wyrażeniami arytmetycznymi można zdefiniować wyrażenie binarne atomowe. Jest to prosta i mam już gramatyka zarysowane, ale mam ten sam problem z nawiasu, czyli muszę być w stanie napisać takie rzeczy (Ja też dodać wsparcie dla zmiennych):

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

EDIT: Naprawiono pierwszeństwo wyrażenia parentetyzowanego, nawias zawsze ma wyższy priorytet.

Odpowiedz

10

Oczywiście, po prostu oznacz to inaczej. Po tym wszystkim, alternatywa '(' expr ')' nie jest #opExpr:

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

I w swoim gościem, chcesz zrobić coś takiego:

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

Jeśli prowadzisz klasę wyżej, można zobaczyć następujący wynik:

2 * (3 + 4) = 14
+1

Rozumiem. Sądzę więc, że nie ma sposobu, aby po prostu ANTLR pozbyć się węzła pośredniego? Co do zasady istnieje również kwestia efektywności kosmicznej? –

+0

Nie jestem do końca pewny, czy wiem, co masz na myśli, ale nie ma możliwości obejścia parentetyzowanej alternatywy: ANTLR jest właśnie tutaj do analizy, wszystkie inne logiki/oceny muszą być obsługiwane bezpośrednio przez ciebie [w klasie odwiedzający/słuchacza]. –

+0

Mam na myśli, jeśli napiszesz to: '(((((((((((((2 + 3))))))))))', zamiast '2 + 3', składnia jest oczywiście poprawna, ale przestrzeń zajmowana przez drzewo jest znacznie większa ze względu na wszystkie nawiasy. Jestem tylko zaskoczony, że nie ma sposobu, aby zdefiniować zwarcie tak, aby poprzednie wyrażenie zostało zamienione na drugie. –