/// <summary>
        /// Replaces ( and ) with open and close group tokens
        /// </summary>
        /// <param name="tokens">tokens input</param>
        /// <returns>tokens</returns>
        private static List <FilterExpressionToken> ReplaceGroupTokens(List <FilterExpressionToken> tokens)
        {
            List <SymbolToken> openClosed = tokens.Where(x => x is PrimitiveExpressionToken pet && (pet.Token.IsSymbolToken('(') || pet.Token.IsSymbolToken(')')))
                                            .Cast <PrimitiveExpressionToken>()
                                            .Select(x => x.Token.CastToSymbolToken())
                                            .ToList();

            if (openClosed.Any())
            {
                if (openClosed.First().IsSymbolToken(')'))
                {
                    throw new UnexpectedTokenException(openClosed.First());
                }
                if (openClosed.Last().IsSymbolToken('('))
                {
                    throw new UnexpectedTokenException(openClosed.Last());
                }

                int openCount   = openClosed.Count(x => x.IsSymbolToken('('));
                int closedCount = openClosed.Count(x => x.IsSymbolToken(')'));

                if (openCount > closedCount)
                {
                    throw new UnexpectedTokenException("Not all ( are closed with )");
                }
                if (openCount < closedCount)
                {
                    throw new UnexpectedTokenException("Not all ) have matching (");
                }
            }
            else
            {
                return(tokens);
            }

            List <FilterExpressionToken> ret = tokens.ToList();
            Stack <int> groupIds             = new Stack <int>();

            for (int i = 0; i < ret.Count; i++)
            {
                if (ret[i] is PrimitiveExpressionToken pet)
                {
                    if (pet.Token.IsSymbolToken('('))
                    {
                        groupIds.Push(i);
                        ret[i] = new OpenGroupToken(pet.Token.CastToSymbolToken(), i, groupIds.Count);
                    }
                    else if (pet.Token.IsSymbolToken(')'))
                    {
                        ret[i] = new CloseGroupToken(pet.Token.CastToSymbolToken(), groupIds.Pop());
                    }
                }
            }

            return(ret);
        }
        private static List <FilterExpressionToken> ReplaceMethodCallsOnProps(List <FilterExpressionToken> tokens)
        {
            List <PropertyExpressionToken> propTokens = tokens.Where(x => x is PropertyExpressionToken).Cast <PropertyExpressionToken>().ToList();
            List <FilterExpressionToken>   ret        = tokens.ToList();

            foreach (PropertyExpressionToken pt in propTokens)
            {
                int nextIndex = ret.IndexOf(pt) + 1;
                if (nextIndex < ret.Count && ret[nextIndex] is OpenGroupToken)
                {
                    OpenGroupToken  open  = ret[nextIndex] as OpenGroupToken;
                    CloseGroupToken close = ret.First(x => x is CloseGroupToken cgt && cgt.GroupId == open.GroupId) as CloseGroupToken;

                    int openIndex  = ret.IndexOf(open);
                    int closeIndex = ret.IndexOf(close);

                    List <FilterExpressionToken> args = new List <FilterExpressionToken>();
                    for (int i = openIndex + 1; i < closeIndex; i++)
                    {
                        args.Add(ret[i]);
                    }
                    args = FormatAndValidateMethodArgumentTokens(args);

                    string methodName;
                    PropertyExpressionToken allButLast = pt.AllButLast(out methodName);
                    int ptIndex = ret.IndexOf(pt);

                    ret[ptIndex] = new MethodCallExpressionToken(allButLast, methodName, args.ToArray());

                    List <FilterExpressionToken> toRemove = new List <FilterExpressionToken>();

                    for (int i = ptIndex + 1; i <= closeIndex; i++)
                    {
                        ret[i] = null;
                    }
                }
            }

            return(ret.Where(x => x != null).ToList());
        }