Exploded the exp4j source into our src/ directory. This allows us to more easily modify changes we make to the source. I inlined one call to Arrays.copyOf.
This commit is contained in:
parent
3c4e33e355
commit
ead406e852
@ -7,6 +7,5 @@
|
|||||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||||
<classpathentry kind="lib" path="libs/android-support-v4.jar"/>
|
<classpathentry kind="lib" path="libs/android-support-v4.jar"/>
|
||||||
<classpathentry kind="lib" path="libs/exp4j-rdg.jar"/>
|
|
||||||
<classpathentry kind="output" path="bin/classes"/>
|
<classpathentry kind="output" path="bin/classes"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
|||||||
Binary file not shown.
@ -30,6 +30,5 @@
|
|||||||
<classpathentry kind="lib" path="lib/jogl/gluegen-rt.jar"/>
|
<classpathentry kind="lib" path="lib/jogl/gluegen-rt.jar"/>
|
||||||
<classpathentry kind="lib" path="lib/jogl/jogl.all.jar"/>
|
<classpathentry kind="lib" path="lib/jogl/jogl.all.jar"/>
|
||||||
<classpathentry kind="lib" path="lib/OrangeExtensions-1.2.jar"/>
|
<classpathentry kind="lib" path="lib/OrangeExtensions-1.2.jar"/>
|
||||||
<classpathentry kind="lib" path="lib/exp4j-rdg.jar"/>
|
|
||||||
<classpathentry kind="output" path="bin"/>
|
<classpathentry kind="output" path="bin"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
|||||||
@ -112,7 +112,6 @@
|
|||||||
<attribute name="Main-Class" value="${main-class}"/>
|
<attribute name="Main-Class" value="${main-class}"/>
|
||||||
<attribute name="SplashScreen-Image" value="pix/splashscreen.png"/>
|
<attribute name="SplashScreen-Image" value="pix/splashscreen.png"/>
|
||||||
</manifest>
|
</manifest>
|
||||||
<zipfileset src="lib/exp4j-rdg.jar" />
|
|
||||||
</jar>
|
</jar>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
|
|||||||
77
core/src/de/congrace/exp4j/AbstractExpression.java
Normal file
77
core/src/de/congrace/exp4j/AbstractExpression.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
31
core/src/de/congrace/exp4j/Calculable.java
Normal file
31
core/src/de/congrace/exp4j/Calculable.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
76
core/src/de/congrace/exp4j/CalculationToken.java
Normal file
76
core/src/de/congrace/exp4j/CalculationToken.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
59
core/src/de/congrace/exp4j/CommandlineInterpreter.java
Normal file
59
core/src/de/congrace/exp4j/CommandlineInterpreter.java
Normal file
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
90
core/src/de/congrace/exp4j/CustomFunction.java
Normal file
90
core/src/de/congrace/exp4j/CustomFunction.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
77
core/src/de/congrace/exp4j/Example.java
Normal file
77
core/src/de/congrace/exp4j/Example.java
Normal file
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
135
core/src/de/congrace/exp4j/ExpressionBuilder.java
Normal file
135
core/src/de/congrace/exp4j/ExpressionBuilder.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
core/src/de/congrace/exp4j/FunctionSeparatorToken.java
Normal file
17
core/src/de/congrace/exp4j/FunctionSeparatorToken.java
Normal file
@ -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(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
161
core/src/de/congrace/exp4j/FunctionToken.java
Normal file
161
core/src/de/congrace/exp4j/FunctionToken.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
109
core/src/de/congrace/exp4j/InfixTranslator.java
Normal file
109
core/src/de/congrace/exp4j/InfixTranslator.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
66
core/src/de/congrace/exp4j/NumberToken.java
Normal file
66
core/src/de/congrace/exp4j/NumberToken.java
Normal file
@ -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(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
243
core/src/de/congrace/exp4j/OperatorToken.java
Normal file
243
core/src/de/congrace/exp4j/OperatorToken.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
69
core/src/de/congrace/exp4j/ParenthesisToken.java
Normal file
69
core/src/de/congrace/exp4j/ParenthesisToken.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
116
core/src/de/congrace/exp4j/PostfixExpression.java
Normal file
116
core/src/de/congrace/exp4j/PostfixExpression.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
core/src/de/congrace/exp4j/Token.java
Normal file
50
core/src/de/congrace/exp4j/Token.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
222
core/src/de/congrace/exp4j/Tokenizer.java
Normal file
222
core/src/de/congrace/exp4j/Tokenizer.java
Normal file
@ -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()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
core/src/de/congrace/exp4j/UnknownFunctionException.java
Normal file
37
core/src/de/congrace/exp4j/UnknownFunctionException.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
107
core/src/de/congrace/exp4j/Variable.java
Normal file
107
core/src/de/congrace/exp4j/Variable.java
Normal file
@ -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() + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
core/src/de/congrace/exp4j/VariableSet.java
Normal file
42
core/src/de/congrace/exp4j/VariableSet.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
47
core/src/de/congrace/exp4j/VariableToken.java
Normal file
47
core/src/de/congrace/exp4j/VariableToken.java
Normal file
@ -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(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user