[FELIX-4671] Provide an expression parser

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1632015 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/runtime/NOTICE b/gogo/runtime/NOTICE
index 6447d3e..cb48c52 100644
--- a/gogo/runtime/NOTICE
+++ b/gogo/runtime/NOTICE
@@ -4,3 +4,7 @@
 This product includes software developed at
 The Apache Software Foundation (http://www.apache.org/).
 Licensed under the Apache License 2.0.
+
+This product includes software developped by
+Udo Klimaschewski (http://UdoJava.com/).
+Licensed under the MIT License.
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
index 896c972..7dec8ec 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
@@ -19,11 +19,7 @@
 package org.apache.felix.gogo.runtime;
 
 import java.io.EOFException;
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.Map.Entry;
 
 import org.apache.felix.gogo.runtime.Tokenizer.Type;
@@ -293,6 +289,10 @@
                 v = t.type;
                 break;
 
+            case EXPR:
+                v = expr(t.value);
+                break;
+
             default:
                 throw new SyntaxError(t.line, t.column, "unexpected token: " + t.type);
         }
@@ -534,6 +534,11 @@
         return value;
     }
 
+    private Object expr(CharSequence expr) throws Exception
+    {
+        return session.expr(expr);
+    }
+
     private Object array(Token array) throws Exception
     {
         List<Token> list = new ArrayList<Token>();
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
index 6f41986..e89878d 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
@@ -21,6 +21,7 @@
 import java.io.InputStream;
 import java.io.PrintStream;
 import java.lang.reflect.Method;
+import java.math.BigDecimal;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -356,4 +357,7 @@
         }
     }
 
+    public Object expr(CommandSessionImpl session, CharSequence expr) {
+        return new Expression(expr.toString()).eval(session.variables);
+    }
 }
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
index 63e4f98..ee3c09b 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
@@ -396,4 +396,9 @@
         }
     }
 
+    public Object expr(CharSequence expr)
+    {
+        return processor.expr(this, expr);
+    }
+
 }
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expression.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expression.java
new file mode 100644
index 0000000..b34c805
--- /dev/null
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expression.java
@@ -0,0 +1,1332 @@
+/*
+ * 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.*;
+
+/**
+ * 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>
+ *   <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>
+ *   <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>&lt;&gt;</td><td>Not equals</td></tr>
+ *   <tr><td>&lt;</td><td>Less than</td></tr>
+ *   <tr><td>&lt;=</td><td>Less than or equal to</td></tr>
+ *   <tr><td>&gt;</td><td>Greater than</td></tr>
+ *   <tr><td>&gt;=</td><td>Greater than or equal to</td></tr>
+ *   <tr><td>&amp;&amp;</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>
+ *   <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>
+ *   <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<String, Expression.Operator>();
+
+    /**
+     * All defined functions with name and implementation.
+     */
+    private Map<String, Function> functions = new HashMap<String, Expression.Function>();
+
+    /**
+     * All defined variables with name and value.
+     */
+    private Map<String, Object> constants = new HashMap<String, Object>();
+
+    /**
+     * 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<BigDecimal>(parameters.size());
+            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 = (v1 instanceof Number || isNumber(v1.toString()))
+                           && (v2 instanceof Number || isNumber(v2.toString()));
+            if (numeric) {
+                return eval(toBigDecimal(variables, v1), toBigDecimal(variables, v2));
+            } else {
+                return eval(v1.toString(), 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")
+            public boolean compare(Comparable v1, Comparable v2) {
+                return v1.compareTo(v2) > 0;
+            }
+        });
+
+        addOperator(new Comparator(">=", 10) {
+            @Override @SuppressWarnings("unchecked")
+            public boolean compare(Comparable v1, Comparable v2) {
+                return v1.compareTo(v2) >= 0;
+            }
+        });
+
+        addOperator(new Comparator("<", 10) {
+            @Override @SuppressWarnings("unchecked")
+            public boolean compare(Comparable v1, Comparable v2) {
+                return v1.compareTo(v2) < 0;
+            }
+        });
+
+        addOperator(new Comparator("<=", 10) {
+            @Override @SuppressWarnings("unchecked")
+            public boolean compare(Comparable v1, Comparable v2) {
+                return v1.compareTo(v2) <= 0;
+            }
+        });
+
+        addOperator(new Comparator("==", 7) {
+            @Override @SuppressWarnings("unchecked")
+            public boolean compare(Comparable v1, Comparable v2) {
+                return v1.compareTo(v2) == 0;
+            }
+        });
+
+        addOperator(new Comparator("!=", 7) {
+            @Override @SuppressWarnings("unchecked")
+            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.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;
+    }
+
+    /**
+     * 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<Token>();
+        Stack<Token> stack = new Stack<Token>();
+
+        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.
+     *
+     * @return The result of the expression.
+     */
+    public Object eval(Map<String, Object> variables) {
+
+        Stack<Object> stack = new Stack<Object>();
+
+        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<Object>(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();
+    }
+
+}
\ No newline at end of file
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java
index 271bb8a..23c2f65 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java
@@ -97,6 +97,7 @@
                 case EXECUTION:
                 case ARRAY:
                 case ASSIGN:
