ANTLR4访问者模式简单的算术例子

Gio*_*tta 20 java antlr lexer 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 ;
Run Code Online (Sandbox Code Playgroud)

这很好,因为它会生成一个非常简单的二叉树,可以使用访问者模式遍历,如幻灯片中所述,例如,这里是访问以下内容的函数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);
  }
}
Run Code Online (Sandbox Code Playgroud)

我想补充的另一件事是支持圆括号.所以我修改了expr如下:

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

不幸的是,上面的代码失败了,因为遇到括号时三个属性op,left并且right为null(与NPE失败).

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

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

我完全不喜欢上述内容,因为它非常脆弱,并且高度依赖于语法结构.

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

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

((2+4*x)/10 >= 11) | ( x>1 & x<3 )
Run Code Online (Sandbox Code Playgroud)

编辑:修复括号表达式的优先级,括号始终具有更高的优先级.

Bar*_*ers 21

当然,只是标记它不同.毕竟,替代方案'(' expr ')'不是#opExpr:

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

在你的访问者中,你会做这样的事情:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

2 * (3 + 4) = 14