/// <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> ReplaceMethodCallsOnMethodsInner(List <FilterExpressionToken> tokens, ref int callCount, out int replacedCount)
        {
            callCount++;
            replacedCount = 0;

            if (callCount > 10 * 1000)
            {
                throw new InternalJsonPathwayException(
                          "Number of calls to ReplaceMethodCallsOnMethodsInner exceeded max expected number of 10000, possible stack overflow or infinite loop.");
            }

            List <FilterExpressionToken> ret = tokens.ToList();

            foreach (MethodCallExpressionToken mc in tokens.Where(x => x is MethodCallExpressionToken))
            {
                int index = ret.IndexOf(mc);

                if (index < ret.Count - 4 && ret[index + 1] is PrimitiveExpressionToken pet1 && pet1.Token.IsSymbolToken('.') &&
                    ret[index + 2] is PrimitiveExpressionToken pet2 && pet2.Token.IsPropertyToken() &&
                    ret[index + 3] is OpenGroupToken)
                {
                    OpenGroupToken        open  = ret[index + 3] as OpenGroupToken;
                    FilterExpressionToken close = ret.Single(x => x is CloseGroupToken cgt && cgt.GroupId == open.GroupId);

                    string methodName = (ret[index + 2] as PrimitiveExpressionToken).Token.CastToPropertyToken().StringValue;

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

                    int closeIndex = ret.IndexOf(close);
                    for (int i = index + 4; i < closeIndex; i++)
                    {
                        args.Add(ret[i]);
                    }
                    args = FormatAndValidateMethodArgumentTokens(args);

                    ret[index] = new MethodCallExpressionToken(mc, methodName, args.ToArray());

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

                    replacedCount++;
                }
            }

            return(ret.Where(x => x != null).ToList());
        }
        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());
        }
        private static List <FilterExpressionToken> ReplaceMethodCallsOnConstants(List <FilterExpressionToken> tokens)
        {
            List <FilterExpressionToken> ret = tokens.ToList();

            foreach (ConstantBaseExpressionToken ct in tokens.Where(x => x is ConstantBaseExpressionToken))
            {
                int index = ret.IndexOf(ct);

                if (index < ret.Count - 4 && ret[index + 1] is PrimitiveExpressionToken pet1 && pet1.Token.IsSymbolToken('.') &&
                    ret[index + 2] is PrimitiveExpressionToken pet2 && pet2.Token.IsPropertyToken() &&
                    ret[index + 3] is OpenGroupToken)
                {
                    OpenGroupToken        open  = ret[index + 3] as OpenGroupToken;
                    FilterExpressionToken close = ret.Single(x => x is CloseGroupToken cgt && cgt.GroupId == open.GroupId);

                    string methodName = (ret[index + 2] as PrimitiveExpressionToken).Token.CastToPropertyToken().StringValue;

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

                    int closeIndex = ret.IndexOf(close);
                    for (int i = index + 4; i < closeIndex; i++)
                    {
                        args.Add(ret[i]);
                    }
                    args = FormatAndValidateMethodArgumentTokens(args);

                    ret[index] = new MethodCallExpressionToken(ct, methodName, args.ToArray());

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

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