package org.openlca.expressions; import java.io.Reader; import java.io.StringReader; import java.util.HashMap; /** * A scope contains bindings of variable names to expressions. Each scope has a * reference to a parent scope except of the global scope. An expression can be * evaluated in a scope. If the * */ public final class Scope { private final Scope parent; private final HashMap<String, Integer> evaluationCalls = new HashMap<>(); private final HashMap<String, Variable> variables = new HashMap<>(); Scope() { this(null); } Scope(Scope parent) { this.parent = parent; } /** * Creates a new binding of a variable name to an expression in this scope. */ public void bind(String variableName, String expression) { if (variableName == null || expression == null) return; String symbol = variableName.toLowerCase().trim(); Variable var = new Variable(); var.name = symbol; var.expression = expression; variables.put(symbol, var); } /** * Removes all variable bindings from the scope. */ public void clear() { variables.clear(); evaluationCalls.clear(); } /** * Evaluates the given expression in this scope. */ public double eval(String expression) throws InterpreterException { // reset the evaluation calls and values for variables for (String var : evaluationCalls.keySet()) evaluationCalls.put(var, 0); for (Variable variable : variables.values()) variable.value = null; try { return tryEval(expression); } catch (Throwable e) { throw new InterpreterException("Evaluation of expression " + expression + " failed: " + e.getMessage(), e); } } private double tryEval(String expression) throws Exception { Reader reader = new StringReader(expression.toLowerCase()); FormulaParser parser = new FormulaParser(reader); parser.parse(); Expression e = parser.getExpression(); e.check(); Object result = e.evaluate(this); if (!(result instanceof Double)) throw new InterpreterException("The given expression " + expression + " does not evaluate to a number."); return ((Double) result); } public Object resolveVariable(String name) throws InterpreterException { Variable var = variables.get(name); if (var != null) { // variable is bound in this scope if (var.isEvaluated()) return var.value; else return eval(var); } else { // search in parent scope or constants if (parent == null) return Constants.get(name); else { return parent.resolveVariable(name); } } } private Object eval(Variable var) throws InterpreterException { Integer call = evaluationCalls.get(var.name); if (call != null && call > 0) throw new InterpreterException( "Second evaluation call on variable " + var.name + ". Cyclic dependencies?"); evaluationCalls.put(var.name, 1); try { var.value = tryEval(var.expression); return var.value; } catch (Throwable e) { throw new InterpreterException("Evaluation of variable " + var.name + " failed: " + e.getMessage(), e); } } private class Variable { private String name; private String expression; private Double value; public boolean isEvaluated() { return value != null; } } }