【问题标题】:java complex logical conditions parserjava复杂逻辑条件解析器
【发布时间】:2015-09-22 21:00:11
【问题描述】:

我有一组传入记录,需要在一组定义和存储的逻辑子句下进行评估。一个示例逻辑子句如下:

Acct1 != 'Y' AND Acct2 > 1004 AND Acct3 >= 96 AND Acct4 < 1004 AND Acct5 = 99 AND ((Acct6 <= 9090 OR Acct7 IN (A1,A2,A6) AND Acct1 NOT IN (A3,A4)) AND Formatted LIKE 'LINUX' AND Acct9 NOT LIKE 'WINDOWS' AND (Acct10 = 'N' AND NOT Acct11 = 'N') AND EditableField BETWEEN (10 AND 20) )

我对子句的数据输入如下:

map.put(Acct1,"Y")
map.put(Acct2,1010)
map.put(Acct3,99)
map.put(Acct4,1015)
map.put(Acct5,99)
map.put(Acct6,9090)
map.put(Acct7,"A3")
map.put(Formatted,"LINUX_INST")
map.put(Updated,"LINUX_TMP")
map.put(Acct10,"Y")
map.put(Acct11,"N")
map.put(EditableFIeld,25)

我必须根据上面定义的子句评估填充到映射中的传入记录,并根据评估结果打印 true 或 false。

子句条件和映射值也将被更改和执行。

我有以下条件子句要评估:

