65.9K
CodeProject 正在变化。 阅读更多。
Home

Java 参数化表达式求值器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (10投票s)

2013年3月20日

CPOL

4分钟阅读

viewsIcon

29881

downloadIcon

673

一个 Java 中的参数化表达式求值器。

引言

表达式求值器是一个过程,它接受人类可读的表达式(在本例中是一个字符串),并求出结果。 在这种情况下,表达式求值器可以接受一组参数并将它们用作附加输入。

背景

我一直在使用各种临时的技术和免费库来实现这个功能;但实际上没有一个能满足我的需求。 我的两个要求是它应该解析一次,并且能够基于一组参数多次求值。 我想,不如自己构建一个!

我的第一步是研究如何评估一个表达式。 忽略语法以及如何对单词进行分词,我而是关注最终结果。 我提出的设计是每个动作都由一个单独的类表示,并且会构建一个层次结构,其中一个简单的表达式可以调用其子表达式,直到它们全部求值。

有了输出结构,从输入到输出就变成了一个简单的练习。 基于每种不同类型的表达式的求值优先级构建了一个层次结构,并将从字符串到表达式的转换放置在工厂中。

构建表达式的类结构是

Class structure for lxExpression

执行表达式的参数使用泛型 DataSet 传入。

类结构

表达式解析器

对我来说,编写这个程序最困难的部分是弄清楚如何从输入字符串构建组件。 执行很容易,但首先构建表达式证明很困难。 经过反复考虑,我最终找到了我喜欢的语法。

EXPRESSION    ::= ID "=" ELEMENT
                | LOGICAL_OR
LOGICAL_OR    ::= LOGICAL_AND "||" LOGICAL_OR
                | LOGICAL_AND
LOGICAL_AND   ::= COMPARE "&&" LOGICAL_AND
                | COMPARE
COMPARE       ::= SUM "<" SUM
                | SUM "<=" SUM
                | SUM "==" SUM
                | SUM "!=" SUM
                | SUM ">=" SUM
                | SUM ">" SUM
                | SUM
SUM           ::= TERM "+" SUM
                | TERM "-" SUM
                | TERM
TERM          ::= FACTOR "*" TERM
                | FACTOR "/" TERM
                | FACTOR
FACTOR        ::= PRIMARY "^" FACTOR
                | PRIMARY "%" FACTOR
                | PRIMARY
PRIMARY       ::= "-" ELEMENT
                | "!" ELEMENT
                | ELEMENT
ELEMENT       ::= ID
                | CONSTANT
                | "(" EXPRESSION ")"

只需要解释最后一个项目 ELEMENT。它要么是括号中的另一个表达式,要么是 CONSTANTID。常量是数字和带引号的字符串,剩下的 ID 是不带引号的字符串。 这些是在评估表达式时将从输入中获取的参数

有了这个结构,我能够构建类 ExpressionParser,它可以从字符串构建表达式。 这分两个阶段完成,首先对表达式进行分词,以找到单词以及每种单词的类型。 然后,完全按照上述结构构建表达式对象。

例如,这是解析顶级表达式的方法
/**
 * Parse the text for an expression:
 * <pre>
 * EXPRESSION ::= ID "=" ELEMENT
 *              | LOGICAL_OR
 * </pre>
 *
 * @param start the word at which to start parsing.
 *
 * @return an {@see Expression} and the index of the last word.
 */
private ReturnExpression expression(int start)
        throws ExpressionException {
    if (this.getType(start) == WordType.LITERAL &&
            this.getType(start + 1) == WordType.ASSIGN) {
        ReturnExpression ex = element(start+2);
        return new ReturnExpression(ex.end, new Assign(this.getWord(start),ex.expression));
    }
    return logicalOr(start);
}

相应的其他方法并不复杂。 将求值与解析分词后的表达式分开创建了一个更简单的过程,并且还允许重复评估表达式而无需再次解析它; 因此满足了第二个要求。

表达式

表达式类在我开始时也经历了严重的变形。 每个动作都由它自己的类表示,并且除了表达式本身的结构之外没有其他状态。 这导致了很多小而简单的类; 我喜欢简单。

基类是 Expression,它是抽象的,并且包含一个简单的方法签名。 这是调用来评估任何表达式的方法,它再次被简化并且有效。 在实现该类时,一个主要的原则是如果表达式没有意义,它应该返回 null 而不是抛出异常。 异常会被抛出,但保持在最低限度以减少对上游使用的压力。

还有另外两个有趣的类用于评估组合表达式。 首先,MultipleExpression 扩展了 Expression 并提供了支持处理多个表达式的结果并将它们组合在一起。 反过来,Numeric 扩展了它,为处理数字表达式提供了基础。

使用代码

这个的前提是解析一个表达式,然后可以对其进行一次或多次求值。 除了 lxExpression 库之外,我还包含了一个测试应用程序,该应用程序只是通过求值器运行表达式。 该测试器展示了如何解析和评估表达式。

此工具使用一个代表 DataSet 的文件,该文件被读取然后被评估。 文件的格式很简单,就是一个要运行的测试列表,然后是每个测试的块

test - <list of names>
[<name> {
  expression - <expression>
  data {
    <requred data>
  }
}
...]

实际运行测试的代码和之前传递的所有内容一样简单。 构建它,解析它并评估它。

/**
 * Perform a test on the expression evaluator.
 * <pre<
 * expression - <expression>
 * data {
 *   <requred data>
 * }
 * </pre<
 * @param test a dataset containing a test.
 */
private static void test(DataSet test) {
    String expression = test.getString("expression");
    DataSet data = test.getDataSet("data");
    System.out.println("Expression: " + expression);
    System.out.println("Data: " + data);
    try {
        ExpressionParser ep = new ExpressionParser();
        ep.setExpression(expression);
        ep.parse();
        DataSet clone = new DataSet(data);
        Expression ex = ep.getExpression();
        System.out.println("Ex: " + ex);
        Object res = ex.evaluate(clone);
        System.out.println("Result: " + res);
        System.out.println("Data: " + clone);
    } catch (ExpressionException ex) {
        ex.printStackTrace();
    }
    System.out.println("---");
    System.out.println("");
}

关于这个没有什么可说的了。 我现在可以使用这个库来代替许多其他的分词和评估代码。

关注点

有一些东西我暂时有意的省略了。 这些都是需要的,我很快就会解决它们; 但是就目前而言,我可以继续没有它们。

  • 支持 if/then/else 结构。
  • 不支持函数。 我想要一些基本的数学函数 - 例如 sin, cos, tan - 以及字符串操作和类型转换。 最终这应该包括用户(或至少是应用程序)定义的函数。
  • 日期无法正常工作,我无法抽出时间来测试它们; 使用它们需要您自己承担风险。

历史

  • 2013-03: 首次提交。
© . All rights reserved.