/* * Copyright 2014 Frank Asseg * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.objecthunter.exp4j; import java.util.*; import java.util.concurrent.*; import net.objecthunter.exp4j.function.Function; import net.objecthunter.exp4j.operator.Operator; import net.objecthunter.exp4j.tokenizer.*; public class Expression { private final Token[] tokens; private final Map<String, Double> variables; private final Set<String> userFunctionNames; Expression(final Token[] tokens) { this.tokens = tokens; this.variables = new HashMap<String, Double>(4); this.userFunctionNames = Collections.<String>emptySet(); } Expression(final Token[] tokens, Set<String> userFunctionNames) { this.tokens = tokens; this.variables = new HashMap<String, Double>(4); this.userFunctionNames = userFunctionNames; } public Expression setVariable(final String name, final double value) { this.checkVariableName(name); this.variables.put(name, value); return this; } private void checkVariableName(String name) { if (this.userFunctionNames.contains(name)) { throw new IllegalArgumentException("The setVariable name '" + name + "' is invalid. Since there exists a function with the same name"); } } public Expression setVariables(Map<String, Double> variables) { for (Map.Entry<String, Double> v : variables.entrySet()) { this.setVariable(v.getKey(), v.getValue()); } return this; } public ValidationResult validate(boolean checkVariablesSet) { final List<String> errors = new ArrayList<String>(0); if (checkVariablesSet) { /* check that all vars have a value set */ for (final Token t : this.tokens) { if (t.getType() == Token.TOKEN_VARIABLE) { final String var = ((VariableToken) t).getName(); if (!variables.containsKey(var)) { errors.add("The setVariable '" + var + "' has not been set"); } } } } /* Check if the number of operands, functions and operators match. The idea is to increment a counter for operands and decrease it for operators. When a function occurs the number of available arguments has to be greater than or equals to the function's expected number of arguments. The count has to be larger than 1 at all times and exactly 1 after all tokens have been processed */ int count = 0; for (Token tok : this.tokens) { switch (tok.getType()) { case Token.TOKEN_NUMBER: case Token.TOKEN_VARIABLE: count++; break; case Token.TOKEN_FUNCTION: final Function func = ((FunctionToken) tok).getFunction(); if (func.getNumArguments() > count) { errors.add("Not enough arguments for '" + func.getName() + "'"); } break; case Token.TOKEN_OPERATOR: Operator op = ((OperatorToken) tok).getOperator(); if (op.getNumOperands() == 2) { count--; } break; } if (count < 1) { errors.add("Too many operators"); return new ValidationResult(false, errors); } } if (count > 1) { errors.add("Too many operands"); } return errors.size() == 0 ? ValidationResult.SUCCESS : new ValidationResult(false, errors); } public ValidationResult validate() { return validate(true); } public Future<Double> evaluateAsync(ExecutorService executor) { return executor.submit(new Callable<Double>() { @Override public Double call() throws Exception { return evaluate(); } }); } public double evaluate() { final Stack<Double> output = new Stack<Double>(); for (int i = 0; i < tokens.length; i++) { Token t = tokens[i]; if (t.getType() == Token.TOKEN_NUMBER) { output.push(((NumberToken) t).getValue()); } else if (t.getType() == Token.TOKEN_VARIABLE) { final String name = ((VariableToken) t).getName(); final Double value = this.variables.get(name); if (value == null) { throw new IllegalArgumentException("No value has been set for the setVariable '" + name + "'."); } output.push(value); } else if (t.getType() == Token.TOKEN_OPERATOR) { OperatorToken op = (OperatorToken) t; if (output.size() < op.getOperator().getNumOperands()) { throw new IllegalArgumentException("Invalid number of operands available"); } if (op.getOperator().getNumOperands() == 2) { /* pop the operands and push the result of the operation */ double rightArg = output.pop(); double leftArg = output.pop(); output.push(op.getOperator().apply(leftArg, rightArg)); } else if (op.getOperator().getNumOperands() == 1) { /* pop the operand and push the result of the operation */ double arg = output.pop(); output.push(op.getOperator().apply(arg)); } } else if (t.getType() == Token.TOKEN_FUNCTION) { FunctionToken func = (FunctionToken) t; if (output.size() < func.getFunction().getNumArguments()) { throw new IllegalArgumentException("Invalid number of arguments available"); } /* collect the arguments from the stack */ double[] args = new double[func.getFunction().getNumArguments()]; for (int j = 0; j < func.getFunction().getNumArguments(); j++) { args[j] = output.pop(); } output.push(func.getFunction().apply(this.reverseInPlace(args))); } } if (output.size() > 1) { throw new IllegalArgumentException("Invalid number of items on the output queue. Might be caused by an invalid number of arguments for a function."); } return output.pop(); } private double[] reverseInPlace(double[] args) { int len = args.length; for (int i = 0; i < len / 2; i++) { double tmp = args[i]; args[i] = args[len - i - 1]; args[len - i - 1] = tmp; } return args; } }