简单算术示例上的 ANTLR4 访问者模式 [英] ANTLR4 visitor pattern on simple arithmetic example

查看:45
本文介绍了简单算术示例上的 ANTLR4 访问者模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是一个完整的 ANTLR4 新手,所以请原谅我的无知.我遇到了这个演示文稿,其中定义了一个非常简单的算术表达式语法.它看起来像:

I am a complete ANTLR4 newbie, so please forgive my ignorance. I ran into this presentation where a very simple arithmetic expression grammar is defined. It looks like:

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 的函数:

Which is great because it will generate a very simple binary tree that can be traversed using the visitor pattern as explained in the slides, e.g., here's the function that visits the 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 如下:

The next thing I would like to add is support for parentheses. So I modified the expr as follows:

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

不幸的是,上面的代码失败了,因为当遇到括号时,三个属性 opleftright 为空(NPE 失败).

Unfortunately, the code above fails because when encountering parentheses the three attributes op,left and right are null (fails with NPE).

我想我可以通过定义一个新属性来解决这个问题,例如 parenthesized='(' expr ')',然后在访问者代码中处理它.但是,在我看来,用一个完整的额外节点类型来表示括号中的表达式似乎有点过分了.一个更简单但更丑陋的解决方案是在 visitOpExpr 方法的开头添加以下代码行:

I think I could work around that by defining a new attribute, e.g., parenthesized='(' expr ')', and then deal with that in the visitor code. However, it seems overkill to me to have a whole extra node type to represent an expression in parentheses. A simpler but uglier solution is to add the following line of code at the beginning of the visitOpExpr method:

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

我一点都不喜欢上面的,因为它非常脆弱,而且高度依赖于语法结构.

I don't like the above at all because it's very fragile and highly dependent on the grammar structure.

我想知道是否有办法告诉 ANTLR 只吃掉"括号并将表达式视为孩子.在那儿?有一个更好的方法吗?

I am wondering if there is a way to tell ANTLR to just "eat" the parentheses and treat the expression like a child. Is there? Is there a better way to do this?

注意:我的最终目标是扩展示例以包含本身可以包含算术表达式的布尔表达式,例如,(2+4*3)/10 >= 11,即算术表达式之间的关系(<、>、==、~=等)可以定义一个原子布尔表达式.这是直截了当的,我已经草拟了语法,但我对括号有同样的问题,即,我需要能够编写类似的东西(我还将添加对变量的支持):

Note: My end goal is to extend the example to include boolean expressions that can themselves contain arithmetic expressions, e.g., (2+4*3)/10 >= 11, that is, a relation (<,>,==,~=,etc.) between arithmetic expressions can define an atomic boolean expression. This is straight forward and I already have the grammar sketched out but I have the same problem with parenthesis, i.e., I need to be able to write stuff like (I will also add support for variables):

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

EDIT:修正括号表达式的优先级,括号总是有更高的优先级.

EDIT: Fixed the precedence of the parenthesized expression, parenthesis always have higher precedence.

推荐答案

当然,只是不同的标签.毕竟,替代的 '(' expr ')' 不是 #opExpr:

Sure, just label it differently. After all, the alternative '(' expr ')' isn't a #opExpr:

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

在您的访问者中,您会执行以下操作:

And in your visitor, you'd do something like this:

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(CharStreams.fromString(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);
    }
}

如果你运行上面的类,你会看到以下输出:

If you run the class above, you'd see the following output:

2 * (3 + 4) = 14

这篇关于简单算术示例上的 ANTLR4 访问者模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