private static bool IsRaisedPrecedenceOperator(ExpressionParserNode current, string @operator)
        {
            var hasPreceedingLower = current.Operators?.Count > 0 &&
                                     !OperatorsWithRaisedPrecedence.Contains(current.Operators[current.Operators.Count - 1].Symbol);

            return(hasPreceedingLower && OperatorsWithRaisedPrecedence.Contains(@operator));
        }
Esempio n. 2
0
        internal void AddChild(ExpressionParserNode node)
        {
            if (_childNodes == null)
            {
                _childNodes = new List <ExpressionParserNode>();
            }

            _childNodes.Add(node);
        }
        private static SyntaxTreeNode ConvertTree(ExpressionParserNode parsingNode)
        {
            List <SyntaxTreeNode> childNodes = new List <SyntaxTreeNode>();

            if (parsingNode.ChildNodes != null)
            {
                foreach (var childNode in parsingNode.ChildNodes)
                {
                    childNodes.Add(ConvertTree(childNode));
                }
            }

            return(new SyntaxTreeNode(
                       type: parsingNode.Type,
                       value: parsingNode.Value,
                       isNegated: parsingNode.IsNegative,
                       operators: parsingNode.Operators,
                       childNodes: childNodes.AsReadOnly()));
        }
        public static SyntaxTreeNode Parse(string expression, ParseOptions?options = null)
        {
            options ??= new ParseOptions();

            if (string.IsNullOrWhiteSpace(expression))
            {
                return(new SyntaxTreeNode(
                           NodeType.Scope,
                           string.Empty,
                           false,
                           new List <SyntaxTreeNode>().AsReadOnly(),
                           new List <Operator>().AsReadOnly()));
            }

            var  stack      = new Stack <ExpressionParserNode>();
            var  chars      = expression.ToCharArray();
            var  startIndex = 0;
            var  index      = 0;
            var  negated    = false;
            char currentChar;

            void popAndPush()
            {
                var popped = stack.Pop();

                stack.Peek().AddChild(popped);
            }

            void closeFunctionParameter()
            {
                if (stack.Peek().ChildNodes.Count == 1)
                {
                    //Unwrap single parameter container context
                    var container = stack.Pop();

                    //These containers can never be negative
                    stack.Peek().AddChild(container.ChildNodes[0]);
                }
                else
                {
                    //Close off parameter container context
                    popAndPush();
                }
            }

            void readWhile(Func <bool> predicate)
            {
                startIndex = index;

                while (index < chars.Length && predicate())
                {
                    index++;
                }
                ;
            }

            //Create base context
            stack.Push(new ExpressionParserNode {
                Type = NodeType.Scope
            });

            while (true)
            {
                currentChar = chars[index];

                switch (currentChar)
                {
                case OpenParen:
                    //Create a new empty context
                    stack.Push(new ExpressionParserNode
                    {
                        Type          = NodeType.Scope,
                        IsParenthesis = true,
                        IsNegative    = negated
                    });
                    negated = false;
                    index++;
                    break;

                case CloseParen:
                    if (stack.Peek().IsRaisedPrecedence)
                    {
                        popAndPush();     //Close off raised precedence context
                        popAndPush();     //Close off parenthesis context
                    }
                    else if (stack.Peek().IsFunction&& !stack.Peek().IsParenthesis)
                    {
                        if (stack.Peek().ChildNodes == null)
                        {
                            stack.Pop();     //Discard the empty function parameter container context
                        }
                        else
                        {
                            closeFunctionParameter();
                        }

                        popAndPush();     //Close off function context
                    }
                    else
                    {
                        if (stack.Peek().ChildNodes.Count == 1)
                        {
                            //Unwrap single node parenthesis generated context
                            var container = stack.Pop();
                            container.ChildNodes[0].IsNegative = container.IsNegative;
                            stack.Peek().AddChild(container.ChildNodes[0]);
                        }
                        else
                        {
                            popAndPush();     //Close off parenthesis generated context
                        }
                    }

                    index++;
                    break;

                case ArgumentSeparator:

                    closeFunctionParameter();

                    //Container context for parameter expressions, will be removed if unused
                    stack.Push(new ExpressionParserNode
                    {
                        Type       = NodeType.Scope,
                        IsFunction = true
                                     //Not possible for container context to be negative
                    });

                    index++;
                    break;

                case NegativeToken:
                    //Same number of operators as nodes; treat additional '-' as unary -ve
                    if (stack.Peek().Operators?.Count == stack.Peek().ChildNodes?.Count)
                    {
                        negated = !negated;
                    }
                    else     //Treat as operator
                    {
                        //Close the raised precedence context
                        if (stack.Peek().IsRaisedPrecedence)
                        {
                            popAndPush();
                        }

                        //Append operator onto current context
                        stack.Peek().AddOperator(new Operator(NegativeTokenStr));
                    }
                    index++;
                    break;

                default:
                    if (IsOperatorCharacter(currentChar))
                    {
                        readWhile(() => IsOperatorCharacter(chars[index]));
                        string @operator = SubstringFromCharArray(chars, startIndex, index - startIndex);

                        //Close the raised precedence context
                        if (!OperatorsWithRaisedPrecedence.Contains(@operator) &&
                            stack.Peek().IsRaisedPrecedence)
                        {
                            popAndPush();
                        }

                        //Should the operator have raised precedence
                        if (IsRaisedPrecedenceOperator(stack.Peek(), @operator))
                        {
                            var raisedPrecedence = new ExpressionParserNode
                            {
                                Type = NodeType.Scope,
                                IsRaisedPrecedence = true
                                                     //Not possible for generated context to be negative
                            };

                            //Pull previous node into raised context
                            raisedPrecedence.AddChild(stack.Peek().PopChild());
                            raisedPrecedence.AddOperator(new Operator(@operator));
                            stack.Push(raisedPrecedence);
                        }
                        else
                        {
                            //Append operator onto current context
                            stack.Peek().AddOperator(new Operator(@operator));
                        }
                    }
                    else if (char.IsNumber(currentChar))
                    {
                        readWhile(() => char.IsNumber(chars[index]) || chars[index] == options.DecimalMarker);
                        stack.Peek().AddChild(new ExpressionParserNode
                        {
                            Value      = SubstringFromCharArray(chars, startIndex, index - startIndex),
                            Type       = NodeType.Constant,
                            IsNegative = negated
                        });

                        negated = false;
                    }
                    else if (char.IsWhiteSpace(currentChar))
                    {
                        //Skip whitespace
                        index++;
                    }
                    else
                    {
                        //If we get to this branch we can assume this is the start of a function or variable
                        readWhile(() => IsParameterCharacter(chars[index]));

                        if (index < chars.Length && chars[index] == OpenParen)
                        {
                            //Create a new context for the function
                            stack.Push(new ExpressionParserNode
                            {
                                Value      = SubstringFromCharArray(chars, startIndex, index - startIndex),
                                Type       = NodeType.Function,
                                IsNegative = negated,
                                IsFunction = true
                            });

                            //Container context for parameter expressions, will be removed if unused
                            stack.Push(new ExpressionParserNode
                            {
                                Type       = NodeType.Scope,
                                IsFunction = true
                                             //Not possible for container context to be negative
                            });

                            negated = false;
                            index++;
                        }
                        else
                        {
                            //Variable
                            stack.Peek().AddChild(new ExpressionParserNode
                            {
                                Value      = SubstringFromCharArray(chars, startIndex, index - startIndex),
                                Type       = NodeType.Variable,
                                IsNegative = negated
                            });

                            negated = false;
                            break;
                        }
                    }
                    break;
                }

                //Check for end of expression
                if (index >= chars.Length)
                {
                    while (stack.Count > 1)
                    {
                        popAndPush();
                    }

                    var rootNode = stack.Pop();

                    return(ConvertTree(rootNode));
                }
            }
        }