<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>\r
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>\r
<classpathentry kind="lib" path="libs/android-support-v4.jar"/>\r
- <classpathentry kind="lib" path="libs/exp4j-rdg.jar"/>\r
<classpathentry kind="output" path="bin/classes"/>\r
</classpath>\r
<classpathentry kind="lib" path="lib/jogl/gluegen-rt.jar"/>
<classpathentry kind="lib" path="lib/jogl/jogl.all.jar"/>
<classpathentry kind="lib" path="lib/OrangeExtensions-1.2.jar"/>
- <classpathentry kind="lib" path="lib/exp4j-rdg.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
<attribute name="Main-Class" value="${main-class}"/>
<attribute name="SplashScreen-Image" value="pix/splashscreen.png"/>
</manifest>
- <zipfileset src="lib/exp4j-rdg.jar" />
</jar>
</target>
--- /dev/null
+/*
+ Copyright 2011 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 de.congrace.exp4j;
+
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Abstract base class for mathematical expressions
+ *
+ * @author fas@congrace.de
+ */
+abstract class AbstractExpression {
+ private final String expression;
+ private final Token[] tokens;
+ private final String[] variableStrings;
+ private final NumberFormat numberFormat = NumberFormat.getInstance();
+
+ /**
+ * Construct a new {@link AbstractExpression}
+ *
+ * @param expression
+ * the mathematical expression to be used
+ * @param tokens
+ * the {@link Token}s in the expression
+ * @param variableNames
+ * an array of variable names which are used in the expression
+ */
+ AbstractExpression(String expression, Token[] tokens, String[] variableStrings) {
+ this.expression = expression;
+ this.tokens = tokens;
+ this.variableStrings = variableStrings;
+ }
+
+ /**
+ * get the mathematical expression {@link String}
+ *
+ * @return the expression
+ */
+ public String getExpression() {
+ return expression;
+ }
+
+ /**
+ * get the used {@link NumberFormat}
+ *
+ * @return the used {@link NumberFormat}
+ */
+ public NumberFormat getNumberFormat() {
+ return numberFormat;
+ }
+
+ /**
+ * get the {@link Token}s
+ *
+ * @return the array of {@link Token}s
+ */
+ Token[] getTokens() {
+ return tokens;
+ }
+
+}
--- /dev/null
+package de.congrace.exp4j;
+
+/**
+ * This is the basic result class of the exp4j {@link ExpressionBuilder}
+ *
+ * @author ruckus
+ *
+ */
+public interface Calculable {
+ /**
+ * calculate the result of the expression
+ *
+ * @return the result of the calculation
+ */
+ public Variable calculate();
+
+ /**
+ * return the expression in reverse polish postfix notation
+ *
+ * @return the expression used to construct this {@link Calculable}
+ */
+ public String getExpression();
+
+ /**
+ * set a variable value for the calculation
+ *
+ * @param value
+ * the value of the variable
+ */
+ public void setVariable(Variable var);
+}
--- /dev/null
+/*
+ Copyright 2011 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 de.congrace.exp4j;
+
+import java.util.Arrays;
+import java.util.Stack;
+
+abstract class CalculationToken extends Token {
+
+ CalculationToken(String value) {
+ super(value);
+ }
+
+ abstract void mutateStackForCalculation(Stack<Variable> stack, VariableSet variables);
+
+ /*
+ * Given an array of variables, check if any are arrays and if so expand any other of the given variables to arrays of the same length.
+ * Doubles are turned into arrays of all the same value as original. Arrays of other lengths are padded with zeros.
+ */
+ public Variable[] expandVariables(Variable[] values){
+ // Check if any variables have preferred representation as arrays
+ int maxLength = 0;
+ for (Variable v : values){
+ if (v.getPrimary() == Variable.Primary.ARRAY && v.getArrayValue().length > maxLength){
+ maxLength = v.getArrayValue().length;
+ }
+ }
+
+ // if necessary, expand any non-array variables to maximum length
+ if (maxLength > 0) {
+ for (int n = 0; n<values.length; n++){
+ Variable v = values[n];
+ if (v.getPrimary() == Variable.Primary.DOUBLE){
+ double[] a = new double[maxLength];
+ Arrays.fill(a, v.getDoubleValue());
+ values[n] = new Variable(v.getName(), a);
+ }
+ else if (v.getPrimary() == Variable.Primary.ARRAY){
+ // inlining Arrays.copyOf to provide compatibility with Froyo
+ double[] a = new double[maxLength];
+ int i = 0;
+ double[] vArrayValues = v.getArrayValue();
+ while( i < vArrayValues.length && i < maxLength ) {
+ a[i] = vArrayValues[i];
+ i++;
+ }
+ while ( i< maxLength ) {
+ a[i] = 0.0;
+ i++;
+ }
+ values[n] = new Variable(v.getName(), a);
+ }
+ else {
+ // Should not happen, if it does return invalid variable
+ return new Variable[] { new Variable("Invalid")};
+ }
+ }
+ }
+
+ return values;
+ }
+}
--- /dev/null
+/*
+ Copyright 2011 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 de.congrace.exp4j;
+
+/**
+ * Simple commandline interpreter for mathematical expressions the interpreter
+ * takes a mathematical expressions as a {@link String} argument, evaluates it
+ * and prints out the result.
+ *
+ *
+ * <pre>
+ * java de.congrace.exp4j.CommandlineInterpreter "2 * log(2.2223) - ((2-3.221) * 14.232^2)"
+ * > 248.91042049521056
+ * </pre>
+ *
+ * @author fas@congrace.de
+ *
+ */
+public class CommandlineInterpreter {
+ private static void calculateExpression(String string) {
+ try {
+ final PostfixExpression pe = PostfixExpression.fromInfix(string);
+ System.out.println(pe.calculate());
+ } catch (UnparsableExpressionException e) {
+ e.printStackTrace();
+ } catch (UnknownFunctionException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void main(String[] args) {
+ if (args.length != 1) {
+ printUsage();
+ } else {
+ calculateExpression(args[0]);
+ }
+ }
+
+ private static void printUsage() {
+ final StringBuilder usage = new StringBuilder();
+ usage.append("Commandline Expression Parser\n\n").append("Example: ").append("\n").append("java -jar exp4j.jar \"2.12 * log(23) * (12 - 4)\"\n\n")
+ .append("written by fas@congrace.de");
+ System.err.println(usage.toString());
+ }
+}
--- /dev/null
+package de.congrace.exp4j;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import de.congrace.exp4j.FunctionToken.Function;
+
+/**
+ * this classed is used to create custom functions for exp4j<br/>
+ * <br/>
+ * <b>Example</b><br/>
+ * <code><pre>{@code
+ * CustomFunction fooFunc = new CustomFunction("foo") {
+ * public double applyFunction(double value) {
+ * return value*Math.E;
+ * }
+ * };
+ * double varX=12d;
+ * Calculable calc = new ExpressionBuilder("foo(x)").withCustomFunction(fooFunc).withVariable("x",varX).build();
+ * assertTrue(calc.calculate() == Math.E * varX);
+ * }</pre></code>
+ *
+ * @author ruckus
+ *
+ */
+public abstract class CustomFunction extends CalculationToken {
+ private int argc=1;
+
+ /**
+ * create a new single value input CustomFunction with a set name
+ *
+ * @param value
+ * the name of the function (e.g. foo)
+ */
+ protected CustomFunction(String value) throws InvalidCustomFunctionException{
+ super(value);
+ for (Function f:Function.values()) {
+ if (value.equalsIgnoreCase(f.toString())){
+ throw new InvalidCustomFunctionException(value + " is already reserved as a function name");
+ }
+ }
+ }
+
+ /**
+ * create a new single value input CustomFunction with a set name
+ *
+ * @param value
+ * the name of the function (e.g. foo)
+ */
+ protected CustomFunction(String value,int argumentCount) throws InvalidCustomFunctionException{
+ super(value);
+ this.argc=argumentCount;
+ for (Function f:Function.values()) {
+ if (value.equalsIgnoreCase(f.toString())){
+ throw new InvalidCustomFunctionException(value + " is already reserved as a function name");
+ }
+ }
+ }
+
+ /**
+ * apply the function to a value
+ *
+ * @param values
+ * the values to which the function should be applied.
+ * @return the function value
+ */
+ public abstract Variable applyFunction(List<Variable> vars);
+
+ @Override
+ void mutateStackForCalculation(Stack<Variable> stack, VariableSet variables) {
+ List<Variable> args = new ArrayList<Variable>(argc);
+ for (int i=0; i < argc; i++) {
+ args.add(i, stack.pop() );
+ }
+ Collections.reverse(args); // Put elements in logical order
+
+ stack.push(this.applyFunction(args));
+ }
+
+ @Override
+ void mutateStackForInfixTranslation(Stack<Token> operatorStack, StringBuilder output) {
+ operatorStack.push(this);
+ }
+ public int getArgumentCount() {
+ return argc;
+ }
+}
--- /dev/null
+package de.congrace.exp4j;
+
+import java.util.List;
+
+public class Example {
+
+ public static void main(String[] args) throws UnknownFunctionException, UnparsableExpressionException, InvalidCustomFunctionException {
+
+ // Test 1
+ // ======
+
+ Calculable calc1 = new ExpressionBuilder("x * y - 2").withVariableNames("x", "y").build();
+ calc1.setVariable(new Variable("x", 1.2));
+ calc1.setVariable(new Variable("y", 2.2));
+
+ System.out.println(calc1.calculate().toString());
+ //double result = calc1.calculate().getDoubleValue();
+ //System.out.println(result);
+
+ // Test 2
+ // ======
+
+ // A function which calculates the mean of an array and scales it
+ CustomFunction meanFn = new CustomFunction("mean",2) {
+ public Variable applyFunction(List<Variable> vars) {
+
+ double[] vals;
+ double scale;
+
+ try{
+ vals = vars.get(0).getArrayValue();
+ scale = vars.get(1).getDoubleValue();
+ } catch (Exception e) {
+ return new Variable("Invalid");
+ }
+
+ double subtotal = 0;
+ for (int i = 0; i < vals.length; i++ ){
+ subtotal += vals[i];
+ }
+
+ subtotal = scale * subtotal / vals.length;
+ return new Variable("double MEAN result, ", subtotal);
+
+ }
+ };
+
+ ExpressionBuilder b = new ExpressionBuilder("mean(x,y)");
+ b.withCustomFunction(meanFn);
+ b.withVariable(new Variable("x", new double[] {1.1,2,10,3,2.4,10.2}));
+ b.withVariable(new Variable("y", 2));
+ Calculable calc2 = b.build();
+
+ System.out.println( calc2.calculate().toString() );
+
+ // Test 3
+ // ======
+
+ Calculable calc3 = new ExpressionBuilder("x * y - 2").withVariableNames("x", "y").build();
+ calc3.setVariable(new Variable("x", new double[]{1.2, 10, 20, 15}));
+ calc3.setVariable(new Variable("y", new double[]{2.2, 5.2, 12, 9 }));
+
+ //double result3 = calc3.calculate().getDoubleValue();
+ System.out.println(calc3.calculate().toString());
+
+
+ // Test 4
+ // ======
+
+ Calculable calc4 = new ExpressionBuilder("log10(sqrt(x) * abs(y))").withVariableNames("x", "y").build();
+ calc4.setVariable(new Variable("x", new double[]{1.2, 10, 10, 15}));
+ calc4.setVariable(new Variable("y", new double[]{2.2, -5.2, 5.2, 9 }));
+
+ //double result3 = calc3.calculate().getDoubleValue();
+ System.out.println(calc4.calculate().toString());
+ }
+}
--- /dev/null
+package de.congrace.exp4j;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This is Builder implementation for the exp4j API used to create a Calculable
+ * instance for the user
+ *
+ * @author ruckus
+ *
+ */
+public class ExpressionBuilder {
+ private VariableSet variables = new VariableSet();
+ private final Set<CustomFunction> customFunctions = new HashSet<CustomFunction>();
+
+ private String expression;
+
+ /**
+ * Create a new ExpressionBuilder
+ *
+ * @param expression
+ * the expression to evaluate
+ */
+ public ExpressionBuilder(String expression) {
+ this.expression = expression;
+ }
+
+ /**
+ * build a new {@link Calculable} from the expression using the supplied
+ * variables
+ *
+ * @return the {@link Calculable} which can be used to evaluate the
+ * expression
+ * @throws UnknownFunctionException
+ * when an unrecognized function name is used in the expression
+ * @throws UnparsableExpressionException
+ * if the expression could not be parsed
+ */
+ public Calculable build() throws UnknownFunctionException, UnparsableExpressionException {
+ if (expression.indexOf('=') == -1 && !variables.isEmpty()) {
+
+ // User supplied an expression without leading "f(...)="
+ // so we just append the user function to a proper "f()="
+ // for PostfixExpression.fromInfix()
+ StringBuilder function = new StringBuilder("f(");
+ for (String name : variables.getVariableNames()) {
+ function.append(name).append(',');
+ }
+ expression = function.deleteCharAt(function.length() - 1).toString() + ")=" + expression;
+ }
+ // create the PostfixExpression and return it as a Calculable
+ PostfixExpression delegate = PostfixExpression.fromInfix(expression, customFunctions);
+ for (Variable var : variables ) {
+ delegate.setVariable(var);
+ for (CustomFunction fn:customFunctions){
+ if (fn.getValue().equalsIgnoreCase(var.getName())){
+ throw new UnparsableExpressionException("variable '" + var + "' cannot have the same name as a custom function " + fn.getValue());
+ }
+ }
+ }
+ return delegate;
+ }
+
+ /**
+ * add a custom function instance for the evaluator to recognize
+ *
+ * @param function
+ * the {@link CustomFunction} to add
+ * @return the {@link ExpressionBuilder} instance
+ */
+ public ExpressionBuilder withCustomFunction(CustomFunction function) {
+ customFunctions.add(function);
+ return this;
+ }
+
+ public ExpressionBuilder withCustomFunctions(Collection<CustomFunction> functions) {
+ customFunctions.addAll(functions);
+ return this;
+ }
+
+ /**
+ * set the value for a variable
+ *
+ * @param variableName
+ * the variable name e.g. "x"
+ * @param value
+ * the value e.g. 2.32d
+ * @return the {@link ExpressionBuilder} instance
+ */
+ public ExpressionBuilder withVariable(Variable value) {
+ variables.add(value);
+ return this;
+ }
+
+ /*
+ * Provided for backwards compatibility
+ */
+ @Deprecated
+ public ExpressionBuilder withVariable(String variableName, double value) {
+ variables.add(new Variable(variableName, value));
+ return this;
+ }
+
+ /**
+ * set the variables names used in the expression without setting their
+ * values. Usefull for building an expression before you know the variable values.
+ *
+ * @param variableNames
+ * vararg {@link String} of the variable names used in the
+ * expression
+ * @return the ExpressionBuilder instance
+ */
+
+ public ExpressionBuilder withVariableNames(String... variableNames) {
+ for (String name : variableNames) {
+ variables.add( new Variable(name, Double.NaN) );
+ }
+ return this;
+ }
+
+
+ /**
+ * set the values for variables
+ *
+ * @param variableMap
+ * a map of variable names to variable values
+ * @return the {@link ExpressionBuilder} instance
+ */
+ public ExpressionBuilder withVariables(VariableSet variables) {
+ this.variables = variables;
+ return this;
+ }
+}
--- /dev/null
+package de.congrace.exp4j;
+
+import java.util.Stack;
+
+public class FunctionSeparatorToken extends Token{
+ public FunctionSeparatorToken() {
+ super(",");
+ }
+ @Override
+ void mutateStackForInfixTranslation(Stack<Token> operatorStack, StringBuilder output) {
+ Token token;
+ while (!((token=operatorStack.peek()) instanceof ParenthesisToken) && !token.getValue().equals("(")){
+ output.append(operatorStack.pop().getValue()).append(" ");
+ }
+ }
+
+}
--- /dev/null
+/*
+Copyright 2011 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 de.congrace.exp4j;
+
+import java.util.Stack;
+
+/**
+ * A {@link Token} for functions
+ *
+ * @author fas@congrace.de
+ *
+ */
+class FunctionToken extends CalculationToken {
+ /**
+ * the functionNames that can be used in an expression
+ *
+ * @author ruckus
+ *
+ */
+ enum Function {
+ ABS, ACOS, ASIN, ATAN, CBRT, CEIL, COS, COSH, EXP, EXPM1, FLOOR, ROUND, RANDOM, LOG, SIN, SINH, SQRT, TAN, TANH, LOG10
+ }
+
+ private Function function;
+
+ /**
+ * construct a new {@link FunctionToken}
+ *
+ * @param value
+ * the name of the function
+ * @throws UnknownFunctionException
+ * if an unknown function name is encountered
+ */
+ FunctionToken(String value) throws UnknownFunctionException {
+ super(value);
+ try {
+ function = Function.valueOf(value.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw new UnknownFunctionException(value);
+ }
+ if (function == null) {
+ throw new UnknownFunctionException(value);
+ }
+ }
+
+ /**
+ * apply a function to a variable
+ *
+ * @param x
+ * the value the function should be applied to
+ * @return the result of the function
+ */
+ public Variable applyFunction(Variable var) {
+
+ // The names here are strictly unused, but are useful for debugging
+ String name = function.name() + " result (#"+var.hashCode()+"), ";
+
+ switch (var.getPrimary()) {
+ case DOUBLE:
+ name = "double "+name;
+ double x = var.getDoubleValue();
+ return new Variable(name, applyFunction(x) );
+
+ case ARRAY:
+ name = "array "+name;
+ double[] input = var.getArrayValue();
+ double[] result = new double[input.length];
+ for (int i = 0; i < input.length; i++){
+ result[i] = applyFunction(input[i]);
+ }
+ return new Variable(name, result);
+
+ default:
+ return new Variable("Invalid");
+ }
+ }
+
+ /*
+ * The actual function application on a double
+ */
+ private double applyFunction(double x){
+ switch (function) {
+ case ABS:
+ return Math.abs(x);
+ case ACOS:
+ return Math.acos(x);
+ case ASIN:
+ return Math.asin(x);
+ case ATAN:
+ return Math.atan(x);
+ case CBRT:
+ return Math.cbrt(x);
+ case CEIL:
+ return Math.ceil(x);
+ case COS:
+ return Math.cos(x);
+ case COSH:
+ return Math.cosh(x);
+ case EXP:
+ return Math.exp(x);
+ case EXPM1:
+ return Math.expm1(x);
+ case FLOOR:
+ return Math.floor(x);
+ case ROUND:
+ return Math.round(x);
+ case RANDOM:
+ return Math.random()*x;
+ case LOG:
+ return Math.log(x);
+ case LOG10:
+ return Math.log10(x);
+ case SIN:
+ return Math.sin(x);
+ case SINH:
+ return Math.sinh(x);
+ case SQRT:
+ return Math.sqrt(x);
+ case TAN:
+ return Math.tan(x);
+ case TANH:
+ return Math.tanh(x);
+ default:
+ return Double.NaN; // should not happen ;)
+ }
+ }
+
+ /**
+ *
+ * get the {@link Function}
+ *
+ * @return the correspoding {@link Function}
+ */
+ Function getFunction() {
+ return function;
+ }
+
+ @Override
+ void mutateStackForCalculation(Stack<Variable> stack, VariableSet variableValues) {
+ stack.push(this.applyFunction(stack.pop()));
+ }
+
+ @Override
+ void mutateStackForInfixTranslation(Stack<Token> operatorStack, StringBuilder output) {
+ operatorStack.push(this);
+ }
+}
--- /dev/null
+/*
+ Copyright 2011 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 de.congrace.exp4j;
+
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * Translate a mathematical expression in human readable infix notation to a
+ * Reverse Polish Notation (postfix) expression for easier parsing. by
+ * implementing the shunting yard algorithm by dijkstra
+ *
+ * @author fas@congrace.de
+ */
+class InfixTranslator {
+
+ private static String substituteUnaryOperators(String expr) {
+ final StringBuilder exprBuilder = new StringBuilder(expr.length());
+ final char[] data = expr.toCharArray();
+ char lastChar = ' ';
+ for (int i = 0; i < expr.length(); i++) {
+ if (exprBuilder.length() > 0) {
+ lastChar = exprBuilder.charAt(exprBuilder.length() - 1);
+ }
+ final char c = data[i];
+ switch (c) {
+ case '+':
+ if (i > 0 && lastChar != '(' && !(OperatorToken.isOperator(lastChar))) {
+ exprBuilder.append(c);
+ }
+ break;
+ case '-':
+ if (i > 0 && lastChar != '(' && !(OperatorToken.isOperator(lastChar))) {
+ exprBuilder.append(c);
+ } else {
+ exprBuilder.append('#');
+ }
+ break;
+ default:
+ if (!Character.isWhitespace(c)) {
+ exprBuilder.append(c);
+ }
+ }
+ }
+ return exprBuilder.toString();
+ }
+
+ /**
+ * Delegation method for simple expression without variables or custom
+ * functions
+ *
+ * @param infixExpression
+ * the infix expression to be translated
+ * @return translated RNP postfix expression
+ * @throws UnparsableExpressionException
+ * when the expression is invalid
+ * @throws UnknownFunctionException
+ * when an unknown function has been used in the input.
+ */
+ static String toPostfixExpression(String infixExpression) throws UnparsableExpressionException, UnknownFunctionException {
+ return toPostfixExpression(infixExpression, null, null);
+ }
+
+ /**
+ * implement the shunting yard algorithm
+ *
+ * @param infixExpression
+ * the human readable expression which should be translated to
+ * RPN
+ * @param variableNames
+ * the variable names used in the expression
+ * @param customFunctions
+ * the CustomFunction implementations used
+ * @return the expression in postfix format
+ * @throws UnparsableExpressionException
+ * if the expression could not be translated to RPN
+ * @throws UnknownFunctionException
+ * if an unknown function was encountered
+ */
+ static String toPostfixExpression(String infixExpression, String[] variableStrings, Set<CustomFunction> customFunctions)
+ throws UnparsableExpressionException, UnknownFunctionException {
+ infixExpression = substituteUnaryOperators(infixExpression);
+ final Token[] tokens = new Tokenizer(variableStrings, customFunctions).tokenize(infixExpression);
+ final StringBuilder output = new StringBuilder(tokens.length);
+ final Stack<Token> operatorStack = new Stack<Token>();
+ for (final Token token : tokens) {
+ token.mutateStackForInfixTranslation(operatorStack, output);
+ }
+ // all tokens read, put the rest of the operations on the output;
+ while (operatorStack.size() > 0) {
+ output.append(operatorStack.pop().getValue()).append(" ");
+ }
+ return output.toString().trim();
+ }
+}
--- /dev/null
+package de.congrace.exp4j;
+
+public class InvalidCustomFunctionException extends Exception{
+ private static final long serialVersionUID = 1L;
+
+ public InvalidCustomFunctionException(String message) {
+ super(message);
+ }
+}
--- /dev/null
+/*
+ Copyright 2011 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 de.congrace.exp4j;
+
+import java.util.Map;
+import java.util.Stack;
+
+/**
+ * A {@link Token} for Numbers
+ *
+ * @author fas@congrace.de
+ *
+ */
+class NumberToken extends CalculationToken {
+
+ private final double doubleValue;
+
+ /**
+ * construct a new {@link NumberToken}
+ *
+ * @param value
+ * the value of the number as a {@link String}
+ */
+ NumberToken(String value) {
+ super(value);
+ this.doubleValue = Double.parseDouble(value);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof NumberToken) {
+ final NumberToken t = (NumberToken) obj;
+ return t.getValue().equals(this.getValue());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return getValue().hashCode();
+ }
+
+ @Override
+ void mutateStackForCalculation(Stack<Variable> stack, VariableSet variables) {
+ stack.push(new Variable("From number "+getValue()+" : "+hashCode(), this.doubleValue));
+ }
+
+ @Override
+ void mutateStackForInfixTranslation(Stack<Token> operatorStack, StringBuilder output) {
+ output.append(this.getValue()).append(' ');
+ }
+}
--- /dev/null
+/*
+ Copyright 2011 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 de.congrace.exp4j;
+
+import java.util.Stack;
+
+/**
+ * {@link Token} for Operations like +,-,*,/,% and ^
+ *
+ * @author fas@congrace.de
+ */
+class OperatorToken extends CalculationToken {
+
+ /**
+ * the valid {@link Operation}s for the {@link OperatorToken}
+ *
+ * @author fas@congrace.de
+ */
+ enum Operation {
+ ADDITION(1, true), SUBTRACTION(1, true), MULTIPLICATION(2, true), DIVISION(2, true), MODULO(2, true), EXPONENTIATION(3, false), UNARY_MINUS(4, false), UNARY_PLUS(
+ 4, false);
+ private final int precedence;
+ private final boolean leftAssociative;
+
+ private Operation(int precedence, boolean leftAssociative) {
+ this.precedence = precedence;
+ this.leftAssociative = leftAssociative;
+ }
+ }
+
+ /**
+ * return a corresponding {@link Operation} for a symbol
+ *
+ * @param c
+ * the symbol of the operation
+ * @return the corresponding {@link Operation}
+ */
+ static Operation getOperation(char c) {
+ switch (c) {
+ case '+':
+ return Operation.ADDITION;
+ case '-':
+ return Operation.SUBTRACTION;
+ case '*':
+ return Operation.MULTIPLICATION;
+ case '/':
+ return Operation.DIVISION;
+ case '^':
+ return Operation.EXPONENTIATION;
+ case '#':
+ return Operation.UNARY_MINUS;
+ case '%':
+ return Operation.MODULO;
+ default:
+ return null;
+ }
+ }
+
+ static boolean isOperator(char c) {
+ return getOperation(c) != null;
+ }
+
+ private final Operation operation;
+
+ /**
+ * construct a new {@link OperatorToken}
+ *
+ * @param value
+ * the symbol (e.g.: '+')
+ * @param operation
+ * the {@link Operation} of this {@link Token}
+ */
+ OperatorToken(String value, Operation operation) {
+ super(value);
+ this.operation = operation;
+ }
+
+ /**
+ * apply the {@link Operation}
+ *
+ * @param values
+ * the doubles to operate on
+ * @return the result of the {@link Operation}
+ * @throws UnparsableExpressionException
+ */
+ public Variable applyOperation(Variable... values) {
+
+ values = expandVariables(values);
+
+ double[] inputs = new double[values.length];
+ switch (values[0].getPrimary()){
+
+ case DOUBLE:
+ for (int i = 0; i<values.length; i++){
+ inputs[i] = values[i].getDoubleValue();
+ }
+ double result = applyOperation(inputs);
+ return new Variable("double " + operation.name()+" result, ", result);
+
+ case ARRAY:
+ int maxLength = values[0].getArrayValue().length;
+ double[] results = new double[maxLength];
+ for (int i = 0; i< maxLength; i++){
+ // assemble the array of input values
+ for (int j = 0; j<values.length; j++){
+ inputs[j] = values[j].getArrayValue()[i];
+ }
+ results[i] = applyOperation(inputs);
+ }
+ //System.out.println("Done applying operation "+operation.name());
+ return new Variable("array " + operation.name()+" result, ", results);
+
+ default:
+ return new Variable("Invalid");
+ }
+ }
+
+ private double applyOperation(double[] values){
+
+ //System.out.println("Applying "+operation.toString()+" to values starting "+values[0]);
+
+ switch (operation) {
+ case ADDITION:
+ return values[0] + values[1];
+ case SUBTRACTION:
+ return values[0] - values[1];
+ case MULTIPLICATION:
+ return values[0] * values[1];
+ case EXPONENTIATION:
+ return Math.pow(values[0], values[1]);
+ case DIVISION:
+ return values[0] / values[1];
+ case UNARY_MINUS:
+ return -values[0];
+ case UNARY_PLUS:
+ return values[0];
+ case MODULO:
+ return values[0] % values[1];
+ default:
+ return 0;
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof OperatorToken) {
+ final OperatorToken t = (OperatorToken) obj;
+ return t.getValue().equals(this.getValue());
+ }
+ return false;
+ }
+
+ int getOperandCount() {
+ switch (operation) {
+ case ADDITION:
+ case SUBTRACTION:
+ case MULTIPLICATION:
+ case DIVISION:
+ case EXPONENTIATION:
+ case MODULO:
+ return 2;
+ case UNARY_MINUS:
+ case UNARY_PLUS:
+ return 1;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * get the {@link Operation} of this {@link Token}
+ *
+ * @return the {@link Operation}
+ */
+ Operation getOperation() {
+ return operation;
+ }
+
+ int getPrecedence() {
+ return operation.precedence;
+ }
+
+ @Override
+ public int hashCode() {
+ return getValue().hashCode();
+ }
+
+ /**
+ * check if the operation is left associative
+ *
+ * @return true if left associative, otherwise false
+ */
+ boolean isLeftAssociative() {
+ return operation.leftAssociative;
+ }
+
+ @Override
+ void mutateStackForCalculation(Stack<Variable> stack, VariableSet variables) {
+ if (this.getOperandCount() == 2) {
+ final Variable n2 = stack.pop();
+ final Variable n1 = stack.pop();
+ stack.push(this.applyOperation(n1, n2));
+ } else if (this.getOperandCount() == 1) {
+ final Variable n1 = stack.pop();
+ stack.push(this.applyOperation(n1));
+ }
+ }
+
+ @Override
+ void mutateStackForInfixTranslation(Stack<Token> operatorStack, StringBuilder output) {
+ Token before;
+ while (!operatorStack.isEmpty() && (before = operatorStack.peek()) != null && (before instanceof OperatorToken || before instanceof FunctionToken)) {
+ if (before instanceof FunctionToken) {
+ operatorStack.pop();
+ output.append(before.getValue()).append(" ");
+ } else {
+ final OperatorToken stackOperator = (OperatorToken) before;
+ if (this.isLeftAssociative() && this.getPrecedence() <= stackOperator.getPrecedence()) {
+ output.append(operatorStack.pop().getValue()).append(" ");
+ } else if (!this.isLeftAssociative() && this.getPrecedence() < stackOperator.getPrecedence()) {
+ output.append(operatorStack.pop().getValue()).append(" ");
+ } else {
+ break;
+ }
+ }
+ }
+ operatorStack.push(this);
+ }
+}
--- /dev/null
+/*
+ Copyright 2011 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 de.congrace.exp4j;
+
+import java.util.Stack;
+
+/**
+ * Token for parenthesis
+ *
+ * @author fas@congrace.de
+ */
+class ParenthesisToken extends Token {
+
+ ParenthesisToken(String value) {
+ super(value);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ParenthesisToken) {
+ final ParenthesisToken t = (ParenthesisToken) obj;
+ return t.getValue().equals(this.getValue());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return getValue().hashCode();
+ }
+
+ /**
+ * check the direction of the parenthesis
+ *
+ * @return true if it's a left parenthesis (open) false if it is a right
+ * parenthesis (closed)
+ */
+ boolean isOpen() {
+ return getValue().equals("(") || getValue().equals("[") || getValue().equals("{");
+ }
+
+ @Override
+ void mutateStackForInfixTranslation(Stack<Token> operatorStack, StringBuilder output) {
+ if (this.isOpen()) {
+ operatorStack.push(this);
+ } else {
+ Token next;
+ while ((next = operatorStack.peek()) instanceof OperatorToken || next instanceof FunctionToken || next instanceof CustomFunction
+ || (next instanceof ParenthesisToken && !((ParenthesisToken) next).isOpen())) {
+ output.append(operatorStack.pop().getValue()).append(" ");
+ }
+ operatorStack.pop();
+ }
+ }
+}
--- /dev/null
+/*
+ Copyright 2011 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 de.congrace.exp4j;
+
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * Class for calculating values from a RPN postfix expression.<br/>
+ * The default way to create a new instance of {@link PostfixExpression} is by
+ * using the static factory method fromInfix()
+ *
+ * @author fas@congrace.de
+ */
+public final class PostfixExpression extends AbstractExpression implements Calculable {
+
+ private VariableSet variables = new VariableSet();
+
+ /**
+ * Factory method for creating {@link PostfixExpression}s from human
+ * readable infix expressions
+ *
+ * @param expression
+ * the infix expression to be used
+ * @return an equivalent {@link PostfixExpression}
+ * @throws UnparsableExpressionException
+ * if the expression was invalid
+ * @throws UnknownFunctionException
+ * if an unknown function has been used
+ * @deprecated please use {@link ExpressionBuilder} API
+ */
+ @Deprecated
+ public static PostfixExpression fromInfix(String expression) throws UnparsableExpressionException, UnknownFunctionException {
+ return fromInfix(expression, null);
+ }
+
+ /**
+ * Factory method for creating {@link PostfixExpression}s from human
+ * readable infix expressions
+ *
+ * @param expression
+ * the infix expression to be used
+ * @param customFunctions
+ * the CustomFunction implementations used
+ * @return an equivalent {@link PostfixExpression}
+ * @throws UnparsableExpressionException
+ * if the expression was invalid
+ * @throws UnknownFunctionException
+ * if an unknown function has been used
+ * @deprecated please use {@link ExpressionBuilder}
+ */
+ @Deprecated
+ public static PostfixExpression fromInfix(String expression, Set<CustomFunction> customFunctions) throws UnparsableExpressionException,
+ UnknownFunctionException {
+ String[] variableStrings = null;
+ int posStart, posEnd;
+ if ((posStart = expression.indexOf('=')) > 0) {
+ String functionDef = expression.substring(0, posStart);
+ expression = expression.substring(posStart + 1);
+ if ((posStart = functionDef.indexOf('(')) > 0 && (posEnd = functionDef.indexOf(')')) > 0) {
+ variableStrings = functionDef.substring(posStart + 1, posEnd).split(",");
+ }
+ }
+ return new PostfixExpression(InfixTranslator.toPostfixExpression(expression, variableStrings, customFunctions), variableStrings, customFunctions);
+ }
+
+ /**
+ * Construct a new simple {@link PostfixExpression}
+ *
+ * @param expression
+ * the postfix expression to be calculated
+ * @param variableNames
+ * the variable names in the expression
+ * @param customFunctions
+ * the CustomFunction implementations used
+ * @throws UnparsableExpressionException
+ * when expression is invalid
+ * @throws UnknownFunctionException
+ * when an unknown function has been used
+ */
+ private PostfixExpression(String expression, String[] variableStrings, Set<CustomFunction> customFunctions) throws UnparsableExpressionException,
+ UnknownFunctionException {
+ super(expression, new Tokenizer(variableStrings, customFunctions).tokenize(expression), variableStrings);
+ }
+
+ /**
+ * delegate the calculation of a simple expression
+ */
+ public Variable calculate() throws IllegalArgumentException {
+
+ final Stack<Variable> stack = new Stack<Variable>();
+ for (final Token t : getTokens()) {
+ ((CalculationToken) t).mutateStackForCalculation(stack, variables);
+ }
+ return stack.pop();
+
+ }
+
+ public void setVariable(Variable value) {
+ variables.add(value);
+ }
+}
--- /dev/null
+/*
+ Copyright 2011 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 de.congrace.exp4j;
+
+import java.util.Stack;
+
+/**
+ * Superclass for tokenized Strings
+ *
+ * @author fas@congrace.de
+ */
+abstract class Token {
+ private final String value;
+
+ /**
+ * construct a new {@link Token}
+ *
+ * @param value
+ * the value of the {@link Token}
+ */
+ Token(String value) {
+ super();
+ this.value = value;
+ }
+
+ /**
+ * get the value (String representation) of the token
+ *
+ * @return the value
+ */
+ String getValue() {
+ return value;
+ }
+
+ abstract void mutateStackForInfixTranslation(Stack<Token> operatorStack, StringBuilder output);
+}
--- /dev/null
+/*
+ Copyright 2011 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 de.congrace.exp4j;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import de.congrace.exp4j.FunctionToken.Function;
+
+/**
+ * Class for tokenizing mathematical expressions by breaking an expression up
+ * into multiple different {@link Token}s
+ *
+ * @author fas@congrace.de
+ */
+class Tokenizer {
+ private String[] variableNames;
+ private final Set<String> functionNames = new HashSet<String>();
+ private final Set<CustomFunction> customFunctions;
+
+ {
+ functionNames.add("abs");
+ functionNames.add("acos");
+ functionNames.add("asin");
+ functionNames.add("atan");
+ functionNames.add("cbrt");
+ functionNames.add("ceil");
+ functionNames.add("cos");
+ functionNames.add("cosh");
+ functionNames.add("exp");
+ functionNames.add("expm1");
+ functionNames.add("floor");
+ functionNames.add("log");
+ functionNames.add("sin");
+ functionNames.add("sinh");
+ functionNames.add("sqrt");
+ functionNames.add("tan");
+ functionNames.add("tanh");
+ }
+
+ Tokenizer() {
+ super();
+ customFunctions = null;
+ }
+
+ /**
+ * construct a new Tokenizer that recognizes variable names
+ *
+ * @param variableNames
+ * the variable names in the expression
+ * @throws IllegalArgumentException
+ * if a variable has the name as a function
+ * @param customFunctions
+ * the CustomFunction implementations used if the variableNames
+ * are not valid
+ */
+ Tokenizer(String[] variableNames, Set<CustomFunction> customFunctions) throws IllegalArgumentException {
+ super();
+ this.variableNames = variableNames;
+
+ if (variableNames != null) {
+ for (String varName : variableNames) {
+ if (functionNames.contains(varName.toLowerCase())) {
+ throw new IllegalArgumentException("Variable '" + varName + "' can not have the same name as a function");
+ }
+ }
+ }
+ this.customFunctions = customFunctions;
+ }
+
+ private Token getCustomFunctionToken(String name) throws UnknownFunctionException {
+ for (CustomFunction func : customFunctions) {
+ if (func.getValue().equals(name)) {
+ return func;
+ }
+ }
+ throw new UnknownFunctionException(name);
+ }
+
+ private boolean isCustomFunction(String name) {
+ if (customFunctions == null) {
+ return false;
+ }
+ for (CustomFunction func : customFunctions) {
+ if (func.getValue().equals(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * check if a char is part of a number
+ *
+ * @param c
+ * the char to be checked
+ * @return true if the char is part of a number
+ */
+ private boolean isDigit(char c) {
+ return Character.isDigit(c) || c == '.';
+ }
+
+ private boolean isFunction(String name) {
+ for (Function fn : Function.values()) {
+ if (fn.name().equals(name.toUpperCase())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * check if a String is a variable name
+ *
+ * @param name
+ * the variable name which is checked to be valid the char to be
+ * checked
+ * @return true if the char is a variable name (e.g. x)
+ */
+ private boolean isVariable(String name) {
+ //String[] variableNames = variables.getVariableNames();
+
+ if (variableNames != null) {
+ for (String var : variableNames) {
+ if (name.equals(var)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * tokenize an infix expression by breaking it up into different
+ * {@link Token} that can represent operations,functions,numbers,
+ * parenthesis or variables
+ *
+ * @param infix
+ * the infix expression to be tokenized
+ * @return the {@link Token}s representing the expression
+ * @throws UnparsableExpressionException
+ * when the expression is invalid
+ * @throws UnknownFunctionException
+ * when an unknown function name has been used.
+ */
+ Token[] tokenize(String infix) throws UnparsableExpressionException, UnknownFunctionException {
+ final List<Token> tokens = new ArrayList<Token>();
+ final char[] chars = infix.toCharArray();
+ // iterate over the chars and fork on different types of input
+ Token lastToken;
+ for (int i = 0; i < chars.length; i++) {
+ char c = chars[i];
+ if (c == ' ')
+ continue;
+ if (isDigit(c)) {
+ final StringBuilder valueBuilder = new StringBuilder(1);
+ // handle the numbers of the expression
+ valueBuilder.append(c);
+ int numberLen = 1;
+ while (chars.length > i + numberLen && isDigit(chars[i + numberLen])) {
+ valueBuilder.append(chars[i + numberLen]);
+ numberLen++;
+ }
+ i += numberLen - 1;
+ lastToken = new NumberToken(valueBuilder.toString());
+ } else if (Character.isLetter(c) || c == '_' || c == '#') {
+ // can be a variable or function
+ final StringBuilder nameBuilder = new StringBuilder();
+ nameBuilder.append(c);
+ int offset = 1;
+ while (chars.length > i + offset && (Character.isLetter(chars[i + offset]) || Character.isDigit(chars[i + offset]) || chars[i + offset] == '_' || chars[i + offset] == '#')) {
+ nameBuilder.append(chars[i + offset++]);
+ }
+ String name = nameBuilder.toString();
+ if (this.isVariable(name)) {
+ // a variable
+ i += offset - 1;
+ lastToken = new VariableToken(name);
+ } else if (this.isFunction(name)) {
+ // might be a function
+ i += offset - 1;
+ lastToken = new FunctionToken(name);
+ } else if (this.isCustomFunction(name)) {
+ // a custom function
+ i += offset - 1;
+ lastToken = getCustomFunctionToken(name);
+ } else {
+ // an unknown symbol was encountered
+ throw new UnparsableExpressionException(c, i);
+ }
+ }else if (c == ',') {
+ // a function separator, hopefully
+ lastToken=new FunctionSeparatorToken();
+ } else if (OperatorToken.isOperator(c)) {
+ lastToken = new OperatorToken(String.valueOf(c), OperatorToken.getOperation(c));
+ } else if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}') {
+ lastToken = new ParenthesisToken(String.valueOf(c));
+ } else {
+ // an unknown symbol was encountered
+ throw new UnparsableExpressionException(c, i);
+ }
+ tokens.add(lastToken);
+ }
+ return tokens.toArray(new Token[tokens.size()]);
+ }
+}
--- /dev/null
+/*
+ Copyright 2011 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 de.congrace.exp4j;
+
+/**
+ * Exception for handling unknown Functions.
+ *
+ * @see FunctionToken
+ * @author fas@congrace.de
+ */
+public class UnknownFunctionException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * construct a new {@link UnknownFunctionException}
+ *
+ * @param functionName
+ * the function name which could not be found
+ */
+ public UnknownFunctionException(String functionName) {
+ super("Unknown function: " + functionName);
+ }
+}
--- /dev/null
+/*
+ Copyright 2011 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 de.congrace.exp4j;
+
+/**
+ * Exception for invalid expressions
+ *
+ * @author fas@congrace.de
+ */
+public class UnparsableExpressionException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * construct a new {@link UnparsableExpressionException}
+ *
+ * @param c
+ * the character which could not be parsed
+ * @param pos
+ * the position of the character in the expression
+ */
+ public UnparsableExpressionException(char c, int pos) {
+ super("Unable to parse character at position " + pos + ": '" + String.valueOf(c) + "'");
+ }
+ /**
+ * construct a new {@link UnparsableExpressionException}
+ *
+ * @param msg
+ * the error message
+ */
+ public UnparsableExpressionException(String msg) {
+ super(msg);
+ }
+}
--- /dev/null
+package de.congrace.exp4j;
+
+/*
+ * Represents a generic variable which can have double or array values.
+ * Optionally the start and step values corresponding to each array index can be specified for array values
+ * Tries to do something sensible if you try and apply a regular function / operator to an array
+ * and vice-versa.
+ */
+public class Variable {
+
+ // The primary or preferred representation
+ public enum Primary {DOUBLE, ARRAY, PLACEHOLDER};
+ private final Primary primary;
+
+ private final String name;
+
+ private final double doubleValue;
+ private final double[] arrayValue;
+
+ private final double start, step;
+
+ /*
+ * Initialize a new variable with a name only. This can be used as a place holder
+ */
+ public Variable(String name){
+ this.name = name;
+ this.primary = Primary.PLACEHOLDER;
+ this.doubleValue = Double.NaN;
+ this.arrayValue = new double[] {Double.NaN};
+ this.start = Double.NaN;
+ this.step = Double.NaN;
+ }
+
+ /*
+ * Initialize a new double variable
+ */
+ public Variable(String name, double d){
+ this.doubleValue = d;
+ this.arrayValue = new double[] {d};
+ this.name = name;
+ this.primary = Primary.DOUBLE;
+ this.start = Double.NaN;
+ this.step = Double.NaN;
+ }
+
+ /*
+ * Initialize a new array variable
+ */
+ public Variable(String name, double[] d){
+ this.arrayValue = d;
+ this.doubleValue = d[0];
+ this.name = name;
+ this.primary = Primary.ARRAY;
+ this.start = Double.NaN;
+ this.step = Double.NaN;
+ }
+
+ /*
+ * Initialize a new array variable, specifying the start and step values
+ */
+ public Variable(String name, double[] d, double start, double step){
+ this.arrayValue = d;
+ this.doubleValue = d[0];
+ this.name = name;
+ this.primary = Primary.ARRAY;
+ this.start = start;
+ this.step = step;
+ }
+
+ public String getName(){
+ return name;
+ }
+
+ public Primary getPrimary(){
+ return this.primary;
+ }
+
+ public double getDoubleValue(){
+ return doubleValue;
+ }
+
+ public double[] getArrayValue(){
+ return arrayValue;
+ }
+
+ public double getStep(){
+ return step;
+ }
+
+ public double getStart(){
+ return start;
+ }
+
+ public String toString(){
+ if ( arrayValue.length > 1 ){
+ String out = name + " is Array (length " + new Integer(arrayValue.length).toString() + ") : {";
+ for (double x : arrayValue){
+ out = out + x + ",";
+ }
+ out = out.substring(0, out.length()-1);
+ return out + "}";
+ }
+ else{
+ return name + " is double : {" + new Double(doubleValue).toString() + "}";
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package de.congrace.exp4j;
+
+import java.util.HashSet;
+
+public class VariableSet extends HashSet<Variable> {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -4212803364398351279L;
+
+ public boolean add(Variable v){
+ Variable previous = getVariableNamed(v.getName());
+ if ( previous != null ){
+ this.remove( previous );
+ }
+
+ return super.add(v);
+ }
+
+ public Variable getVariableNamed(String name){
+ for (Variable var : this){
+ if (var.getName().equals(name) ){
+ return var;
+ }
+ }
+ return null;
+ }
+
+ public String[] getVariableNames(){
+ if (this.size() == 0){
+ return null;
+ }
+ String names[] = new String[this.size()];
+ int i = 0;
+ for (Variable var : this){
+ names[i] = var.getName();
+ i++;
+ }
+ return names;
+ }
+}
--- /dev/null
+/*
+ Copyright 2011 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 de.congrace.exp4j;
+
+import java.util.Stack;
+
+/**
+ * A {@link Token} for representing variables
+ *
+ * @author fas
+ */
+class VariableToken extends CalculationToken {
+ /**
+ * construct a new {@link VariableToken}
+ *
+ * @param value
+ * the value of the token
+ */
+ VariableToken(String value) {
+ super(value);
+ }
+
+ @Override
+ void mutateStackForCalculation(Stack<Variable> stack, VariableSet variableValues) {
+ Variable value = variableValues.getVariableNamed(this.getValue());
+ stack.push(value);
+ }
+
+ @Override
+ void mutateStackForInfixTranslation(Stack<Token> operatorStack, StringBuilder output) {
+ output.append(this.getValue()).append(" ");
+ }
+}