public ExpressionEvaluator(string _expression, bool verbose = false)
        {
            this._expression = _expression.Replace(" ", ""); // space is just formatting
            _verbose         = verbose;
            if (_verbose)
            {
                Console.WriteLine($"Expression: {_expression}");
            }

            _operands  = new Operands(this._expression.Length, _verbose);
            _operators = new Operators(this._expression.Length, _verbose);
        }
        public double?Evaluate()
        {
            if (String.IsNullOrWhiteSpace(_expression))
            {
                Console.WriteLine("expression invalid: empty");
                return(null);
            }

            bool pushedOperator = true;

            for (int i = 0; i < _expression.Length; ++i)
            {
                string c = _expression[i].ToString();
                if (_verbose)
                {
                    Console.WriteLine($"Evaluate: {c}");
                }
                if (
                    pushedOperator &&
                    Operators.IsPositiveOp(c) &&
                    i + 1 < _expression.Length
                    ) // plus unary is not needed, skip to next character
                {
                    continue;
                }
                else
                if (
                    pushedOperator &&
                    Operators.IsNegativeOp(c) &&
                    i + 1 < _expression.Length
                    )
                {
                    _operators.ToggleNegation();
                }
                else
                if (
                    Operands.IsEulerConst(c)
                    )
                {
                    pushedOperator = false;
                    _operands.Push(Math.E);
                }
                else
                if (
                    Operands.IsPiConst(c)
                    )
                {
                    pushedOperator = false;
                    _operands.Push(Math.PI);
                }
                else
                if (
                    Char.GetNumericValue(c[0]) != -1.0 ||
                    c == "."
                    )  // this character represents the beginning of a number
                {
                    var parsedNum = false;

                    for (var j = _expression.Length - i; j > 0; --j)   // try to find the end of the number by starting to parse the rest of the expression as a number, shorten the attempted parsed substring until success
                    {
                        if (
                            !(parsedNum = double.TryParse(_expression.Substring(i, j), out var num))
                            )
                        {
                            continue;
                        }

                        // we found the longest possible number (e.g. 12 instead of 1)
                        i += j - 1;
                        pushedOperator = false;
                        num            = _operators.MakeNegative() ? -num : num;
                        _operands.Push(num);
                        break;
                    }

                    if (!parsedNum)
                    {
                        return(null);
                    }
                }
                else
                if (
                    pushedOperator &&
                    Operators.IsOpenParenthesisOp(c)
                    )  // binary expression whose second operand is a sub expression, don't treat as implicit multiplication, unless sub expression is negated
                {
                    if (_operators.MakeNegative())
                    {
                        _operands.Push(-1);
                        _operators.Push(Operators.MultiplicationOp());
                    }
                    pushedOperator = EvaluateUntilOperatorPush(c);
                    if (!pushedOperator)
                    {
                        return(null);
                    }
                }
                else
                if (!pushedOperator)                      // character is a binary operator
                {
                    if (Operators.IsOpenParenthesisOp(c)) // treat as implicit multiplication
                    {
                        if (!EvaluateUntilOperatorPush(Operators.MultiplicationOp()))
                        {
                            return(null);
                        }

                        pushedOperator = EvaluateUntilOperatorPush(c);
                        if (!pushedOperator)
                        {
                            return(null);
                        }
                    }
                    else
                    if (
                        Operators.IsBinaryOp(c)
                        )
                    {
                        pushedOperator = EvaluateUntilOperatorPush(c);
                        if (!pushedOperator)
                        {
                            return(null);
                        }
                    }
                    else
                    if (
                        Operators.IsCloseParenthesisOp(c)
                        )
                    {
                        pushedOperator = false;
                        if (!EvaluateUntilOpenParenthesis())
                        {
                            return(null);
                        }
                    }
                    else
                    {
                        Console.WriteLine($"character {c} is not a valid binary operator");
                        return(null);
                    }
                }
                else
                {
                    Console.WriteLine($"character {c} is not a valid operand or valid unary operator");
                    return(null);
                }
            }

            var done = EvaluateUntilDone();

            if (!done)      // there was error evaluating the expression
            {
                return(null);
            }

            while (_operands.CanPerformBinaryOperation())      // reduce operand stack to a single value by implicitly multiplying stack items together
            {
                _operands.Push(Multiplication());
            }

            return(_operands.Peek());
        }