Exploded the exp4j source into our src/ directory. This allows us to more easily...
authorkruland2607 <kruland2607@180e2498-e6e9-4542-8430-84ac67f01cd8>
Fri, 10 Aug 2012 19:13:50 +0000 (19:13 +0000)
committerkruland2607 <kruland2607@180e2498-e6e9-4542-8430-84ac67f01cd8>
Fri, 10 Aug 2012 19:13:50 +0000 (19:13 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@967 180e2498-e6e9-4542-8430-84ac67f01cd8

26 files changed:
android/.classpath
android/libs/exp4j-rdg.jar [deleted file]
core/.classpath
core/build.xml
core/src/de/congrace/exp4j/AbstractExpression.java [new file with mode: 0644]
core/src/de/congrace/exp4j/Calculable.java [new file with mode: 0644]
core/src/de/congrace/exp4j/CalculationToken.java [new file with mode: 0644]
core/src/de/congrace/exp4j/CommandlineInterpreter.java [new file with mode: 0644]
core/src/de/congrace/exp4j/CustomFunction.java [new file with mode: 0644]
core/src/de/congrace/exp4j/Example.java [new file with mode: 0644]
core/src/de/congrace/exp4j/ExpressionBuilder.java [new file with mode: 0644]
core/src/de/congrace/exp4j/FunctionSeparatorToken.java [new file with mode: 0644]
core/src/de/congrace/exp4j/FunctionToken.java [new file with mode: 0644]
core/src/de/congrace/exp4j/InfixTranslator.java [new file with mode: 0644]
core/src/de/congrace/exp4j/InvalidCustomFunctionException.java [new file with mode: 0644]
core/src/de/congrace/exp4j/NumberToken.java [new file with mode: 0644]
core/src/de/congrace/exp4j/OperatorToken.java [new file with mode: 0644]
core/src/de/congrace/exp4j/ParenthesisToken.java [new file with mode: 0644]
core/src/de/congrace/exp4j/PostfixExpression.java [new file with mode: 0644]
core/src/de/congrace/exp4j/Token.java [new file with mode: 0644]
core/src/de/congrace/exp4j/Tokenizer.java [new file with mode: 0644]
core/src/de/congrace/exp4j/UnknownFunctionException.java [new file with mode: 0644]
core/src/de/congrace/exp4j/UnparsableExpressionException.java [new file with mode: 0644]
core/src/de/congrace/exp4j/Variable.java [new file with mode: 0644]
core/src/de/congrace/exp4j/VariableSet.java [new file with mode: 0644]
core/src/de/congrace/exp4j/VariableToken.java [new file with mode: 0644]

index 4620a5b3ea2dde3faed3c659c6f807723eac081e..07677d89fafe2264c948a7eb2b8a0167f98636d5 100644 (file)
@@ -7,6 +7,5 @@
        <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
diff --git a/android/libs/exp4j-rdg.jar b/android/libs/exp4j-rdg.jar
deleted file mode 100644 (file)
index 8a98016..0000000
Binary files a/android/libs/exp4j-rdg.jar and /dev/null differ
index fd1223fb10e1f21f5769455cdd4ea44a1d19dcc2..fb297f54eaccbf077503a4f3ff953321db661194 100644 (file)
@@ -30,6 +30,5 @@
        <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>
index 11f2fc352320bd0faaae17b548f76d47ec64e39e..9b178defe66a59021edbec0287b652ba09fdaa0a 100644 (file)
                                <attribute name="Main-Class" value="${main-class}"/>
                                <attribute name="SplashScreen-Image" value="pix/splashscreen.png"/>
                        </manifest>
-                       <zipfileset src="lib/exp4j-rdg.jar" />
                </jar>
        </target>
 
diff --git a/core/src/de/congrace/exp4j/AbstractExpression.java b/core/src/de/congrace/exp4j/AbstractExpression.java
new file mode 100644 (file)
index 0000000..fc7f56b
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+   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;
+       }
+       
+}
diff --git a/core/src/de/congrace/exp4j/Calculable.java b/core/src/de/congrace/exp4j/Calculable.java
new file mode 100644 (file)
index 0000000..4009cb5
--- /dev/null
@@ -0,0 +1,31 @@
+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);
+}
diff --git a/core/src/de/congrace/exp4j/CalculationToken.java b/core/src/de/congrace/exp4j/CalculationToken.java
new file mode 100644 (file)
index 0000000..c40408a
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+   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;
+       }
+}
diff --git a/core/src/de/congrace/exp4j/CommandlineInterpreter.java b/core/src/de/congrace/exp4j/CommandlineInterpreter.java
new file mode 100644 (file)
index 0000000..78670a4
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+   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());
+       }
+}
diff --git a/core/src/de/congrace/exp4j/CustomFunction.java b/core/src/de/congrace/exp4j/CustomFunction.java
new file mode 100644 (file)
index 0000000..f49f948
--- /dev/null
@@ -0,0 +1,90 @@
+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;
+       }
+}
diff --git a/core/src/de/congrace/exp4j/Example.java b/core/src/de/congrace/exp4j/Example.java
new file mode 100644 (file)
index 0000000..0d70b0b
--- /dev/null
@@ -0,0 +1,77 @@
+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());               
+       }
+}
diff --git a/core/src/de/congrace/exp4j/ExpressionBuilder.java b/core/src/de/congrace/exp4j/ExpressionBuilder.java
new file mode 100644 (file)
index 0000000..8ecf1b5
--- /dev/null
@@ -0,0 +1,135 @@
+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;
+       }
+}
diff --git a/core/src/de/congrace/exp4j/FunctionSeparatorToken.java b/core/src/de/congrace/exp4j/FunctionSeparatorToken.java
new file mode 100644 (file)
index 0000000..24b1109
--- /dev/null
@@ -0,0 +1,17 @@
+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(" ");
+        }
+    }
+
+}
diff --git a/core/src/de/congrace/exp4j/FunctionToken.java b/core/src/de/congrace/exp4j/FunctionToken.java
new file mode 100644 (file)
index 0000000..6bc620c
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+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);
+       }
+}
diff --git a/core/src/de/congrace/exp4j/InfixTranslator.java b/core/src/de/congrace/exp4j/InfixTranslator.java
new file mode 100644 (file)
index 0000000..9712bce
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+   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();
+       }
+}
diff --git a/core/src/de/congrace/exp4j/InvalidCustomFunctionException.java b/core/src/de/congrace/exp4j/InvalidCustomFunctionException.java
new file mode 100644 (file)
index 0000000..e3810db
--- /dev/null
@@ -0,0 +1,9 @@
+package de.congrace.exp4j;
+
+public class InvalidCustomFunctionException extends Exception{
+       private static final long serialVersionUID = 1L;
+
+       public InvalidCustomFunctionException(String message) {
+               super(message);
+       }
+}
diff --git a/core/src/de/congrace/exp4j/NumberToken.java b/core/src/de/congrace/exp4j/NumberToken.java
new file mode 100644 (file)
index 0000000..b765d45
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+   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(' ');
+       }
+}
diff --git a/core/src/de/congrace/exp4j/OperatorToken.java b/core/src/de/congrace/exp4j/OperatorToken.java
new file mode 100644 (file)
index 0000000..765f450
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+   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);
+       }
+}
diff --git a/core/src/de/congrace/exp4j/ParenthesisToken.java b/core/src/de/congrace/exp4j/ParenthesisToken.java
new file mode 100644 (file)
index 0000000..ad8ab0f
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+   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();
+               }
+       }
+}
diff --git a/core/src/de/congrace/exp4j/PostfixExpression.java b/core/src/de/congrace/exp4j/PostfixExpression.java
new file mode 100644 (file)
index 0000000..0bbcc14
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+   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);
+       }
+}
diff --git a/core/src/de/congrace/exp4j/Token.java b/core/src/de/congrace/exp4j/Token.java
new file mode 100644 (file)
index 0000000..2ce4030
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+   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);
+}
diff --git a/core/src/de/congrace/exp4j/Tokenizer.java b/core/src/de/congrace/exp4j/Tokenizer.java
new file mode 100644 (file)
index 0000000..1591995
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+   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()]);
+       }
+}
diff --git a/core/src/de/congrace/exp4j/UnknownFunctionException.java b/core/src/de/congrace/exp4j/UnknownFunctionException.java
new file mode 100644 (file)
index 0000000..ae35093
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+   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);
+       }
+}
diff --git a/core/src/de/congrace/exp4j/UnparsableExpressionException.java b/core/src/de/congrace/exp4j/UnparsableExpressionException.java
new file mode 100644 (file)
index 0000000..fcbf7da
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+   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);
+       }
+}
diff --git a/core/src/de/congrace/exp4j/Variable.java b/core/src/de/congrace/exp4j/Variable.java
new file mode 100644 (file)
index 0000000..0030e70
--- /dev/null
@@ -0,0 +1,107 @@
+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
diff --git a/core/src/de/congrace/exp4j/VariableSet.java b/core/src/de/congrace/exp4j/VariableSet.java
new file mode 100644 (file)
index 0000000..f567006
--- /dev/null
@@ -0,0 +1,42 @@
+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;
+       }       
+}
diff --git a/core/src/de/congrace/exp4j/VariableToken.java b/core/src/de/congrace/exp4j/VariableToken.java
new file mode 100644 (file)
index 0000000..716ef2e
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+   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(" ");
+       }
+}