+                case EXPR:
                     break;
                     
                 default:
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
index b1c529e..8b5d91f 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
@@ -46,7 +46,7 @@
 {
     public enum Type
     {
-        ASSIGN('='), PIPE('|'), SEMICOLON(';'), NEWLINE, ARRAY, CLOSURE, EXECUTION, WORD, EOT;
+        ASSIGN('='), PIPE('|'), SEMICOLON(';'), NEWLINE, ARRAY, CLOSURE, EXPR, EXECUTION, WORD, EOT;
 
         private char c;
 
@@ -265,25 +265,43 @@
     private CharSequence group()
     {
         final char push = ch;
+        final char push2;
         final char pop;
+        final char pop2;
 
         switch (ch)
         {
             case '{':
                 type = Type.CLOSURE;
+                push2 = 0;
                 pop = '}';
+                pop2 = 0;
                 break;
             case '(':
-                type = Type.EXECUTION;
-                pop = ')';
+                if (peek() == '(') {
+                    getch();
+                    push2 = '(';
+                    type = Type.EXPR;
+                    pop = ')';
+                    pop2 = ')';
+                } else {
+                    type = Type.EXECUTION;
+                    push2 = 0;
+                    pop = ')';
+                    pop2 = 0;
+                }
                 break;
             case '[':
                 type = Type.ARRAY;
+                push2 = 0;
                 pop = ']';
+                pop2 = 0;
                 break;
             default:
                 assert false;
+                push2 = 0;
                 pop = 0;
+                pop2 = 0;
         }
 
         short sLine = line;
@@ -327,10 +345,17 @@
                     break;
 
                 default:
-                    if (push == ch)
+                    if (push == ch) {
                         depth++;
-                    else if (pop == ch && --depth == 0)
-                        return text.subSequence(start, index - 1);
+                    }
+                    else if (pop == ch && --depth == 0) {
+                        if (pop2 == 0)
+                            return text.subSequence(start, index - 1);
+                        else if (pop2 == peek()) {
+                            getch();
+                            return text.subSequence(start, index - 2);
+                        }
+                    }
             }
         }
 
@@ -589,11 +614,20 @@
         if (getch() != '{')
         {
             if ('(' == ch)
-            { // support $(...) FELIX-2433
+            {
                 short sLine = line;
                 short sCol = column;
-                val = evaluate.eval(new Token(Type.EXECUTION, group(), sLine, sCol));
-                getch();
+                if ('(' == peek())
+                {
+                    val = evaluate.eval(new Token(Type.EXPR, group(), sLine, sCol));
+                    getch();
+                }
+                else
+                {
+                    // support $(...) FELIX-2433
+                    val = evaluate.eval(new Token(Type.EXECUTION, group(), sLine, sCol));
+                    getch();
+                }
             }
             else
             {
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/Context.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/Context.java
index adbb66f..b96a313 100644
--- a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/Context.java
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/Context.java
@@ -68,4 +68,9 @@
         session.put(name, value);
     }
 
+    public Object get(String name)
+    {
+        return session.get(name);
+    }
+
 }
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser.java
index 13c52f1..937ef80 100644
--- a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser.java
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser.java
@@ -46,7 +46,7 @@
 
         assertEquals("a", c.execute("echo a | capture"));
         assertEquals("a", c.execute("(echo a) | capture"));
-        assertEquals("a", c.execute("((echo a)) | capture"));
+        assertEquals("a", c.execute("( (echo a) ) | capture"));
     }
 
     public void testUnknownCommand() throws Exception
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser3.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser3.java
new file mode 100644
index 0000000..7e65c09
--- /dev/null
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser3.java
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+package org.apache.felix.gogo.runtime;
+
+import junit.framework.TestCase;
+
+import java.io.EOFException;
+
+/*
+ * Test features of the new parser/tokenizer, many of which are not supported
+ * by the original parser.
+ */
+public class TestParser3 extends TestCase
+{
+    public void testArithmetic() throws Exception
+    {
+        Context c = new Context();
+        c.addCommand("echo", this);
+
+        assertEquals("10", c.execute("echo $((2*(3+2)))"));
+        assertEquals(3l, c.execute("((1+2))"));
+
+        c.set("a", 2l);
+        assertEquals(3l, c.execute("((a+=1))"));
+        assertEquals(3l, c.get("a"));
+    }
+
+    public CharSequence echo(Object args[])
+    {
+        if (args == null)
+        {
+            return "null args!";
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (Object arg : args)
+        {
+            if (sb.length() > 0)
+                sb.append(' ');
+            sb.append(String.valueOf(arg));
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/expr/ExpressionTest.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/expr/ExpressionTest.java
new file mode 100644
index 0000000..bb16cd9
--- /dev/null
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/expr/ExpressionTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+package org.apache.felix.gogo.runtime.expr;
+
+import junit.framework.TestCase;
+import org.apache.felix.gogo.runtime.Expression;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ExpressionTest extends TestCase {
+
+    public void testExpr() {
+
+        Map<String, Object> variables = new HashMap<String, Object>();
+        variables.put("a", 2d);
+        variables.put("b", 5l);
+        variables.put("c", 1l);
+        variables.put("d", 2l);
+        variables.put("s", " foo ");
+        variables.put("t", "bar");
+
+        assertEquals(4l, new Expression("c+=1, d+=2").eval(variables));
+
+        assertEquals(" foo ", new Expression("\" foo \"").eval());
+        assertEquals(" foo bar", new Expression("s + t").eval(variables));
+        assertEquals(1l, new Expression("s < t").eval(variables));
+        assertEquals(1l, new Expression("s > t || t == \"bar\"").eval(variables));
+
+        assertEquals(3l, new Expression("a += 1").eval(variables));
+        assertEquals(3l, variables.get("a"));
+
+        assertEquals(30l, new Expression("10 + 20 | 30").eval());
+
+        assertEquals(8l, new Expression("a + b").eval(variables));
+        assertEquals(3l, new Expression("if(a < b, a, b)").eval(variables));
+
+        assertEquals(16l, new Expression("2 + 2 << 2").eval());
+        assertEquals(8l, new Expression("2 | 2 << 2").eval());
+    }
+
+}