!=
>
>=
<
=
<=
IN(
NOT IN(
LIKE(
NOT LIKE(
BETWEEN(
AND
OR
AND NOT
OR NOT

我曾尝试使用语法生成器,但有人告诉我这不是我们应用程序的推荐解决方案,因此我正在寻找 java 代码,我有这个详细的示例供参考 AND、OR、=。 resolving logical operations - AND, OR, looping conditions dynamically 并在可能的情况下寻找 sn-ps 以在此基础上进行构建。

【问题讨论】:

  • 你做了什么,什么没用?
  • 我尝试过使用语法生成器,因为我无法在 java 中编写如此复杂的运算符,但由于技术限制,这不被接受。因此,如果可用于评估 java 中的条件,则寻找 sn-ps。
  • 您知道哪些约束使解析器生成器不适合您的情况吗?一些解析器生成器生成没有标准库的纯代码,并且通常被调整为在速度和存储方面非常有效。如果解析器生成器不可行,了解原因可能有助于避免编写由于相同原因而无法执行的详细答案。
  • @JeffBowman 如果它是纯 Java 代码,那么它没有问题。如果它派生第三方库(不推荐用于某些应用),则会出现问题。
  • 您可以轻松编写一个递归下降解析器来评估(逻辑)表达式。见stackoverflow.com/a/2336769/120163

标签: java algorithm parsing logical-operators recursive-descent


【解决方案1】:

如果您想避免使用解析器生成器,请考虑使用 StreamTokenizer 来实现递归下降解析器,每个语法规则使用一种方法。

对于您的语法子集,这应该大致如下(并且应该可以直接扩展到您的完整语法):

public class Parser {

  public static Node parse(String expr) {
    StreamTokenizer tokenizer = 
        new StreamTokenizer(new StringReader(expr));
    tokenizer.nextToken();
    Parser parser = new Parser(tokenizer);
    Node result = parser.parseExpression();
    if (tokenizer.ttype != StreamTokenizer.TT_EOF) {
      throw new RuntimeException("EOF expected, got " 
          + tokenizer.ttype + "/" + tokenizer.sval);
  }

  private StreamTokenizer tokenizer;

  private Parser(StreamTokenizer tokenizer) {
    this.tokenizer = tokenizer;
  } 

  private Node parseExpression() {
    Node left = parseAnd();
    if (tokenizer.ttype == StreamTokenizer.TT_WORD
        && tokenizer.sval.equals("OR")) {
      tokenizer.nextToken();
      return new OperationNode(OperationNode.Type.OR, 
          left, parseExpression());
    }
    return left;
  }

  private Node parseAnd() {
    Node left = parseRelational();
    if (tokenizer.ttype == StreamTokenizer.TT_WORD
        && tokenizer.sval.equals("AND")) {
      tokenizer.nextToken();
      return new OperationNode(OperationNode.Type.AND, 
          left, parseAnd());
    }
    return left;
  }

  private Node parseRelational() {
    Node left = parsePrimary();
    OperationNode.Type type;
    switch (tokenizer.ttype) {
      case '<': type = OperationNode.Type.LESS; break;
      case '=': type = OperationNode.Type.EQUAL; break;
      case '>': type = OperationNode.Type.GREATER; break;
      default:  
        return left;
    }
    tokenizer.nextToken();
    return new OperationNode(type, left, parseRelational());
  }

  private Node parsePrimary() {
    Node result;
    if (tokenizer.ttype == '(') {
      tokenizer.nextToken();
      result = parseExpression();
      if (tokenizer.ttype != ')') {
        throw new RuntimeException(") expected, got "
          + tokenizer.ttype + "/" + tokenizer.sval);
       }
    } else if (tokenizer.ttype == '"' || tokenizer.ttype == '\'') {
      result = new LiteralNode(tokenizer.sval);
    } else if (tokenizer.ttype == TT_NUMBER) {
      result = new LiteralNode(tokenizer.nval);
    } else if (tokenizer.ttype == StreamTokenizer.TT_WORD) {
      result = new FieldNode(tokenizer.sval);
    } else {
      throw new RuntimeException("Unrecognized token: " 
          + tokenizer.ttype + "/" + tokenizer.sval);
    }
    tokenizer.nextToken();
    return result;
  }
}

这假设一个 Node 对象层次结构是这样的:

interface Node {
   Object eval(Map<String,Object> data);
}

class FieldNode implements Node {
   private String name; 
   FieldNode(String name) {
     this.name = name;
   }
   public Object eval(Map<String,Object> data) {
     return data.get(name);
   }
}

class LiteralNode implements Node {
   private Object value; 
   FieldNode(Object value) {
     this.value = value;
   }
   public Object eval(Map<String,Object> data) {
     return value;
   }
}

class OperationNode implements Node {
  enum Type {
    AND, OR, LESS, GREATER, EQUALS
  }
  private Type type;
  private Node leftChild;
  private Node rightChild;

  OperationNode(Type type, Node leftChild, Node rightChild) {
    this.type = type;
    this.leftChild = leftChild;
    this.rightChild = rightChild;
  }

  public Object eval(Map<String,Object> data) {
    Object left = leftChild.eval(data);
    Object right = rightChild.eval(data);
    switch (type) {
      case AND: return ((Boolean) left) && ((Boolean) right);
      case OR: return ((Boolean) left) || ((Boolean) right);
      case LESS: return ((Comparable) left).compareTo(right) < 0;
      case EQUALS: return left.equals(right);
      case GREATE: return ((Comparable) left).compareTo(right) > 0;
      default:
        throw new RuntimeException("Invalid op: " + type);
    }
  }    

【讨论】:

    【解决方案2】:

    为了直接回答这个问题,一些 SO 问题(例如 12)描述了手动编写解析器的基础知识,但实际上在大学编译器之外手动编写解析器是非常不常见的由于所涉及的样板和严格的细节,课程。

    正如 cmets 中所讨论的,听起来避免使用语法生成器的主要原因是避免对外部库的依赖。但是,当使用像 JavaCC(Java Compiler-Compiler)这样的语法生成器(解析器生成器)时,不涉及 JAR 文件或外部依赖项:JavaCC 二进制文件将语法规范转换为 Java 代码,可以在不涉及任何更多的图书馆。

    请参阅此 IBM 教程,以 JoAnn Brereton 的 "Use JavaCC to build a user friendly boolean query language" (via archive.org) 为例,其中顺便涉及与您的搜索语言不同的语法。

    示例输入:

    actor = "Christopher Reeve" and keyword=action and keyword=adventure
    (actor = "Christopher Reeve" and keyword=action) or keyword=romance
    actor = "Christopher Reeve" and (keyword=action or keyword=romance)
    

    语法摘录:

    TOKEN : 
    {
    <STRING : (["A"-"Z", "0"-"9"])+ >
    <QUOTED_STRING: "\"" (~["\""])+ "\"" >
    }
    
    void queryTerm() :
    {
    }
    {
            (<TITLE> | <ACTOR> |
             <DIRECTOR> | <KEYWORD>)
            ( <EQUALS> | <NOTEQUAL>)
            ( <STRING> | <QUOTED_STRING> )
            |
           <LPAREN> expression() <RPAREN>
    }
    

    输出文件:

    • UQLParser.java
    • UQLParserConstants.java
    • UQLParserTokenManager.java
    • TokenMgrError.java
    • ParseException.java
    • Token.java
    • SimpleCharStream.java

    这是您可以考虑的几个解析器生成器之一;其他人,如yacc and bison,也可以生成独立的 Java 文件,而不需要外部库。如有必要,您可以将生成的 Java 文件直接检查到您的存储库中,仅当您需要调整语法时才保留 .jj 编译器源文件。 (虽然作为构建过程的一部分从源代码重新编译可能会更好,并避免将生成的文件检查到源代码控制中,但这可能更适合您对纯 Java 解决方案的限制。)

    【讨论】:

    • IBM 教程的链接已损坏
    • @seinecle 已更新。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-13
    相关资源
    最近更新 更多