web-dev-qa-db-fra.com

Patron de visiteurs ANTLR4 sur un exemple arithmétique simple

Je suis un débutant ANTLR4 complet, alors pardonnez-moi mon ignorance. J'ai couru dans cette présentation où une grammaire d'expression arithmétique très simple est définie. Ça ressemble à:

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 ;

Ce qui est très bien car il va générer un arbre binaire très simple qui peut être parcouru en utilisant le modèle de visiteur comme expliqué dans les diapositives, par exemple, voici la fonction qui visite la variable 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);
  }
}

La prochaine chose que je voudrais ajouter est le support pour les parenthèses. J'ai donc modifié la expr comme suit:

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

Malheureusement, le code ci-dessus échoue car lorsqu’il rencontre des parenthèses, les trois attributs op, left et right sont nuls (échec avec NPE).

Je pense que je pourrais contourner ce problème en définissant un nouvel attribut, par exemple, parenthesized='(' expr ')', puis en traiter dans le code de visiteur. Cependant, il me semble exagéré de disposer d’un type de nœud supplémentaire pour représenter une expression entre parenthèses. Une solution plus simple mais plus laide consiste à ajouter la ligne de code suivante au début de la méthode visitOpExpr:

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

Ce que je n’aime pas du tout, c’est très fragile et très dépendant de la structure grammaticale.

Je me demande s’il existe un moyen de dire à ANTLR de simplement "manger" les parenthèses et de traiter l’expression comme un enfant. Y a-t-il? Y a-t-il une meilleure manière de faire cela? 

Note: Mon objectif final est d'étendre l'exemple pour inclure des expressions booléennes pouvant elles-mêmes contenir des expressions arithmétiques, par exemple (2+4*3)/10 >= 11, c'est-à-dire une relation (<,>, ==, ~ =, etc.). entre expressions arithmétiques peut définir une expression booléenne atomique. C’est très simple et la grammaire est déjà esquissée, mais j’ai le même problème avec les parenthèses, c’est-à-dire que je dois être capable d’écrire des choses comme (je vais aussi ajouter un support pour les variables):

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

EDIT: correction de la priorité de l'expression entre parenthèses, les parenthèses ont toujours une priorité supérieure.

16
Giovanni Botta

Bien sûr, étiquetez-le différemment. Après tout, l’autre '(' expr ')' n’est pas un #opExpr:

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

Et dans votre visiteur, vous feriez quelque chose comme ça:

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

Si vous exécutez la classe ci-dessus, vous verrez la sortie suivante:

2 * (3 + 4) = 14
16
Bart Kiers

J'ai porté ci-dessus sur Python Visitor et même Python Listener

auditeur Python

from antlr4 import *
from arithmeticLexer import arithmeticLexer
from arithmeticListener import arithmeticListener
from arithmeticParser import arithmeticParser
import sys

##  grammar arithmetic;
##  
##  start : expr ;
##  
##  expr  : left=expr op=('*'|'/') right=expr #opExpr
##        | left=expr op=('+'|'-') right=expr #opExpr
##        | '(' expr ')'                      #parenExpr
##        | atom=INT                          #atomExpr
##        ;
##  
##  INT   : ('0'..'9')+ ;
##  
##  WS    : [ \t\r\n]+ -> skip ;

import codecs
import sys

def dump(obj):
  for attr in dir(obj):
    print("obj.%s = %r" % (attr, getattr(obj, attr)))

def is_number(s):
    try:
        float(s)
        return True
    except ValueError:
        return False


class arithmeticPrintListener(arithmeticListener):

    def __init__(self):
        self.stack = []

    # Exit a parse tree produced by arithmeticParser#opExpr.
    def exitOpExpr(self, ctx:arithmeticParser.OpExprContext):

        print('exitOpExpr INP',ctx.op.text,ctx.left.getText(),ctx.right.getText())

        op = ctx.op.text

        opchar1=op[0]
        right= self.stack.pop()
        left= self.stack.pop()

        if opchar1 == '*':
           val = left * right 
        Elif opchar1 == '/':
           val = left / right 
        Elif opchar1 == '+':
           val = left + right 
        Elif opchar1 == '-':
           val = left - right
        else:
           raise ValueError("Unknown operator " + op) 

        print("exitOpExpr OUT",opchar1,left,right,val)

        self.stack.append(val)


    # Exit a parse tree produced by arithmeticParser#atomExpr.
    def exitAtomExpr(self, ctx:arithmeticParser.AtomExprContext):
         val=int(ctx.getText())
         print('exitAtomExpr',val)
         self.stack.append(val)

def main():
    #lexer = arithmeticLexer(StdinStream())
    expression = "(( 4 - 10 ) * ( 3 + 4 )) / (( 2 - 5 ) * ( 3 + 4 ))"
    lexer = arithmeticLexer(InputStream(expression))
    stream = CommonTokenStream(lexer)
    parser = arithmeticParser(stream)
    tree = parser.start()
    printer = arithmeticPrintListener()
    walker = ParseTreeWalker()
    walker.walk(printer, tree)

if __== '__main__':
    main()

Python Visitor

from antlr4 import *
from arithmeticLexer import arithmeticLexer
from arithmeticVisitor import arithmeticVisitor
from arithmeticParser import arithmeticParser
import sys
from pprint import pprint


##  grammar arithmetic;
##  
##  start : expr ;
##  
##  expr  : left=expr op=('*'|'/') right=expr #opExpr
##        | left=expr op=('+'|'-') right=expr #opExpr
##        | '(' expr ')'                      #parenExpr
##        | atom=INT                          #atomExpr
##        ;
##  
##  INT   : ('0'..'9')+ ;
##  
##  WS    : [ \t\r\n]+ -> skip ;

import codecs
import sys

class EvalVisitor(arithmeticVisitor):
    def visitOpExpr(self, ctx):

        #print("visitOpExpr",ctx.getText())

        left = self.visit(ctx.left)
        right = self.visit(ctx.right)
        op = ctx.op.text;

        # for attr in dir(ctx.op): ########### BEST 
        #   print("ctx.op.%s = %r" % (attr, getattr(ctx.op, attr)))
        #print("visitOpExpr",dir(ctx.op),left,right)

        opchar1=op[0]
        if opchar1 == '*':
           val = left * right 
        Elif opchar1 == '/':
           val = left / right 
        Elif opchar1 == '+':
           val = left + right 
        Elif opchar1 == '-':
           val = left - right
        else:
           raise ValueError("Unknown operator " + op) 
        print("visitOpExpr",opchar1,left,right,val)
        return val 

    def visitStart(self, ctx):
        print("visitStart",ctx.getText())
        return self.visit(ctx.expr())

    def visitAtomExpr(self, ctx):
        print("visitAtomExpr",int(ctx.getText()))
        return int(ctx.getText())

    def visitParenExpr(self, ctx):
        print("visitParenExpr",ctx.getText())
        return self.visit(ctx.expr())

def main():
    #lexer = arithmeticLexer(StdinStream())
    expression = "(( 4 - 10 ) * ( 3 + 4 )) / (( 2 - 5 ) * ( 3 + 4 ))"
    lexer = arithmeticLexer(InputStream(expression))
    stream = CommonTokenStream(lexer)
    parser = arithmeticParser(stream)
    tree = parser.start()
    answer = EvalVisitor().visit(tree) 
    print(answer)

if __== '__main__':
    main()
0
Jayanta