context: #region License
  // Copyright (c) 2007 James Newton-King
  //
  // Permission is hereby granted, free of charge, to any person
  // obtaining a copy of this software and associated documentation
  // files (the "Software"), to deal in the Software without
  // restriction, including without limitation the rights to use,
  // copy, modify, merge, publish, distribute, sublicense, and/or sell
  // copies of the Software, and to permit persons to whom the
  // Software is furnished to do so, subject to the following
  // conditions:
  //
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  // OTHER DEALINGS IN THE SOFTWARE.
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.Globalization;
  using System.Text;
  using Newtonsoft.Json.Utilities;
  
  namespace Newtonsoft.Json.Linq.JsonPath
  {
      internal class JPath
      {
          private readonly string _expression;
          public List<PathFilter> Filters { get; }
  
          private int _currentIndex;
  
          public JPath(string expression)
          {
              ValidationUtils.ArgumentNotNull(expression, nameof(expression));
              _expression = expression;
              Filters = new List<PathFilter>();
  
              ParseMain();
          }
  
          private void ParseMain()
          {
              int currentPartStartIndex = _currentIndex;
  
              EatWhitespace();
  
              if (_expression.Length == _currentIndex)
              {
                  return;
              }
  
              if (_expression[_currentIndex] == '$')
              {
                  if (_expression.Length == 1)
                  {
                      return;
                  }
  
                  // only increment position for "$." or "$["
                  // otherwise assume property that starts with $
                  char c = _expression[_currentIndex + 1];
                  if (c == '.' || c == '[')
                  {
                      _currentIndex++;
                      currentPartStartIndex = _currentIndex;
                  }
              }
  
              if (!ParsePath(Filters, currentPartStartIndex, false))
              {
                  int lastCharacterIndex = _currentIndex;
  
                  EatWhitespace();
  
                  if (_currentIndex < _expression.Length)
                  {
                      throw new JsonException("Unexpected character while parsing path: " + _expression[lastCharacterIndex]);
                  }
              }
          }
  
          private bool ParsePath(List<PathFilter> filters, int currentPartStartIndex, bool query)
          {
              bool scan = false;
              bool followingIndexer = false;
              bool followingDot = false;
  
              bool ended = false;
              while (_currentIndex < _expression.Length && !ended)
              {
                  char currentChar = _expression[_currentIndex];
  
                  switch (currentChar)
                  {
                      case '[':
                      case '(':
                          if (_currentIndex > currentPartStartIndex)
                          {
                              string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex);
                              if (member == "*")
                              {
                                  member = null;
                              }
  
                              filters.Add(CreatePathFilter(member, scan));
                              scan = false;
                          }
  
                          filters.Add(ParseIndexer(currentChar, scan));
                          _currentIndex++;
                          currentPartStartIndex = _currentIndex;
                          followingIndexer = true;
                          followingDot = false;
                          break;
                      case ']':
                      case ')':
                          ended = true;
                          break;
                      case ' ':
                          if (_currentIndex < _expression.Length)
                          {
                              ended = true;
                          }
                          break;
                      case '.':
                          if (_currentIndex > currentPartStartIndex)
                          {
                              string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex);
                              if (member == "*")
                              {
                                  member = null;
                              }
  
                              filters.Add(CreatePathFilter(member, scan));
                              scan = false;
                          }
                          if (_currentIndex + 1 < _expression.Length && _expression[_currentIndex + 1] == '.')
                          {
                              scan = true;
                              _currentIndex++;
                          }
                          _currentIndex++;
                          currentPartStartIndex = _currentIndex;
                          followingIndexer = false;
                          followingDot = true;
                          break;
                      default:
                          if (query && (currentChar == '=' || currentChar == '<' || currentChar == '!' || currentChar == '>' || currentChar == '|' || currentChar == '&'))
                          {
                              ended = true;
                          }
                          else
                          {
                              if (followingIndexer)
                              {
                                  throw new JsonException("Unexpected character following indexer: " + currentChar);
                              }
  
                              _currentIndex++;
                          }
                          break;
                  }
              }
  
              bool atPathEnd = (_currentIndex == _expression.Length);
  
              if (_currentIndex > currentPartStartIndex)
              {
                  string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex).TrimEnd();
                  if (member == "*")
                  {
                      member = null;
                  }
                  filters.Add(CreatePathFilter(member, scan));
              }
              else
              {
                  // no field name following dot in path and at end of base path/query
                  if (followingDot && (atPathEnd || query))
                  {
                      throw new JsonException("Unexpected end while parsing path.");
                  }
              }
  
              return atPathEnd;
          }
  
          private static PathFilter CreatePathFilter(string member, bool scan)
          {
              PathFilter filter = (scan) ? (PathFilter)new ScanFilter {Name = member} : new FieldFilter {Name = member};
              return filter;
          }
  
          private PathFilter ParseIndexer(char indexerOpenChar, bool scan)
          {
              _currentIndex++;
  
              char indexerCloseChar = (indexerOpenChar == '[') ? ']' : ')';
  
              EnsureLength("Path ended with open indexer.");
  
              EatWhitespace();
  
              if (_expression[_currentIndex] == '\'')
              {
                  return ParseQuotedField(indexerCloseChar, scan);
              }
              else if (_expression[_currentIndex] == '?')
              {
                  return ParseQuery(indexerCloseChar);
              }
              else
              {
                  return ParseArrayIndexer(indexerCloseChar);
              }
          }
  
          private PathFilter ParseArrayIndexer(char indexerCloseChar)
          {
              int start = _currentIndex;
  
gt:             int? end = null;