com.udojava.evalex.Expression.java Source code

Java tutorial

Introduction

Here is the source code for com.udojava.evalex.Expression.java

Source

/*
 * 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 com.udojava.evalex;

import org.apache.commons.math3.analysis.integration.SimpsonIntegrator;
import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.complex.ComplexUtils;
import org.apache.commons.math3.random.MersenneTwister;
import org.apache.commons.math3.stat.descriptive.moment.GeometricMean;
import org.apache.commons.math3.stat.descriptive.moment.Mean;
import org.apache.commons.math3.stat.descriptive.summary.Product;
import org.apache.commons.math3.stat.descriptive.summary.Sum;
import org.apache.commons.math3.util.ArithmeticUtils;
import org.apache.commons.math3.util.CombinatoricsUtils;

import java.math.BigInteger;
import java.util.*;

import static org.apache.commons.math3.primes.Primes.nextPrime;
import static org.apache.commons.math3.stat.StatUtils.variance;

/**
 * <h1>EvalEx - Java Expression Evaluator</h1>
 *
 * @author Udo Klimaschewski (http://about.me/udo.klimaschewski)
 */
public class Expression {
    /**
     * What character to use for decimal separators.
     */
    private static final char decimalSeparator = '.';
    /**
     * What character to use for minus sign (negative values).
     */
    private static final char minusSign = '-';
    /**
     * The MyComplex representation of the left parenthesis,
     * used for parsing varying numbers of function parameters.
     */
    private static final LazyNumber PARAMS_START = () -> null;
    private final LinkedList<String> history;
    /**
     * All defined operators with name and implementation.
     */
    private final Map<String, Operator> operators = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    /**
     * All defined functions with name and implementation.
     */
    private final Map<String, LazyFunction> functions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    /**
     * All defined variables with name and value.
     */
    //private final Map<String, MyComplex> variables = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    private final Variables mainVars;

    /**
     * The current infix expression, with optional variable substitutions.
     */
    private String expression = null;
    /**
     * The cached RPN (Reverse Polish Notation) of the expression.
     */
    private List<String> rpn = null;

