public ParsedExpression Parse(string expression)
        {
            IList<IExpressionElement> tokens = Tokenize(expression);

            if (tokens.Count == 0)
            {
                throw new ArgumentException("Empty expression.");
            }

            List<IExpressionElement> output = new List<IExpressionElement>();
            Stack<IExpressionElement> stack = new Stack<IExpressionElement>();

            Stack<int> argumentsStack = new Stack<int>();
            Stack<bool> insideFunctionStack = new Stack<bool>();
            bool afterFunction = false;
            bool afterFnParenthesis = false;
            bool afterNonFnParenthesis = false;
            bool afterClosingParenthesis = false;
            bool afterSeparator = false;
            bool insideFunction = false;
            int arguments = 0;
            bool mustBeUnary = true;

            int tokenIndex = -1;
            foreach (IExpressionElement token in tokens)
            {
                tokenIndex++;
                IExpressionElement nextToken = tokenIndex + 1 < tokens.Count ? tokens[tokenIndex + 1] : null;
                // If the token is a number, then add it to the output queue.
                if (!afterFunction && !afterClosingParenthesis && (token is ILiteral || token is IConstant || token is IVariable))
                {
                    output.Add(token);
                    mustBeUnary = false;
                    afterClosingParenthesis = afterNonFnParenthesis = afterFnParenthesis = afterSeparator = false;
                }
                // If the token is a function token, then push it onto the stack.
                else if (!afterFunction && !afterClosingParenthesis && (token is IFunction || (token is MultipleElements && (token as MultipleElements).Elements[0] is IFunction)))
                {
                    stack.Push(token);
                    afterFunction = true;
                    afterClosingParenthesis = afterNonFnParenthesis = afterFnParenthesis = afterSeparator = false;
                }
                // If the token is a function argument separator (e.g., a comma):
                // • Until the topmost element of the stack is a left parenthesis, pop the element onto the output queue.
                //   If no left parentheses are encountered, either the separator was misplaced or parentheses were mismatched. 
                else if (!afterFunction && insideFunction && token == Symbol.FunctionArgumentSeparator)
                {
                    if (afterFnParenthesis)
                    {
                        throw new ArgumentException("Missing argument.");
                    }
                    while (stack.Peek() != Symbol.LeftParenthesis)
                    {
                        output.Add(stack.Pop());
                    }
                    if (stack.Peek() != Symbol.LeftParenthesis)
                    {
                        throw new ArgumentException("Either the separator is misplaced or parentheses are mismatched.");
                    }
                    arguments++;
                    afterClosingParenthesis = afterNonFnParenthesis = afterFnParenthesis = false;
                    afterSeparator = true;
                }
                // If the token is an operator, o1, then:
                // • while there is an operator, o2, at the top of the stack, and either
                //       o1 is associative or left-associative and its precedence is less than (lower precedence) or equal to that of o2, or
                //       o1 is right-associative and its precedence is less than (lower precedence) that of o2,
                //     pop o2 off the stack, onto the output queue;
                // • push o1 onto the stack. 
                else if (!afterFunction && (token is IOperator || (token is MultipleElements && (token as MultipleElements).Elements[0] is IOperator)))
                {
                    IOperator op = null;
                    if ((mustBeUnary && token is IUnaryOperator) || (!mustBeUnary && token is IBinaryOperator))
                    {
                        op = token as IOperator;
                    }
                    else if (token is MultipleElements)
                    {
                        foreach (IExpressionElement elem in (token as MultipleElements).Elements)
                        {
                            if ((mustBeUnary && elem is IUnaryOperator) || (!mustBeUnary && elem is IBinaryOperator))
                            {
                                op = elem as IOperator;
                                break;
                            }
                        }
                    }
                    if (op == null)
                    {
                        throw new ArgumentException(string.Format("{0} is not {1} operator.", token.Name, mustBeUnary ? "an unary" : "a binary"));
                    }
                    IOperator sop = stack.Count > 0 ? stack.Peek() as IOperator : null;
                    while (sop != null && (
                        (
                            op is IBinaryOperator &&
                            (op as IBinaryOperator).Associativity != OperatorAssociativity.RightAssociative &&
                            op.Precedence <= sop.Precedence
                        )
                        ||
                        (
                            (
                                (op is IBinaryOperator && (op as IBinaryOperator).Associativity == OperatorAssociativity.RightAssociative) ||
                                op is IUnaryOperator
                            )
                            &&
                            op.Precedence < sop.Precedence
                        )))
                    {
                        output.Add(stack.Pop());
                        sop = stack.Count > 0 ? stack.Peek() as IOperator : null;
                    }

                    stack.Push(op);
                    mustBeUnary = true;
                    afterClosingParenthesis = afterFnParenthesis = afterNonFnParenthesis = afterSeparator = false;
                }
                // If the token is a left parenthesis, then push it onto the stack.
                else if (!afterClosingParenthesis && token == Symbol.LeftParenthesis)
                {
                    stack.Push(token);
                    argumentsStack.Push(arguments);
                    insideFunctionStack.Push(insideFunction);
                    // initial argument count
                    arguments = nextToken == Symbol.RightParenthesis ? 0 : 1;
                    if (afterFunction)
                    {
                        afterNonFnParenthesis = afterFunction = false;
                        insideFunction = afterFnParenthesis = true;
                    }
                    else
                    {
                        afterNonFnParenthesis = true;
                        insideFunction = afterFnParenthesis = false;
                    }
                    mustBeUnary = true;
                    afterClosingParenthesis = afterSeparator = false;
                }
                // If the token is a right parenthesis:
                // • Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue.
                // • Pop the left parenthesis from the stack, but not onto the output queue.
                // • If the token at the top of the stack is a function token, pop it and onto the output queue.
                // • If the stack runs out without finding a left parenthesis, then there are mismatched parentheses.
                else if (!afterFunction && !afterSeparator && token == Symbol.RightParenthesis)
                {
                    if (afterNonFnParenthesis)
                    {
                        throw new ArgumentException("Unexpected token: '()'.");
                    }
                    if (stack.Count == 0)
                    {
                        throw new ArgumentException("There are mismatched parentheses.");
                    }
                    while (stack.Peek() != Symbol.LeftParenthesis)
                    {
                        output.Add(stack.Pop());
                        if (stack.Count == 0)
                        {
                            throw new ArgumentException("There are mismatched parentheses.");
                        }
                    }
                    IExpressionElement parenthesis = stack.Pop(); // parenthesis
                    if (stack.Count > 0)
                    {
                        IExpressionElement elem = stack.Peek();
                        if (elem is IFunction || (elem is MultipleElements && (elem as MultipleElements).Elements[0] is IFunction))
                        {
                            elem = stack.Pop();
                            IExpressionElement func = null;
                            if (elem is IFunction)
                            {
                                if ((elem as IFunction).ParametersCount != arguments)
                                {
                                    UndefinedFunctionFoundEventArgs args = new UndefinedFunctionFoundEventArgs(elem.Name, arguments);
                                    if (UndefinedFunctionFound != null)
                                    {
                                        UndefinedFunctionFound(this, args);
                                    }
                                    if (!args.Handled)
                                    {
                                        throw new ArgumentException(string.Format("There is no function ‘{0}’ with {1} parameters defined.", elem.Name, arguments));
                                    }
                                    elem = new UnknownFunction(elem.Name);
                                    (elem as UnknownFunction).ParametersCount = arguments;
                                }
                                func = elem;
                            }
                            else
                            {
                                foreach (IFunction f in (elem as MultipleElements).Elements)
                                {
                                    if (f.ParametersCount == arguments)
                                    {
                                        func = f;
                                        break;
                                    }
                                }
                                if (func == null)
                                {
                                    UndefinedFunctionFoundEventArgs args = new UndefinedFunctionFoundEventArgs((elem as MultipleElements).Elements[0].Name, arguments);
                                    if (UndefinedFunctionFound != null)
                                    {
                                        UndefinedFunctionFound(this, args);
                                    }
                                    if (!args.Handled)
                                    {
                                        throw new ArgumentException(string.Format("There is no function ‘{0}’ with {1} parameters defined.", args.Name, arguments));
                                    }
                                    func = new UnknownFunction(args.Name);
                                    (func as UnknownFunction).ParametersCount = arguments;
                                }
                            }
                            output.Add(func);
                        }
                    }
                    arguments = argumentsStack.Count > 0 ? argumentsStack.Pop() : 0;
                    insideFunction = insideFunctionStack.Pop();
                    afterFnParenthesis = afterNonFnParenthesis = mustBeUnary = afterSeparator = false;
                    afterClosingParenthesis = true;
                }
                else
                {
                    IExpressionElement elem = token;
                    if (token is MultipleElements)
                    {
                        elem = (token as MultipleElements).Elements[0];
                    }
                    throw new ArgumentException(string.Format("Unexpected token: ‘{0}’", elem.Name));
                }
            }
            // When there are no more tokens to read:
            // • While there are still operator tokens in the stack:
            //   • If the operator token on the top of the stack is a parenthesis, then there are mismatched parentheses.
            //   • Pop the operator onto the output queue.
            while (stack.Count > 0)
            {
                if (stack.Peek() == Symbol.LeftParenthesis || stack.Peek() == Symbol.RightParenthesis)
                {
                    var r = stack.Peek();
                    throw new ArgumentException("There are mismatched parentheses.");
                }
                output.Add(stack.Pop());
            }
            return new ParsedExpression(output, ZeroOnError);
        }
