微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何使用 ANTLR 访问者处理逻辑连接的表达式

如何解决如何使用 ANTLR 访问者处理逻辑连接的表达式

在我们的软件中,我们可以点击一个查询来选择元素的子集。在内存中,完成的查询是表示过滤条件的列表和对象 (CLO) 的构造。现在我将要实现一种查询语言,通过使用 ANTLR 对其进行解析来创建相同的内容

语言必须仅涵盖简单的表达式及其逻辑连接“AND”和“OR”。

name LIKE 'foo'
name LIKE 'bar' AND size > 42 OR comment LIKE 'ignore'

单词“name”、“size”和“comment”是属性。由“AND”连接的每个属性都将是列表中的对象 A。与“OR”相连的每个属性都将是一个对象 B 并进入 A 的列表中。

我的语法(摘录):

expr
    : expr AND expr                   # andExpr
    | expr OR expr                    # orExpr
    | IDENTIFIER numOp INT            # numExpr
    | IDENTIFIER strOp STR            # strExpr
    ;
    
numOp : (GT | LT | EQ);
strOp : (EQ | LIKE);

在 ANTLR 中使用访问者模式,我覆盖了 numExpr 和 strExpr 的方法并返回了 A 对象。但我还需要将这些对象与逻辑运算符“AND”|“OR”相关联。 当我覆盖 logicExpr 方法调用 visit() 方法时,首先计算两个逻辑运算符,然后按顺序计算基本表达式。这感觉有点尴尬,因为表达式与逻辑运算符相当分离。

OR
AND
name
size
comment

问:如何根据之前的逻辑运算符处理相应的表达式?什么是好的方法

问:此外,逻辑表达式语句在语法规则“expr”中的位置是否正确,还是应该首先出现基本表达式?

解决方法

我只是去找一个可以即时评估事物的访问者(而不是创建自定义对象并存储它们)。此类访问者的快速演示如下所示:

public class EvalVisitor extends ExprBaseVisitor<Object> {

  private final Map<String,Object> attributes;

  public EvalVisitor(Map<String,Object> attributes) {
    this.attributes = attributes;
  }

  @Override
  public Boolean visitEval(ExprParser.EvalContext ctx) {
    return (Boolean) visit(ctx.expr());
  }

  @Override
  public Boolean visitStrExpr(ExprParser.StrExprContext ctx) {
    String lhs = this.attributes.get(ctx.IDENTIFIER().getText()).toString();
    String rhs = ctx.STR().getText().substring(1,ctx.STR().getText().length() - 1);
    int op = ctx.strOp().start.getType();

    switch (op) {
      case ExprLexer.EQ:
        return lhs.equals(rhs);
      case ExprLexer.LIKE:
        return lhs.toLowerCase().contains(rhs.toLowerCase());
      default:
        throw new RuntimeException("unknown operator: " + ctx.strOp().getText());
    }
  }

  @Override
  public Object visitAndExpr(ExprParser.AndExprContext ctx) {
    Boolean lhs = (Boolean) this.visit(ctx.expr(0));
    Boolean rhs = (Boolean) this.visit(ctx.expr(1));

    return lhs && rhs;
  }

  @Override
  public Object visitOrExpr(ExprParser.OrExprContext ctx) {
    Boolean lhs = (Boolean) this.visit(ctx.expr(0));
    Boolean rhs = (Boolean) this.visit(ctx.expr(1));

    return lhs || rhs;
  }

  @Override
  public Boolean visitNumExpr(ExprParser.NumExprContext ctx) {
    Integer lhs = (Integer) this.attributes.get(ctx.IDENTIFIER().getText());
    Integer rhs = Integer.valueOf(ctx.INT().getText());
    int op = ctx.numOp().start.getType();

    switch (op) {
      case ExprLexer.GT:
        return lhs > rhs;
      case ExprLexer.LT:
        return lhs < rhs;
      case ExprLexer.EQ:
        return lhs.equals(rhs);
      default:
        throw new RuntimeException("unknown operator: " + ctx.numOp().getText());
    }
  }
}

可以这样测试:

String source = "name LIKE 'bar' AND size > 42 OR comment LIKE 'ignore'";
ExprLexer lexer = new ExprLexer(CharStreams.fromString(source));
ExprParser parser = new ExprParser(new CommonTokenStream(lexer));

Map<String,Object> attributes = new HashMap<>();

attributes.put("name","Barista");
attributes.put("size",40);
attributes.put("comment","Ignore this please");

Object result = new EvalVisitor(attributes).visit(parser.eval());

System.out.printf("source:\n  %s\n\nresult:\n  %s\n",source,result);

结果整数:

source:
  name LIKE 'bar' AND size > 42 OR comment LIKE 'ignore'

result:
  true
,

我认为您对事情发生的时间有一些误解。

当您解析您的输入时,会根据您的解析规则构建一个解析树。

然后,您可以使用访问者或侦听器作为便利类,帮助您导航从解析返回的解析树。

如果您设置 ANTLR 书中概述的“grun”功能,然后使用“-gui”选项来获得访问者或侦听器将处理的解析树的可视化呈现,这可能会有所帮助。

>

通常,侦听器界面更易于使用,因为它会为您导航解析树,并在进入或退出节点时回调您的方法(您无需执行任何代码来处理导航。

>

访问者界面的工作级别稍低,但您必须自己处理导航。

在您了解清楚之前,很难知道如何回答您的问题,而且我怀疑这可能会改变您表达问题的方式(如果您仍有疑问)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。