    /**
     * Creates a new expression instance from an expression string with a given
     * default match context.
     *
     * @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, LinkedList<String> hist, Variables vars) {
        this.history = hist;
        this.expression = expression;

        mainVars = vars;

        addOperator(new Operator("+", 20, true, "Addition") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                if (v1.type == ValueType.ARRAY) {
                    MyComplex vo = new MyComplex(v1.list);
                    vo.list.add(v2);
                    return vo;
                }
                return v1.add(v2);
            }
        });

        addOperator(new Operator("-", 20, true, "Subtraction") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                if (v1.type == ValueType.ARRAY) {
                    MyComplex vo = new MyComplex(v1.list);
                    vo.list.removeIf(o -> o.equals(v2));
                    return vo;
                }
                return v1.subtract(v2);
            }
        });
        addOperator(new Operator("*", 30, true, "Real number multiplication") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                return v1.multiply(v2);
            }
        });
        addOperator(new Operator("/", 30, true, "Real number division") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                return v1.divide(v2);
            }
        });
        addOperator(new Operator("%", 30, true, "Remainder of integer division") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                double r = v1.real % v2.real;
                return new MyComplex(r);
            }
        });
        addOperator(
                new Operator("^", 40, false, "Exponentation. See: https://en.wikipedia.org/wiki/Exponentiation") {
                    @Override
                    public MyComplex eval(MyComplex v1, MyComplex v2) {
                        return v1.pow(v2);
                    }
                });
        addOperator(new Operator("&&", 4, false, "Logical AND. Evaluates to 1 if both operands are not 0") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                boolean b1 = (v1.real == 0.0 && v2.real == 0.0);
                return new MyComplex(b1 ? 1 : 0);
            }
        });

        addOperator(new Operator("||", 2, false, "Logical OR. Evaluates to 0 if both operands are 0") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                boolean b1 = (v1.real == 0.0 && v2.real == 0.0);
                return new MyComplex(b1 ? 0 : 1);
            }
        });

        addOperator(new Operator(">", 10, false,
                "Greater than. See: See: https://en.wikipedia.org/wiki/Inequality_(mathematics)") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                if (v1.type == ValueType.REAL && v2.type == ValueType.REAL) {
                    return new MyComplex(v1.real > v2.real ? 1 : 0);
                } else {
                    return new MyComplex(v1.abs() > v2.abs() ? 1 : 0);
                }
            }
        });

        addOperator(new Operator(">=", 10, false, "Greater or equal") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                if (v1.type == ValueType.REAL && v2.type == ValueType.REAL) {
                    return new MyComplex(v1.real >= v2.real ? 1 : 0);
                } else {
                    return new MyComplex(v1.abs() >= v2.abs() ? 1 : 0);
                }
            }
        });

        addOperator(new Operator("<", 10, false,
                "Less than. See: https://en.wikipedia.org/wiki/Inequality_(mathematics)") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                if (v1.type == ValueType.REAL && v2.type == ValueType.REAL) {
                    return new MyComplex(v1.real < v2.real ? 1 : 0);
                } else {
                    return new MyComplex(v1.abs() < v2.abs() ? 1 : 0);
                }
            }
        });

        addOperator(new Operator("<=", 10, false, "less or equal") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                if (v1.type == ValueType.REAL && v2.type == ValueType.REAL) {
                    return new MyComplex(v1.real <= v2.real ? 1 : 0);
                } else {
                    return new MyComplex(v1.abs() <= v2.abs() ? 1 : 0);
                }
            }
        });

        addOperator(new Operator("->", 7, false, "Set variable v to new value ") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                if (v1 instanceof PitDecimal) {
                    PitDecimal target = (PitDecimal) v1;
                    String s = target.getVarToken();
                    setVariable(s, v2);
                    return v2;
                }
                throw new ExpressionException("LHS not variable");
            }
        });

        addOperator(new Operator("=", 7, false, "Equality") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                if (v1.type == ValueType.REAL && v2.type == ValueType.REAL) {
                    return new MyComplex(v1.real == v2.real ? 1 : 0);
                } else {
                    return new MyComplex(v1.abs() == v2.abs() ? 1 : 0);
                }
            }
        });

        addOperator(new Operator("!=", 7, false,
                "Inequality. See: https://en.wikipedia.org/wiki/Inequality_(mathematics)") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                if (v1.type == ValueType.REAL && v2.type == ValueType.REAL) {
                    return new MyComplex(v1.real != v2.real ? 1 : 0);
                } else {
                    return new MyComplex(v1.abs() != v2.abs() ? 1 : 0);
                }
            }
        });
        addOperator(
                new Operator("or", 7, false, "Bitwise OR. See: https://en.wikipedia.org/wiki/Logical_disjunction") {
                    @Override
                    public MyComplex eval(MyComplex v1, MyComplex v2) {
                        return new MyComplex((long) v1.real | (long) v2.real);
                    }
                });
        addOperator(new Operator("and", 7, false,
                "Bitwise AND. See: https://en.wikipedia.org/wiki/Logical_conjunction") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                return new MyComplex((long) v1.real & (long) v2.real);
            }
        });
        addOperator(new Operator("xor", 7, false, "Bitwise XOR, See: https://en.wikipedia.org/wiki/Exclusive_or") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                return new MyComplex((long) v1.real ^ (long) v2.real);
            }
        });

        addOperator(new Operator("!", 50, true, "Factorial. See https://en.wikipedia.org/wiki/Factorial") {
            public BigInteger factorial(long n) {
                BigInteger factorial = BigInteger.ONE;
                for (long i = 1; i <= n; i++) {
                    factorial = factorial.multiply(BigInteger.valueOf(i));
                }
                return factorial;
            }

            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                BigInteger fact = factorial((long) v1.real);
                return new MyComplex(fact, BigInteger.ZERO);
            }
        });

        addOperator(new Operator("~", 8, false, "Bitwise negation") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                BigInteger bi = v2.toBigIntegerReal();
                int c = bi.bitLength();
                if (c == 0) {
                    return new MyComplex(1);
                }
                for (int s = 0; s < c; s++) {
                    bi = bi.flipBit(s);
                }
                return new MyComplex(bi);
            }
        });

        addOperator(new Operator("shl", 8, false, "Left Bit shift") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                return new MyComplex((long) v1.real << (long) v2.real);
            }
        });

        addOperator(new Operator("shr", 8, false, "Right bit shift") {
            @Override
            public MyComplex eval(MyComplex v1, MyComplex v2) {
                return new MyComplex((long) v1.real >>> (long) v2.real);
            }
        });

        addFunction(new Function("NOT", 1, "evaluates to 0 if argument != 0") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                boolean zero = parameters.get(0).abs() == 0;
                return new MyComplex(zero ? 1 : 0);
            }
        });

        addFunction(new Function("RND", 2, "Give random number in the range between first and second argument") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                double low = parameters.get(0).real;
                double high = parameters.get(1).real;
                return new MyComplex(low + Math.random() * (high - low));
            }
        });

        MersenneTwister mers = new MersenneTwister(System.nanoTime());

        addFunction(new Function("MRS", 0, "Mersenne twister random generator") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return new MyComplex(mers.nextDouble());
            }
        });

        addFunction(new Function("BIN", 2, "Binomial Coefficient 'n choose k'") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                int n = (int) parameters.get(0).real;
                int k = (int) parameters.get(1).real;
                double d = CombinatoricsUtils.binomialCoefficientDouble(n, k);
                return new MyComplex(d);
            }
        });
        addFunction(new Function("STIR", 2,
                "Stirling number of 2nd kind: http://mathworld.wolfram.com/StirlingNumberoftheSecondKind.html") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                int n = (int) parameters.get(0).real;
                int k = (int) parameters.get(1).real;
                double d = CombinatoricsUtils.stirlingS2(n, k);
                return new MyComplex(d);
            }
        });

        addFunction(new Function("SIN", 1, "Sine function") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return parameters.get(0).sin();
            }
        });
        addFunction(new Function("COS", 1, "Cosine function") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return parameters.get(0).cos();
            }
        });
        addFunction(new Function("TAN", 1, "Tangent") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return parameters.get(0).tan();
            }
        });
        addFunction(new Function("ASIN", 1, "Reverse Sine") { // added by av
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return parameters.get(0).asin();
            }
        });
        addFunction(new Function("ACOS", 1, "Reverse Cosine") { // added by av
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return parameters.get(0).acos();
            }
        });
        addFunction(new Function("ATAN", 1, "Reverse Tangent") { // added by av
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return parameters.get(0).atan();
            }
        });
        addFunction(new Function("SINH", 1, "Hyperbolic Sine") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return parameters.get(0).sinh();
            }
        });
        addFunction(new Function("COSH", 1, "Hyperbolic Cosine") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return parameters.get(0).cosh();
            }
        });
        addFunction(new Function("TANH", 1, "Hyperbolic Tangent") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return parameters.get(0).tanh();
            }
        });
        addFunction(new Function("RAD", 1, "Transform degree to radian") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                double d = Math.toRadians(parameters.get(0).real);
                return new MyComplex(d);
            }
        });
        addFunction(new Function("DEG", 1, "Transform radian to degree") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                double d = Math.toDegrees(parameters.get(0).real);
                return new MyComplex(d);
            }
        });
        addFunction(new Function("MAX", -1, "Find the biggest value in a list") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                MyComplex save = new MyComplex(Double.MIN_VALUE);
                if (parameters.size() == 0) {
                    throw new ExpressionException("MAX requires at least one parameter");
                }
                //                if (parameters.get(0).type == ValueType.ARRAY)
                //                    parameters = parameters.get(0).list;
                if (parameters.get(0).type == ValueType.COMPLEX) {
                    for (MyComplex parameter : parameters) {
                        if (parameter.abs() > save.abs()) {
                            save = parameter;
                        }
                    }
                    save.type = ValueType.COMPLEX;
                } else {
                    for (MyComplex parameter : parameters) {
                        if (parameter.real > save.real) {
                            save = parameter;
                        }
                    }
                    save.type = ValueType.REAL;
                }
                return save;
            }
        });
        ///////////////////////////////////////////////////////
        addFunction(new Function("IF", 3, "Conditional: give param3 if param1 is 0, otherwise param2") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                if (parameters.get(0).real == 0.0) {
                    return parameters.get(2);
                }
                return parameters.get(1);
            }
        });

        addFunction(new Function("PERC", 2, "Get param1 percent of param2") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return parameters.get(0).divide(new MyComplex(100)).multiply(parameters.get(1));
            }
        });

        addFunction(new Function("PER", 2, "How many percent is param1 of param2") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return parameters.get(0).multiply(new MyComplex(100)).divide(parameters.get(1));
            }
        });

        addFunction(new Function("H", 1, "Evaluate _history element") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                int i = (int) parameters.get(0).real;
                Expression ex = new Expression(history.get(i), history, mainVars);
                return ex.eval();
            }
        });

        addFunction(new Function("MERS", 1, "Calculate Mersenne Number") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                MyComplex p = parameters.get(0);
                return new MyComplex(2).pow(p).subtract(new MyComplex(1));
            }
        });

        addFunction(new Function("GCD", 2, "Find greatest common divisor of 2 values") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                double a = parameters.get(0).real;
                double b = parameters.get(1).real;
                long r = ArithmeticUtils.gcd((long) a, (long) b);
                return new MyComplex(r);
            }
        });
        addFunction(new Function("LCM", 2, "Find least common multiple of 2 values") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                double a = parameters.get(0).real;
                double b = parameters.get(1).real;
                long r = ArithmeticUtils.lcm((long) a, (long) b);
                return new MyComplex(r);
            }
        });
        addFunction(new Function("AMEAN", -1, "Arithmetic mean of a set of values") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                if (parameters.size() == 0) {
                    throw new ExpressionException("MEAN requires at least one parameter");
                }
                Mean m = new Mean();
                double[] d = MyComplex.getRealArray(parameters);
                double d2 = m.evaluate(d);
                return new MyComplex(d2);
            }
        });
        //        addFunction(new Function("BYT", -1,
        //                "Value from sequence of bytes")
        //        {
        //            @Override
        //            public MyComplex eval (List<MyComplex> parameters)
        //            {
        //                if (parameters.size() == 0)
        //                {
        //                    return MyComplex.ZERO;
        //                }
        //                BigInteger res = BigInteger.ZERO;
        //                for (MyComplex parameter : parameters)
        //                {
        //                    if (parameter.intValue() < 0 || parameter.intValue() > 255)
        //                    {
        //                        throw new ExpressionException("not a byte value");
        //                    }
        //                    res = res.shiftLeft(8);
        //                    res = res.or(parameter.toBigInteger());
        //                }
        //                return new MyComplex(res, BigInteger.ZERO);
        //            }
        //        });
        addFunction(new Function("SEQ", 3, "Generate Sequence p1=start, p2=step, p3=count") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                double start = parameters.get(0).real;
                ArrayList<MyComplex> arr = new ArrayList<>();
                for (int s = 0; s < (int) (parameters.get(2).real); s++) {
                    arr.add(new MyComplex(start));
                    start += parameters.get(1).real;
                }
                return new MyComplex(arr);
            }
        });

        addFunction(new Function("PROD", -1, "Product of real values") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                Product p = new Product();
                double[] d = MyComplex.getRealArray(parameters);
                return new MyComplex(p.evaluate(d));
            }
        });

        addFunction(new Function("SUM", -1, "Sum of values") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                Sum p = new Sum();
                double[] d = MyComplex.getRealArray(parameters);
                return new MyComplex(p.evaluate(d));
            }
        });

        addFunction(new Function("ANG", 1, "Angle phi of complex number in radians") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                double b = parameters.get(0).angle();
                return new MyComplex(b);
            }
        });

        addFunction(new Function("IM", 1, "Get imaginary part") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return new MyComplex(parameters.get(0).imaginary);
            }
        });

        addFunction(new Function("RE", 1, "Get real part") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return new MyComplex(parameters.get(0).real);
            }
        });

        addFunction(new Function("POL", 2, "Make complex number from polar coords. angle is first arg") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                double angle = parameters.get(0).real;
                double len = parameters.get(1).real;
                Complex c = ComplexUtils.polar2Complex(len, angle);
                return new MyComplex(c);
            }
        });

        addFunction(new Function("GMEAN", -1, "Geometric mean of a set of values") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                if (parameters.size() == 0) {
                    throw new ExpressionException("MEAN requires at least one parameter");
                }
                GeometricMean m = new GeometricMean();
                double[] d = MyComplex.getRealArray(parameters);
                double d2 = m.evaluate(d);
                return new MyComplex(d2);
            }
        });

        addFunction(new Function("HMEAN", -1, "Harmonic mean of a set of values") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                if (parameters.size() == 0) {
                    throw new ExpressionException("MEAN requires at least one parameter");
                }
                MyComplex res = new MyComplex(0);
                int num = 0;
                for (MyComplex parameter : parameters) {
                    res = res.add(new MyComplex(1).divide(parameter));
                    num++;
                }
                res = new MyComplex(res.abs());
                return new MyComplex(num).divide(res);
            }
        });

        addFunction(new Function("VAR", -1, "Variance of a set of values") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                if (parameters.size() == 0) {
                    throw new ExpressionException("MEAN requires at least one parameter");
                }
                double[] arr = new double[parameters.size()];
                for (int s = 0; s < parameters.size(); s++) {
                    arr[s] = parameters.get(s).real;
                }
                return new MyComplex(variance(arr));
            }
        });

        addFunction(new Function("NPR", 1, "Next prime number greater or equal the argument") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return new MyComplex(nextPrime((int) parameters.get(0).real));
            }
        });

        addFunction(new Function("NSWP", 1, "Swap nibbles") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                BigInteger bi = parameters.get(0).toBigIntegerReal();
                String s = bi.toString(16);
                s = new StringBuilder(s).reverse().toString();
                return new MyComplex(new BigInteger(s, 16), BigInteger.ZERO);
            }
        });

        addFunction(new Function("BSWP", 1, "Swap bytes") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                BigInteger bi = parameters.get(0).toBigIntegerReal();
                String s = bi.toString(16);
                while (s.length() % 4 != 0) {
                    s = s + "0";
                }
                if (bi.intValue() < 256) {
                    s = "00" + s;
                }
                s = Misc.reverseHex(s);
                return new MyComplex(new BigInteger(s, 16), BigInteger.ZERO);
            }
        });

        addFunction(new Function("PYT", 2,
                "Pythagoras's result = sqrt(param1^2+param2^2) https://en.wikipedia.org/wiki/Pythagorean_theorem") {
            @Override
            public MyComplex eval(List<MyComplex> par) {
                double a = par.get(0).real;
                double b = par.get(1).real;
                return new MyComplex(Math.sqrt(a * a + b * b));
            }
        });

        addFunction(new Function("FIB", 1, "Fibonacci number") {
            // --Commented out by Inspection (2/19/2017 7:46 PM):private final Operator exp = operators.get("^");

            @Override
            public MyComplex eval(List<MyComplex> par) {
                return Misc.iterativeFibonacci((int) par.get(0).real);
            }
        });

        ///////////////////////////////////////////////

        addFunction(new Function("MIN", -1, "Find the smallest in a list of values") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                MyComplex save = new MyComplex(Double.MAX_VALUE);
                if (parameters.size() == 0) {
                    throw new ExpressionException("MAX requires at least one parameter");
                }
                if (parameters.get(0).type == ValueType.COMPLEX) {
                    for (MyComplex parameter : parameters) {
                        if (parameter.abs() < save.abs()) {
                            save = parameter;
                        }
                    }
                    save.type = ValueType.COMPLEX;
                } else {
                    for (MyComplex parameter : parameters) {
                        if (parameter.real < save.real) {
                            save = parameter;
                        }
                    }
                    save.type = ValueType.REAL;
                }
                return save;
            }
        });
        addFunction(new Function("ABS", 1, "Get absolute value of a number") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return new MyComplex(parameters.get(0).abs());
            }
        });
        addFunction(new Function("LN", 1, "Logarithm base e of the argument") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                double d = Math.log(parameters.get(0).real);
                return new MyComplex(d);
            }
        });
        addFunction(new Function("LOG", 1, "Logarithm base 10 of the argument") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                double d = Math.log10(parameters.get(0).real);
                return new MyComplex(d);
            }
        });
        addFunction(new Function("FLOOR", 1, "Rounds DOWN to nearest Integer") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                double d = Math.floor(parameters.get(0).real);
                return new MyComplex(d);
            }
        });
        addFunction(new Function("CEIL", 1, "Rounds UP to nearest Integer") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                double d = Math.ceil(parameters.get(0).real);
                return new MyComplex(d);
            }
        });
        addFunction(new Function("ROU", 1, "Rounds to nearest Integer") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                int d = (int) (parameters.get(0).real + 0.5);
                return new MyComplex(d);
            }
        });
        addFunction(new Function("SQRT", 1, "Square root") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                MyComplex p = parameters.get(0);
                if (p.type == ValueType.REAL) {
                    return new MyComplex(Math.sqrt(p.real));
                }
                return p.sqrt();
            }
        });
        addFunction(new Function("ARR", -1, "Create array") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                return new MyComplex(parameters);
            }
        });
        addFunction(new Function("POLY", -1, "Treat array as Polynom") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                double[] d = MyComplex.getRealArray(parameters);
                PolynomialFunction p = new PolynomialFunction(d);
                return new MyComplex(p);
            }
        });
        addFunction(new Function("DRVE", -1, "Make derivative of polynomial") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                PolynomialFunction p;
                if (parameters.get(0).isPoly()) {
                    p = new PolynomialFunction(parameters.get(0).getRealArray());
                } else {
                    double[] d = MyComplex.getRealArray(parameters);
                    p = new PolynomialFunction(d);
                }
                return new MyComplex(p.polynomialDerivative());
            }
        });
        addFunction(new Function("ADRVE", -1, "Make antiderivative of polynomial. Constant is always zero") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                PolynomialFunction p;
                if (parameters.get(0).isPoly()) {
                    p = new PolynomialFunction(parameters.get(0).getRealArray());
                } else {
                    double[] d = MyComplex.getRealArray(parameters);
                    p = new PolynomialFunction(d);
                }
                return new MyComplex(Misc.antiDerive(p));
            }
        });

        addFunction(new Function("PVAL", 2, "Compute value of polynom for the given argument.") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                if (parameters.get(0).isPoly()) {
                    PolynomialFunction p = new PolynomialFunction(parameters.get(0).getRealArray());
                    double v = p.value(parameters.get(1).real);
                    return new MyComplex(v);
                }
                throw new ExpressionException("first arg must be polynomial");
            }
        });

        addFunction(new Function("INTGR", 3, "Numerical integration") {
            @Override
            public MyComplex eval(List<MyComplex> parameters) {
                if (parameters.get(0).isPoly()) {
                    PolynomialFunction p = new PolynomialFunction(parameters.get(0).getRealArray());
                    double start = parameters.get(1).real;
                    double end = parameters.get(2).real;
                    SimpsonIntegrator si = new SimpsonIntegrator();
                    double d = si.integrate(1000, p, start, end);
                    return new MyComplex(d);
                }
                throw new ExpressionException("first arg must be polynomial");
            }
        });

    }

    /**
     * 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.
     */
    private void addOperator(Operator operator) {
        operators.put(operator.getName(), operator);
    }

