diff --git a/java_console/models/src/com/autsia/bracer/test/BracerParserTest.java b/java_console/models/src/com/autsia/bracer/test/BracerParserTest.java
index 58f455f491..08b0824246 100644
--- a/java_console/models/src/com/autsia/bracer/test/BracerParserTest.java
+++ b/java_console/models/src/com/autsia/bracer/test/BracerParserTest.java
@@ -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());
diff --git a/java_console/models/src/com/fathzer/soft/javaluator/AbstractEvaluator.java b/java_console/models/src/com/fathzer/soft/javaluator/AbstractEvaluator.java
new file mode 100644
index 0000000000..64aaa6cf78
--- /dev/null
+++ b/java_console/models/src/com/fathzer/soft/javaluator/AbstractEvaluator.java
@@ -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.
+ *
Some standard evaluators are included in the library, you can define your own by subclassing this class.
+ *
This class is thread safe.
+ * @param The type of values handled by the evaluator
+ * @author Jean-Marc Astesana
+ * @see License information
+ */
+public abstract class AbstractEvaluator {
+ private final Tokenizer tokenizer;
+ private final Map functions;
+ private final Map> operators;
+ private final Map constants;
+ private final String functionArgumentSeparator;
+ private final Map functionBrackets;
+ private final Map expressionBrackets;
+
+ public final Stack stackRPN = new Stack() {
+ @Override
+ public String push(String t) {
+ System.out.println("RPN push " + t);
+ return super.push(t);
+ }
+
+ };
+
+
+ /** Constructor.
+ * @param parameters The evaluator parameters.
+ *
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 tokenDelimitersBuilder = new ArrayList();
+ this.functions = new HashMap();
+ this.operators = new HashMap>();
+ this.constants = new HashMap();
+ this.functionBrackets = new HashMap();
+ 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();
+ 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 known = this.operators.get(ope.getSymbol());
+ if (known==null) {
+ known = new ArrayList();
+ 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.
+ *
Homonym operators are operators with the same name (like the unary - and the binary - operators)
+ *
This method is called when homonyms are passed to the constructor.
+ *
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 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.
+ *
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.
+ *
Warning: 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 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 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)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.
+ *
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.
+ *
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 operands, Object evaluationContext) {
+ throw new RuntimeException("evaluate(Operator, Iterator) is not implemented for "+operator.getSymbol());
+ }
+
+ /** Evaluates a function.
+ *
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 arguments, Object evaluationContext) {
+ throw new RuntimeException("evaluate(Function, Iterator) is not implemented for "+function.getName());
+ }
+
+ private void doFunction(Deque values, Function function, int argCount, Object evaluationContext) {
+ System.out.println("doFunction " + function + " " + argCount);
+
+ if (function.getMinimumArgumentCount()>argCount || function.getMaximumArgumentCount() getArguments(Deque 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() result = new LinkedList();
+ for (int i = 0; i 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).
The context is not limited to variable values but
+ * can be used for any dynamic information. A good example is the BooleanSetEvaluator 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 values = new ArrayDeque() {
+ @Override
+ public void push(T t) {
+ System.out.println("v push " + t);
+ super.push(t);
+ }
+ }; // values stack
+ final Deque stack = new ArrayDeque() {
+ @Override
+ public void push(Token t) {
+ System.out.println("fun push " + t);
+ super.push(t);
+ }
+
+ }; // operator stack
+ final Deque previousValuesSize = functions.isEmpty()?null:new ArrayDeque();
+ final Iterator 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 list = new ArrayList<>(stackRPN);
+ ListIterator li = list.listIterator(list.size());
+// List 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 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 getOperators() {
+ ArrayList result = new ArrayList();
+ Collection> values = this.operators.values();
+ for (List list : values) {
+ result.addAll(list);
+ }
+ return result;
+ }
+
+ /** Gets the functions supported by this evaluator.
+ * @return a collection of functions.
+ */
+ public Collection getFunctions() {
+ return this.functions.values();
+ }
+
+ /** Gets the constants supported by this evaluator.
+ * @return a collection of constants.
+ */
+ public Collection getConstants() {
+ return this.constants.values();
+ }
+
+ /** Converts the evaluated expression into tokens.
+ *
Example: The result for the expression "-1+min(10,3)" is an iterator on "-", "1", "+", "min", "(", "10", ",", "3", ")".
+ *
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 tokenize(String expression) {
+ return tokenizer.tokenize(expression);
+ }
+}
diff --git a/java_console/models/src/com/fathzer/soft/javaluator/AbstractVariableSet.java b/java_console/models/src/com/fathzer/soft/javaluator/AbstractVariableSet.java
new file mode 100644
index 0000000000..5185dfabf0
--- /dev/null
+++ b/java_console/models/src/com/fathzer/soft/javaluator/AbstractVariableSet.java
@@ -0,0 +1,16 @@
+package com.fathzer.soft.javaluator;
+
+/** An abstract variable set.
+ *
Javaluator supports expression that contains variables (for example sin(x)).
+ *
An AbstractVariableSet converts, during the expression evaluation, each variable to its value.
+ * @param The type of the values of the variable (the one handled by the evaluator).
+ * @author Jean-Marc Astesana
+ * @see License information
+ */
+public interface AbstractVariableSet {
+ /** 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);
+}
diff --git a/java_console/models/src/com/fathzer/soft/javaluator/BracketPair.java b/java_console/models/src/com/fathzer/soft/javaluator/BracketPair.java
new file mode 100644
index 0000000000..f10df76408
--- /dev/null
+++ b/java_console/models/src/com/fathzer/soft/javaluator/BracketPair.java
@@ -0,0 +1,48 @@
+package com.fathzer.soft.javaluator;
+
+/** A bracket pair.
+ * @author Jean-Marc Astesana
+ * @see License information
+ */
+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;
+ }
+}
diff --git a/java_console/models/src/com/fathzer/soft/javaluator/Constant.java b/java_console/models/src/com/fathzer/soft/javaluator/Constant.java
new file mode 100644
index 0000000000..85b2cc5db7
--- /dev/null
+++ b/java_console/models/src/com/fathzer/soft/javaluator/Constant.java
@@ -0,0 +1,31 @@
+package com.fathzer.soft.javaluator;
+
+/**
+ * A constant in an expression.
+ *
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.
+ *
A constant for pi would be defined by :
+ * Constant pi = new Constant("pi");
+ *
With such a constant, you will be able to evaluate the expression "sin(pi/4)"
+ * @author Jean-Marc Astesana
+ * @see License information
+ * @see AbstractEvaluator#evaluate(Constant, Object)
+ */
+public class Constant {
+ private String name;
+
+ /** Constructor
+ * @param name The mnemonic of the constant.
+ *
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;
+ }
+}
diff --git a/java_console/models/src/com/fathzer/soft/javaluator/DoubleEvaluator.java b/java_console/models/src/com/fathzer/soft/javaluator/DoubleEvaluator.java
new file mode 100644
index 0000000000..763b41e4e7
--- /dev/null
+++ b/java_console/models/src/com/fathzer/soft/javaluator/DoubleEvaluator.java
@@ -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.
+ *
Built-in operators:
+ * - +: Addition
+ * - -: Subtraction
+ * - -: Unary minus
+ * - *: Multiplication
+ * - /: Division
+ * - ^: Exponentiation.
Warning: Exponentiation is implemented using java.lang.Math.pow which has some limitations (please read oracle documentation about this method to known details).
For example (-1)^(1/3) returns NaN.
+ * - %: Modulo
+ *
+ * Built-in functions:
+ * - abs: absolute value
+ * - acos: arc cosine
+ * - asin: arc sine
+ * - atan: arc tangent
+ * - average: average of arguments
+ * - ceil: nearest upper integer
+ * - cos: cosine
+ * - cosh: hyperbolic cosine
+ * - floor: nearest lower integer
+ * - ln: natural logarithm (base e)
+ * - log: base 10 logarithm
+ * - max: maximum of arguments
+ * - min: minimum of arguments
+ * - round: nearest integer
+ * - sin: sine
+ * - sinh: hyperbolic sine
+ * - sum: sum of arguments
+ * - tan: tangent
+ * - tanh: hyperbolic tangent
+ * - random: pseudo-random number (between 0 and 1)
+ *
+ * Built-in constants:
+ * - e: Base of natural algorithms
+ * - pi: Ratio of the circumference of a circle to its diameter
+ *
+ * @author Jean-Marc Astesana
+ * @see License information
+ */
+public class DoubleEvaluator extends AbstractEvaluator {
+ /** The order or operations (operator precedence) is not clearly defined, especially between the unary minus operator and exponentiation
+ * operator (see http://en.wikipedia.org/wiki/Order_of_operations).
+ * 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.
+ *
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 modulo operator.*/
+ 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 FORMATTER = new ThreadLocal() {
+ @Override
+ protected NumberFormat initialValue() {
+ return NumberFormat.getNumberInstance(Locale.US);
+ }
+ };
+
+ /** Gets a copy of DoubleEvaluator standard default parameters.
+ *
The returned parameters contains all the predefined operators, functions and constants.
+ *
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.
+ *
The returned parameters contains all the predefined operators, functions and constants.
+ *
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.
+ *
This default constructor builds an instance with all predefined operators, functions and constants.
+ */
+ public DoubleEvaluator() {
+ this(getParameters());
+ }
+
+ /** Constructor.
+ *
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 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 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());
+ }
+ }
+}
diff --git a/java_console/models/src/com/fathzer/soft/javaluator/Function.java b/java_console/models/src/com/fathzer/soft/javaluator/Function.java
new file mode 100644
index 0000000000..7995c25ffb
--- /dev/null
+++ b/java_console/models/src/com/fathzer/soft/javaluator/Function.java
@@ -0,0 +1,71 @@
+package com.fathzer.soft.javaluator;
+
+/** A function.
+ * @author Jean-Marc Astesana
+ * @see License information
+ */
+public class Function {
+ private String name;
+ private int minArgumentCount;
+ private int maxArgumentCount;
+
+ /** Constructor.
+ *
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.
+ *
This constructor builds a function with a variable arguments count.
+ *
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 +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/java_console/models/src/com/fathzer/soft/javaluator/Operator.java b/java_console/models/src/com/fathzer/soft/javaluator/Operator.java
new file mode 100644
index 0000000000..e3c654bbe5
--- /dev/null
+++ b/java_console/models/src/com/fathzer/soft/javaluator/Operator.java
@@ -0,0 +1,138 @@
+package com.fathzer.soft.javaluator;
+
+/** An operator.
+ * @author Jean-Marc Astesana
+ * @see License information
+ */
+public class Operator {
+ public final String rpnSymbol;
+
+ /** An Operator's associativity.
+ */
+ 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 precedence of the operator.
+ *
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 "1+3*4" * 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 Operator's associativity in Wikipedia
+ */
+ public Associativity getAssociativity() {
+ return this.associativity;
+ }
+
+ /** Gets the operator's precedence.
+ * @return an integer
+ * @see Operator's associativity in Wikipedia
+ */
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/java_console/models/src/com/fathzer/soft/javaluator/Parameters.java b/java_console/models/src/com/fathzer/soft/javaluator/Parameters.java
new file mode 100644
index 0000000000..ade3feeafa
--- /dev/null
+++ b/java_console/models/src/com/fathzer/soft/javaluator/Parameters.java
@@ -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.
+ *
An evaluator may have different parameters as the supported operators, the supported functions, etc ...
+ * @author Jean-Marc Astesana
+ * @see License information
+ */
+public class Parameters {
+ private String functionSeparator;
+ private final List operators;
+ private final List functions;
+ private final List constants;
+ private final Map translations;
+ private final List expressionBrackets;
+ private final List functionBrackets;
+
+ /** Constructor.
+ *
This method builds an instance with no operator, no function, no constant, no translation and no bracket
+ *
Function argument separator is set to ','.
+ */
+ public Parameters() {
+ this.operators = new ArrayList();
+ this.functions = new ArrayList();
+ this.constants = new ArrayList();
+ this.translations = new HashMap();
+ this.expressionBrackets = new ArrayList();
+ this.functionBrackets = new ArrayList();
+ setFunctionArgumentSeparator(',');
+ }
+
+ /** Gets the supported operators.
+ * @return a Collection of operators.
+ */
+ public Collection getOperators() {
+ return this.operators;
+ }
+
+ /** Gets the supported functions.
+ * @return a Collection of functions.
+ */
+ public Collection getFunctions() {
+ return this.functions;
+ }
+
+ /** Gets the supported constants.
+ * @return a Collection of constants.
+ */
+ public Collection getConstants() {
+ return this.constants;
+ }
+
+ /** Gets the supported bracket pairs for expressions.
+ * @return a Collection of bracket pairs.
+ */
+ public Collection getExpressionBrackets() {
+ return this.expressionBrackets;
+ }
+
+ /** Gets the supported bracket pairs for functions.
+ * @return a Collection of bracket pairs.
+ */
+ public Collection getFunctionBrackets() {
+ return this.functionBrackets;
+ }
+
+ /** Adds operators to the supported ones.
+ * @param operators The operators to be added.
+ */
+ public void addOperators(Collection 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 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 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 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 brackets) {
+ this.functionBrackets.addAll(brackets);
+ }
+
+ /** Sets the translated term for a function.
+ *
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.
+ *
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;
+ }
+}
diff --git a/java_console/models/src/com/fathzer/soft/javaluator/Token.java b/java_console/models/src/com/fathzer/soft/javaluator/Token.java
new file mode 100644
index 0000000000..709398c8de
--- /dev/null
+++ b/java_console/models/src/com/fathzer/soft/javaluator/Token.java
@@ -0,0 +1,142 @@
+package com.fathzer.soft.javaluator;
+
+/** A token.
+ *
When evaluating an expression, it is first split into tokens.
+ * These tokens can be operators, constants, etc ...
+ * @author Jean-Marc Astesana
+ * @see License information
+ */
+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 +
+ '}';
+ }
+}
diff --git a/java_console/models/src/com/fathzer/soft/javaluator/Tokenizer.java b/java_console/models/src/com/fathzer/soft/javaluator/Tokenizer.java
new file mode 100644
index 0000000000..4c673342d6
--- /dev/null
+++ b/java_console/models/src/com/fathzer/soft/javaluator/Tokenizer.java
@@ -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 License information
+ */
+public class Tokenizer {
+ private Pattern pattern;
+ private String tokenDelimiters;
+ private boolean trimTokens;
+
+ /** Constructor.
+ *
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 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.
+ *
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 delimiters) {
+ for (String delimiter : delimiters) {
+ if (delimiter.length()!=1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static Pattern delimitersToRegexp(List 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() {
+ @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 tokens, String token) {
+ if (trimTokens) {
+ token = token.trim();
+ }
+ if (!token.isEmpty()) {
+ tokens.add(token);
+ }
+ }
+
+ /** Converts a string into tokens.
+ *
Example: The result for the expression "-1+min(10,3)" 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 tokenize(String string) {
+ if (pattern!=null) {
+ List res = new ArrayList();
+ 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 {
+ 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();
+ }
+ }
+}
diff --git a/java_console/models/src/com/rusefi/test/ParserTest.java b/java_console/models/src/com/rusefi/test/ParserTest.java
new file mode 100644
index 0000000000..ca121b405f
--- /dev/null
+++ b/java_console/models/src/com/rusefi/test/ParserTest.java
@@ -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)");
+
+
+ }
+}
\ No newline at end of file