ANTLR4 шаблон посетителя на простой пример арифметики

Я полный новичок ANTLR4, поэтому, пожалуйста, простите мое невежество. Я столкнулся с этой презентацией, где определена очень простая арифметическая грамматика выражения. Это выглядит так:

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 ;

Это здорово, потому что оно создаст очень простое двоичное дерево, которое может быть пройдено с использованием шаблона посетителя, как описано в слайдах, например, здесь функция, которая посещает 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);
  }
}

Следующее, что я хотел бы добавить, это поддержка скобок. Поэтому я изменил expr следующим образом:

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

К сожалению, приведенный выше код не сработает, поскольку при столкновении с круглыми скобками три атрибута op, left и right равны нулю (сбой NPE).

Я думаю, что смогу обойти это, указав новый атрибут, например parenthesized='(' expr ')', а затем обработайте его в коде посетителя. Однако мне кажется излишним иметь целый дополнительный node тип для представления выражения в круглых скобках. Более простым, но уродливым решением является добавление следующей строки кода в начале метода visitOpExpr:

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

Мне не нравится это вообще, потому что он очень хрупкий и сильно зависит от структуры грамматики.

Мне интересно, есть ли способ сказать ANTLR просто "съесть" круглые скобки и обработать выражение, подобное ребенку. Здесь? Есть лучший способ сделать это?

Примечание. Моя конечная цель - расширить пример, чтобы включить булевы выражения, которые могут сами содержать арифметические выражения, например, (2+4*3)/10 >= 11, то есть отношение (<, > , ==, ~ = и т.д.). между арифметическими выражениями может быть определено атомное булево выражение. Это прямолинейно, и у меня уже есть грамматика, но у меня такая же проблема с круглыми скобками, т.е. Мне нужно писать такие вещи, как (я также добавлю поддержку переменных):

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

EDIT. Исправлено приоритет выражения в скобках, скобки всегда имеют более высокий приоритет.

Ответ 1

Конечно, просто помечайте его по-разному. В конце концов, альтернатива '(' expr ')' не является #opExpr:

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

И в вашем посетителе вы сделаете что-то вроде этого:

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

Если вы запустите класс выше, вы увидите следующий вывод:

2 * (3 + 4) = 14