    /**
     * Sets a variable value.
     *
     * @param variable The variable name.
     * @param value    The variable value.
     * @return The expression, allows to chain methods.
     */
    private void setVariable(String variable, MyComplex value) {
        mainVars.put(variable, value);
    }

    /**
     * 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.
     */
    private void addFunction(Function function) {
        functions.put(function.getName(), function);
    }

    /**
     * Evaluates the expression.
     *
     * @return The result of the expression.
     */
    public MyComplex eval() {
        Stack<LazyNumber> stack = new Stack<>();

        for (final String token : getRPN()) {
            if (operators.containsKey(token)) {
                final LazyNumber v1 = stack.pop();
                final LazyNumber v2 = stack.pop();
                LazyNumber number = () -> operators.get(token).eval(v2.eval(), v1.eval());
                stack.push(number);
            } else if (mainVars.getMap().containsKey(token)) {
                MyComplex v = mainVars.get(token);
                if (v.type == ValueType.ARRAY) {
                    stack.push(() -> v);
                } else {
                    PitDecimal bd = new PitDecimal(v.real, v.imaginary);
                    bd.type = v.type;
                    bd.setVarToken(token);
                    stack.push(() -> bd);
                }
            } else if (functions.containsKey(token.toUpperCase(Locale.ROOT))) {
                LazyFunction f = functions.get(token.toUpperCase(Locale.ROOT));
                ArrayList<LazyNumber> p = new ArrayList<>(!f.numParamsVaries() ? f.getNumParams() : 0);
                // pop parameters off the stack until we hit the start of
                // this function's parameter list
                while (!stack.isEmpty() && stack.peek() != PARAMS_START) {
                    p.add(0, stack.pop());
                }
                if (stack.peek() == PARAMS_START) {
                    stack.pop();
                }
                LazyNumber fResult = f.lazyEval(p);
                stack.push(fResult);
            } else if ("(".equals(token)) {
                stack.push(PARAMS_START);
            } else {
                MyComplex bd;
                if (token.endsWith("i")) {
                    String str = token.substring(0, token.length() - 1);
                    if (str.isEmpty())
                        str = "1";
                    bd = new MyComplex("0", str);
                } else {
                    bd = new MyComplex(token);
                }
                MyComplex finalBd = bd;
                stack.push(() -> finalBd); // blank constant
            }
        }
        return stack.pop().eval();
    }

