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:
Kevin Ruland 2012-08-10 19:13:50 +00:00
parent 3c4e33e355
commit ead406e852
26 changed files with 1887 additions and 3 deletions

View File

@ -7,6 +7,5 @@
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="lib" path="libs/android-support-v4.jar"/>
<classpathentry kind="lib" path="libs/exp4j-rdg.jar"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>

Binary file not shown.

View 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>

View File

@ -112,7 +112,6 @@
<attribute name="Main-Class" value="${main-class}"/>
<attribute name="SplashScreen-Image" value="pix/splashscreen.png"/>
</manifest>
<zipfileset src="lib/exp4j-rdg.jar" />
</jar>
</target>

View 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;
}
}

View 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);
}

View 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;
}
}

View 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());
}
}

View 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;
}
}

View 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());
}
}

View 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;
}
}

View 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(" ");
}
}
}

View 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);
}
}

View 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();
}
}

View File

@ -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);
}
}

View 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(' ');
}
}

View 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);
}
}

View 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();
}
}
}

View 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);
}
}

View 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);
}

View 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()]);
}
}

View 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);
}
}

View 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;
/**
* 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);
}
}

View 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() + "}";
}
}
}

View 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;
}
}

View 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(" ");
}
}