| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| /* |
| * Copyright 2012 Udo Klimaschewski |
| * |
| * http://UdoJava.com/ |
| * http://about.me/udo.klimaschewski |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files (the |
| * "Software"), to deal in the Software without restriction, including |
| * without limitation the rights to use, copy, modify, merge, publish, |
| * distribute, sublicense, and/or sell copies of the Software, and to |
| * permit persons to whom the Software is furnished to do so, subject to |
| * the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
| * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
| * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| * |
| */ |
| package org.apache.felix.gogo.runtime; |
| |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.math.MathContext; |
| import java.math.RoundingMode; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Stack; |
| |
| /** |
| * Enhanced to provide assignment operators and variables from a map, comparison operators, string operations and more. |
| */ |
| /** |
| * <h1>EvalEx - Java Expression Evaluator</h1> |
| * |
| * <h2>Introduction</h2> |
| * EvalEx is a handy expression evaluator for Java, that allows to evaluate simple mathematical and boolean expressions. |
| * <br> |
| * Key Features: |
| * <ul> |
| * <li>Uses BigDecimal for calculation and result</li> |
| * <li>Single class implementation, very compact</li> |
| * <li>No dependencies to external libraries</li> |
| * <li>Precision and rounding mode can be set</li> |
| * <li>Supports variables</li> |
| * <li>Standard boolean and mathematical operators</li> |
| * <li>Standard basic mathematical and boolean functions</li> |
| * <li>Custom functions and operators can be added at runtime</li> |
| * </ul> |
| * <br> |
| * <h2>Examples</h2> |
| * <pre> |
| * BigDecimal result = null; |
| * |
| * Expression expression = new Expression("1+1/3"); |
| * result = expression.eval(): |
| * expression.setPrecision(2); |
| * result = expression.eval(): |
| * |
| * result = new Expression("(3.4 + -4.1)/2").eval(); |
| * |
| * result = new Expression("SQRT(a^2 + b^2").with("a","2.4").and("b","9.253").eval(); |
| * |
| * BigDecimal a = new BigDecimal("2.4"); |
| * BigDecimal b = new BigDecimal("9.235"); |
| * result = new Expression("SQRT(a^2 + b^2").with("a",a).and("b",b).eval(); |
| * |
| * result = new Expression("2.4/PI").setPrecision(128).setRoundingMode(RoundingMode.UP).eval(); |
| * |
| * result = new Expression("random() > 0.5").eval(); |
| * |
| * result = new Expression("not(x < 7 || sqrt(max(x,9)) <= 3))").with("x","22.9").eval(); |
| * </pre> |
| * <br> |
| * <h2>Supported Operators</h2> |
| * <table summary="Mathematical Operators"> |
| * <tr><th>Mathematical Operators</th></tr> |
| * <tr><th>Operator</th><th>Description</th></tr> |
| * <tr><td>+</td><td>Additive operator</td></tr> |
| * <tr><td>-</td><td>Subtraction operator</td></tr> |
| * <tr><td>*</td><td>Multiplication operator</td></tr> |
| * <tr><td>/</td><td>Division operator</td></tr> |
| * <tr><td>%</td><td>Remainder operator (Modulo)</td></tr> |
| * <tr><td>^</td><td>Power operator</td></tr> |
| * </table> |
| * <br> |
| * <table summary="Boolean Operators"> |
| * <tr><th>Boolean Operators<sup>*</sup></th></tr> |
| * <tr><th>Operator</th><th>Description</th></tr> |
| * <tr><td>=</td><td>Equals</td></tr> |
| * <tr><td>==</td><td>Equals</td></tr> |
| * <tr><td>!=</td><td>Not equals</td></tr> |
| * <tr><td><></td><td>Not equals</td></tr> |
| * <tr><td><</td><td>Less than</td></tr> |
| * <tr><td><=</td><td>Less than or equal to</td></tr> |
| * <tr><td>></td><td>Greater than</td></tr> |
| * <tr><td>>=</td><td>Greater than or equal to</td></tr> |
| * <tr><td>&&</td><td>Boolean and</td></tr> |
| * <tr><td>||</td><td>Boolean or</td></tr> |
| * </table> |
| * *Boolean operators result always in a BigDecimal value of 1 or 0 (zero). Any non-zero value is treated as a _true_ value. Boolean _not_ is implemented by a function. |
| * <br> |
| * <h2>Supported Functions</h2> |
| * <table summary="Supported Functions"> |
| * <tr><th>Function<sup>*</sup></th><th>Description</th></tr> |
| * <tr><td>NOT(<i>expression</i>)</td><td>Boolean negation, 1 (means true) if the expression is not zero</td></tr> |
| * <tr><td>IF(<i>condition</i>,<i>value_if_true</i>,<i>value_if_false</i>)</td><td>Returns one value if the condition evaluates to true or the other if it evaluates to false</td></tr> |
| * <tr><td>RANDOM()</td><td>Produces a random number between 0 and 1</td></tr> |
| * <tr><td>MIN(<i>e1</i>,<i>e2</i>)</td><td>Returns the smaller of both expressions</td></tr> |
| * <tr><td>MAX(<i>e1</i>,<i>e2</i>)</td><td>Returns the bigger of both expressions</td></tr> |
| * <tr><td>ABS(<i>expression</i>)</td><td>Returns the absolute (non-negative) value of the expression</td></tr> |
| * <tr><td>ROUND(<i>expression</i>,precision)</td><td>Rounds a value to a certain number of digits, uses the current rounding mode</td></tr> |
| * <tr><td>FLOOR(<i>expression</i>)</td><td>Rounds the value down to the nearest integer</td></tr> |
| * <tr><td>CEILING(<i>expression</i>)</td><td>Rounds the value up to the nearest integer</td></tr> |
| * <tr><td>LOG(<i>expression</i>)</td><td>Returns the natural logarithm (base e) of an expression</td></tr> |
| * <tr><td>SQRT(<i>expression</i>)</td><td>Returns the square root of an expression</td></tr> |
| * <tr><td>SIN(<i>expression</i>)</td><td>Returns the trigonometric sine of an angle (in degrees)</td></tr> |
| * <tr><td>COS(<i>expression</i>)</td><td>Returns the trigonometric cosine of an angle (in degrees)</td></tr> |
| * <tr><td>TAN(<i>expression</i>)</td><td>Returns the trigonometric tangens of an angle (in degrees)</td></tr> |
| * <tr><td>SINH(<i>expression</i>)</td><td>Returns the hyperbolic sine of a value</td></tr> |
| * <tr><td>COSH(<i>expression</i>)</td><td>Returns the hyperbolic cosine of a value</td></tr> |
| * <tr><td>TANH(<i>expression</i>)</td><td>Returns the hyperbolic tangens of a value</td></tr> |
| * <tr><td>RAD(<i>expression</i>)</td><td>Converts an angle measured in degrees to an approximately equivalent angle measured in radians</td></tr> |
| * <tr><td>DEG(<i>expression</i>)</td><td>Converts an angle measured in radians to an approximately equivalent angle measured in degrees</td></tr> |
| * </table> |
| * *Functions names are case insensitive. |
| * <br> |
| * <h2>Supported Constants</h2> |
| * <table summary="Supported Constants"> |
| * <tr><th>Constant</th><th>Description</th></tr> |
| * <tr><td>PI</td><td>The value of <i>PI</i>, exact to 100 digits</td></tr> |
| * <tr><td>TRUE</td><td>The value one</td></tr> |
| * <tr><td>FALSE</td><td>The value zero</td></tr> |
| * </table> |
| * |
| * <h2>Add Custom Operators</h2> |
| * |
| * Custom operators can be added easily, simply create an instance of `Expression.Operator` and add it to the expression. |
| * Parameters are the operator string, its precedence and if it is left associative. The operators `eval()` method will be called with the BigDecimal values of the operands. |
| * All existing operators can also be overridden. |
| * <br> |
| * For example, add an operator `x >> n`, that moves the decimal point of _x_ _n_ digits to the right: |
| * |
| * <pre> |
| * Expression e = new Expression("2.1234 >> 2"); |
| * |
| * e.addOperator(e.new Operator(">>", 30, true) { |
| * {@literal @}Override |
| * public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| * return v1.movePointRight(v2.toBigInteger().intValue()); |
| * } |
| * }); |
| * |
| * e.eval(); // returns 212.34 |
| * </pre> |
| * <br> |
| * <h2>Add Custom Functions</h2> |
| * |
| * Adding custom functions is as easy as adding custom operators. Create an instance of `Expression.Function`and add it to the expression. |
| * Parameters are the function name and the count of required parameters. The functions `eval()` method will be called with a list of the BigDecimal parameters. |
| * All existing functions can also be overridden. |
| * <br> |
| * For example, add a function `average(a,b,c)`, that will calculate the average value of a, b and c: |
| * <br> |
| * <pre> |
| * Expression e = new Expression("2 * average(12,4,8)"); |
| * |
| * e.addFunction(e.new Function("average", 3) { |
| * {@literal @}Override |
| * public BigDecimal eval(List<BigDecimal> parameters) { |
| * BigDecimal sum = parameters.get(0).add(parameters.get(1)).add(parameters.get(2)); |
| * return sum.divide(new BigDecimal(3)); |
| * } |
| * }); |
| * |
| * e.eval(); // returns 16 |
| * </pre> |
| * The software is licensed under the MIT Open Source license (see LICENSE file). |
| * <br> |
| * <ul> |
| * <li>The *power of* operator (^) implementation was copied from [Stack Overflow](http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power-on-bigdecimal-in-java) Thanks to Gene Marin</li> |
| * <li>The SQRT() function implementation was taken from the book [The Java Programmers Guide To numerical Computing](http://www.amazon.de/Java-Number-Cruncher-Programmers-Numerical/dp/0130460419) (Ronald Mak, 2002)</li> |
| * </ul> |
| * |
| *@author Udo Klimaschewski (http://about.me/udo.klimaschewski) |
| */ |
| public class Expression { |
| |
| /** |
| * Definition of PI as a constant, can be used in expressions as variable. |
| */ |
| public static final BigDecimal PI = new BigDecimal( |
| "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"); |
| |
| /** |
| * The {@link MathContext} to use for calculations. |
| */ |
| private MathContext mc = MathContext.DECIMAL32; |
| |
| /** |
| * The original infix expression. |
| */ |
| private String expression = null; |
| |
| /** |
| * The cached RPN (Reverse Polish Notation) of the expression. |
| */ |
| private List<Token> rpn = null; |
| |
| /** |
| * All defined operators with name and implementation. |
| */ |
| private Map<String, Operator> operators = new HashMap<>(); |
| |
| /** |
| * All defined functions with name and implementation. |
| */ |
| private Map<String, Function> functions = new HashMap<>(); |
| |
| /** |
| * All defined variables with name and value. |
| */ |
| private Map<String, Object> constants = new HashMap<>(); |
| |
| /** |
| * What character to use for decimal separators. |
| */ |
| private final char decimalSeparator = '.'; |
| |
| /** |
| * What character to use for minus sign (negative values). |
| */ |
| private final char minusSign = '-'; |
| |
| /** |
| * The expression evaluators exception class. |
| */ |
| public class ExpressionException extends RuntimeException { |
| private static final long serialVersionUID = 1118142866870779047L; |
| |
| public ExpressionException(String message) { |
| super(message); |
| } |
| } |
| |
| interface Token { |
| |
| } |
| |
| public class Constant implements Token { |
| |
| private final Object value; |
| |
| public Constant(Object value) { |
| this.value = value; |
| } |
| |
| public Object getValue() { |
| return value; |
| } |
| } |
| |
| public class Variable implements Token { |
| |
| private final String name; |
| |
| public Variable(String name) { |
| this.name = name; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| @Override |
| public String toString() { |
| return name; |
| } |
| } |
| |
| public class LeftParen implements Token { |
| @Override |
| public String toString() { |
| return "("; |
| } |
| } |
| |
| public class Comma implements Token { |
| } |
| |
| /** |
| * Abstract definition of a supported expression function. A function is |
| * defined by a name, the number of parameters and the actual processing |
| * implementation. |
| */ |
| public abstract class Function implements Token { |
| /** |
| * Name of this function. |
| */ |
| private String name; |
| /** |
| * Number of parameters expected for this function. |
| */ |
| private int numParams; |
| |
| /** |
| * Creates a new function with given name and parameter count. |
| * |
| * @param name |
| * The name of the function. |
| * @param numParams |
| * The number of parameters for this function. |
| */ |
| public Function(String name, int numParams) { |
| this.name = name.toUpperCase(); |
| this.numParams = numParams; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| public int getNumParams() { |
| return numParams; |
| } |
| |
| public BigDecimal eval(Map<String, Object> variables, List<Object> parameters) { |
| List<BigDecimal> numericParameters = new ArrayList<>(); |
| for (Object o : parameters) { |
| numericParameters.add(toBigDecimal(variables, o)); |
| } |
| return eval(numericParameters); |
| } |
| |
| /** |
| * Implementation for this function. |
| * |
| * @param parameters |
| * Parameters will be passed by the expression evaluator as a |
| * {@link List} of {@link BigDecimal} values. |
| * @return The function must return a new {@link BigDecimal} value as a |
| * computing result. |
| */ |
| public abstract BigDecimal eval(List<BigDecimal> parameters); |
| |
| @Override |
| public String toString() { |
| return name; |
| } |
| } |
| |
| /** |
| * Abstract definition of a supported operator. An operator is defined by |
| * its name (pattern), precedence and if it is left- or right associative. |
| */ |
| public abstract class Operator implements Token { |
| /** |
| * This operators name (pattern). |
| */ |
| private String oper; |
| /** |
| * Operators precedence. |
| */ |
| private int precedence; |
| /** |
| * Operator is left associative. |
| */ |
| private boolean leftAssoc; |
| |
| /** |
| * Creates a new operator. |
| * |
| * @param oper |
| * The operator name (pattern). |
| * @param precedence |
| * The operators precedence. |
| * @param leftAssoc |
| * <code>true</code> if the operator is left associative, |
| * else <code>false</code>. |
| */ |
| public Operator(String oper, int precedence, boolean leftAssoc) { |
| this.oper = oper; |
| this.precedence = precedence; |
| this.leftAssoc = leftAssoc; |
| } |
| |
| public String getOper() { |
| return oper; |
| } |
| |
| public int getPrecedence() { |
| return precedence; |
| } |
| |
| public boolean isLeftAssoc() { |
| return leftAssoc; |
| } |
| |
| public Object eval(Map<String, Object> variables, Object v1, Object v2) { |
| if (v1 instanceof Variable) { |
| v1 = variables.get(((Variable) v1).getName()); |
| } |
| if (v2 instanceof Variable) { |
| v2 = variables.get(((Variable) v2).getName()); |
| } |
| boolean numeric = isNumber(v1) && isNumber(v2); |
| if (numeric) { |
| return eval(toBigDecimal(variables, v1), toBigDecimal(variables, v2)); |
| } else { |
| return eval(v1 != null ? v1.toString() : "", v2 != null ? v2.toString() : ""); |
| } |
| } |
| |
| public Object eval(String v1, String v2) { |
| return eval(toBigDecimal(v1), toBigDecimal(v2)); |
| } |
| |
| /** |
| * Implementation for this operator. |
| * |
| * @param v1 |
| * Operand 1. |
| * @param v2 |
| * Operand 2. |
| * @return The result of the operation. |
| */ |
| public abstract BigDecimal eval(BigDecimal v1, BigDecimal v2); |
| |
| @Override |
| public String toString() { |
| return oper; |
| } |
| } |
| |
| public abstract class Comparator extends Operator { |
| |
| public Comparator(String oper, int precedence) { |
| super(oper, precedence, false); |
| } |
| |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return compare(v1, v2) ? BigDecimal.ONE : BigDecimal.ZERO; |
| } |
| |
| @Override |
| public Object eval(String v1, String v2) { |
| return compare(v1, v2) ? BigDecimal.ONE : BigDecimal.ZERO; |
| } |
| |
| /** |
| * This method actually implements the comparison. |
| * It will be called with either 2 BigIntegers or 2 Strings. |
| * |
| * @param v1 |
| * Operand 1. |
| * @param v2 |
| * Operand 2. |
| * @return The result of the comparison. |
| */ |
| public abstract boolean compare(Comparable<?> v1, Comparable<?> v2); |
| } |
| |
| /** |
| * Marker class for assignment operators. |
| * Those operators need a variable on the left hand side. |
| */ |
| public abstract class Assignment extends Operator { |
| |
| public Assignment(String assign, int precedence) { |
| super(assign, precedence, true); |
| } |
| |
| public Object eval(Map<String, Object> variables, Object v1, Object v2) { |
| if (!(v1 instanceof Variable)) { |
| throw new IllegalArgumentException("Left hand side of operator " + getOper() + " should be a variable but found " + v1.toString()); |
| } |
| String name = ((Variable) v1).getName(); |
| Object r = super.eval(variables, v1, v2); |
| if (r instanceof Number) { |
| r = toResult(toBigDecimal(r)); |
| } |
| variables.put(name, r); |
| return r; |
| } |
| |
| } |
| |
| |
| /** |
| * Expression tokenizer that allows to iterate over a {@link String} |
| * expression token by token. Blank characters will be skipped. |
| */ |
| private class Tokenizer implements Iterator<String> { |
| |
| /** |
| * Actual position in expression string. |
| */ |
| private int pos = 0; |
| |
| /** |
| * The original input expression. |
| */ |
| private String input; |
| /** |
| * The previous token or <code>null</code> if none. |
| */ |
| private String previousToken; |
| |
| /** |
| * Creates a new tokenizer for an expression. |
| * |
| * @param input |
| * The expression string. |
| */ |
| public Tokenizer(String input) { |
| this.input = input; |
| } |
| |
| public boolean hasNext() { |
| return (pos < input.length()); |
| } |
| |
| /** |
| * Peek at the next character, without advancing the iterator. |
| * |
| * @return The next character or character 0, if at end of string. |
| */ |
| private char peekNextChar() { |
| if (pos < (input.length() - 1)) { |
| return input.charAt(pos + 1); |
| } else { |
| return 0; |
| } |
| } |
| |
| public String next() { |
| StringBuilder token = new StringBuilder(); |
| if (pos >= input.length()) { |
| return previousToken = null; |
| } |
| char ch = input.charAt(pos); |
| while (Character.isWhitespace(ch) && pos < input.length()) { |
| ch = input.charAt(++pos); |
| } |
| if (Character.isDigit(ch)) { |
| while ((Character.isDigit(ch) || ch == decimalSeparator) |
| && (pos < input.length())) { |
| token.append(input.charAt(pos++)); |
| ch = pos == input.length() ? 0 : input.charAt(pos); |
| } |
| } else if (ch == minusSign |
| && Character.isDigit(peekNextChar()) |
| && ("(".equals(previousToken) || ",".equals(previousToken) |
| || previousToken == null || operators |
| .containsKey(previousToken))) { |
| token.append(minusSign); |
| pos++; |
| token.append(next()); |
| } else if (Character.isLetter(ch)) { |
| while ((Character.isLetter(ch) || Character.isDigit(ch) || (ch == '_')) && (pos < input.length())) { |
| token.append(input.charAt(pos++)); |
| ch = pos == input.length() ? 0 : input.charAt(pos); |
| } |
| } else if (ch == '"') { |
| boolean escaped = false; |
| token.append(input.charAt(pos++)); |
| do { |
| if (pos == input.length()) { |
| throw new IllegalArgumentException("Non terminated quote"); |
| } |
| ch = input.charAt(pos++); |
| escaped = (!escaped && ch == '\\'); |
| token.append(ch); |
| } while (escaped || ch != '"'); |
| } else if (ch == '(' || ch == ')' || ch == ',') { |
| token.append(ch); |
| pos++; |
| } else { |
| while (!Character.isLetter(ch) && !Character.isDigit(ch) |
| && !Character.isWhitespace(ch) && ch != '(' |
| && ch != ')' && ch != ',' && (pos < input.length())) { |
| token.append(input.charAt(pos)); |
| pos++; |
| ch = pos == input.length() ? 0 : input.charAt(pos); |
| if (ch == minusSign) { |
| break; |
| } |
| } |
| if (!operators.containsKey(token.toString())) { |
| throw new ExpressionException("Unknown operator '" + token |
| + "' at position " + (pos - token.length() + 1)); |
| } |
| } |
| return previousToken = token.toString(); |
| } |
| |
| public void remove() { |
| throw new ExpressionException("remove() not supported"); |
| } |
| |
| /** |
| * Get the actual character position in the string. |
| * |
| * @return The actual character position. |
| */ |
| public int getPos() { |
| return pos; |
| } |
| |
| } |
| |
| /** |
| * Creates a new expression instance from an expression string. |
| * |
| * @param expression |
| * The expression. E.g. <code>"2.4*sin(3)/(2-4)"</code> or |
| * <code>"sin(y)>0 & max(z, 3)>3"</code> |
| */ |
| public Expression(String expression) { |
| this.expression = expression; |
| |
| addOperator(new Assignment("=", 5) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return v2; |
| } |
| }); |
| addOperator(new Assignment("+=", 5) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return v1.add(v2, mc); |
| } |
| |
| @Override |
| public Object eval(String v1, String v2) { |
| return v1 + v2; |
| } |
| }); |
| addOperator(new Assignment("-=", 5) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return v1.subtract(v2, mc); |
| } |
| }); |
| addOperator(new Assignment("*=", 5) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return v1.multiply(v2, mc); |
| } |
| }); |
| addOperator(new Assignment("/=", 5) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return v1.divide(v2, mc); |
| } |
| }); |
| addOperator(new Assignment("%=", 5) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return v1.remainder(v2, mc); |
| } |
| }); |
| addOperator(new Assignment("|=", 5) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return new BigDecimal(v1.toBigInteger().or(v2.toBigInteger()), mc); |
| } |
| }); |
| addOperator(new Assignment("&=", 5) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return new BigDecimal(v1.toBigInteger().and(v2.toBigInteger()), mc); |
| } |
| }); |
| addOperator(new Assignment("^=", 5) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return new BigDecimal(v1.toBigInteger().xor(v2.toBigInteger()), mc); |
| } |
| }); |
| addOperator(new Assignment("<<=", 5) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return new BigDecimal(v1.toBigInteger().shiftLeft(v2.intValue()), mc); |
| } |
| }); |
| addOperator(new Assignment(">>=", 5) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return new BigDecimal(v1.toBigInteger().shiftRight(v2.intValue()), mc); |
| } |
| }); |
| |
| addOperator(new Operator("<<", 10, true) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return new BigDecimal(v1.toBigInteger().shiftLeft(v2.intValue()), mc); |
| } |
| }); |
| addOperator(new Operator(">>", 10, true) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return new BigDecimal(v1.toBigInteger().shiftRight(v2.intValue()), mc); |
| } |
| }); |
| addOperator(new Operator("|", 15, true) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return new BigDecimal(v1.toBigInteger().or(v2.toBigInteger()), mc); |
| } |
| }); |
| addOperator(new Operator("&", 15, true) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return new BigDecimal(v1.toBigInteger().and(v2.toBigInteger()), mc); |
| } |
| }); |
| addOperator(new Operator("^", 15, true) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return new BigDecimal(v1.toBigInteger().xor(v2.toBigInteger()), mc); |
| } |
| }); |
| addOperator(new Operator("+", 20, true) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return v1.add(v2, mc); |
| } |
| @Override |
| public Object eval(String v1, String v2) { |
| return v1 + v2; |
| } |
| }); |
| addOperator(new Operator("-", 20, true) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return v1.subtract(v2, mc); |
| } |
| }); |
| addOperator(new Operator("*", 30, true) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return v1.multiply(v2, mc); |
| } |
| }); |
| addOperator(new Operator("/", 30, true) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return v1.divide(v2, mc); |
| } |
| }); |
| addOperator(new Operator("%", 30, true) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| return v1.remainder(v2, mc); |
| } |
| }); |
| addOperator(new Operator("**", 40, false) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| /*- |
| * Thanks to Gene Marin: |
| * http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power-on-bigdecimal-in-java |
| */ |
| int signOf2 = v2.signum(); |
| double dn1 = v1.doubleValue(); |
| v2 = v2.multiply(new BigDecimal(signOf2)); // n2 is now positive |
| BigDecimal remainderOf2 = v2.remainder(BigDecimal.ONE); |
| BigDecimal n2IntPart = v2.subtract(remainderOf2); |
| BigDecimal intPow = v1.pow(n2IntPart.intValueExact(), mc); |
| BigDecimal doublePow = new BigDecimal(Math.pow(dn1, |
| remainderOf2.doubleValue())); |
| |
| BigDecimal result = intPow.multiply(doublePow, mc); |
| if (signOf2 == -1) { |
| result = BigDecimal.ONE.divide(result, mc.getPrecision(), |
| RoundingMode.HALF_UP); |
| } |
| return result; |
| } |
| }); |
| addOperator(new Operator("&&", 4, false) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| boolean b1 = !v1.equals(BigDecimal.ZERO); |
| boolean b2 = !v2.equals(BigDecimal.ZERO); |
| return b1 && b2 ? BigDecimal.ONE : BigDecimal.ZERO; |
| } |
| }); |
| |
| addOperator(new Operator("||", 2, false) { |
| @Override |
| public BigDecimal eval(BigDecimal v1, BigDecimal v2) { |
| boolean b1 = !v1.equals(BigDecimal.ZERO); |
| boolean b2 = !v2.equals(BigDecimal.ZERO); |
| return b1 || b2 ? BigDecimal.ONE : BigDecimal.ZERO; |
| } |
| }); |
| |
| addOperator(new Comparator(">", 10) { |
| @Override @SuppressWarnings({ "unchecked", "rawtypes" }) |
| public boolean compare(Comparable v1, Comparable v2) { |
| return v1.compareTo(v2) > 0; |
| } |
| }); |
| |
| addOperator(new Comparator(">=", 10) { |
| @Override @SuppressWarnings({ "unchecked", "rawtypes" }) |
| public boolean compare(Comparable v1, Comparable v2) { |
| return v1.compareTo(v2) >= 0; |
| } |
| }); |
| |
| addOperator(new Comparator("<", 10) { |
| @Override @SuppressWarnings({ "unchecked", "rawtypes" }) |
| public boolean compare(Comparable v1, Comparable v2) { |
| return v1.compareTo(v2) < 0; |
| } |
| }); |
| |
| addOperator(new Comparator("<=", 10) { |
| @Override @SuppressWarnings({ "unchecked", "rawtypes" }) |
| public boolean compare(Comparable v1, Comparable v2) { |
| return v1.compareTo(v2) <= 0; |
| } |
| }); |
| |
| addOperator(new Comparator("==", 7) { |
| @Override @SuppressWarnings({ "unchecked", "rawtypes" }) |
| public boolean compare(Comparable v1, Comparable v2) { |
| return v1.compareTo(v2) == 0; |
| } |
| }); |
| |
| addOperator(new Comparator("!=", 7) { |
| @Override @SuppressWarnings({ "unchecked", "rawtypes" }) |
| public boolean compare(Comparable v1, Comparable v2) { |
| return v1.compareTo(v2) != 0; |
| } |
| }); |
| |
| addFunction(new Function("NOT", 1) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| boolean zero = parameters.get(0).compareTo(BigDecimal.ZERO) == 0; |
| return zero ? BigDecimal.ONE : BigDecimal.ZERO; |
| } |
| }); |
| |
| addFunction(new Function("IF", 3) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| boolean isTrue = !parameters.get(0).equals(BigDecimal.ZERO); |
| return isTrue ? parameters.get(1) : parameters.get(2); |
| } |
| }); |
| |
| addFunction(new Function("RANDOM", 0) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| double d = Math.random(); |
| return new BigDecimal(d, mc); |
| } |
| }); |
| addFunction(new Function("SIN", 1) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| double d = Math.sin(Math.toRadians(parameters.get(0) |
| .doubleValue())); |
| return new BigDecimal(d, mc); |
| } |
| }); |
| addFunction(new Function("COS", 1) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| double d = Math.cos(Math.toRadians(parameters.get(0) |
| .doubleValue())); |
| return new BigDecimal(d, mc); |
| } |
| }); |
| addFunction(new Function("TAN", 1) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| double d = Math.tan(Math.toRadians(parameters.get(0) |
| .doubleValue())); |
| return new BigDecimal(d, mc); |
| } |
| }); |
| addFunction(new Function("SINH", 1) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| double d = Math.sinh(parameters.get(0).doubleValue()); |
| return new BigDecimal(d, mc); |
| } |
| }); |
| addFunction(new Function("COSH", 1) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| double d = Math.cosh(parameters.get(0).doubleValue()); |
| return new BigDecimal(d, mc); |
| } |
| }); |
| addFunction(new Function("TANH", 1) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| double d = Math.tanh(parameters.get(0).doubleValue()); |
| return new BigDecimal(d, mc); |
| } |
| }); |
| addFunction(new Function("RAD", 1) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| double d = Math.toRadians(parameters.get(0).doubleValue()); |
| return new BigDecimal(d, mc); |
| } |
| }); |
| addFunction(new Function("DEG", 1) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| double d = Math.toDegrees(parameters.get(0).doubleValue()); |
| return new BigDecimal(d, mc); |
| } |
| }); |
| addFunction(new Function("MAX", 2) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| BigDecimal v1 = parameters.get(0); |
| BigDecimal v2 = parameters.get(1); |
| return v1.compareTo(v2) > 0 ? v1 : v2; |
| } |
| }); |
| addFunction(new Function("MIN", 2) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| BigDecimal v1 = parameters.get(0); |
| BigDecimal v2 = parameters.get(1); |
| return v1.compareTo(v2) < 0 ? v1 : v2; |
| } |
| }); |
| addFunction(new Function("ABS", 1) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| return parameters.get(0).abs(mc); |
| } |
| }); |
| addFunction(new Function("LOG", 1) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| double d = Math.log(parameters.get(0).doubleValue()); |
| return new BigDecimal(d, mc); |
| } |
| }); |
| addFunction(new Function("ROUND", 2) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| BigDecimal toRound = parameters.get(0); |
| int precision = parameters.get(1).intValue(); |
| return toRound.setScale(precision, mc.getRoundingMode()); |
| } |
| }); |
| addFunction(new Function("FLOOR", 1) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| BigDecimal toRound = parameters.get(0); |
| return toRound.setScale(0, RoundingMode.FLOOR); |
| } |
| }); |
| addFunction(new Function("CEILING", 1) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| BigDecimal toRound = parameters.get(0); |
| return toRound.setScale(0, RoundingMode.CEILING); |
| } |
| }); |
| addFunction(new Function("SQRT", 1) { |
| @Override |
| public BigDecimal eval(List<BigDecimal> parameters) { |
| /* |
| * From The Java Programmers Guide To numerical Computing |
| * (Ronald Mak, 2003) |
| */ |
| BigDecimal x = parameters.get(0); |
| if (x.compareTo(BigDecimal.ZERO) == 0) { |
| return new BigDecimal(0); |
| } |
| if (x.signum() < 0) { |
| throw new ExpressionException( |
| "Argument to SQRT() function must not be negative"); |
| } |
| BigInteger n = x.movePointRight(mc.getPrecision() << 1) |
| .toBigInteger(); |
| |
| int bits = (n.bitLength() + 1) >> 1; |
| BigInteger ix = n.shiftRight(bits); |
| BigInteger ixPrev; |
| |
| do { |
| ixPrev = ix; |
| ix = ix.add(n.divide(ix)).shiftRight(1); |
| // Give other threads a chance to work; |
| Thread.yield(); |
| } while (ix.compareTo(ixPrev) != 0); |
| |
| return new BigDecimal(ix, mc.getPrecision()); |
| } |
| }); |
| |
| constants.put("PI", PI); |
| constants.put("TRUE", Boolean.TRUE); |
| constants.put("FALSE", Boolean.FALSE); |
| |
| } |
| |
| /** |
| * Is the string a number? |
| * |
| * @param st |
| * The string. |
| * @return <code>true</code>, if the input string is a number. |
| */ |
| private boolean isNumber(String st) { |
| if (st == null || st.isEmpty() || st.charAt(0) == minusSign && st.length() == 1) |
| return false; |
| for (char ch : st.toCharArray()) { |
| if (!Character.isDigit(ch) && ch != minusSign |
| && ch != decimalSeparator) |
| return false; |
| } |
| return true; |
| } |
| |
| private boolean isNumber(Object obj) { |
| if (obj instanceof Number) { |
| return true; |
| } else if (obj != null) { |
| return isNumber(obj.toString()); |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * Implementation of the <i>Shunting Yard</i> algorithm to transform an |
| * infix expression to a RPN expression. |
| * |
| * @param expression |
| * The input expression in infx. |
| * @return A RPN representation of the expression, with each token as a list |
| * member. |
| */ |
| private List<Token> shuntingYard(String expression) { |
| List<Token> outputQueue = new ArrayList<>(); |
| Stack<Token> stack = new Stack<>(); |
| |
| Tokenizer tokenizer = new Tokenizer(expression); |
| |
| String previousToken = null; |
| while (tokenizer.hasNext()) { |
| String token = tokenizer.next(); |
| if (token.charAt(0) == '"') { |
| StringBuilder sb = new StringBuilder(); |
| boolean escaped = false; |
| for (int i = 1; i < token.length() - 1; i++) { |
| char ch = token.charAt(i); |
| if (escaped || ch != '\\') { |
| sb.append(ch); |
| } else { |
| escaped = true; |
| } |
| } |
| outputQueue.add(new Constant(sb.toString())); |
| } else if (isNumber(token)) { |
| outputQueue.add(new Constant(toBigDecimal(token))); |
| } else if (constants.containsKey(token)) { |
| outputQueue.add(new Constant(constants.get(token))); |
| } else if (functions.containsKey(token.toUpperCase())) { |
| stack.push(functions.get(token.toUpperCase())); |
| } else if (Character.isLetter(token.charAt(0))) { |
| outputQueue.add(new Variable(token)); |
| } else if (",".equals(token)) { |
| while (!stack.isEmpty() && !(stack.peek() instanceof LeftParen)) { |
| outputQueue.add(stack.pop()); |
| } |
| if (stack.isEmpty()) { |
| outputQueue.add(new Comma()); |
| } |
| } else if (operators.containsKey(token)) { |
| Operator o1 = operators.get(token); |
| Token token2 = stack.isEmpty() ? null : stack.peek(); |
| while (token2 instanceof Operator |
| && ((o1.isLeftAssoc() && o1.getPrecedence() <= ((Operator) token2).getPrecedence()) |
| || (o1.getPrecedence() < ((Operator) token2).getPrecedence()))) { |
| outputQueue.add(stack.pop()); |
| token2 = stack.isEmpty() ? null : stack.peek(); |
| } |
| stack.push(o1); |
| } else if ("(".equals(token)) { |
| if (previousToken != null) { |
| if (isNumber(previousToken)) { |
| throw new ExpressionException("Missing operator at character position " + tokenizer.getPos()); |
| } |
| } |
| stack.push(new LeftParen()); |
| } else if (")".equals(token)) { |
| while (!stack.isEmpty() && !(stack.peek() instanceof LeftParen)) { |
| outputQueue.add(stack.pop()); |
| } |
| if (stack.isEmpty()) { |
| throw new RuntimeException("Mismatched parentheses"); |
| } |
| stack.pop(); |
| if (!stack.isEmpty() && stack.peek() instanceof Function) { |
| outputQueue.add(stack.pop()); |
| } |
| } |
| previousToken = token; |
| } |
| while (!stack.isEmpty()) { |
| Token element = stack.pop(); |
| if (element instanceof LeftParen) { |
| throw new RuntimeException("Mismatched parentheses"); |
| } |
| if (!(element instanceof Operator)) { |
| throw new RuntimeException("Unknown operator or function: " + element); |
| } |
| outputQueue.add(element); |
| } |
| return outputQueue; |
| } |
| |
| /** |
| * Evaluates the expression. |
| * |
| * @return The result of the expression. |
| */ |
| public Object eval() { |
| return eval(new HashMap<String, Object>()); |
| } |
| |
| /** |
| * Evaluates the expression. |
| * |
| * @param variables the variables |
| * @return The result of the expression. |
| */ |
| public Object eval(Map<String, Object> variables) { |
| |
| Stack<Object> stack = new Stack<>(); |
| |
| for (Token token : getRPN()) { |
| if (token instanceof Operator) { |
| Object v1 = stack.pop(); |
| Object v2 = stack.pop(); |
| Object oResult = ((Operator) token).eval(variables, v2, v1); |
| stack.push(oResult); |
| } else if (token instanceof Constant) { |
| stack.push(((Constant) token).getValue()); |
| } else if (token instanceof Function) { |
| Function f = (Function) token; |
| List<Object> p = new ArrayList<>(f.getNumParams()); |
| for (int i = 0; i < f.numParams; i++) { |
| p.add(0, stack.pop()); |
| } |
| Object fResult = f.eval(variables, p); |
| stack.push(fResult); |
| } else if (token instanceof Comma) { |
| stack.pop(); |
| } else { |
| stack.push(token); |
| } |
| } |
| if (stack.size() > 1) { |
| throw new IllegalArgumentException("Missing operator"); |
| } |
| Object result = stack.pop(); |
| if (result instanceof Variable) { |
| result = variables.get(((Variable) result).getName()); |
| } |
| if (result instanceof BigDecimal) { |
| result = toResult((BigDecimal) result); |
| } |
| return result; |
| } |
| |
| private Number toResult(BigDecimal r) { |
| long l = r.longValue(); |
| if (new BigDecimal(l).compareTo(r) == 0) { |
| return l; |
| } |
| double d = r.doubleValue(); |
| if (new BigDecimal(d).compareTo(r) == 0) { |
| return d; |
| } else { |
| return r.stripTrailingZeros(); |
| } |
| } |
| |
| private BigDecimal toBigDecimal(Map<String, Object> variables, Object o) { |
| if (o instanceof Variable) { |
| o = variables.get(((Variable) o).getName()); |
| } |
| if (o instanceof String) { |
| if (isNumber((String) o)) { |
| return new BigDecimal((String) o, mc); |
| } else if (Character.isLetter(((String) o).charAt(0))) { |
| o = variables.get(o); |
| } |
| } |
| return toBigDecimal(o); |
| } |
| |
| private BigDecimal toBigDecimal(Object o) { |
| if (o == null) { |
| return BigDecimal.ZERO; |
| } else if (o instanceof Boolean) { |
| return ((Boolean) o) ? BigDecimal.ONE : BigDecimal.ZERO; |
| } else if (o instanceof BigDecimal) { |
| return ((BigDecimal) o).round(mc); |
| } else if (o instanceof BigInteger) { |
| return new BigDecimal((BigInteger) o, mc); |
| } else if (o instanceof Number) { |
| return new BigDecimal(((Number) o).doubleValue(), mc); |
| } else { |
| try { |
| return new BigDecimal(o.toString(), mc); |
| } catch (NumberFormatException e) { |
| return new BigDecimal(Double.NaN); |
| } |
| } |
| } |
| |
| /** |
| * Sets the precision for expression evaluation. |
| * |
| * @param precision |
| * The new precision. |
| * |
| * @return The expression, allows to chain methods. |
| */ |
| public Expression setPrecision(int precision) { |
| this.mc = new MathContext(precision); |
| return this; |
| } |
| |
| /** |
| * Sets the rounding mode for expression evaluation. |
| * |
| * @param roundingMode |
| * The new rounding mode. |
| * @return The expression, allows to chain methods. |
| */ |
| public Expression setRoundingMode(RoundingMode roundingMode) { |
| this.mc = new MathContext(mc.getPrecision(), roundingMode); |
| return this; |
| } |
| |
| /** |
| * Adds an operator to the list of supported operators. |
| * |
| * @param operator |
| * The operator to add. |
| * @return The previous operator with that name, or <code>null</code> if |
| * there was none. |
| */ |
| public Operator addOperator(Operator operator) { |
| return operators.put(operator.getOper(), operator); |
| } |
| |
| /** |
| * Adds a function to the list of supported functions |
| * |
| * @param function |
| * The function to add. |
| * @return The previous operator with that name, or <code>null</code> if |
| * there was none. |
| */ |
| public Function addFunction(Function function) { |
| return functions.put(function.getName(), function); |
| } |
| |
| /** |
| * Sets a constant value. |
| * |
| * @param name |
| * The constant name. |
| * @param value |
| * The constant value. |
| * @return The expression, allows to chain methods. |
| */ |
| public Expression addConstant(String name, Object value) { |
| constants.put(name, value); |
| return this; |
| } |
| |
| /** |
| * Get an iterator for this expression, allows iterating over an expression |
| * token by token. |
| * |
| * @return A new iterator instance for this expression. |
| */ |
| public Iterator<String> getExpressionTokenizer() { |
| return new Tokenizer(this.expression); |
| } |
| |
| /** |
| * Cached access to the RPN notation of this expression, ensures only one |
| * calculation of the RPN per expression instance. If no cached instance |
| * exists, a new one will be created and put to the cache. |
| * |
| * @return The cached RPN instance. |
| */ |
| private List<Token> getRPN() { |
| if (rpn == null) { |
| rpn = shuntingYard(this.expression); |
| } |
| return rpn; |
| } |
| |
| /** |
| * Get a string representation of the RPN (Reverse Polish Notation) for this |
| * expression. |
| * |
| * @return A string with the RPN representation for this expression. |
| */ |
| public String toRPN() { |
| StringBuilder result = new StringBuilder(); |
| for (Token st : getRPN()) { |
| if (result.length() > 0) { |
| result.append(" "); |
| } |
| result.append(st); |
| } |
| return result.toString(); |
| } |
| |
| } |