    /*
    * 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<String> getRPN() {
        if (rpn == null) {
            rpn = shuntingYard(this.expression);
            validate(rpn);
        }
        return rpn;
    }

    /**
     * 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<String> shuntingYard(String expression) {
        List<String> outputQueue = new ArrayList<>();
        Stack<String> stack = new Stack<>();

        Tokenizer tokenizer = new Tokenizer(expression);

        String lastFunction = null;
        String previousToken = null;
        while (tokenizer.hasNext()) {
            String token = tokenizer.next();
            if (isNumber(token)) {
                if (token.startsWith("x")) {

                    BigInteger bd = new BigInteger(token.substring(1), 16);
                    outputQueue.add(bd.toString(10));
                } else if (token.startsWith("b")) {
                    BigInteger bd = new BigInteger(token.substring(1), 2);
                    outputQueue.add(bd.toString(10));
                } else if (token.startsWith("o")) {
                    BigInteger bd = new BigInteger(token.substring(1), 8);
                    outputQueue.add(bd.toString(10));
                } else {
                    outputQueue.add(token);
                }
            } else if (mainVars.containsKey(token)) {
                outputQueue.add(token);
            } else if (functions.containsKey(token.toUpperCase(Locale.ROOT))) {
                stack.push(token);
                lastFunction = token;
            } else if ((Character.isLetter(token.charAt(0)) || token.charAt(0) == '_')
                    && !operators.containsKey(token)) {
                mainVars.put(token, new MyComplex(0, 0)); // create variable
                outputQueue.add(token);
                //stack.push(token);
            } else if (",".equals(token)) {
                if (operators.containsKey(previousToken)) {
                    throw new ExpressionException("Missing parameter(s) for operator " + previousToken
                            + " at character position " + (tokenizer.getPos() - 1 - previousToken.length()));
                }
                while (!stack.isEmpty() && !"(".equals(stack.peek())) {
                    outputQueue.add(stack.pop());
                }
                if (stack.isEmpty()) {
                    throw new ExpressionException("Parse error for function '" + lastFunction + "'");
                }
            } else if (operators.containsKey(token)) {
                if (",".equals(previousToken) || "(".equals(previousToken)) {
                    throw new ExpressionException("Missing parameter(s) for operator " + token
                            + " at character position " + (tokenizer.getPos() - token.length()));
                }
                Operator o1 = operators.get(token);
                String token2 = stack.isEmpty() ? null : stack.peek();
                while (token2 != null && operators.containsKey(token2)
                        && ((o1.isLeftAssoc() && o1.getPrecedence() <= operators.get(token2).getPrecedence())
                                || (o1.getPrecedence() < operators.get(token2).getPrecedence()))) {
                    outputQueue.add(stack.pop());
                    token2 = stack.isEmpty() ? null : stack.peek();
                }
                stack.push(token);
            } else if ("(".equals(token)) {
                if (previousToken != null) {
                    if (isNumber(previousToken)) {
                        throw new ExpressionException(
                                "Missing operator at character position " + tokenizer.getPos());
                    }
                    // if the ( is preceded by a valid function, then it
                    // denotes the start of a parameter list
                    if (functions.containsKey(previousToken.toUpperCase(Locale.ROOT))) {
                        outputQueue.add(token);
                    }
                }
                stack.push(token);
            } else if (")".equals(token)) {
                if (operators.containsKey(previousToken)) {
                    throw new ExpressionException("Missing parameter(s) for operator " + previousToken
                            + " at character position " + (tokenizer.getPos() - 1 - previousToken.length()));
                }
                while (!stack.isEmpty() && !"(".equals(stack.peek())) {
                    outputQueue.add(stack.pop());
                }
                if (stack.isEmpty()) {
                    throw new ExpressionException("Mismatched parentheses");
                }
                stack.pop();
                if (!stack.isEmpty() && functions.containsKey(stack.peek().toUpperCase(Locale.ROOT))) {
                    outputQueue.add(stack.pop());
                }
            }
            previousToken = token;
        }
        while (!stack.isEmpty()) {
            String element = stack.pop();
            if ("(".equals(element) || ")".equals(element)) {
                throw new ExpressionException("Mismatched parentheses");
            }

            if (!operators.containsKey(element)) {
                throw new ExpressionException("Unknown operator or function: " + element);
            }
            outputQueue.add(element);
        }
        return outputQueue;
    }

    /**
     * Check that the expression has enough numbers and variables to fit the
     * requirements of the operators and functions, also check
     * for only 1 result stored at the end of the evaluation.
     */
    private void validate(List<String> rpn) {
        /*-
        * Thanks to Norman Ramsey:
        * http://http://stackoverflow.com/questions/789847/postfix-notation-validation
        */
        // each push on to this stack is a new function scope, with the value of each
        // layer on the stack being the count of the number of parameters in that scope
        Stack<Integer> stack = new Stack<>();

        // push the 'global' scope
        stack.push(0);

        for (final String token : rpn) {
            if (operators.containsKey(token)) {
                if (stack.peek() < 2) {
                    throw new ExpressionException("Missing parameter(s) for operator " + token);
                }
                // pop the operator's 2 parameters and add the result
                stack.set(stack.size() - 1, stack.peek() - 2 + 1);
            } else if (mainVars.containsKey(token)) {
                stack.set(stack.size() - 1, stack.peek() + 1);
            } else if (functions.containsKey(token.toUpperCase(Locale.ROOT))) {
                LazyFunction f = functions.get(token.toUpperCase(Locale.ROOT));
                int numParams = stack.pop();
                if (!f.numParamsVaries() && numParams != f.getNumParams()) {
                    throw new ExpressionException("Function " + token + " expected " + f.getNumParams()
                            + " parameters, got " + numParams);
                }
                if (stack.size() <= 0) {
                    throw new ExpressionException("Too many function calls, maximum scope exceeded");
                }
                // push the result of the function
                stack.set(stack.size() - 1, stack.peek() + 1);
            } else if ("(".equals(token)) {
                stack.push(0);
            } else {
                stack.set(stack.size() - 1, stack.peek() + 1);
            }
        }

        if (stack.size() > 1) {
            throw new ExpressionException("Too many unhandled function parameter lists");
        } else if (stack.peek() > 1) {
            throw new ExpressionException("Too many numbers or variables");
        } else if (stack.peek() < 1) {
            throw new ExpressionException("Empty expression");
        }
    }

