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.Runtime.CompilerServices;
  using System.IO;
  using System.Globalization;
  #if HAVE_BIG_INTEGER
  using System.Numerics;
  #endif
  using Newtonsoft.Json.Utilities;
  
  namespace Newtonsoft.Json
  {
      internal enum ReadType
      {
          Read,
          ReadAsInt32,
          ReadAsBytes,
          ReadAsString,
          ReadAsDecimal,
          ReadAsDateTime,
  #if HAVE_DATE_TIME_OFFSET
          ReadAsDateTimeOffset,
  #endif
          ReadAsDouble,
          ReadAsBoolean
      }
  
      /// <summary>
      /// Represents a reader that provides fast, non-cached, forward-only access to JSON text data.
      /// </summary>
      public partial class JsonTextReader : JsonReader, IJsonLineInfo
      {
          private const char UnicodeReplacementChar = '\uFFFD';
          private const int MaximumJavascriptIntegerCharacterLength = 380;
  
          private readonly TextReader _reader;
          private char[] _chars;
          private int _charsUsed;
          private int _charPos;
          private int _lineStartPos;
          private int _lineNumber;
          private bool _isEndOfFile;
          private StringBuffer _stringBuffer;
          private StringReference _stringReference;
          private IArrayPool<char> _arrayPool;
          internal PropertyNameTable NameTable;
  
          /// <summary>
          /// Initializes a new instance of the <see cref="JsonTextReader"/> class with the specified <see cref="TextReader"/>.
          /// </summary>
          /// <param name="reader">The <see cref="TextReader"/> containing the JSON data to read.</param>
          public JsonTextReader(TextReader reader)
          {
              if (reader == null)
              {
                  throw new ArgumentNullException(nameof(reader));
              }
  
              _reader = reader;
              _lineNumber = 1;
  
  #if HAVE_ASYNC
              _safeAsync = GetType() == typeof(JsonTextReader);
  #endif
          }
  
  #if DEBUG
          internal void SetCharBuffer(char[] chars)
          {
              _chars = chars;
          }
  #endif
  
          /// <summary>
          /// Gets or sets the reader's character buffer pool.
          /// </summary>
          public IArrayPool<char> ArrayPool
          {
              get { return _arrayPool; }
              set
              {
                  if (value == null)
                  {
                      throw new ArgumentNullException(nameof(value));
                  }
  
                  _arrayPool = value;
              }
          }
  
          private void EnsureBufferNotEmpty()
          {
              if (_stringBuffer.IsEmpty)
              {
                  _stringBuffer = new StringBuffer(_arrayPool, 1024);
              }
          }
  
          private void SetNewLine(bool hasNextChar)
          {
              if (hasNextChar && _chars[_charPos] == StringUtils.LineFeed)
              {
                  _charPos++;
              }
  
              OnNewLine(_charPos);
          }
  
          private void OnNewLine(int pos)
          {
              _lineNumber++;
              _lineStartPos = pos;
          }
  
          private void ParseString(char quote, ReadType readType)
          {
              _charPos++;
  
              ShiftBufferIfNeeded();
              ReadStringIntoBuffer(quote);
              ParseReadString(quote, readType);
          }
  
          private void ParseReadString(char quote, ReadType readType)
          { 
              SetPostValueState(true);
  
              switch (readType)
              {
                  case ReadType.ReadAsBytes:
                      Guid g;
                      byte[] data;
                      if (_stringReference.Length == 0)
                      {
                          data = CollectionUtils.ArrayEmpty<byte>();
                      }
                      else if (_stringReference.Length == 36 && ConvertUtils.TryConvertGuid(_stringReference.ToString(), out g))
                      {
                          data = g.ToByteArray();
                      }
                      else
                      {
                          data = Convert.FromBase64CharArray(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length);
                      }
  
                      SetToken(JsonToken.Bytes, data, false);
                      break;
                  case ReadType.ReadAsString:
                      string text = _stringReference.ToString();
  
                      SetToken(JsonToken.String, text, false);
                      _quoteChar = quote;
                      break;
                  case ReadType.ReadAsInt32:
                  case ReadType.ReadAsDecimal:
                  case ReadType.ReadAsBoolean:
                      // caller will convert result
                      break;
                  default:
                      if (_dateParseHandling != DateParseHandling.None)
                      {
                          DateParseHandling dateParseHandling;
                          if (readType == ReadType.ReadAsDateTime)
                          {
                              dateParseHandling = DateParseHandling.DateTime;
                          }
  #if HAVE_DATE_TIME_OFFSET
                          else if (readType == ReadType.ReadAsDateTimeOffset)
                          {
                              dateParseHandling = DateParseHandling.DateTimeOffset;
                          }
  #endif
                          else
                          {
                              dateParseHandling = _dateParseHandling;
                          }
  
                          if (dateParseHandling == DateParseHandling.DateTime)
                          {
                              DateTime dt;
                              if (DateTimeUtils.TryParseDateTime(_stringReference, DateTimeZoneHandling, DateFormatString, Culture, out dt))
                              {
                                  SetToken(JsonToken.Date, dt, false);
                                  return;
                              }
                          }
  #if HAVE_DATE_TIME_OFFSET
                          else
                          {
                              DateTimeOffset dt;
                              if (DateTimeUtils.TryParseDateTimeOffset(_stringReference, DateFormatString, Culture, out dt))
                              {
                                  SetToken(JsonToken.Date, dt, false);
                                  return;
                              }
                          }
  #endif
                      }
  
                      SetToken(JsonToken.String, _stringReference.ToString(), false);
                      _quoteChar = quote;
                      break;
              }
          }
  
          private static void BlockCopyChars(char[] src, int srcOffset, char[] dst, int dstOffset, int count)
          {
              const int charByteCount = 2;
  
              Buffer.BlockCopy(src, srcOffset * charByteCount, dst, dstOffset * charByteCount, count * charByteCount);
          }
  
          private void ShiftBufferIfNeeded()
          {
              // once in the last 10% of the buffer shift the remaining content to the start to avoid
              // unnecessarily increasing the buffer size when reading numbers/strings
              int length = _chars.Length;
              if (length - _charPos <= length * 0.1)
              {
                  int count = _charsUsed - _charPos;
                  if (count > 0)
                  {
                      BlockCopyChars(_chars, _charPos, _chars, 0, count);
                  }
  
                  _lineStartPos -= _charPos;
                  _charPos = 0;
                  _charsUsed = count;
                  _chars[_charsUsed] = '\0';
              }
          }
  
          private int ReadData(bool append)
          {
              return ReadData(append, 0);
          }
  
          private void PrepareBufferForReadData(bool append, int charsRequired)
          {
              // char buffer is full
              if (_charsUsed + charsRequired >= _chars.Length - 1)
              {
                  if (append)
                  {
                      // copy to new array either double the size of the current or big enough to fit required content
                      int newArrayLength = Math.Max(_chars.Length * 2, _charsUsed + charsRequired + 1);
  
                      // increase the size of the buffer
                      char[] dst = BufferUtils.RentBuffer(_arrayPool, newArrayLength);
  
                      BlockCopyChars(_chars, 0, dst, 0, _chars.Length);
  
                      BufferUtils.ReturnBuffer(_arrayPool, _chars);
  
                      _chars = dst;
                  }
                  else
                  {
                      int remainingCharCount = _charsUsed - _charPos;
  
                      if (remainingCharCount + charsRequired + 1 >= _chars.Length)
                      {
                          // the remaining count plus the required is bigger than the current buffer size
                          char[] dst = BufferUtils.RentBuffer(_arrayPool, remainingCharCount + charsRequired + 1);
  
                          if (remainingCharCount > 0)
                          {
                              BlockCopyChars(_chars, _charPos, dst, 0, remainingCharCount);
                          }
  
                          BufferUtils.ReturnBuffer(_arrayPool, _chars);
  
                          _chars = dst;
                      }
                      else
                      {
                          // copy any remaining data to the beginning of the buffer if needed and reset positions
                          if (remainingCharCount > 0)
                          {
                              BlockCopyChars(_chars, _charPos, _chars, 0, remainingCharCount);
                          }
                      }
  
                      _lineStartPos -= _charPos;
                      _charPos = 0;
                      _charsUsed = remainingCharCount;
                  }
              }
          }
  
          private int ReadData(bool append, int charsRequired)
          {
              if (_isEndOfFile)
              {
                  return 0;
              }
  
              PrepareBufferForReadData(append, charsRequired);
  
              int attemptCharReadCount = _chars.Length - _charsUsed - 1;
  
              int charsRead = _reader.Read(_chars, _charsUsed, attemptCharReadCount);
  
              _charsUsed += charsRead;
  
              if (charsRead == 0)
              {
                  _isEndOfFile = true;
              }
  
              _chars[_charsUsed] = '\0';
              return charsRead;
          }
  
          private bool EnsureChars(int relativePosition, bool append)
          {
              if (_charPos + relativePosition >= _charsUsed)
              {
                  return ReadChars(relativePosition, append);
              }
  
              return true;
          }
  
          private bool ReadChars(int relativePosition, bool append)
          {
              if (_isEndOfFile)
              {
                  return false;
              }
  
              int charsRequired = _charPos + relativePosition - _charsUsed + 1;
  
              int totalCharsRead = 0;
  
              // it is possible that the TextReader doesn't return all data at once
              // repeat read until the required text is returned or the reader is out of content
              do
              {
                  int charsRead = ReadData(append, charsRequired - totalCharsRead);
  
                  // no more content
                  if (charsRead == 0)
                  {
                      break;
                  }
  
                  totalCharsRead += charsRead;
              } while (totalCharsRead < charsRequired);
  
              if (totalCharsRead < charsRequired)
              {
                  return false;
              }
              return true;
          }
  
          /// <summary>
          /// Reads the next JSON token from the underlying <see cref="TextReader"/>.
          /// </summary>
          /// <returns>
          /// <c>true</c> if the next token was read successfully; <c>false</c> if there are no more tokens to read.
          /// </returns>
          public override bool Read()
          {
              EnsureBuffer();
  
              while (true)
              {
                  switch (_currentState)
                  {
                      case State.Start:
                      case State.Property:
                      case State.Array:
                      case State.ArrayStart:
                      case State.Constructor:
                      case State.ConstructorStart:
                          return ParseValue();
                      case State.Object:
                      case State.ObjectStart:
                          return ParseObject();
                      case State.PostValue:
                          // returns true if it hits
                          // end of object or array
                          if (ParsePostValue(false))
                          {
                              return true;
                          }
                          break;
                      case State.Finished:
                          if (EnsureChars(0, false))
                          {
                              EatWhitespace();
                              if (_isEndOfFile)
                              {
                                  SetToken(JsonToken.None);
                                  return false;
                              }
                              if (_chars[_charPos] == '/')
                              {
                                  ParseComment(true);
                                  return true;
                              }
  
                              throw JsonReaderException.Create(this, "Additional text encountered after finished reading JSON content: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos]));
                          }
                          SetToken(JsonToken.None);
                          return false;
                      default:
                          throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState));
                  }
              }
          }
  
          /// <summary>
          /// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="Nullable{T}"/> of <see cref="Int32"/>.
          /// </summary>
          /// <returns>A <see cref="Nullable{T}"/> of <see cref="Int32"/>. This method will return <c>null</c> at the end of an array.</returns>
          public override int? ReadAsInt32()
          {
              return (int?)ReadNumberValue(ReadType.ReadAsInt32);
          }
  
          /// <summary>
          /// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="Nullable{T}"/> of <see cref="DateTime"/>.
          /// </summary>
          /// <returns>A <see cref="Nullable{T}"/> of <see cref="DateTime"/>. This method will return <c>null</c> at the end of an array.</returns>
          public override DateTime? ReadAsDateTime()
          {
              return (DateTime?)ReadStringValue(ReadType.ReadAsDateTime);
          }
  
          /// <summary>
          /// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="String"/>.
          /// </summary>
          /// <returns>A <see cref="String"/>. This method will return <c>null</c> at the end of an array.</returns>
          public override string ReadAsString()
          {
              return (string)ReadStringValue(ReadType.ReadAsString);
          }
  
          /// <summary>
          /// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="Byte"/>[].
          /// </summary>
          /// <returns>A <see cref="Byte"/>[] or <c>null</c> if the next JSON token is null. This method will return <c>null</c> at the end of an array.</returns>
          public override byte[] ReadAsBytes()
          {
              EnsureBuffer();
              bool isWrapped = false;
  
              switch (_currentState)
              {
                  case State.PostValue:
                      if (ParsePostValue(true))
                      {
                          return null;
                      }
                      goto case State.Start;
                  case State.Start:
                  case State.Property:
                  case State.Array:
                  case State.ArrayStart:
                  case State.Constructor:
                  case State.ConstructorStart:
                      while (true)
                      {
                          char currentChar = _chars[_charPos];
  
                          switch (currentChar)
                          {
                              case '\0':
                                  if (ReadNullChar())
                                  {
                                      SetToken(JsonToken.None, null, false);
                                      return null;
                                  }
                                  break;
                              case '"':
                              case '\'':
                                  ParseString(currentChar, ReadType.ReadAsBytes);
                                  byte[] data = (byte[])Value;
                                  if (isWrapped)
                                  {
                                      ReaderReadAndAssert();
                                      if (TokenType != JsonToken.EndObject)
                                      {
                                          throw JsonReaderException.Create(this, "Error reading bytes. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType));
                                      }
                                      SetToken(JsonToken.Bytes, data, false);
                                  }
                                  return data;
                              case '{':
                                  _charPos++;
                                  SetToken(JsonToken.StartObject);
                                  ReadIntoWrappedTypeObject();
                                  isWrapped = true;
                                  break;
                              case '[':
                                  _charPos++;
                                  SetToken(JsonToken.StartArray);
                                  return ReadArrayIntoByteArray();
                              case 'n':
                                  HandleNull();
                                  return null;
                              case '/':
                                  ParseComment(false);
                                  break;
                              case ',':
                                  ProcessValueComma();
                                  break;
                              case ']':
                                  _charPos++;
                                  if (_currentState == State.Array || _currentState == State.ArrayStart || _currentState == State.PostValue)
                                  {
                                      SetToken(JsonToken.EndArray);
                                      return null;
                                  }
                                  throw CreateUnexpectedCharacterException(currentChar);
                              case StringUtils.CarriageReturn:
                                  ProcessCarriageReturn(false);
                                  break;
                              case StringUtils.LineFeed:
                                  ProcessLineFeed();
                                  break;
                              case ' ':
                              case StringUtils.Tab:
                                  // eat
                                  _charPos++;
                                  break;
                              default:
                                  _charPos++;
  
                                  if (!char.IsWhiteSpace(currentChar))
                                  {
                                      throw CreateUnexpectedCharacterException(currentChar);
                                  }
  
                                  // eat
                                  break;
                          }
                      }
                  case State.Finished:
                      ReadFinished();
                      return null;
                  default:
                      throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState));
              }
          }
  
          private object ReadStringValue(ReadType readType)
          {
              EnsureBuffer();
  
              switch (_currentState)
              {
                  case State.PostValue:
                      if (ParsePostValue(true))
                      {
                          return null;
                      }
                      goto case State.Start;
                  case State.Start:
                  case State.Property:
                  case State.Array:
                  case State.ArrayStart:
                  case State.Constructor:
                  case State.ConstructorStart:
                      while (true)
                      {
                          char currentChar = _chars[_charPos];
  
                          switch (currentChar)
                          {
                              case '\0':
                                  if (ReadNullChar())
                                  {
                                      SetToken(JsonToken.None, null, false);
                                      return null;
                                  }
                                  break;
                              case '"':
                              case '\'':
                                  ParseString(currentChar, readType);
                                  return FinishReadQuotedStringValue(readType);
                              case '-':
                                  if (EnsureChars(1, true) && _chars[_charPos + 1] == 'I')
                                  {
                                      return ParseNumberNegativeInfinity(readType);
                                  }
                                  else
                                  {
                                      ParseNumber(readType);
                                      return Value;
                                  }
                              case '.':
                              case '0':
                              case '1':
                              case '2':
                              case '3':
                              case '4':
                              case '5':
                              case '6':
                              case '7':
                              case '8':
                              case '9':
                                  if (readType != ReadType.ReadAsString)
                                  {
                                      _charPos++;
                                      throw CreateUnexpectedCharacterException(currentChar);
                                  }
                                  ParseNumber(ReadType.ReadAsString);
                                  return Value;
                              case 't':
                              case 'f':
                                  if (readType != ReadType.ReadAsString)
                                  {
                                      _charPos++;
                                      throw CreateUnexpectedCharacterException(currentChar);
                                  }
                                  string expected = currentChar == 't' ? JsonConvert.True : JsonConvert.False;
                                  if (!MatchValueWithTrailingSeparator(expected))
                                  {
                                      throw CreateUnexpectedCharacterException(_chars[_charPos]);
                                  }
                                  SetToken(JsonToken.String, expected);
                                  return expected;
                              case 'I':
                                  return ParseNumberPositiveInfinity(readType);
                              case 'N':
                                  return ParseNumberNaN(readType);
                              case 'n':
                                  HandleNull();
                                  return null;
                              case '/':
                                  ParseComment(false);
                                  break;
                              case ',':
                                  ProcessValueComma();
                                  break;
                              case ']':
                                  _charPos++;
                                  if (_currentState == State.Array || _currentState == State.ArrayStart || _currentState == State.PostValue)
                                  {
                                      SetToken(JsonToken.EndArray);
                                      return null;
                                  }
                                  throw CreateUnexpectedCharacterException(currentChar);
                              case StringUtils.CarriageReturn:
                                  ProcessCarriageReturn(false);
                                  break;
                              case StringUtils.LineFeed:
                                  ProcessLineFeed();
                                  break;
                              case ' ':
                              case StringUtils.Tab:
                                  // eat
                                  _charPos++;
                                  break;
                              default:
                                  _charPos++;
  
                                  if (!char.IsWhiteSpace(currentChar))
                                  {
                                      throw CreateUnexpectedCharacterException(currentChar);
                                  }
  
                                  // eat
                                  break;
                          }
                      }
                  case State.Finished:
                      ReadFinished();
                      return null;
                  default:
                      throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState));
              }
          }
  
          private object FinishReadQuotedStringValue(ReadType readType)
          {
              switch (readType)
              {
                  case ReadType.ReadAsBytes:
                  case ReadType.ReadAsString:
                      return Value;
                  case ReadType.ReadAsDateTime:
                      if (Value is DateTime)
                      {
                          return (DateTime)Value;
                      }
  
                      return ReadDateTimeString((string)Value);
  #if HAVE_DATE_TIME_OFFSET
                  case ReadType.ReadAsDateTimeOffset:
                      if (Value is DateTimeOffset)
                      {
                          return (DateTimeOffset)Value;
                      }
  
                      return ReadDateTimeOffsetString((string)Value);
  #endif
                  default:
                      throw new ArgumentOutOfRangeException(nameof(readType));
              }
          }
  
          private JsonReaderException CreateUnexpectedCharacterException(char c)
          {
              return JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, c));
          }
  
          /// <summary>
          /// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="Nullable{T}"/> of <see cref="Boolean"/>.
          /// </summary>
          /// <returns>A <see cref="Nullable{T}"/> of <see cref="Boolean"/>. This method will return <c>null</c> at the end of an array.</returns>
          public override bool? ReadAsBoolean()
          {
              EnsureBuffer();
  
              switch (_currentState)
              {
                  case State.PostValue:
                      if (ParsePostValue(true))
                      {
                          return null;
                      }
                      goto case State.Start;
                  case State.Start:
                  case State.Property:
                  case State.Array:
                  case State.ArrayStart:
                  case State.Constructor:
                  case State.ConstructorStart:
                      while (true)
                      {
                          char currentChar = _chars[_charPos];
  
                          switch (currentChar)
                          {
                              case '\0':
                                  if (ReadNullChar())
                                  {
                                      SetToken(JsonToken.None, null, false);
                                      return null;
                                  }
                                  break;
                              case '"':
                              case '\'':
                                  ParseString(currentChar, ReadType.Read);
                                  return ReadBooleanString(_stringReference.ToString());
                              case 'n':
                                  HandleNull();
                                  return null;
                              case '-':
                              case '.':
                              case '0':
                              case '1':
                              case '2':
                              case '3':
                              case '4':
                              case '5':
                              case '6':
                              case '7':
                              case '8':
                              case '9':
                                  ParseNumber(ReadType.Read);
                                  bool b;
  #if HAVE_BIG_INTEGER
                                  if (Value is BigInteger)
                                  {
                                      b = (BigInteger)Value != 0;
                                  }
                                  else
  #endif
                                  {
                                      b = Convert.ToBoolean(Value, CultureInfo.InvariantCulture);
                                  }
                                  SetToken(JsonToken.Boolean, b, false);
                                  return b;
                              case 't':
                              case 'f':
                                  bool isTrue = currentChar == 't';
                                  string expected = isTrue ? JsonConvert.True : JsonConvert.False;
  
                                  if (!MatchValueWithTrailingSeparator(expected))
                                  {
                                      throw CreateUnexpectedCharacterException(_chars[_charPos]);
                                  }
                                  SetToken(JsonToken.Boolean, isTrue);
                                  return isTrue;
                              case '/':
                                  ParseComment(false);
                                  break;
                              case ',':
                                  ProcessValueComma();
                                  break;
                              case ']':
                                  _charPos++;
                                  if (_currentState == State.Array || _currentState == State.ArrayStart || _currentState == State.PostValue)
                                  {
                                      SetToken(JsonToken.EndArray);
                                      return null;
                                  }
                                  throw CreateUnexpectedCharacterException(currentChar);
                              case StringUtils.CarriageReturn:
                                  ProcessCarriageReturn(false);
                                  break;
                              case StringUtils.LineFeed:
                                  ProcessLineFeed();
                                  break;
                              case ' ':
                              case StringUtils.Tab:
                                  // eat
                                  _charPos++;
                                  break;
                              default:
                                  _charPos++;
  
                                  if (!char.IsWhiteSpace(currentChar))
                                  {
                                      throw CreateUnexpectedCharacterException(currentChar);
                                  }
  
                                  // eat
                                  break;
                          }
                      }
                  case State.Finished:
                      ReadFinished();
                      return null;
                  default:
                      throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState));
              }
          }
  
          private void ProcessValueComma()
          {
              _charPos++;
  
              if (_currentState != State.PostValue)
              {
                  SetToken(JsonToken.Undefined);
                  JsonReaderException ex = CreateUnexpectedCharacterException(',');
                  // so the comma will be parsed again
                  _charPos--;
  
                  throw ex;
              }
  
              SetStateBasedOnCurrent();
          }
  
          private object ReadNumberValue(ReadType readType)
          {
              EnsureBuffer();
  
              switch (_currentState)
              {
                  case State.PostValue:
                      if (ParsePostValue(true))
                      {
                          return null;
                      }
                      goto case State.Start;
                  case State.Start:
                  case State.Property:
                  case State.Array:
                  case State.ArrayStart:
                  case State.Constructor:
                  case State.ConstructorStart:
                      while (true)
                      {
                          char currentChar = _chars[_charPos];
  
                          switch (currentChar)
                          {
                              case '\0':
                                  if (ReadNullChar())
                                  {
                                      SetToken(JsonToken.None, null, false);
                                      return null;
                                  }
                                  break;
                              case '"':
                              case '\'':
                                  ParseString(currentChar, readType);
                                  return FinishReadQuotedNumber(readType);
                              case 'n':
                                  HandleNull();
                                  return null;
                              case 'N':
                                  return ParseNumberNaN(readType);
                              case 'I':
                                  return ParseNumberPositiveInfinity(readType);
                              case '-':
                                  if (EnsureChars(1, true) && _chars[_charPos + 1] == 'I')
                                  {
                                      return ParseNumberNegativeInfinity(readType);
                                  }
                                  else
                                  {
                                      ParseNumber(readType);
                                      return Value;
                                  }
                              case '.':
                              case '0':
                              case '1':
                              case '2':
                              case '3':
                              case '4':
                              case '5':
                              case '6':
                              case '7':
                              case '8':
                              case '9':
                                  ParseNumber(readType);
                                  return Value;
                              case '/':
                                  ParseComment(false);
                                  break;
                              case ',':
                                  ProcessValueComma();
                                  break;
                              case ']':
                                  _charPos++;
                                  if (_currentState == State.Array || _currentState == State.ArrayStart || _currentState == State.PostValue)
                                  {
                                      SetToken(JsonToken.EndArray);
                                      return null;
                                  }
                                  throw CreateUnexpectedCharacterException(currentChar);
                              case StringUtils.CarriageReturn:
                                  ProcessCarriageReturn(false);
                                  break;
                              case StringUtils.LineFeed:
                                  ProcessLineFeed();
                                  break;
                              case ' ':
                              case StringUtils.Tab:
                                  // eat
                                  _charPos++;
                                  break;
                              default:
                                  _charPos++;
  
                                  if (!char.IsWhiteSpace(currentChar))
                                  {
                                      throw CreateUnexpectedCharacterException(currentChar);
                                  }
  
                                  // eat
                                  break;
                          }
                      }
                  case State.Finished:
                      ReadFinished();
                      return null;
                  default:
                      throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState));
              }
          }
  
          private object FinishReadQuotedNumber(ReadType readType)
          {
              switch (readType)
              {
                  case ReadType.ReadAsInt32:
                      return ReadInt32String(_stringReference.ToString());
                  case ReadType.ReadAsDecimal:
                      return ReadDecimalString(_stringReference.ToString());
                  case ReadType.ReadAsDouble:
                      return ReadDoubleString(_stringReference.ToString());
                  default:
                      throw new ArgumentOutOfRangeException(nameof(readType));
              }
          }
  
  #if HAVE_DATE_TIME_OFFSET
          /// <summary>
          /// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="Nullable{T}"/> of <see cref="DateTimeOffset"/>.
          /// </summary>
          /// <returns>A <see cref="Nullable{T}"/> of <see cref="DateTimeOffset"/>. This method will return <c>null</c> at the end of an array.</returns>
          public override DateTimeOffset? ReadAsDateTimeOffset()
          {
              return (DateTimeOffset?)ReadStringValue(ReadType.ReadAsDateTimeOffset);
          }
  #endif
  
          /// <summary>
          /// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="Nullable{T}"/> of <see cref="Decimal"/>.
          /// </summary>
          /// <returns>A <see cref="Nullable{T}"/> of <see cref="Decimal"/>. This method will return <c>null</c> at the end of an array.</returns>
          public override decimal? ReadAsDecimal()
          {
              return (decimal?)ReadNumberValue(ReadType.ReadAsDecimal);
          }
  
          /// <summary>
          /// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="Nullable{T}"/> of <see cref="Double"/>.
          /// </summary>
          /// <returns>A <see cref="Nullable{T}"/> of <see cref="Double"/>. This method will return <c>null</c> at the end of an array.</returns>
          public override double? ReadAsDouble()
          {
              return (double?)ReadNumberValue(ReadType.ReadAsDouble);
          }
  
          private void HandleNull()
          {
              if (EnsureChars(1, true))
              {
                  char next = _chars[_charPos + 1];
  
                  if (next == 'u')
                  {
                      ParseNull();
                      return;
                  }
  
                  _charPos += 2;
                  throw CreateUnexpectedCharacterException(_chars[_charPos - 1]);
              }
  
              _charPos = _charsUsed;
              throw CreateUnexpectedEndException();
          }
  
          private void ReadFinished()
          {
              if (EnsureChars(0, false))
              {
                  EatWhitespace();
                  if (_isEndOfFile)
                  {
                      return;
                  }
                  if (_chars[_charPos] == '/')
                  {
                      ParseComment(false);
                  }
                  else
                  {
                      throw JsonReaderException.Create(this, "Additional text encountered after finished reading JSON content: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos]));
                  }
              }
  
              SetToken(JsonToken.None);
          }
  
          private bool ReadNullChar()
          {
              if (_charsUsed == _charPos)
              {
                  if (ReadData(false) == 0)
                  {
                      _isEndOfFile = true;
                      return true;
                  }
              }
              else
              {
                  _charPos++;
              }
  
              return false;
          }
  
          private void EnsureBuffer()
          {
              if (_chars == null)
              {
                  _chars = BufferUtils.RentBuffer(_arrayPool, 1024);
                  _chars[0] = '\0';
              }
          }
  
          private void ReadStringIntoBuffer(char quote)
          {
              int charPos = _charPos;
              int initialPosition = _charPos;
              int lastWritePosition = _charPos;
              _stringBuffer.Position = 0;
  
              while (true)
              {
                  switch (_chars[charPos++])
                  {
                      case '\0':
                          if (_charsUsed == charPos - 1)
                          {
                              charPos--;
  
                              if (ReadData(true) == 0)
                              {
                                  _charPos = charPos;
                                  throw JsonReaderException.Create(this, "Unterminated string. Expected delimiter: {0}.".FormatWith(CultureInfo.InvariantCulture, quote));
                              }
                          }
                          break;
                      case '\\':
                          _charPos = charPos;
                          if (!EnsureChars(0, true))
                          {
                              throw JsonReaderException.Create(this, "Unterminated string. Expected delimiter: {0}.".FormatWith(CultureInfo.InvariantCulture, quote));
                          }
  
                          // start of escape sequence
                          int escapeStartPos = charPos - 1;
  
                          char currentChar = _chars[charPos];
                          charPos++;
  
                          char writeChar;
  
                          switch (currentChar)
                          {
                              case 'b':
                                  writeChar = '\b';
                                  break;
                              case 't':
                                  writeChar = '\t';
                                  break;
                              case 'n':
                                  writeChar = '\n';
                                  break;
                              case 'f':
                                  writeChar = '\f';
                                  break;
                              case 'r':
                                  writeChar = '\r';
                                  break;
                              case '\\':
                                  writeChar = '\\';
                                  break;
                              case '"':
                              case '\'':
                              case '/':
                                  writeChar = currentChar;
                                  break;
                              case 'u':
                                  _charPos = charPos;
                                  writeChar = ParseUnicode();
  
                                  if (StringUtils.IsLowSurrogate(writeChar))
                                  {
                                      // low surrogate with no preceding high surrogate; this char is replaced
                                      writeChar = UnicodeReplacementChar;
                                  }
                                  else if (StringUtils.IsHighSurrogate(writeChar))
                                  {
                                      bool anotherHighSurrogate;
  
                                      // loop for handling situations where there are multiple consecutive high surrogates
                                      do
                                      {
                                          anotherHighSurrogate = false;
  
                                          // potential start of a surrogate pair
                                          if (EnsureChars(2, true) && _chars[_charPos] == '\\' && _chars[_charPos + 1] == 'u')
                                          {
                                              char highSurrogate = writeChar;
  
                                              _charPos += 2;
                                              writeChar = ParseUnicode();
  
                                              if (StringUtils.IsLowSurrogate(writeChar))
                                              {
                                                  // a valid surrogate pair!
                                              }
                                              else if (StringUtils.IsHighSurrogate(writeChar))
                                              {
                                                  // another high surrogate; replace current and start check over
                                                  highSurrogate = UnicodeReplacementChar;
                                                  anotherHighSurrogate = true;
                                              }
                                              else
                                              {
                                                  // high surrogate not followed by low surrogate; original char is replaced
                                                  highSurrogate = UnicodeReplacementChar;
                                              }
  
                                              EnsureBufferNotEmpty();
  
                                              WriteCharToBuffer(highSurrogate, lastWritePosition, escapeStartPos);
                                              lastWritePosition = _charPos;
                                          }
                                          else
                                          {
                                              // there are not enough remaining chars for the low surrogate or is not follow by unicode sequence
                                              // replace high surrogate and continue on as usual
                                              writeChar = UnicodeReplacementChar;
                                          }
                                      } while (anotherHighSurrogate);
                                  }
  
                                  charPos = _charPos;
                                  break;
                              default:
                                  _charPos = charPos;
                                  throw JsonReaderException.Create(this, "Bad JSON escape sequence: {0}.".FormatWith(CultureInfo.InvariantCulture, @"\" + currentChar));
                          }
  
                          EnsureBufferNotEmpty();
                          WriteCharToBuffer(writeChar, lastWritePosition, escapeStartPos);
  
                          lastWritePosition = charPos;
                          break;
                      case StringUtils.CarriageReturn:
                          _charPos = charPos - 1;
                          ProcessCarriageReturn(true);
                          charPos = _charPos;
                          break;
                      case StringUtils.LineFeed:
                          _charPos = charPos - 1;
                          ProcessLineFeed();
                          charPos = _charPos;
                          break;
                      case '"':
                      case '\'':
                          if (_chars[charPos - 1] == quote)
                          {
                              FinishReadStringIntoBuffer(charPos - 1, initialPosition, lastWritePosition);
                              return;
                          }
                          break;
                  }
              }
          }
  
          private void FinishReadStringIntoBuffer(int charPos, int initialPosition, int lastWritePosition)
          {
              if (initialPosition == lastWritePosition)
              {
                  _stringReference = new StringReference(_chars, initialPosition, charPos - initialPosition);
              }
              else
              {
                  EnsureBufferNotEmpty();
  
                  if (charPos > lastWritePosition)
                  {
                      _stringBuffer.Append(_arrayPool, _chars, lastWritePosition, charPos - lastWritePosition);
                  }
  
                  _stringReference = new StringReference(_stringBuffer.InternalBuffer, 0, _stringBuffer.Position);
              }
  
              _charPos = charPos + 1;
          }
  
          private void WriteCharToBuffer(char writeChar, int lastWritePosition, int writeToPosition)
          {
              if (writeToPosition > lastWritePosition)
              {
                  _stringBuffer.Append(_arrayPool, _chars, lastWritePosition, writeToPosition - lastWritePosition);
              }
  
              _stringBuffer.Append(_arrayPool, writeChar);
          }
  
          private char ConvertUnicode(bool enoughChars)
          {
              if (enoughChars)
              {
                  int value;
                  if (ConvertUtils.TryHexTextToInt(_chars, _charPos, _charPos + 4, out value))
                  {
                      char hexChar = Convert.ToChar(value);
                      _charPos += 4;
                      return hexChar;
                  }
                  else
                  {
                      throw JsonReaderException.Create(this, @"Invalid Unicode escape sequence: \u{0}.".FormatWith(CultureInfo.InvariantCulture, new string(_chars, _charPos, 4)));
                  }
              }
              else
              {
                  throw JsonReaderException.Create(this, "Unexpected end while parsing Unicode escape sequence.");
              }
          }
  
          private char ParseUnicode()
          {
              return ConvertUnicode(EnsureChars(4, true));
          }
  
          private void ReadNumberIntoBuffer()
          {
              int charPos = _charPos;
  
              while (true)
              {
                  char currentChar = _chars[charPos];
                  if (currentChar == '\0')
                  {
                      _charPos = charPos;
  
                      if (_charsUsed == charPos)
                      {
                          if (ReadData(true) == 0)
                          {
                              return;
                          }
                      }
                      else
                      {
                          return;
                      }
                  }
                  else if (ReadNumberCharIntoBuffer(currentChar, charPos))
                  {
                      return;
                  }
                  else
                  {
                      charPos++;
                  }
              }
          }
  
          private bool ReadNumberCharIntoBuffer(char currentChar, int charPos)
          {
              switch (currentChar)
              {
                  case '-':
                  case '+':
                  case 'a':
                  case 'A':
                  case 'b':
                  case 'B':
                  case 'c':
                  case 'C':
                  case 'd':
                  case 'D':
                  case 'e':
                  case 'E':
                  case 'f':
                  case 'F':
                  case 'x':
                  case 'X':
                  case '.':
                  case '0':
                  case '1':
                  case '2':
                  case '3':
                  case '4':
                  case '5':
                  case '6':
                  case '7':
                  case '8':
                  case '9':
                      return false;
                  default:
                      _charPos = charPos;
  
                      if (char.IsWhiteSpace(currentChar) || currentChar == ',' || currentChar == '}' || currentChar == ']' || currentChar == ')' || currentChar == '/')
                      {
                          return true;
                      }
  
                      throw JsonReaderException.Create(this, "Unexpected character encountered while parsing number: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar));
              }
          }
  
          private void ClearRecentString()
          {
              _stringBuffer.Position = 0;
              _stringReference = new StringReference();
          }
  
          private bool ParsePostValue(bool ignoreComments)
          {
              while (true)
              {
                  char currentChar = _chars[_charPos];
  
                  switch (currentChar)
                  {
                      case '\0':
                          if (_charsUsed == _charPos)
                          {
                              if (ReadData(false) == 0)
                              {
                                  _currentState = State.Finished;
                                  return false;
                              }
                          }
                          else
                          {
                              _charPos++;
                          }
                          break;
                      case '}':
                          _charPos++;
                          SetToken(JsonToken.EndObject);
                          return true;
                      case ']':
                          _charPos++;
                          SetToken(JsonToken.EndArray);
                          return true;
                      case ')':
                          _charPos++;
                          SetToken(JsonToken.EndConstructor);
                          return true;
                      case '/':
                          ParseComment(!ignoreComments);
                          if (!ignoreComments)
                          {
                              return true;
                          }
                          break;
                      case ',':
                          _charPos++;
  
                          // finished parsing
                          SetStateBasedOnCurrent();
                          return false;
                      case ' ':
                      case StringUtils.Tab:
                          // eat
                          _charPos++;
                          break;
                      case StringUtils.CarriageReturn:
                          ProcessCarriageReturn(false);
                          break;
                      case StringUtils.LineFeed:
                          ProcessLineFeed();
                          break;
                      default:
                          if (char.IsWhiteSpace(currentChar))
                          {
                              // eat
                              _charPos++;
                          }
                          else
                          {
                              throw JsonReaderException.Create(this, "After parsing a value an unexpected character was encountered: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar));
                          }
                          break;
                  }
              }
          }
  
          private bool ParseObject()
          {
              while (true)
              {
                  char currentChar = _chars[_charPos];
  
                  switch (currentChar)
                  {
                      case '\0':
                          if (_charsUsed == _charPos)
                          {
  
gt:                             if (ReadData(false) == 0)