auto-sync
This commit is contained in:
parent
d043d78212
commit
0c2e468f48
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)");
|
||||
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue