/// <summary>
        /// Parses and extracts a numeric value at the current position.
        /// </summary>
        /// <param name="parser">Current parsing helper object.</param>
        /// <returns>The extracted number token string.</returns>
        private Variable ParseNumber(ParsingHelper parser)
        {
            Debug.Assert(IsNumberChar(parser.Peek()));

            bool hasDecimal = false;
            int  start      = parser.Index;

            while (char.IsDigit(parser.Peek()) || parser.Peek() == '.')
            {
                if (parser.Peek() == '.')
                {
                    if (hasDecimal)
                    {
                        throw new ExpressionException(ErrMultipleDecimalPoints, parser.Index);
                    }
                    hasDecimal = true;
                }
                parser++;
            }
            // Extract token
            string token = parser.Extract(start, parser.Index);

            if (token == ".")
            {
                throw new ExpressionException(ErrInvalidOperand, parser.Index - 1);
            }

            if (hasDecimal)
            {
                return(new Variable(double.Parse(token)));
            }
            return(new Variable(int.Parse(token)));
        }
        /// <summary>
        /// Evaluates a function and returns its value. It is assumed the current
        /// position is at the opening parenthesis of the argument list.
        /// </summary>
        /// <param name="parser">ParsingHelper object.</param>
        /// <param name="name">Name of function.</param>
        /// <param name="pos">Position at start of function.</param>
        /// <returns></returns>
        private Variable ParseFunction(ParsingHelper parser, string name, int pos)
        {
            FunctionEventArgs args = new FunctionEventArgs
            {
                Name       = name,
                Parameters = ParseParameters(parser),
                Result     = new Variable(),
                Status     = FunctionStatus.OK,
            };

            if (EvaluateFunction != null)
            {
                EvaluateFunction(this, args);
            }
            else
            {
                args.Status = FunctionStatus.UndefinedFunction;
            }

            if (args.Status == FunctionStatus.UndefinedFunction)
            {
                throw new ExpressionException(string.Format(ErrUndefinedFunction, name), pos);
            }
            if (args.Status == FunctionStatus.WrongParameterCount)
            {
                throw new ExpressionException(ErrWrongParamCount, pos);
            }
            return(args.Result);
        }
        /// <summary>
        /// Evaluates each parameter of a function's parameter list and returns
        /// a list of those values. An empty list is returned if no parameters
        /// were found. It is assumed the current position is at the opening
        /// parenthesis of the argument list.
        /// </summary>
        /// <param name="parser">ParsingHelper object.</param>
        /// <returns>A list of parameter values.</returns>
        private Variable[] ParseParameters(ParsingHelper parser)
        {
            List <Variable> parameters = new();

            // Move past open parenthesis
            parser++;
            parser.SkipWhiteSpace();
            // If function has any parameters
            if (parser.Peek() != ')')
            {
                // Parse function parameter list
                int start      = parser.Index;
                int parenCount = 1;

                while (!parser.EndOfText)
                {
                    if (parser.Peek() == ',')
                    {
                        // Note: Ignore commas inside parentheses. They could be
                        // for a parameter list in a function inside the parameters
                        if (parenCount == 1)
                        {
                            parameters.Add(ParseParameter(parser, start));
                            start = parser.Index + 1;
                        }
                    }
                    if (parser.Peek() == ')')
                    {
                        parenCount--;
                        if (parenCount == 0)
                        {
                            parameters.Add(ParseParameter(parser, start));
                            break;
                        }
                    }
                    else if (parser.Peek() == '(')
                    {
                        parenCount++;
                    }
                    parser++;
                }
            }
            // Make sure we found a closing parenthesis
            if (parser.Peek() != ')')
            {
                throw new ExpressionException(ErrClosingParenExpected, parser.Index);
            }
            // Move past closing parenthesis
#pragma warning disable IDE0059 // Unnecessary assignment of a value
            parser++;
#pragma warning restore IDE0059 // Unnecessary assignment of a value
            // Return parameter list
            return(parameters.ToArray());
        }
 /// <summary>
 /// Extracts and evaluates a function parameter and returns its value. If an
 /// exception occurs, it is caught and the column is adjusted to reflect the
 /// position in original string, and the exception is rethrown.
 /// </summary>
 /// <param name="parser">ParsingHelper object.</param>
 /// <param name="start">Index where current parameter starts.</param>
 /// <returns>The parameter value.</returns>
 private Variable ParseParameter(ParsingHelper parser, int start)
 {
     try
     {
         // Recursively evaluate expression
         string expression = parser.Extract(start, parser.Index);
         return(Evaluate(expression));
     }
     catch (ExpressionException ex)
     {
         // Adjust column and rethrow exception
         ex.Index += start;
         throw ex;
     }
 }
        /// <summary>
        /// Converts a standard infix expression to list of tokens in
        /// postfix order.
        /// </summary>
        /// <param name="expression">Expression to evaluate.</param>
        /// <returns>List of tokens in postfix order.</returns>
        private List <IToken> TokenizeExpression(string expression)
        {
            ParsingHelper  parser     = new ParsingHelper(expression);
            List <IToken>  tokens     = new List <IToken>();
            Stack <IToken> stack      = new Stack <IToken>();
            State          state      = State.None;
            int            parenCount = 0;
            IToken         token;

            while (true)
            {
                parser.SkipWhiteSpace();
                if (parser.EndOfText)
                {
                    break;
                }

                // Get next character
                char c = parser.Peek();
                if (c == '(')
                {
                    // Cannot follow operand
                    if (state == State.Operand)
                    {
                        throw new ExpressionException(ErrOperatorExpected, parser.Index);
                    }
                    // Allow additional unary operators after "("
                    if (state == State.UnaryOperator)
                    {
                        state = State.Operator;
                    }
                    // Push opening parenthesis onto stack
                    stack.Push(new LeftParenthesisToken());
                    // Track number of parentheses
                    parenCount++;
                }
                else if (c == ')')
                {
                    // Must follow operand
                    if (state != State.Operand)
                    {
                        throw new ExpressionException(ErrOperandExpected, parser.Index);
                    }
                    // Must have matching open parenthesis
                    if (parenCount == 0)
                    {
                        throw new ExpressionException(ErrUnmatchedClosingParen, parser.Index);
                    }
                    // Pop all operators until matching "(" found
                    token = stack.Pop();
                    while (token.Type != TokenType.LeftParenthesis)
                    {
                        tokens.Add(token);
                        token = stack.Pop();
                    }
                    // Track number of parentheses
                    parenCount--;
                }
                else if (OperatorToken.GetOperatorInfo(c, out OperatorInfo info))
                {
                    // Need a bit of extra code to support unary operators
                    if (state == State.Operand)
                    {
                        // Pop operators with precedence >= current operator
                        while (stack.Count > 0 && stack.Peek().Precedence >= info.Precedence)
                        {
                            tokens.Add(stack.Pop());
                        }
                        stack.Push(new OperatorToken(info));
                        state = State.Operator;
                    }
                    else if (state == State.UnaryOperator)
                    {
                        // Don't allow two unary operators together
                        throw new ExpressionException(ErrOperandExpected, parser.Index);
                    }
                    else
                    {
                        // Test for unary operator
                        if (c == '-')
                        {
                            // Push unary minus
                            stack.Push(new OperatorToken(OperatorToken.OpNegate));
                            state = State.UnaryOperator;
                        }
                        else if (c == '+')
                        {
                            // Just ignore unary plus
                            state = State.UnaryOperator;
                        }
                        else
                        {
                            throw new ExpressionException(ErrOperandExpected, parser.Index);
                        }
                    }
                }
                else if (IsNumberChar(c))
                {
                    if (state == State.Operand)
                    {
                        // Cannot follow other operand
                        throw new ExpressionException(ErrOperatorExpected, parser.Index);
                    }
                    // Parse number
                    tokens.Add(new OperandToken(ParseNumber(parser)));
                    state = State.Operand;
                    continue;
                }
                else if (IsSymbolFirstChar(c))
                {
                    // Parse symbols and functions
                    if (state == State.Operand)
                    {
                        // Symbol or function cannot follow other operand
                        throw new ExpressionException(ErrOperatorExpected, parser.Index);
                    }

                    // Save start of symbol for error reporting
                    int symbolPos = parser.Index;

                    // Parse this symbol
                    string symbol = parser.ParseWhile(IsSymbolChar);

                    // Skip whitespace
                    parser.SkipWhiteSpace();
                    // Check for parameter list
                    Variable result;
                    if (parser.Peek() == '(')
                    {
                        // Found parameter list, evaluate function
                        result = ParseFunction(parser, symbol, symbolPos);
                    }
                    else
                    {
                        // No parameter list, evaluate symbol (variable)
                        result = ParseSymbol(symbol, symbolPos);
                    }
                    tokens.Add(new OperandToken(result));
                    state = State.Operand;
                    // Don't MoveAhead again
                    continue;
                }
                else if (c == '"' || c == '\'')
                {
                    // String literal
                    string text = parser.ParseQuotedText();
                    tokens.Add(new OperandToken(new Variable(text)));
                    state = State.Operand;
                }
                else
                {
                    // Unrecognized character
                    throw new ExpressionException(ErrUnexpectedCharacter, c, parser.Index);
                }
                parser++;
            }
            // Expression cannot end with operator
            if (state == State.Operator || state == State.UnaryOperator)
            {
                throw new ExpressionException(ErrOperandExpected, parser.Index);
            }
            // Check for balanced parentheses
            if (parenCount > 0)
            {
                throw new ExpressionException(ErrClosingParenExpected, parser.Index);
            }
            // Retrieve remaining operators from stack
            while (stack.Count > 0)
            {
                tokens.Add(stack.Pop());
            }
            return(tokens);
        }