Example #2
0
        public ParsedExpression Parse(string expression)
        {
            IList <IExpressionElement> tokens = Tokenize(expression);

            if (tokens.Count == 0)
            {
                throw new ArgumentException("Empty expression.");
            }

            List <IExpressionElement>  output = new List <IExpressionElement>();
            Stack <IExpressionElement> stack  = new Stack <IExpressionElement>();

            Stack <int>  argumentsStack          = new Stack <int>();
            Stack <bool> insideFunctionStack     = new Stack <bool>();
            bool         afterFunction           = false;
            bool         afterFnParenthesis      = false;
            bool         afterNonFnParenthesis   = false;
            bool         afterClosingParenthesis = false;
            bool         afterSeparator          = false;
            bool         insideFunction          = false;
            int          arguments   = 0;
            bool         mustBeUnary = true;

            int tokenIndex = -1;

            foreach (IExpressionElement token in tokens)
            {
                tokenIndex++;
                IExpressionElement nextToken = tokenIndex + 1 < tokens.Count ? tokens[tokenIndex + 1] : null;
                // If the token is a number, then add it to the output queue.
                if (!afterFunction && !afterClosingParenthesis && (token is ILiteral || token is IConstant || token is IVariable))
                {
                    output.Add(token);
                    mustBeUnary             = false;
                    afterClosingParenthesis = afterNonFnParenthesis = afterFnParenthesis = afterSeparator = false;
                }
                // If the token is a function token, then push it onto the stack.
                else if (!afterFunction && !afterClosingParenthesis && (token is IFunction || (token is MultipleElements && (token as MultipleElements).Elements[0] is IFunction)))
                {
                    stack.Push(token);
                    afterFunction           = true;
                    afterClosingParenthesis = afterNonFnParenthesis = afterFnParenthesis = afterSeparator = false;
                }
                // If the token is a function argument separator (e.g., a comma):
                // • Until the topmost element of the stack is a left parenthesis, pop the element onto the output queue.
                //   If no left parentheses are encountered, either the separator was misplaced or parentheses were mismatched.
                else if (!afterFunction && insideFunction && token == Symbol.FunctionArgumentSeparator)
                {
                    if (afterFnParenthesis)
                    {
                        throw new ArgumentException("Missing argument.");
                    }
                    while (stack.Peek() != Symbol.LeftParenthesis)
                    {
                        output.Add(stack.Pop());
                    }
                    if (stack.Peek() != Symbol.LeftParenthesis)
                    {
                        throw new ArgumentException("Either the separator is misplaced or parentheses are mismatched.");
                    }
                    arguments++;
                    afterClosingParenthesis = afterNonFnParenthesis = afterFnParenthesis = false;
                    afterSeparator          = true;
                }
                // If the token is an operator, o1, then:
                // • while there is an operator, o2, at the top of the stack, and either
                //       o1 is associative or left-associative and its precedence is less than (lower precedence) or equal to that of o2, or
                //       o1 is right-associative and its precedence is less than (lower precedence) that of o2,
                //     pop o2 off the stack, onto the output queue;
                // • push o1 onto the stack.
                else if (!afterFunction && (token is IOperator || (token is MultipleElements && (token as MultipleElements).Elements[0] is IOperator)))
                {
                    IOperator op = null;
                    if ((mustBeUnary && token is IUnaryOperator) || (!mustBeUnary && token is IBinaryOperator))
                    {
                        op = token as IOperator;
                    }
                    else if (token is MultipleElements)
                    {
                        foreach (IExpressionElement elem in (token as MultipleElements).Elements)
                        {
                            if ((mustBeUnary && elem is IUnaryOperator) || (!mustBeUnary && elem is IBinaryOperator))
                            {
                                op = elem as IOperator;
                                break;
                            }
                        }
                    }
                    if (op == null)
                    {
                        throw new ArgumentException(string.Format("{0} is not {1} operator.", token.Name, mustBeUnary ? "an unary" : "a binary"));
                    }
                    IOperator sop = stack.Count > 0 ? stack.Peek() as IOperator : null;
                    while (sop != null && (
                               (
                                   op is IBinaryOperator &&
                                   (op as IBinaryOperator).Associativity != OperatorAssociativity.RightAssociative &&
                                   op.Precedence <= sop.Precedence
                               )
                               ||
                               (
                                   (
                                       (op is IBinaryOperator && (op as IBinaryOperator).Associativity == OperatorAssociativity.RightAssociative) ||
                                       op is IUnaryOperator
                                   )
                                   &&
                                   op.Precedence < sop.Precedence
                               )))
                    {
                        output.Add(stack.Pop());
                        sop = stack.Count > 0 ? stack.Peek() as IOperator : null;
                    }

                    stack.Push(op);
                    mustBeUnary             = true;
                    afterClosingParenthesis = afterFnParenthesis = afterNonFnParenthesis = afterSeparator = false;
                }
                // If the token is a left parenthesis, then push it onto the stack.
                else if (!afterClosingParenthesis && token == Symbol.LeftParenthesis)
                {
                    stack.Push(token);
                    argumentsStack.Push(arguments);
                    insideFunctionStack.Push(insideFunction);
                    // initial argument count
                    arguments = nextToken == Symbol.RightParenthesis ? 0 : 1;
                    if (afterFunction)
                    {
                        afterNonFnParenthesis = afterFunction = false;
                        insideFunction        = afterFnParenthesis = true;
                    }
                    else
                    {
                        afterNonFnParenthesis = true;
                        insideFunction        = afterFnParenthesis = false;
                    }
                    mustBeUnary             = true;
                    afterClosingParenthesis = afterSeparator = false;
                }
                // If the token is a right parenthesis:
                // • Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue.
                // • Pop the left parenthesis from the stack, but not onto the output queue.
                // • If the token at the top of the stack is a function token, pop it and onto the output queue.
                // • If the stack runs out without finding a left parenthesis, then there are mismatched parentheses.
                else if (!afterFunction && !afterSeparator && token == Symbol.RightParenthesis)
                {
                    if (afterNonFnParenthesis)
                    {
                        throw new ArgumentException("Unexpected token: '()'.");
                    }
                    if (stack.Count == 0)
                    {
                        throw new ArgumentException("There are mismatched parentheses.");
                    }
                    while (stack.Peek() != Symbol.LeftParenthesis)
                    {
                        output.Add(stack.Pop());
                        if (stack.Count == 0)
                        {
                            throw new ArgumentException("There are mismatched parentheses.");
                        }
                    }
                    IExpressionElement parenthesis = stack.Pop(); // parenthesis
                    if (stack.Count > 0)
                    {
                        IExpressionElement elem = stack.Peek();
                        if (elem is IFunction || (elem is MultipleElements && (elem as MultipleElements).Elements[0] is IFunction))
                        {
                            elem = stack.Pop();
                            IExpressionElement func = null;
                            if (elem is IFunction)
                            {
                                if ((elem as IFunction).ParametersCount != arguments)
                                {
                                    UndefinedFunctionFoundEventArgs args = new UndefinedFunctionFoundEventArgs(elem.Name, arguments);
                                    if (UndefinedFunctionFound != null)
                                    {
                                        UndefinedFunctionFound(this, args);
                                    }
                                    if (!args.Handled)
                                    {
                                        throw new ArgumentException(string.Format("There is no function ‘{0}’ with {1} parameters defined.", elem.Name, arguments));
                                    }
                                    elem = new UnknownFunction(elem.Name);
                                    (elem as UnknownFunction).ParametersCount = arguments;
                                }
                                func = elem;
                            }
                            else
                            {
                                foreach (IFunction f in (elem as MultipleElements).Elements)
                                {
                                    if (f.ParametersCount == arguments)
                                    {
                                        func = f;
                                        break;
                                    }
                                }
                                if (func == null)
                                {
                                    UndefinedFunctionFoundEventArgs args = new UndefinedFunctionFoundEventArgs((elem as MultipleElements).Elements[0].Name, arguments);
                                    if (UndefinedFunctionFound != null)
                                    {
                                        UndefinedFunctionFound(this, args);
                                    }
                                    if (!args.Handled)
                                    {
                                        throw new ArgumentException(string.Format("There is no function ‘{0}’ with {1} parameters defined.", args.Name, arguments));
                                    }
                                    func = new UnknownFunction(args.Name);
                                    (func as UnknownFunction).ParametersCount = arguments;
                                }
                            }
                            output.Add(func);
                        }
                    }
                    arguments               = argumentsStack.Count > 0 ? argumentsStack.Pop() : 0;
                    insideFunction          = insideFunctionStack.Pop();
                    afterFnParenthesis      = afterNonFnParenthesis = mustBeUnary = afterSeparator = false;
                    afterClosingParenthesis = true;
                }
                else
                {
                    IExpressionElement elem = token;
                    if (token is MultipleElements)
                    {
                        elem = (token as MultipleElements).Elements[0];
                    }
                    throw new ArgumentException(string.Format("Unexpected token: ‘{0}’", elem.Name));
                }
            }
            // When there are no more tokens to read:
            // • While there are still operator tokens in the stack:
            //   • If the operator token on the top of the stack is a parenthesis, then there are mismatched parentheses.
            //   • Pop the operator onto the output queue.
            while (stack.Count > 0)
            {
                if (stack.Peek() == Symbol.LeftParenthesis || stack.Peek() == Symbol.RightParenthesis)
                {
                    var r = stack.Peek();
                    throw new ArgumentException("There are mismatched parentheses.");
                }
                output.Add(stack.Pop());
            }
            return(new ParsedExpression(output, ZeroOnError));
        }