    /**
     * 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.startsWith("x") && !st.equals("xor")
                || (st.startsWith("b") && (st.charAt(1) == '0' || st.charAt(1) == '1'))
                || st.startsWith("o") && !st.equals("or")) {
            return true;
        }
        if (st.charAt(0) == minusSign && st.length() == 1) {
            return false;
        }
        if (st.charAt(0) == '+' && st.length() == 1) {
            return false;
        }
        if (st.charAt(0) == 'e' || st.charAt(0) == 'E') {
            return false;
        }
        for (char ch : st.toCharArray()) {
            if (!Character.isDigit(ch) && ch != minusSign && ch != decimalSeparator && ch != 'e' && ch != 'i'
                    && ch != 'E' && ch != '+') {
                return false;
            }
        }
        return true;
    }

    public Map<String, Operator> getOps() {
        return operators;
    }

    public Map<String, LazyFunction> getFuncs() {
        return functions;
    }

    // --Commented out by Inspection START (2/25/2017 11:54 AM):
    //    /**
    //     * Sets a variable value.
    //     *
    //     * @param variable The variable to set.
    //     * @param value    The variable value.
    //     * @return The expression, allows to chain methods.
    //     */
    //    private Expression setVariable (String variable, String value)
    //    {
    //        if (isNumber(value))
    //        {
    //            mainVars.put(variable, new MyComplex(value, "0"));
    //        }
    //        else
    //        {
    //            expression = expression.replaceAll("(?i)\\b" + variable + "\\b", "("
    //                    + value + ")");
    //            rpn = null;
    //        }
    //        return this;
    //    }
    // --Commented out by Inspection STOP (2/25/2017 11:54 AM)

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return this.expression == null ? 0 : this.expression.hashCode();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Expression that = (Expression) o;
        if (this.expression == null) {
            return that.expression == null;
        } else {
            return this.expression.equals(that.expression);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return this.expression;
    }

