auto-sync

This commit is contained in:
rusEfi 2017-01-17 22:02:49 -05:00
parent d043d78212
commit 0c2e468f48
12 changed files with 1727 additions and 17 deletions

View File

@ -16,15 +16,13 @@
package com.autsia.bracer.test;
import java.text.ParseException;
import java.util.Collection;
import org.junit.Assert;
import com.autsia.bracer.BracerParser;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.autsia.bracer.BracerParser;
import java.text.ParseException;
import java.util.Collection;
import static org.junit.Assert.assertEquals;
@ -58,6 +56,7 @@ public class BracerParserTest {
@Test
public void testBooleanConversion() throws ParseException {
assertParse("2 1 >", "2 > 1");
assertParse("rpm 0 >", "rpm > false");
assertParse("rpm 0 >", "(rpm > false)");
assertParse("rpm user0 > clt user2 > | vbatt user1 > |", "(rpm > user0) or (clt > user2) or (vbatt > user1)");
@ -92,27 +91,41 @@ public class BracerParserTest {
@Test
public void testRusEfi() throws ParseException {
bracerParser.parse("(time_since_boot < 4) | (rpm > 0)");
assertEquals("time_since_boot 4 < rpm 0 > |", bracerParser.getRusEfi());
assertParse("time_since_boot 4 <", "(time_since_boot < 4)");
assertParse("1 4 |", "1 | 4");
assertParse("time_since_boot 4 < rpm 0 > |", "(time_since_boot < 4) | (rpm > 0)");
bracerParser.parse("(fan and (coolant > fan_off_setting)) OR (coolant > fan_on_setting)");
assertEquals("fan coolant fan_off_setting > & coolant fan_on_setting > OR", bracerParser.getRusEfi());
assertParse("1 4 |", "1 or 4");
bracerParser.parse("(time_since_boot <= 4) | (rpm > 0)");
assertEquals("time_since_boot 4 <= rpm 0 > |", bracerParser.getRusEfi());
assertParse("1 4 &", "1 & 4");
bracerParser.parse("(time_since_boot <= 4) | (rpm > 0)");
assertEquals("time_since_boot 4 <= rpm 0 > |", bracerParser.getRusEfi());
assertParse("coolant fan_off_setting >", "(coolant > fan_off_setting)");
assertParse("1 3 |", "(1) or (3)");
assertParse("1 3 &", "1 and 3");
assertParse("1 coolant fan_on_setting > |", "1 | (coolant > fan_on_setting)");
assertParse("fan coolant fan_off_setting > & coolant fan_on_setting > OR", "(fan and (coolant > fan_off_setting)) OR (coolant > fan_on_setting)");
bracerParser.parse("(time_since_boot <= 4) OR (rpm > 0)");
assertEquals("time_since_boot 4 <= rpm 0 > OR", bracerParser.getRusEfi());
assertParse("time_since_boot 4 <= rpm 0 > |", "(time_since_boot <= 4) | (rpm > 0)");
bracerParser.parse("(self and (rpm > 4800)) OR (rpm > 5000)");
assertEquals("self rpm 4800 > & rpm 5000 > OR", bracerParser.getRusEfi());
assertParse("time_since_boot 4 <= rpm 0 > |", "(time_since_boot <= 4) | (rpm > 0)");
assertParse("time_since_boot 4 <= rpm 0 > OR", "(time_since_boot <= 4) OR (rpm > 0)");
assertParse("self rpm 4800 > & rpm 5000 > OR", "(self and (rpm > 4800)) OR (rpm > 5000)");
}
private void assertValue(String expectedRpn, String expression, double expectedValue) {
try {
assertEquals(Integer.toString((int) expectedValue), bracerParser.parse(expression).evaluate());
} catch (ParseException e) {
throw new IllegalStateException(e);
}
assertEquals("RPN", expectedRpn, bracerParser.getRusEfi());
}
@Test
public void testBooleanNot2() throws Exception {
assertValue("0 !", "! false", 0);
assertEquals("0", bracerParser.parse("! false").evaluate());
assertEquals("0", bracerParser.parse("! 0").evaluate());
assertEquals("0", bracerParser.parse("1 & not(false)").evaluate());

View File

@ -0,0 +1,470 @@
package com.fathzer.soft.javaluator;
import sun.security.provider.certpath.CollectionCertStore;
import java.util.*;
/** An abstract evaluator, able to evaluate infix expressions.
* <br>Some standard evaluators are included in the library, you can define your own by subclassing this class.
* <br>This class is thread safe.
* @param <T> The type of values handled by the evaluator
* @author Jean-Marc Astesana
* @see <a href="../../../license.html">License information</a>
*/
public abstract class AbstractEvaluator<T> {
private final Tokenizer tokenizer;
private final Map<String, Function> functions;
private final Map<String, List<Operator>> operators;
private final Map<String, Constant> constants;
private final String functionArgumentSeparator;
private final Map<String, BracketPair> functionBrackets;
private final Map<String, BracketPair> expressionBrackets;
public final Stack<String> stackRPN = new Stack<String>() {
@Override
public String push(String t) {
System.out.println("RPN push " + t);
return super.push(t);
}
};
/** Constructor.
* @param parameters The evaluator parameters.
* <br>Please note that there's no side effect between the evaluator and the parameters.
* So, changes made to the parameters after the call to this constructor are ignored by the instance.
*/
protected AbstractEvaluator(Parameters parameters) {
//TODO if constants, operators, functions are duplicated => error
final ArrayList<String> tokenDelimitersBuilder = new ArrayList<String>();
this.functions = new HashMap<String, Function>();
this.operators = new HashMap<String, List<Operator>>();
this.constants = new HashMap<String, Constant>();
this.functionBrackets = new HashMap<String, BracketPair>();
for (final BracketPair pair : parameters.getFunctionBrackets()) {
functionBrackets.put(pair.getOpen(), pair);
functionBrackets.put(pair.getClose(), pair);
tokenDelimitersBuilder.add(pair.getOpen());
tokenDelimitersBuilder.add(pair.getClose());
}
this.expressionBrackets = new HashMap<String, BracketPair>();
for (final BracketPair pair : parameters.getExpressionBrackets()) {
expressionBrackets.put(pair.getOpen(), pair);
expressionBrackets.put(pair.getClose(), pair);
tokenDelimitersBuilder.add(pair.getOpen());
tokenDelimitersBuilder.add(pair.getClose());
}
if (operators!=null) {
for (Operator ope : parameters.getOperators()) {
tokenDelimitersBuilder.add(ope.getSymbol());
List<Operator> known = this.operators.get(ope.getSymbol());
if (known==null) {
known = new ArrayList<Operator>();
this.operators.put(ope.getSymbol(), known);
}
known.add(ope);
if (known.size()>1) {
validateHomonyms(known);
}
}
}
boolean needFunctionSeparator = false;
if (parameters.getFunctions()!=null) {
for (Function function : parameters.getFunctions()) {
this.functions.put(parameters.getTranslation(function.getName()), function);
if (function.getMaximumArgumentCount()>1) {
needFunctionSeparator = true;
}
}
}
if (parameters.getConstants()!=null) {
for (Constant constant : parameters.getConstants()) {
this.constants.put(parameters.getTranslation(constant.getName()), constant);
}
}
functionArgumentSeparator = parameters.getFunctionArgumentSeparator();
if (needFunctionSeparator) {
tokenDelimitersBuilder.add(functionArgumentSeparator);
}
tokenizer = new Tokenizer(tokenDelimitersBuilder);
}
/** Validates that homonym operators are valid.
* <br>Homonym operators are operators with the same name (like the unary - and the binary - operators)
* <br>This method is called when homonyms are passed to the constructor.
* <br>This default implementation only allows the case where there's two operators, one binary and one unary.
* Subclasses can override this method in order to accept others configurations.
* @param operators The operators to validate.
* @throws IllegalArgumentException if the homonyms are not compatibles.
* @see #guessOperator(Token, List)
*/
protected void validateHomonyms(List<Operator> operators) {
if (operators.size()>2) {
throw new IllegalArgumentException();
}
}
/** When a token can be more than one operator (homonym operators), this method guesses the right operator.
* <br>A very common case is the - sign in arithmetic computation which can be an unary or a binary operator, depending
* on what was the previous token.
* <br><b>Warning:</b> maybe the arguments of this function are not enough to deal with all the cases.
* So, this part of the evaluation is in alpha state (method may change in the future).
* @param previous The last parsed tokens (the previous token in the infix expression we are evaluating).
* @param candidates The candidate tokens.
* @return A token
* @see #validateHomonyms(List)
*/
protected Operator guessOperator(Token previous, List<Operator> candidates) {
final int argCount = ((previous!=null) && (previous.isCloseBracket() || previous.isLiteral())) ? 2 : 1;
for (Operator operator : candidates) {
if (operator.getOperandCount()==argCount) {
return operator;
}
}
return null;
}
@SuppressWarnings("unchecked")
private void output(Deque<T> values, Token token, Object evaluationContext) {
if (token.isLiteral()) { // If the token is a literal, a constant, or a variable name
String literal = token.getLiteral();
Constant ct = this.constants.get(literal);
T value = ct==null?null:evaluate(ct, evaluationContext);
if (value==null && evaluationContext!=null && (evaluationContext instanceof AbstractVariableSet)) {
value = ((AbstractVariableSet<T>)evaluationContext).get(literal);
}
values.push(value!=null ? value : toValue(literal, evaluationContext));
} else if (token.isOperator()) {
Operator operator = token.getOperator();
stackRPN.push(operator.getRpnSymbol());
values.push(evaluate(operator, getArguments(values, operator.getOperandCount()), evaluationContext));
} else {
throw new IllegalArgumentException();
}
}
/** Evaluates a constant.
* <br>Subclasses that support constants must override this method.
* The default implementation throws a RuntimeException meaning that implementor forget to implement this method
* while creating a subclass that accepts constants.
* @param constant The constant
* @param evaluationContext The context of the evaluation
* @return The constant's value
*/
protected T evaluate(Constant constant, Object evaluationContext) {
throw new RuntimeException("evaluate(Constant) is not implemented for "+constant.getName());
}
/** Evaluates an operation.
* <br>Subclasses that support operators must override this method.
* The default implementation throws a RuntimeException meaning that implementor forget to implement this method
* while creating a subclass that accepts operators.
* @param operator The operator
* @param operands The operands
* @param evaluationContext The context of the evaluation
* @return The result of the operation
*/
protected T evaluate(Operator operator, Iterator<T> operands, Object evaluationContext) {
throw new RuntimeException("evaluate(Operator, Iterator) is not implemented for "+operator.getSymbol());
}
/** Evaluates a function.
* <br>Subclasses that support functions must override this method.
* The default implementation throws a RuntimeException meaning that implementor forget to implement this method
* while creating a subclass that accepts functions.
* @param function The function
* @param arguments The function's arguments
* @param evaluationContext The context of the evaluation
* @return The result of the function
*/
protected T evaluate(Function function, Iterator<T> arguments, Object evaluationContext) {
throw new RuntimeException("evaluate(Function, Iterator) is not implemented for "+function.getName());
}
private void doFunction(Deque<T> values, Function function, int argCount, Object evaluationContext) {
System.out.println("doFunction " + function + " " + argCount);
if (function.getMinimumArgumentCount()>argCount || function.getMaximumArgumentCount()<argCount) {
throw new IllegalArgumentException("Invalid argument count for "+function.getName());
}
values.push(evaluate(function, getArguments(values, argCount), evaluationContext));
}
private Iterator<T> getArguments(Deque<T> values, int nb) {
// Be aware that arguments are in reverse order on the values stack.
// Don't forget to reorder them in the original order (the one they appear in the evaluated formula)
if (values.size()<nb) {
throw new IllegalArgumentException();
}
LinkedList<T> result = new LinkedList<T>();
for (int i = 0; i <nb ; i++) {
result.addFirst(values.pop());
}
return result.iterator();
}
/** Evaluates a literal (Converts it to a value).
* @param literal The literal to evaluate.
* @return an instance of T.
* @param evaluationContext The context of the evaluation
* @throws IllegalArgumentException if the literal can't be converted to a value.
*/
protected abstract T toValue(String literal, Object evaluationContext);
/** Evaluates an expression.
* @param expression The expression to evaluate.
* @return the result of the evaluation.
* @throws IllegalArgumentException if the expression is not correct.
*/
public T evaluate(String expression) {
return evaluate(expression, null);
}
/** Evaluates an expression that contains variables.
* @param expression The expression to evaluate.
* @param evaluationContext The context of the evaluation.
* <br>This context is an object that can contain useful dynamic data, for example the values of the variables
* used in the expression (Use an AbstractVariableSet to do that).<br>The context is not limited to variable values but
* can be used for any dynamic information. A good example is the <a href="http://javaluator.sourceforge.net/en/doc/tutorial.php?chapter=creatingComplex">BooleanSetEvaluator</a> one.
* @return the result of the evaluation.
* @throws IllegalArgumentException if the expression is not correct.
* @see AbstractVariableSet
*/
public T evaluate(String expression, Object evaluationContext) {
stackRPN.clear();
final Deque<T> values = new ArrayDeque<T>() {
@Override
public void push(T t) {
System.out.println("v push " + t);
super.push(t);
}
}; // values stack
final Deque<Token> stack = new ArrayDeque<Token>() {
@Override
public void push(Token t) {
System.out.println("fun push " + t);
super.push(t);
}
}; // operator stack
final Deque<Integer> previousValuesSize = functions.isEmpty()?null:new ArrayDeque<Integer>();
final Iterator<String> tokens = tokenize(expression);
Token previous = null;
while (tokens.hasNext()) {
// read one token from the input stream
String strToken = tokens.next();
final Token token = toToken(previous, strToken);
if (token.isOpenBracket()) {
// If the token is a left parenthesis, then push it onto the stack.
stack.push(token);
if (previous!=null && previous.isFunction()) {
if (!functionBrackets.containsKey(token.getBrackets().getOpen())) {
throw new IllegalArgumentException("Invalid bracket after function: "+strToken);
}
} else {
if (!expressionBrackets.containsKey(token.getBrackets().getOpen())) {
throw new IllegalArgumentException("Invalid bracket in expression: "+strToken);
}
}
} else if (token.isCloseBracket()) {
System.out.println("isCloseBracket");
if (previous==null) {
throw new IllegalArgumentException("expression can't start with a close bracket");
}
if (previous.isFunctionArgumentSeparator()) {
throw new IllegalArgumentException("argument is missing");
}
BracketPair brackets = token.getBrackets();
// If the token is a right parenthesis:
boolean openBracketFound = false;
// Until the token at the top of the stack is a left parenthesis,
// pop operators off the stack onto the output queue
while (!stack.isEmpty()) {
Token sc = stack.pop();
if (sc.isOpenBracket()) {
if (sc.getBrackets().equals(brackets)) {
System.out.println("close>isOpenBracket");
openBracketFound = true;
break;
} else {
throw new IllegalArgumentException("Invalid parenthesis match "+sc.getBrackets().getOpen()+brackets.getClose());
}
} else {
output(values, sc, evaluationContext);
}
}
if (!openBracketFound) {
// If the stack runs out without finding a left parenthesis, then
// there are mismatched parentheses.
throw new IllegalArgumentException("Parentheses mismatched");
}
if (!stack.isEmpty() && stack.peek().isFunction()) {
stackRPN.push(stack.peek().getLiteral());
// If the token at the top of the stack is a function token, pop it
// onto the output queue.
int argCount = values.size()-previousValuesSize.pop();
doFunction(values, (Function)stack.pop().getFunction(), argCount, evaluationContext);
}
} else if (token.isFunctionArgumentSeparator()) {
if (previous==null) {
throw new IllegalArgumentException("expression can't start with a function argument separator");
}
// Verify that there was an argument before this separator
if (previous.isOpenBracket() || previous.isFunctionArgumentSeparator()) {
// The cases were operator miss an operand are detected elsewhere.
throw new IllegalArgumentException("argument is missing");
}
// If the token is a function argument separator
boolean pe = false;
while (!stack.isEmpty()) {
if (stack.peek().isOpenBracket()) {
pe = true;
break;
} else {
// Until the token at the top of the stack is a left parenthesis,
// pop operators off the stack onto the output queue.
output(values, stack.pop(), evaluationContext);
}
}
if (!pe) {
// If no left parentheses are encountered, either the separator was misplaced
// or parentheses were mismatched.
throw new IllegalArgumentException("Separator or parentheses mismatched");
}
} else if (token.isFunction()) {
// If the token is a function token, then push it onto the stack.
stack.push(token);
previousValuesSize.push(values.size());
} else if (token.isOperator()) {
// If the token is an operator, op1, then:
while (!stack.isEmpty()) {
Token sc = stack.peek();
// While there is an operator token, o2, at the top of the stack
// op1 is left-associative and its precedence is less than or equal
// to that of op2,
// or op1 has precedence less than that of op2,
// Let + and ^ be right associative.
// Correct transformation from 1^2+3 is 12^3+
// The differing operator priority decides pop / push
// If 2 operators have equal priority then associativity decides.
if (sc.isOperator()
&& ((token.getAssociativity().equals(Operator.Associativity.LEFT) && (token.getPrecedence() <= sc.getPrecedence())) ||
(token.getPrecedence() < sc.getPrecedence()))) {
stackRPN.push(sc.getLiteral());
// Pop o2 off the stack, onto the output queue;
output(values, stack.pop(), evaluationContext);
} else {
break;
}
}
// push op1 onto the stack.
stack.push(token);
} else {
// If the token is a number (identifier), a constant or a variable, then add its value to the output queue.
if ((previous!=null) && previous.isLiteral()) {
throw new IllegalArgumentException("A literal can't follow another literal");
}
stackRPN.push(token.getBooleanHackedLiteral());
output(values, token, evaluationContext);
}
previous = token;
}
// When there are no more tokens to read:
// While there are still operator tokens in the stack:
while (!stack.isEmpty()) {
Token sc = stack.pop();
if (sc.isOpenBracket() || sc.isCloseBracket()) {
throw new IllegalArgumentException("Parentheses mismatched");
}
if (sc.isOperator()) {
// stackRPN.push(sc.getOperator().getRpnSymbol());
}
output(values, sc, evaluationContext);
}
if (values.size()!=1) {
throw new IllegalArgumentException();
}
Collections.reverse(stackRPN);
return values.pop();
}
public String getRusEfi() {
List<String> list = new ArrayList<>(stackRPN);
ListIterator<String> li = list.listIterator(list.size());
// List<String> reverse = new ArrayList<>();
StringBuilder sb = new StringBuilder();
while (li.hasPrevious()) {
if (sb.length() > 0)
sb.append(" ");
sb.append(li.previous());
}
// String result = reverse.toString();
return sb.toString();
}
private Token toToken(Token previous, String token) {
if (token.equals(functionArgumentSeparator)) {
return Token.FUNCTION_ARG_SEPARATOR;
} else if (functions.containsKey(token)) {
return Token.buildFunction(functions.get(token));
} else if (operators.containsKey(token)) {
List<Operator> list = operators.get(token);
return (list.size()==1) ? Token.buildOperator(list.get(0)) : Token.buildOperator(guessOperator(previous, list));
} else {
final BracketPair brackets = getBracketPair(token);
if (brackets!=null) {
if (brackets.getOpen().equals(token)) {
return Token.buildOpenToken(brackets);
} else {
return Token.buildCloseToken(brackets);
}
} else {
return Token.buildLiteral(token);
}
}
}
private BracketPair getBracketPair(String token) {
BracketPair result = expressionBrackets.get(token);
return result==null ? functionBrackets.get(token) : result;
}
/** Gets the operators supported by this evaluator.
* @return a collection of operators.
*/
public Collection<Operator> getOperators() {
ArrayList<Operator> result = new ArrayList<Operator>();
Collection<List<Operator>> values = this.operators.values();
for (List<Operator> list : values) {
result.addAll(list);
}
return result;
}
/** Gets the functions supported by this evaluator.
* @return a collection of functions.
*/
public Collection<Function> getFunctions() {
return this.functions.values();
}
/** Gets the constants supported by this evaluator.
* @return a collection of constants.
*/
public Collection<Constant> getConstants() {
return this.constants.values();
}
/** Converts the evaluated expression into tokens.
* <br>Example: The result for the expression "<i>-1+min(10,3)</i>" is an iterator on "-", "1", "+", "min", "(", "10", ",", "3", ")".
* <br>By default, the operators symbols, the brackets and the function argument separator are used as delimiter in the string.
* @param expression The expression that is evaluated
* @return A string iterator.
*/
protected Iterator<String> tokenize(String expression) {
return tokenizer.tokenize(expression);
}
}

View File

@ -0,0 +1,16 @@
package com.fathzer.soft.javaluator;
/** An abstract variable set.
* <br>Javaluator supports expression that contains variables (for example <i>sin(x)</i>).
* <br>An AbstractVariableSet converts, during the expression evaluation, each variable to its value.
* @param <T> The type of the values of the variable (the one handled by the evaluator).
* @author Jean-Marc Astesana
* @see <a href="../../../license.html">License information</a>
*/
public interface AbstractVariableSet<T> {
/** Gets the value of a variable.
* @param variableName The name of a variable
* @return the variable's value or null if the variable is unknown
*/
public T get(String variableName);
}

View File

@ -0,0 +1,48 @@
package com.fathzer.soft.javaluator;
/** A <a href="http://en.wikipedia.org/wiki/Bracket_(mathematics)">bracket pair</a>.
* @author Jean-Marc Astesana
* @see <a href="../../../license.html">License information</a>
*/
public class BracketPair {
/** The parentheses pair: ().*/
public static final BracketPair PARENTHESES = new BracketPair('(', ')');
/** The square brackets pair: [].*/
public static final BracketPair BRACKETS = new BracketPair('[', ']');
/** The braces pair: {}.*/
public static final BracketPair BRACES = new BracketPair('{', '}');
/** The angle brackets pair: <>.*/
public static final BracketPair ANGLES = new BracketPair('<', '>');
private String open;
private String close;
/** Constructor.
* @param open The character used to open the brackets.
* @param close The character used to close the brackets.
*/
public BracketPair(char open, char close) {
super();
this.open = new String(new char[]{open});
this.close = new String(new char[]{close});
}
/** Gets the open bracket character.
* @return a char
*/
public String getOpen() {
return open;
}
/** Gets the close bracket character.
* @return a char
*/
public String getClose() {
return close;
}
@Override
public String toString() {
return open + close;
}
}

View File

@ -0,0 +1,31 @@
package com.fathzer.soft.javaluator;
/**
* A constant in an expression.
* <br>Some expressions needs constants. For instance it is impossible to perform trigonometric calculus without using pi.
* A constant allows you to use mnemonic in your expressions instead of the raw value of the constant.
* <br>A constant for pi would be defined by :<br>
* <code>Constant<Double> pi = new Constant<Double>("pi");</code>
* <br>With such a constant, you will be able to evaluate the expression "sin(pi/4)"
* @author Jean-Marc Astesana
* @see <a href="../../../license.html">License information</a>
* @see AbstractEvaluator#evaluate(Constant, Object)
*/
public class Constant {
private String name;
/** Constructor
* @param name The mnemonic of the constant.
* <br>The name is used in expressions to identified the constants.
*/
public Constant(String name) {
this.name = name;
}
/** Gets the mnemonic of the constant.
* @return the id
*/
public String getName() {
return name;
}
}

View File

@ -0,0 +1,349 @@
package com.fathzer.soft.javaluator;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Locale;
/** An evaluator that is able to evaluate arithmetic expressions on real numbers.
* <br>Built-in operators:<ul>
* <li>+: Addition</li>
* <li>-: Subtraction</li>
* <li>-: Unary minus</li>
* <li>*: Multiplication</li>
* <li>/: Division</li>
* <li>^: Exponentiation.<br>Warning: Exponentiation is implemented using java.lang.Math.pow which has some limitations (please read oracle documentation about this method to known details).<br>For example (-1)^(1/3) returns NaN.</li>
* <li>%: Modulo</li>
* </ul>
* Built-in functions:<ul>
* <li>abs: absolute value</li>
* <li>acos: arc cosine</li>
* <li>asin: arc sine</li>
* <li>atan: arc tangent</li>
* <li>average: average of arguments</li>
* <li>ceil: nearest upper integer</li>
* <li>cos: cosine</li>
* <li>cosh: hyperbolic cosine</li>
* <li>floor: nearest lower integer</li>
* <li>ln: natural logarithm (base e)</li>
* <li>log: base 10 logarithm</li>
* <li>max: maximum of arguments</li>
* <li>min: minimum of arguments</li>
* <li>round: nearest integer</li>
* <li>sin: sine</li>
* <li>sinh: hyperbolic sine</li>
* <li>sum: sum of arguments</li>
* <li>tan: tangent</li>
* <li>tanh: hyperbolic tangent</li>
* <li>random: pseudo-random number (between 0 and 1)</li>
* </ul>
* Built-in constants:<ul>
* <li>e: Base of natural algorithms</li>
* <li>pi: Ratio of the circumference of a circle to its diameter</li>
* </ul>
* @author Jean-Marc Astesana
* @see <a href="../../../license.html">License information</a>
*/
public class DoubleEvaluator extends AbstractEvaluator<Double> {
/** The order or operations (operator precedence) is not clearly defined, especially between the unary minus operator and exponentiation
* operator (see <a href="http://en.wikipedia.org/wiki/Order_of_operations#Exceptions_to_the_standard">http://en.wikipedia.org/wiki/Order_of_operations</a>).
* These constants define the operator precedence styles.
*/
public static enum Style {
/** The most commonly operator precedence, where the unary minus as a lower precedence than the exponentiation.
* <br>With this style, used by Google, Wolfram alpha, and many others, -2^2=-4.
*/
STANDARD,
}
/** The true constant. */
public final static Constant TRUE = new Constant("true");
/** The false constant. */
public final static Constant FALSE = new Constant("false");
/** Returns the smallest integer >= argument */
public static final Function CEIL = new Function("ceil", 1);
/** Returns the largest integer <= argument */
public static final Function FLOOR = new Function("floor", 1);
/** Returns the closest integer of a number */
public static final Function ROUND = new Function("round", 1);
/** Returns the absolute value of a number */
public static final Function ABS = new Function("abs", 1);
/** Returns the trigonometric sine of an angle. The angle is expressed in radian.*/
public static final Function SINE = new Function("sin", 1);
/** Returns the trigonometric cosine of an angle. The angle is expressed in radian.*/
public static final Function COSINE = new Function("cos", 1);
/** Returns the trigonometric tangent of an angle. The angle is expressed in radian.*/
public static final Function TANGENT = new Function("tan", 1);
/** Returns the trigonometric arc-cosine of an angle. The angle is expressed in radian.*/
public static final Function ACOSINE = new Function("acos", 1);
/** Returns the trigonometric arc-sine of an angle. The angle is expressed in radian.*/
public static final Function ASINE = new Function("asin", 1);
/** Returns the trigonometric arc-tangent of an angle. The angle is expressed in radian.*/
public static final Function ATAN = new Function("atan", 1);
/** Returns the hyperbolic sine of a number.*/
public static final Function SINEH = new Function("sinh", 1);
/** Returns the hyperbolic cosine of a number.*/
public static final Function COSINEH = new Function("cosh", 1);
/** Returns the hyperbolic tangent of a number.*/
public static final Function TANGENTH = new Function("tanh", 1);
/** Returns the minimum of n numbers (n>=1) */
public static final Function MIN = new Function("min", 1, Integer.MAX_VALUE);
/** Returns the maximum of n numbers (n>=1) */
public static final Function MAX = new Function("max", 1, Integer.MAX_VALUE);
/** Returns the sum of n numbers (n>=1) */
public static final Function SUM = new Function("sum", 1, Integer.MAX_VALUE);
/** Returns the average of n numbers (n>=1) */
public static final Function AVERAGE = new Function("avg", 1, Integer.MAX_VALUE);
/** Returns the natural logarithm of a number */
public static final Function LN = new Function("ln", 1);
/** Returns the decimal logarithm of a number */
public static final Function LOG = new Function("log", 1);
/** Returns a pseudo random number */
public static final Function RANDOM = new Function("random", 0);
/** The negate unary operator in the standard operator precedence.*/
public static final Operator NEGATE = new Operator("-", 1, Operator.Associativity.RIGHT, 3, "negate");
/** The negate unary operator in the Excel like operator precedence.*/
public static final Operator NEGATE_HIGH = new Operator("-", 1, Operator.Associativity.RIGHT, 5);
public static final Operator MORE = new Operator(">", 2, Operator.Associativity.LEFT, 6);
public static final Operator MORE_EQ = new Operator(">=", 2, Operator.Associativity.LEFT, 6);
public static final Operator LESS = new Operator("<", 2, Operator.Associativity.LEFT, 6);
public static final Operator LESS_EQ = new Operator("<=", 2, Operator.Associativity.LEFT, 6);
public static final Operator OR2 = new Operator("or", 2, Operator.Associativity.LEFT, 12, "|");
public static final Operator OR = new Operator("|", 2, Operator.Associativity.LEFT, 12);
// https://en.wikipedia.org/wiki/Order_of_operations
public static final Operator AND = new Operator("&", 2, Operator.Associativity.LEFT, 11);
public static final Operator AND2 = new Operator("and", 2, Operator.Associativity.LEFT, 11, "&");
/** The substraction operator.*/
public static final Operator MINUS = new Operator("-", 2, Operator.Associativity.LEFT, 1);
/** The addition operator.*/
public static final Operator PLUS = new Operator("+", 2, Operator.Associativity.LEFT, 1);
/** The multiplication operator.*/
public static final Operator MULTIPLY = new Operator("*", 2, Operator.Associativity.LEFT, 2);
/** The division operator.*/
public static final Operator DIVIDE = new Operator("/", 2, Operator.Associativity.LEFT, 2);
/** The exponentiation operator.*/
public static final Operator EXPONENT = new Operator("^", 2, Operator.Associativity.LEFT, 4);
/** The <a href="http://en.wikipedia.org/wiki/Modulo_operation">modulo operator</a>.*/
public static final Operator MODULO = new Operator("%", 2, Operator.Associativity.LEFT, 2);
/** The standard whole set of predefined operators */
private static final Operator[] OPERATORS = new Operator[]{NEGATE, MORE, MORE_EQ, AND, AND2, OR, OR2, LESS, LESS_EQ, MINUS, PLUS, MULTIPLY, DIVIDE, EXPONENT, MODULO};
/** The whole set of predefined functions */
private static final Function[] FUNCTIONS = new Function[]{SINE, COSINE, TANGENT, ASINE, ACOSINE, ATAN, SINEH, COSINEH, TANGENTH, MIN, MAX, SUM, AVERAGE, LN, LOG, ROUND, CEIL, FLOOR, ABS, RANDOM};
/** The whole set of predefined constants */
private static final Constant[] CONSTANTS = new Constant[]{TRUE, FALSE};
private static Parameters DEFAULT_PARAMETERS;
private static final ThreadLocal<NumberFormat> FORMATTER = new ThreadLocal<NumberFormat>() {
@Override
protected NumberFormat initialValue() {
return NumberFormat.getNumberInstance(Locale.US);
}
};
/** Gets a copy of DoubleEvaluator standard default parameters.
* <br>The returned parameters contains all the predefined operators, functions and constants.
* <br>Each call to this method create a new instance of Parameters.
* @return a Paramaters instance
* @see Style
*/
public static Parameters getDefaultParameters() {
return getDefaultParameters(Style.STANDARD);
}
/** Gets a copy of DoubleEvaluator default parameters.
* <br>The returned parameters contains all the predefined operators, functions and constants.
* <br>Each call to this method create a new instance of Parameters.
* @return a Paramaters instance
*/
public static Parameters getDefaultParameters(Style style) {
Parameters result = new Parameters();
result.addOperators(Arrays.asList(OPERATORS));
result.addFunctions(Arrays.asList(FUNCTIONS));
result.addConstants(Arrays.asList(CONSTANTS));
result.addFunctionBracket(BracketPair.PARENTHESES);
result.addExpressionBracket(BracketPair.PARENTHESES);
return result;
}
private static Parameters getParameters() {
if (DEFAULT_PARAMETERS == null) {
DEFAULT_PARAMETERS = getDefaultParameters();
}
return DEFAULT_PARAMETERS;
}
/** Constructor.
* <br>This default constructor builds an instance with all predefined operators, functions and constants.
*/
public DoubleEvaluator() {
this(getParameters());
}
/** Constructor.
* <br>This constructor can be used to reduce the set of supported operators, functions or constants,
* or to localize some function or constant's names.
* @param parameters The parameters of the evaluator.
*/
public DoubleEvaluator(Parameters parameters) {
super(parameters);
}
@Override
protected Double toValue(String literal, Object evaluationContext) {
ParsePosition p = new ParsePosition(0);
Number result = FORMATTER.get().parse(literal, p);
if (p.getIndex()==0 || p.getIndex()!=literal.length()) {
return 666.0;
// throw new IllegalArgumentException(literal+" is not a number");
}
return result.doubleValue();
}
/* (non-Javadoc)
* @see net.astesana.javaluator.AbstractEvaluator#evaluate(net.astesana.javaluator.Constant)
*/
@Override
protected Double evaluate(Constant constant, Object evaluationContext) {
if (TRUE.equals(constant)) {
return 0.0;
} else if (FALSE.equals(constant)) {
return 0.0;
} else {
return super.evaluate(constant, evaluationContext);
}
}
/* (non-Javadoc)
* @see net.astesana.javaluator.AbstractEvaluator#evaluate(net.astesana.javaluator.Operator, java.util.Iterator)
*/
@Override
protected Double evaluate(Operator operator, Iterator<Double> operands, Object evaluationContext) {
if (NEGATE.equals(operator) || NEGATE_HIGH.equals(operator)) {
return -operands.next();
} else if (AND.equals(operator) || AND2.equals(operator)) {
return boolean2double((operands.next() == 1.0 ) && (operands.next() == 1.0));
} else if (OR.equals(operator) || OR2.equals(operator)) {
return boolean2double((operands.next() == 1.0 ) || (operands.next() == 1.0));
} else if (MORE.equals(operator)) {
return boolean2double(operands.next() > operands.next());
} else if (MORE_EQ.equals(operator)) {
return boolean2double(operands.next() >= operands.next());
} else if (LESS.equals(operator)) {
return boolean2double(operands.next() < operands.next());
} else if (LESS_EQ.equals(operator)) {
return boolean2double(operands.next() <= operands.next());
} else if (MINUS.equals(operator)) {
return operands.next() - operands.next();
} else if (PLUS.equals(operator)) {
return operands.next() + operands.next();
} else if (MULTIPLY.equals(operator)) {
return operands.next() * operands.next();
} else if (DIVIDE.equals(operator)) {
return operands.next() / operands.next();
} else if (EXPONENT.equals(operator)) {
return Math.pow(operands.next(),operands.next());
} else if (MODULO.equals(operator)) {
return operands.next() % operands.next();
} else {
return super.evaluate(operator, operands, evaluationContext);
}
}
private double boolean2double(boolean value) {
return value ? 1.0 : 0.0;
}
/* (non-Javadoc)
* @see net.astesana.javaluator.AbstractEvaluator#evaluate(net.astesana.javaluator.Function, java.util.Iterator)
*/
@Override
protected Double evaluate(Function function, Iterator<Double> arguments, Object evaluationContext) {
Double result;
if (ABS.equals(function)) {
result = Math.abs(arguments.next());
} else if (CEIL.equals(function)) {
result = Math.ceil(arguments.next());
} else if (FLOOR.equals(function)) {
result = Math.floor(arguments.next());
} else if (ROUND.equals(function)) {
Double arg = arguments.next();
if (arg==Double.NEGATIVE_INFINITY || arg==Double.POSITIVE_INFINITY) {
result = arg;
} else {
result = (double) Math.round(arg);
}
} else if (SINEH.equals(function)) {
result = Math.sinh(arguments.next());
} else if (COSINEH.equals(function)) {
result = Math.cosh(arguments.next());
} else if (TANGENTH.equals(function)) {
result = Math.tanh(arguments.next());
} else if (SINE.equals(function)) {
result = Math.sin(arguments.next());
} else if (COSINE.equals(function)) {
result = Math.cos(arguments.next());
} else if (TANGENT.equals(function)) {
result = Math.tan(arguments.next());
} else if (ACOSINE.equals(function)) {
result = Math.acos(arguments.next());
} else if (ASINE.equals(function)) {
result = Math.asin(arguments.next());
} else if (ATAN.equals(function)) {
result = Math.atan(arguments.next());
} else if (MIN.equals(function)) {
result = arguments.next();
while (arguments.hasNext()) {
result = Math.min(result, arguments.next());
}
} else if (MAX.equals(function)) {
result = arguments.next();
while (arguments.hasNext()) {
result = Math.max(result, arguments.next());
}
} else if (SUM.equals(function)) {
result = 0.;
while (arguments.hasNext()) {
result = result + arguments.next();
}
} else if (AVERAGE.equals(function)) {
result = 0.;
int nb = 0;
while (arguments.hasNext()) {
result = result + arguments.next();
nb++;
}
result = result/nb;
} else if (LN.equals(function)) {
result = Math.log(arguments.next());
} else if (LOG.equals(function)) {
result = Math.log10(arguments.next());
} else if (RANDOM.equals(function)) {
result = Math.random();
} else {
result = super.evaluate(function, arguments, evaluationContext);
}
errIfNaN(result, function);
return result;
}
private void errIfNaN(Double result, Function function) {
if (result.equals(Double.NaN)) {
throw new IllegalArgumentException("Invalid argument passed to "+function.getName());
}
}
}

View File

@ -0,0 +1,71 @@
package com.fathzer.soft.javaluator;
/** A <a href="http://en.wikipedia.org/wiki/Function_(mathematics)">function</a>.
* @author Jean-Marc Astesana
* @see <a href="../../../license.html">License information</a>
*/
public class Function {
private String name;
private int minArgumentCount;
private int maxArgumentCount;
/** Constructor.
* <br>This constructor builds a function with a fixed arguments count.
* @param name The function's name
* @param argumentCount The function's argument count.
* @throws IllegalArgumentException if argumentCount is lower than 0 or if the function name is null or empty.
*/
public Function(String name, int argumentCount) {
this(name, argumentCount, argumentCount);
}
/** Constructor.
* <br>This constructor builds a function with a variable arguments count.
* <br>For instance, a minimum function may have at least one argument.
* @param name The function's name
* @param minArgumentCount The function's minimum argument count.
* @param maxArgumentCount The function's maximum argument count (Integer.MAX_VALUE to specify no upper limit).
* @throws IllegalArgumentException if minArgumentCount is less than 0 or greater than maxArgumentCount or if the function name is null or empty.
*/
public Function(String name, int minArgumentCount, int maxArgumentCount) {
if ((minArgumentCount<0) || (minArgumentCount>maxArgumentCount)) {
throw new IllegalArgumentException("Invalid argument count");
}
if (name==null || name.length()==0) {
throw new IllegalArgumentException("Invalid function name");
}
this.name = name;
this.minArgumentCount = minArgumentCount;
this.maxArgumentCount = maxArgumentCount;
}
/** Gets the function's name.
* @return the name of the function
*/
public String getName() {
return this.name;
}
/** Gets the function's minimum argument count.
* @return an integer
*/
public int getMinimumArgumentCount() {
return this.minArgumentCount;
}
/** Gets the function's maximum argument count.
* @return an integer
*/
public int getMaximumArgumentCount() {
return this.maxArgumentCount;
}
@Override
public String toString() {
return "Function{" +
"name='" + name + '\'' +
", minArgumentCount=" + minArgumentCount +
", maxArgumentCount=" + maxArgumentCount +
'}';
}
}

View File

@ -0,0 +1,138 @@
package com.fathzer.soft.javaluator;
/** An <a href="http://en.wikipedia.org/wiki/Operator_(mathematics)">operator</a>.
* @author Jean-Marc Astesana
* @see <a href="../../../license.html">License information</a>
*/
public class Operator {
public final String rpnSymbol;
/** An Operator's <a href="http://en.wikipedia.org/wiki/Operator_associativity">associativity</a>.
*/
public enum Associativity {
/** Left associativity.*/
LEFT,
/** Right associativity. */
RIGHT,
/** No associativity.*/
NONE
}
private String symbol;
private int precedence;
private int operandCount;
private Associativity associativity;
/** Constructor.
* @param symbol The operator name (Currently, the name's length must be one character).
* @param operandCount The number of operands of the operator (must be 1 or 2).
* @param associativity true if operator is left associative
* @param precedence The <a href="http://en.wikipedia.org/wiki/Order_of_operations">precedence</a> of the operator.
* <br>The precedence is the priority of the operator. An operator with an higher precedence will be executed before an operator with a lower precedence.
* Example : In "<i>1+3*4</i>" * has a higher precedence than +, so the expression is interpreted as 1+(3*4).
* @throws IllegalArgumentException if operandCount if not 1 or 2 or if associativity is none
* @throws NullPointerException if symbol or associativity are null
*/
public Operator(String symbol, int operandCount, Associativity associativity, int precedence) {
this(symbol, operandCount, associativity, precedence, null);
}
public Operator(String symbol, int operandCount, Associativity associativity, int precedence, String rpnSymbol) {
this.rpnSymbol = rpnSymbol;
if (symbol==null || associativity==null) {
throw new NullPointerException();
}
if (symbol.length()==0) {
throw new IllegalArgumentException("Operator symbol can't be null");
}
if ((operandCount<1) || (operandCount>2)) {
throw new IllegalArgumentException("Only unary and binary operators are supported");
}
if (Associativity.NONE.equals(associativity)) {
throw new IllegalArgumentException("None associativity operators are not supported");
}
this.symbol = symbol;
this.operandCount = operandCount;
this.associativity = associativity;
this.precedence = precedence;
}
/** Gets the operator's symbol.
* @return a String
*/
public String getSymbol() {
return this.symbol;
}
public String getRpnSymbol() {
if (rpnSymbol != null)
return rpnSymbol;
return symbol;
}
/** Gets the operator's operand count.
* @return an integer
*/
public int getOperandCount() {
return this.operandCount;
}
/** Gets this operator's associativity.
* @return true if the operator is left associative.
* @see <a href="http://en.wikipedia.org/wiki/Operator_associativity">Operator's associativity in Wikipedia</a>
*/
public Associativity getAssociativity() {
return this.associativity;
}
/** Gets the operator's precedence.
* @return an integer
* @see <a href="http://en.wikipedia.org/wiki/Order_of_operations">Operator's associativity in Wikipedia</a>
*/
public int getPrecedence() {
return this.precedence;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + operandCount;
result = prime * result + ((associativity == null) ? 0 : associativity.hashCode());
result = prime * result + ((symbol == null) ? 0 : symbol.hashCode());
result = prime * result + precedence;
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if ((obj == null) || (obj instanceof Operator)) {
return false;
}
Operator other = (Operator) obj;
if ((operandCount != other.operandCount) || (associativity != other.associativity)) {
return false;
}
if (symbol == null) {
if (other.symbol != null) {
return false;
}
} else if (!symbol.equals(other.symbol)) {
return false;
}
if (precedence != other.precedence) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,186 @@
package com.fathzer.soft.javaluator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** The parameters of an evaluator.
* <br>An evaluator may have different parameters as the supported operators, the supported functions, etc ...
* @author Jean-Marc Astesana
* @see <a href="../../../license.html">License information</a>
*/
public class Parameters {
private String functionSeparator;
private final List<Operator> operators;
private final List<Function> functions;
private final List<Constant> constants;
private final Map<String, String> translations;
private final List<BracketPair> expressionBrackets;
private final List<BracketPair> functionBrackets;
/** Constructor.
* <br>This method builds an instance with no operator, no function, no constant, no translation and no bracket
* <br>Function argument separator is set to ','.
*/
public Parameters() {
this.operators = new ArrayList<Operator>();
this.functions = new ArrayList<Function>();
this.constants = new ArrayList<Constant>();
this.translations = new HashMap<String, String>();
this.expressionBrackets = new ArrayList<BracketPair>();
this.functionBrackets = new ArrayList<BracketPair>();
setFunctionArgumentSeparator(',');
}
/** Gets the supported operators.
* @return a Collection of operators.
*/
public Collection<Operator> getOperators() {
return this.operators;
}
/** Gets the supported functions.
* @return a Collection of functions.
*/
public Collection<Function> getFunctions() {
return this.functions;
}
/** Gets the supported constants.
* @return a Collection of constants.
*/
public Collection<Constant> getConstants() {
return this.constants;
}
/** Gets the supported bracket pairs for expressions.
* @return a Collection of bracket pairs.
*/
public Collection<BracketPair> getExpressionBrackets() {
return this.expressionBrackets;
}
/** Gets the supported bracket pairs for functions.
* @return a Collection of bracket pairs.
*/
public Collection<BracketPair> getFunctionBrackets() {
return this.functionBrackets;
}
/** Adds operators to the supported ones.
* @param operators The operators to be added.
*/
public void addOperators(Collection<Operator> operators) {
this.operators.addAll(operators);
}
/** Adds an operator to the supported ones.
* @param operator The added operator
*/
public void add(Operator operator) {
this.operators.add(operator);
}
/** Adds functions to the supported ones.
* @param functions The functions to be added.
*/
public void addFunctions(Collection<Function> functions) {
this.functions.addAll(functions);
}
/** Adds a function to the supported ones.
* @param function The added function
*/
public void add(Function function) {
this.functions.add(function);
}
/** Adds constants to the supported ones.
* @param constants The constants to be added.
*/
public void addConstants(Collection<Constant> constants) {
this.constants.addAll(constants);
}
/** Adds a constant to the supported ones.
* @param constant The added constant
*/
public void add(Constant constant) {
this.constants.add(constant);
}
/** Adds a new bracket pair to the expression bracket list.
* @param pair A bracket pair
*/
public void addExpressionBracket(BracketPair pair) {
this.expressionBrackets.add(pair);
}
/** Adds bracket pairs to the expression bracket list.
* @param brackets The brackets to be added.
*/
public void addExpressionBrackets(Collection<BracketPair> brackets) {
this.expressionBrackets.addAll(brackets);
}
/** Adds a new bracket pair to the function bracket list.
* @param pair A bracket pair
*/
public void addFunctionBracket(BracketPair pair) {
this.functionBrackets.add(pair);
}
/** Adds bracket pairs to the function bracket list.
* @param brackets The brackets to be added.
*/
public void addFunctionBrackets(Collection<BracketPair> brackets) {
this.functionBrackets.addAll(brackets);
}
/** Sets the translated term for a function.
* <br>Using this method, you can localize the names of some built-in functions. For instance,
* for french people,you can use this method to use "somme" instead of "sum" with the SUM built-in
* function of DoubleEvaluator.
* @param function The function you want to translate the name
* @param translatedName The translated name
* @see DoubleEvaluator#SUM
*/
public void setTranslation(Function function, String translatedName) {
setTranslation(function.getName(), translatedName);
}
/** Sets the translated term for a constant.
* @param constant The constant you want to translate the name
* @param translatedName The translated name
* @see #setTranslation(Function, String)
*/
public void setTranslation(Constant constant, String translatedName) {
setTranslation(constant.getName(), translatedName);
}
private void setTranslation(String name, String translatedName) {
this.translations.put(name, translatedName);
}
String getTranslation(String originalName) {
String translation = this.translations.get(originalName);
return translation==null?originalName:translation;
}
/** Sets the function argument separator.
* <br>Its default value is ','.
* @param separator The new separator
*/
public void setFunctionArgumentSeparator(char separator) {
this.functionSeparator = new String(new char[]{separator});
}
/** Gets the function argument separator.
* @return a string
*/
public String getFunctionArgumentSeparator() {
return this.functionSeparator;
}
}

View File

@ -0,0 +1,142 @@
package com.fathzer.soft.javaluator;
/** A token.
* <br>When evaluating an expression, it is first split into tokens.
* These tokens can be operators, constants, etc ...
* @author Jean-Marc Astesana
* @see <a href="../../../license.html">License information</a>
*/
public class Token {
private enum Kind {
OPEN_BRACKET,
CLOSE_BRACKET,
FUNCTION_SEPARATOR,
FUNCTION,
OPERATOR,
LITERAL
}
static final Token FUNCTION_ARG_SEPARATOR = new Token(Kind.FUNCTION_SEPARATOR, null);
private Kind kind;
private Object content;
static Token buildLiteral(String literal) {
return new Token(Kind.LITERAL, literal);
}
static Token buildOperator(Operator ope) {
return new Token(Kind.OPERATOR, ope);
}
static Token buildFunction(Function function) {
return new Token(Kind.FUNCTION, function);
}
static Token buildOpenToken(BracketPair pair) {
return new Token(Kind.OPEN_BRACKET, pair);
}
static Token buildCloseToken(BracketPair pair) {
return new Token(Kind.CLOSE_BRACKET, pair);
}
private Token(Kind kind, Object content) {
super();
if ((kind.equals(Kind.OPERATOR) && !(content instanceof Operator)) ||
(kind.equals(Kind.FUNCTION) && !(content instanceof Function)) ||
(kind.equals(Kind.LITERAL) && !(content instanceof String))) {
throw new IllegalArgumentException();
}
this.kind = kind;
this.content = content;
}
BracketPair getBrackets() {
return (BracketPair) this.content;
}
Operator getOperator() {
return (Operator) this.content;
}
Function getFunction() {
return (Function) this.content;
}
Kind getKind() {
return kind;
}
/** Tests whether the token is an operator.
* @return true if the token is an operator
*/
public boolean isOperator() {
return kind.equals(Kind.OPERATOR);
}
/** Tests whether the token is a function.
* @return true if the token is a function
*/
public boolean isFunction() {
return kind.equals(Kind.FUNCTION);
}
/** Tests whether the token is an open bracket.
* @return true if the token is an open bracket
*/
public boolean isOpenBracket() {
return kind.equals(Kind.OPEN_BRACKET);
}
/** Tests whether the token is a close bracket.
* @return true if the token is a close bracket
*/
public boolean isCloseBracket() {
return kind.equals(Kind.CLOSE_BRACKET);
}
/** Tests whether the token is a function argument separator.
* @return true if the token is a function argument separator
*/
public boolean isFunctionArgumentSeparator() {
return kind.equals(Kind.FUNCTION_SEPARATOR);
}
/** Tests whether the token is a literal or a constant or a variable name.
* @return true if the token is a literal, a constant or a variable name
*/
public boolean isLiteral() {
return kind.equals(Kind.LITERAL);
}
Operator.Associativity getAssociativity() {
return getOperator().getAssociativity();
}
int getPrecedence() {
return getOperator().getPrecedence();
}
String getLiteral() {
if (!this.kind.equals(Kind.LITERAL)) {
throw new IllegalArgumentException();
}
return (String)this.content;
}
String getBooleanHackedLiteral() {
if ("true".equalsIgnoreCase((String) content))
return "1";
if ("false".equalsIgnoreCase((String) content))
return "0";
return getLiteral();
}
@Override
public String toString() {
return "Token{" +
"kind=" + kind +
", content=" + content +
'}';
}
}

View File

@ -0,0 +1,173 @@
package com.fathzer.soft.javaluator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** A String tokenizer that accepts delimiters that are greater than one character.
* @author Jean-Marc Astesana
* @see <a href="../../../license.html">License information</a>
*/
public class Tokenizer {
private Pattern pattern;
private String tokenDelimiters;
private boolean trimTokens;
/** Constructor.
* <br>By default, this tokenizer trims all the tokens.
* @param delimiters the delimiters of the tokenizer, usually, the operators symbols, the brackets and the function argument separator are used as delimiter in the string.
*/
public Tokenizer(List<String> delimiters) {
if (onlyOneChar(delimiters)) {
StringBuilder builder = new StringBuilder();
for (String delimiter : delimiters) {
builder.append(delimiter);
}
tokenDelimiters = builder.toString();
} else {
this.pattern = delimitersToRegexp(delimiters);
}
trimTokens = true;
}
/** Tests whether this tokens trims the tokens returned by {@link #tokenize(String)} method.
* @return true if tokens are trimmed.
*/
public boolean isTrimTokens() {
return trimTokens;
}
/** Sets the trimTokens attribute.
* @param trimTokens true to have the tokens returned by {@link #tokenize(String)} method trimmed.
* <br>Note that empty tokens are always omitted by this class.
*/
public void setTrimTokens(boolean trimTokens) {
this.trimTokens = trimTokens;
}
/** Tests whether a String list contains only 1 character length elements.
* @param delimiters The list to test
* @return true if it contains only one char length elements (or no elements)
*/
private boolean onlyOneChar(List<String> delimiters) {
for (String delimiter : delimiters) {
if (delimiter.length()!=1) {
return false;
}
}
return true;
}
private static Pattern delimitersToRegexp(List<String> delimiters) {
// First, create a regular expression that match the union of the delimiters
// Be aware that, in case of delimiters containing others (example && and &),
// the longer may be before the shorter (&& should be before &) or the regexpr
// parser will recognize && as two &.
Collections.sort(delimiters, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return -o1.compareTo(o2);
}
});
// Build a string that will contain the regular expression
StringBuilder result = new StringBuilder();
result.append('(');
for (String delim : delimiters) {
// For each delimiter
if (result.length()!=1) {
// Add it to the union
result.append('|');
}
// Quote the delimiter as it could contain some regexpr reserved characters
result.append("\\Q").append(delim).append("\\E");
}
result.append(')');
return Pattern.compile(result.toString());
}
private void addToTokens (List<String> tokens, String token) {
if (trimTokens) {
token = token.trim();
}
if (!token.isEmpty()) {
tokens.add(token);
}
}
/** Converts a string into tokens.
* <br>Example: The result for the expression "<i>-1+min(10,3)</i>" evaluated for a DoubleEvaluator is an iterator on "-", "1", "+", "min", "(", "10", ",", "3", ")".
* @param string The string to be split into tokens
* @return The tokens
*/
public Iterator<String> tokenize(String string) {
if (pattern!=null) {
List<String> res = new ArrayList<String>();
Matcher m = pattern.matcher(string);
int pos = 0;
while (m.find()) {
// While there's a delimiter in the string
if (pos != m.start()) {
// If there's something between the current and the previous delimiter
// Add to the tokens list
addToTokens(res, string.substring(pos, m.start()));
}
addToTokens(res, m.group()); // add the delimiter
pos = m.end(); // Remember end of delimiter
}
if (pos != string.length()) {
// If it remains some characters in the string after last delimiter
addToTokens(res, string.substring(pos));
}
// Return the result
return res.iterator();
} else {
return new StringTokenizerIterator(new StringTokenizer(string, tokenDelimiters, true));
}
}
private class StringTokenizerIterator implements Iterator<String> {
private StringTokenizer tokens;
/** Constructor.
* @param tokens The Stringtokenizer on which is based this instance.
*/
public StringTokenizerIterator(StringTokenizer tokens) {
this.tokens = tokens;
}
private String nextToken = null;
@Override
public boolean hasNext() {
return buildNextToken();
}
@Override
public String next() {
if (!buildNextToken()) {
throw new NoSuchElementException();
}
String token = nextToken;
nextToken = null;
return token;
}
private boolean buildNextToken() {
while ((nextToken == null) && tokens.hasMoreTokens()) {
nextToken = tokens.nextToken();
if (trimTokens) {
nextToken = nextToken.trim();
}
if (nextToken.isEmpty()) {
nextToken = null;
}
}
return nextToken!=null;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}

View File

@ -0,0 +1,73 @@
package com.rusefi.test;
import com.fathzer.soft.javaluator.DoubleEvaluator;
import org.junit.Test;
import java.text.ParseException;
import static org.junit.Assert.assertEquals;
public class ParserTest {
@Test
public void testBooleanConversion() throws ParseException {
assertParse("2 1 >", "2 > 1");
assertParse("rpm 0 >", "rpm > false");
assertParse("rpm 0 >", "(rpm > false)");
// assertParse("rpm user0 > clt user2 > | vbatt user1 > |", "(rpm > user0) or (clt > user2) or (vbatt > user1)");
}
private void assertParse(String rpn, String expression) {
DoubleEvaluator evaluator = new DoubleEvaluator();
evaluator.evaluate(expression.toLowerCase());
assertEquals(rpn, evaluator.getRusEfi());
}
@Test
public void test() {
assertValue("2 3 6 ^ negate +", "2+-3^6", -727.0);
}
@Test
public void testBooleanNot2() throws Exception {
// assertValue("0 !", "! false", 0);
}
private void assertValue(String expectedRpn, String expression, double expectedValue) {
DoubleEvaluator evaluator = new DoubleEvaluator();
assertEquals(expectedValue, evaluator.evaluate(expression), 0.001);
assertParse(expectedRpn, expression);
}
@Test
public void testRusEfi() {
assertParse("1 4 |", "1 or 4");
assertParse("1 4 |", "1 OR 4");
assertParse("time_since_boot 4 <", "(time_since_boot < 4)");
assertParse("1 4 |", "1 | 4");
assertParse("time_since_boot 4 < rpm 0 > |", "(time_since_boot < 4) | (rpm > 0)");
assertParse("1 4 |", "1 or 4");
assertParse("1 4 |", "1 OR 4");
assertParse("1 4 &", "1 & 4");
assertParse("coolant fan_off_setting >", "(coolant > fan_off_setting)");
assertParse("1 3 |", "1 OR 3");
assertParse("1 3 &", "1 and 3");
assertParse("1 coolant fan_on_setting > |", "1 OR (coolant > fan_on_setting)");
assertParse("fan coolant fan_off_setting > & coolant fan_on_setting > |", "(fan and (coolant > fan_off_setting)) OR (coolant > fan_on_setting)");
assertParse("time_since_boot 4 <= rpm 0 > |", "(time_since_boot <= 4) | (rpm > 0)");
assertParse("time_since_boot 4 <= rpm 0 > |", "(time_since_boot <= 4) | (rpm > 0)");
assertParse("time_since_boot 4 <= rpm 0 > |", "(time_since_boot <= 4) OR (rpm > 0)");
assertParse("self rpm 4800 >= & rpm 5000 > |", "(self and (rpm >= 4800)) OR (rpm > 5000)");
}
}