/// <summary>
        /// Parses an object path expression from a string.
        /// </summary>
        /// <param name="expression">The expression text to parse</param>
        /// <returns>The parsed expression</returns>
        public static ObjectPathExpression Parse(string expression)
        {
            if (expression == null)
            {
                throw new ArgumentNullException("path cannot be null");
            }
            if (expression.Length == 0)
            {
                throw new FormatException("Cannot parse empty string");
            }

            Tokenizer <ObjectPathToken> tokenizer = new Tokenizer <ObjectPathToken>();

            tokenizer.TokenFactory        = ObjectPathToken.TokenFactoryImpl;
            tokenizer.WhitespaceBehavior  = WhitespaceBehavior.DelimitAndInclude;
            tokenizer.DoubleQuoteBehavior = DoubleQuoteBehavior.IncludeQuotedTokensAsStringLiterals;
            tokenizer.Delimiters.Add("[");
            tokenizer.Delimiters.Add("]");
            tokenizer.Delimiters.Add(".");
            List <ObjectPathToken> tokens = tokenizer.Tokenize(expression);

            TokenReader <ObjectPathToken> reader = new TokenReader <ObjectPathToken>(tokens);

            List <IObjectPathElement> pathElements = new List <IObjectPathElement>();

            bool lastTokenWasNavigation = false;

            while (reader.CanAdvance(skipWhitespace: true))
            {
                var currentToken = reader.Advance(skipWhitespace: true);

                if (lastTokenWasNavigation == true && currentToken.TokenType == ObjectPathTokenType.IndexerOpen)
                {
                    throw new FormatException("Expected property, got '['" + " at " + currentToken.Position);
                }

                lastTokenWasNavigation = false;

                if (pathElements.Count == 0 && currentToken.TokenType == ObjectPathTokenType.NavigationElement)
                {
                    throw new FormatException("Expected property or index, got '" + currentToken.Value + "'" + " at " + currentToken.Position);
                }

                if (currentToken.TokenType == ObjectPathTokenType.IndexerClose ||
                    currentToken.TokenType == ObjectPathTokenType.StringLiteral)
                {
                    throw new FormatException("Expected property or index, got '" + currentToken.Value + "'" + " at " + currentToken.Position);
                }

                if (currentToken.TokenType == ObjectPathTokenType.IndexerOpen)
                {
                    // read index value
                    if (reader.TryAdvance(out currentToken, skipWhitespace: true) == false)
                    {
                        throw new FormatException("Expected index value, got end of string");
                    }

                    if (currentToken.TokenType == ObjectPathTokenType.Identifier || currentToken.TokenType == ObjectPathTokenType.StringLiteral)
                    {
                        string indexValueText = currentToken.Value;

                        if (currentToken.TokenType == ObjectPathTokenType.StringLiteral)
                        {
                            indexValueText = indexValueText.Substring(1, indexValueText.Length - 2);
                        }

                        object indexValue;
                        int    indexValueInt;
                        if (int.TryParse(indexValueText, out indexValueInt) == false)
                        {
                            indexValue = indexValueText;
                        }
                        else
                        {
                            indexValue = indexValueInt;
                        }

                        // read index close
                        if (reader.TryAdvance(out currentToken, skipWhitespace: true) == false)
                        {
                            throw new FormatException("Expected ']', got end of string");
                        }
                        if (currentToken.TokenType != ObjectPathTokenType.IndexerClose)
                        {
                            throw new FormatException("Expected ']', got '" + currentToken.Value + "' at " + currentToken.Position);
                        }

                        IndexerPathElement el = new IndexerPathElement(indexValue);
                        pathElements.Add(el);

                        if (reader.TryAdvance(out currentToken, skipWhitespace: true))
                        {
                            if (currentToken.TokenType != ObjectPathTokenType.NavigationElement)
                            {
                                throw new FormatException("Expected '.', got '" + currentToken.Value + "' at " + currentToken.Position);
                            }
                            if (reader.CanAdvance(skipWhitespace: true) == false)
                            {
                                throw new FormatException("Expected property, got end of string");
                            }
                            lastTokenWasNavigation = true;
                        }
                    }
                    else
                    {
                        throw new ArgumentException("Unexpected token '" + currentToken.Value + "' at " + currentToken.Position);
                    }
                }
                else if (currentToken.TokenType == ObjectPathTokenType.Identifier)
                {
                    PropertyPathElement el = new PropertyPathElement(currentToken.Value);
                    pathElements.Add(el);
                }
                else if (currentToken.TokenType == ObjectPathTokenType.NavigationElement)
                {
                    // do nothing
                }
                else
                {
                    throw new ArgumentException("Unexpected token '" + currentToken.Value + "' at " + currentToken.Position);
                }
            }

            return(new ObjectPathExpression(pathElements));
        }
        /// <summary>
        /// Parses an object path expression from a string.
        /// </summary>
        /// <param name="expression">The expression text to parse</param>
        /// <returns>The parsed expression</returns>
        public static ObjectPathExpression Parse(string expression)
        {
            if (expression == null) throw new ArgumentNullException("path cannot be null");
            if (expression.Length == 0) throw new FormatException("Cannot parse empty string");

            Tokenizer<ObjectPathToken> tokenizer = new Tokenizer<ObjectPathToken>();
            tokenizer.TokenFactory = ObjectPathToken.TokenFactoryImpl;
            tokenizer.WhitespaceBehavior = WhitespaceBehavior.DelimitAndInclude;
            tokenizer.DoubleQuoteBehavior = DoubleQuoteBehavior.IncludeQuotedTokensAsStringLiterals;
            tokenizer.Delimiters.Add("[");
            tokenizer.Delimiters.Add("]");
            tokenizer.Delimiters.Add(".");
            List<ObjectPathToken> tokens = tokenizer.Tokenize(expression);

            TokenReader<ObjectPathToken> reader = new TokenReader<ObjectPathToken>(tokens);

            List<IObjectPathElement> pathElements = new List<IObjectPathElement>();

            bool lastTokenWasNavigation = false;
            while (reader.CanAdvance(skipWhitespace: true))
            {
                var currentToken = reader.Advance(skipWhitespace: true);

                if (lastTokenWasNavigation == true && currentToken.TokenType == ObjectPathTokenType.IndexerOpen)
                {
                    throw new FormatException("Expected property, got '['" + " at " + currentToken.Position);
                }

                lastTokenWasNavigation = false;

                if(pathElements.Count == 0 && currentToken.TokenType == ObjectPathTokenType.NavigationElement)
                {
                    throw new FormatException("Expected property or index, got '" + currentToken.Value + "'" + " at " + currentToken.Position);
                }

                if (currentToken.TokenType == ObjectPathTokenType.IndexerClose ||
                    currentToken.TokenType == ObjectPathTokenType.StringLiteral)
                {
                    throw new FormatException("Expected property or index, got '" + currentToken.Value + "'" + " at " + currentToken.Position);
                }

                if (currentToken.TokenType == ObjectPathTokenType.IndexerOpen)
                {
                    // read index value
                    if (reader.TryAdvance(out currentToken,skipWhitespace: true) == false) throw new FormatException("Expected index value, got end of string");

                    if (currentToken.TokenType == ObjectPathTokenType.Identifier || currentToken.TokenType == ObjectPathTokenType.StringLiteral)
                    {
                        string indexValueText = currentToken.Value;

                        if(currentToken.TokenType == ObjectPathTokenType.StringLiteral)
                        {
                            indexValueText = indexValueText.Substring(1, indexValueText.Length - 2);
                        }

                        object indexValue;
                        int indexValueInt;
                        if (int.TryParse(indexValueText, out indexValueInt) == false)
                        {
                            indexValue = indexValueText;
                        }
                        else
                        {
                            indexValue = indexValueInt;
                        }

                        // read index close
                        if (reader.TryAdvance(out currentToken, skipWhitespace: true) == false) throw new FormatException("Expected ']', got end of string");
                        if (currentToken.TokenType != ObjectPathTokenType.IndexerClose) throw new FormatException("Expected ']', got '" + currentToken.Value + "' at " + currentToken.Position);

                        IndexerPathElement el = new IndexerPathElement(indexValue);
                        pathElements.Add(el);

                        if (reader.TryAdvance(out currentToken, skipWhitespace: true))
                        {
                            if (currentToken.TokenType != ObjectPathTokenType.NavigationElement) throw new FormatException("Expected '.', got '" + currentToken.Value + "' at " + currentToken.Position);
                            if (reader.CanAdvance(skipWhitespace: true) == false) throw new FormatException("Expected property, got end of string");
                            lastTokenWasNavigation = true;
                        }
                    }
                    else
                    {
                        throw new ArgumentException("Unexpected token '" + currentToken.Value + "' at " + currentToken.Position);
                    }
                }
                else if(currentToken.TokenType == ObjectPathTokenType.Identifier)
                {
                    PropertyPathElement el = new PropertyPathElement(currentToken.Value);
                    pathElements.Add(el);
                }
                else if(currentToken.TokenType == ObjectPathTokenType.NavigationElement)
                {
                    // do nothing
                }
                else
                {
                    throw new ArgumentException("Unexpected token '" + currentToken.Value + "' at " + currentToken.Position);
                }
            }

            return new ObjectPathExpression(pathElements);
        }