    /**
     * 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> {
        /**
         * The original input expression.
         */
        private final String input;
        /**
         * Actual position in expression string.
         */
        private int pos = 0;
        /**
         * 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.trim();
        }

        //@Override
        public boolean hasNext() {
            return (pos < input.length());
        }

        //@Override
        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);
            }
            /*
            The characters (other than letters and digits) allowed as the first character in a variable.
            */
            String firstVarChars = "_";
            if (Character.isDigit(ch)) {
                while ((Character.isDigit(ch) || ch == decimalSeparator || ch == 'e' || ch == 'i' || ch == 'E'
                        || (ch == minusSign && token.length() > 0
                                && ('e' == token.charAt(token.length() - 1)
                                        || 'E' == token.charAt(token.length() - 1)))
                        || (ch == '+' && token.length() > 0 && ('e' == token.charAt(token.length() - 1)
                                || 'E' == token.charAt(token.length() - 1))))
                        && (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) || firstVarChars.indexOf(ch) >= 0) {
                /*
                The characters (other than letters and digits) allowed as the second or subsequent characters in a variable.
                */
                String varChars = "_";
                while ((Character.isLetter(ch) || Character.isDigit(ch) || varChars.indexOf(ch) >= 0
                        || token.length() == 0 && firstVarChars.indexOf(ch) >= 0) && (pos < input.length())) {
                    token.append(input.charAt(pos++));
                    ch = pos == input.length() ? 0 : input.charAt(pos);
                }
            } else if (ch == '(' || ch == ')' || ch == ',') {
                token.append(ch);
                pos++;
            } else {
                while (!Character.isLetter(ch) && !Character.isDigit(ch) && firstVarChars.indexOf(ch) < 0
                        && !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();
        }

        //@Override
        public void remove() {
            throw new ExpressionException("remove() not supported");
        }

        /**
         * 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;
            }
        }

        /**
         * Get the actual character position in the string.
         *
         * @return The actual character position.
         */
        public int getPos() {
            return pos;
        }

    }

}