递归下降解析器 - 添加未初始化的变量

时间:2015-02-05 20:49:29

标签: java parsing

所以,我有一个递归下降解析器,用于分析中缀中的数学表达式。表达式被标记化,使用前面提到的解析器进行解析,该解析器动态生成AST(每种类型的表达式都有节点)并计算最终值。我将所有这些值都处理为doubles;所以,我像这样使用这个解析器:

Parser parser = new Parser();

try {
    ExpressionNode expression = parser.parse("5 + 4*cos(pi)");
    System.out.println("The value of the expression is "
            + expression.getValue());
} catch (ParserException | EvaluationException e) {
    System.out.println(e.getMessage());
}

}

Exceptions我定义了自己。 行expression.getValue()返回double,我的解析器的工作方式是每个表达式节点都返回double,因此每个分支都是自下而上进行评估,直到它最终为一个{ {1}}回答。

问题是,我想在我的表达式中处理单位化变量,如果我想解析double(其中x未在之前初始化),表达式的值将返回{{1} }。

我是否必须将表达式节点的5 + x返回类型更改为5 + x?我觉得这会使程序变得复杂和膨胀,必须有更好的方法来实现这一目标。有没有人有这类事情的经验?

我知道我的解析器的描述可能有点模糊,所以this是我学习如何实现它的大部分内容。

1 个答案:

答案 0 :(得分:2)

我假设你的表达式树中有为运算符和常量定义的类。您需要为变量定义一个新类。

然后,您需要添加getAllVariables之类的方法,该方法可以返回树中任意点下方的所有变量。

我建议您更改getValue以接受Map<String, Double>,以便在评估时为任何变量提供值。除了将从地图返回自己的值的变量之外的所有节点都需要忽略这一点。如果他们没有找到自己作为密钥的映射,则应该抛出EvaluationException

最后,如果您希望能够将表达式打印为字符串,那么这对您的getValue来说确实是一个单独的方法。也许是getExpressionText。然后每个类都可以覆盖它,以返回一个表示该点表达式的String,其中变量只返回变量名。

现在,一旦解析了表达式,就可以获取所有变量,提示用户输入值,为给定值计算表达式(如果未定义则捕获异常)并再次打印出来。 / p>

ExpressionNode expression = Parser.parse("x + 5 * y");
System.out.println(expression.getExpressionText());
System.out.println(expression.getAllVariables());
Map<String, Double> variableValues = new TreeMap<>();
variableValues.put("x", 4);
variableValues.put("y", -2);
System.out.println("Evaluates to " + expression.getValue(variableValues));

我希望您的Variable课程最终看起来像:

public class Variable implements ExpressionNode {
    private final String name;

    public double getValue(Map<String, Double> variableValues) {
        if (variableValues.containsKey(name)) {
            return variableValues.get(name);
        } else {
            throw new EvaluationException(name + " is undefined");
        }
    }

    public String getExpressionText() {
        return name;
    }

    public List<String> getAllVariables() {
        return Arrays.asList(name);
    }
}

您可能希望在表达式树上执行的另一个常见操作是简化它。这基本上意味着评估任何可以评估的常量。在我看来,最好的方法是返回一个新的简化树而不是更改当前树。因此,我建议为ExpressionNode添加新方法:

public ExpressionNode simplify();

对于变量和常量,这只会返回this。对于运营商来说,需要做一些更复杂的事情。类似的东西:

class Operator implements ExpressionNode {
    public ExpressionNode simplify() {
    if (getAllVariables().isEmpty()) {
        return new Constant(getValue());
    } else {
        Operator simplified = new Operator(operation);
        for (ExpressionNode operand: operands) {
            simplified.addOperand(operand.simplify());
        }
        return simplified;
    }
}

希望你看到它的作用。如果可以完全评估操作,则将其转换为常量。否则它仍然是一个操作,但它的每个操作数都会被简化。

现在,如果你想简化表达式,你可以这样做:

System.out.println(Parser.parse("7 * 2 + x * 3").simplify().getExpressionText());

将返回&#34; 14 + x * 3&#34;。

如果您希望变得更加复杂,您可以建立关联和分发到操作员的意识并更改simplify,以便它将树重新组织为组变量。但我认为这有点超出了这个问题的范围!