/// <summary>
        /// Compute the numeric value of the expression
        /// </summary>
        /// <param name="reducedExpressionElement"></param>
        /// <returns>The value of the expression element</returns>
        public string Compute(ExpressionElement reducedExpressionElement)
        {
            string expression = reducedExpressionElement.ReducedExpression;

            // break up into numbers and operators
            // (a naive alternative to using something like Polish notation
            string[] operators = _expressionConfig.ExtractOperators(expression);
            string[] numbers = _expressionConfig.ExtractNumerands(expression);

            // if 1st op is a leading unary, apply it and discard
            if (_expressionConfig.UnaryOpChars.Contains(expression[0]))
            {
                numbers[0] = _expressionConfig.UnaryOperations[expression[0].ToString()](numbers[0]);
                operators = operators.Skip(1).ToArray();
            }

            // This handles case where input is a simple number
            if (numbers.Length == 1)
                return numbers[0];

            foreach (char[] opersToProcess in _expressionConfig.OperatorsByPrecedence)
            {
                while (operators.Any(t => t.IndexOfAny(opersToProcess) == 0))
                {

                    List<string> remainingOperators = new List<string>();
                    List<string> remainingNums= new List<string>();

                    for (int operIdx = 0; operIdx < operators.Length; operIdx++)
                    {
                        // when we find something to process, mutate arrays and start over ....
                        // TODO Operator processing feels klunky
                        if (operators[operIdx].IndexOfAny(opersToProcess) == 0)
                        {
                            var computedValue = Compute(numbers[operIdx], numbers[operIdx + 1], operators[operIdx]);
                            remainingOperators.AddRange(operators.Skip(operIdx+1));
                            remainingNums.Add(computedValue);
                            remainingNums.AddRange(numbers.Skip(operIdx+2));
                            break;
                        }
                        remainingOperators.Add(operators[operIdx]);
                        remainingNums.Add(numbers[operIdx]);
                    }

                    operators = remainingOperators.ToArray();
                    numbers = remainingNums.ToArray();
                }
            }

            if (numbers.Length != 1 && operators.Length != 0)
                throw new Exception($"Error during compute: {expression}, numbers: {numbers.Length}; operators: {operators.Length}");

            return reducedExpressionElement.Unary.HasValue ?  ComputeUnary(reducedExpressionElement.Unary.ToString(), numbers[0]) : numbers[0];
        }
 // Reduce helpers ...
 public string Evaluate(ExpressionElement reducedExpressionElement)
 {
     return reducedExpressionElement.HadParens ? Compute(reducedExpressionElement) : reducedExpressionElement.ReducedExpression;
 }
        /// <summary>
        /// Recusively decompose an expression into a tree of ExpressionElements
        /// </summary>
        /// <param name="expressionElement"></param>
        /// <param name="level"></param>
        /// <returns>The ExpressionElement with the SubExpressionElements property populated</returns>
        private ExpressionElement Decompose(ExpressionElement expressionElement, int level = 0)
        {
            IEnumerable<ExpressionElement> expressionElementChildren
                    = FindExpressionsAtSameLevel(expressionElement.Expression, level);

                expressionElement.PopulateSubExpressionElements(expressionElementChildren.Select(e => Decompose(e, level + 1)));

                return expressionElement;
        }
        /// <summary>
        /// Recusively process an ExpressionElement to compute its "reduced" value
        /// i.e. an expression with all parenthetical expressions computed
        /// </summary>
        /// <param name="expressionElement"></param>
        /// <returns></returns>
        private ExpressionElement Reduce(ExpressionElement expressionElement)
        {
            if (!expressionElement.HasChildren())
                {
                    expressionElement.ReducedExpression = expressionElement.Expression;
                }
                else
                {
                    ExpressionElement[] itsReducedChildren =
                        expressionElement.SubExpressionElements.Select(Reduce).ToArray();

                    StringBuilder reducedExpression = new StringBuilder();
                    foreach (ExpressionElement reducedChild in itsReducedChildren)
                    {
                        reducedExpression.Append(Evaluate(reducedChild) + reducedChild.JoinOp);
                    }
                    expressionElement.ReducedExpression = reducedExpression.ToString();
                }
                return expressionElement;
        }
        /// <summary>
        /// Produce a processed \ExpressionElement with either its value (if its valid) or with relevant errors if it is not 
        /// </summary>
        /// <param name="minifiedExpression">A validated, compressed expression produced by the IExpressionValidator</param>
        /// <returns>A processed ExpressionElement</returns>
        public ExpressionElement Process(string minifiedExpression)
        {
            ExpressionElement expressionElement = new ExpressionElement(minifiedExpression, 0, 0);

            // catch exceptions here after potentially recursive calls have been allowed to unwind ...
            try
            {
                expressionElement = Decompose(expressionElement);
            }
            catch (Exception e)
            {
                throw new Exception("Processing exception [Decompose]", e);
            }
            try
            {
                expressionElement = Reduce(expressionElement);
            }
            catch (Exception e)
            {
                throw new Exception("Processing exception [Reduce]", e);
            }

            return expressionElement;
        }