1ffe3c632Sopenharmony_ci#region Copyright notice and license 2ffe3c632Sopenharmony_ci// Protocol Buffers - Google's data interchange format 3ffe3c632Sopenharmony_ci// Copyright 2008 Google Inc. All rights reserved. 4ffe3c632Sopenharmony_ci// https://developers.google.com/protocol-buffers/ 5ffe3c632Sopenharmony_ci// 6ffe3c632Sopenharmony_ci// Redistribution and use in source and binary forms, with or without 7ffe3c632Sopenharmony_ci// modification, are permitted provided that the following conditions are 8ffe3c632Sopenharmony_ci// met: 9ffe3c632Sopenharmony_ci// 10ffe3c632Sopenharmony_ci// * Redistributions of source code must retain the above copyright 11ffe3c632Sopenharmony_ci// notice, this list of conditions and the following disclaimer. 12ffe3c632Sopenharmony_ci// * Redistributions in binary form must reproduce the above 13ffe3c632Sopenharmony_ci// copyright notice, this list of conditions and the following disclaimer 14ffe3c632Sopenharmony_ci// in the documentation and/or other materials provided with the 15ffe3c632Sopenharmony_ci// distribution. 16ffe3c632Sopenharmony_ci// * Neither the name of Google Inc. nor the names of its 17ffe3c632Sopenharmony_ci// contributors may be used to endorse or promote products derived from 18ffe3c632Sopenharmony_ci// this software without specific prior written permission. 19ffe3c632Sopenharmony_ci// 20ffe3c632Sopenharmony_ci// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21ffe3c632Sopenharmony_ci// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22ffe3c632Sopenharmony_ci// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23ffe3c632Sopenharmony_ci// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24ffe3c632Sopenharmony_ci// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25ffe3c632Sopenharmony_ci// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26ffe3c632Sopenharmony_ci// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27ffe3c632Sopenharmony_ci// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28ffe3c632Sopenharmony_ci// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29ffe3c632Sopenharmony_ci// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30ffe3c632Sopenharmony_ci// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31ffe3c632Sopenharmony_ci#endregion 32ffe3c632Sopenharmony_ciusing System; 33ffe3c632Sopenharmony_ciusing System.Collections.Generic; 34ffe3c632Sopenharmony_ciusing System.Globalization; 35ffe3c632Sopenharmony_ciusing System.IO; 36ffe3c632Sopenharmony_ciusing System.Text; 37ffe3c632Sopenharmony_ci 38ffe3c632Sopenharmony_cinamespace Google.Protobuf 39ffe3c632Sopenharmony_ci{ 40ffe3c632Sopenharmony_ci /// <summary> 41ffe3c632Sopenharmony_ci /// Simple but strict JSON tokenizer, rigidly following RFC 7159. 42ffe3c632Sopenharmony_ci /// </summary> 43ffe3c632Sopenharmony_ci /// <remarks> 44ffe3c632Sopenharmony_ci /// <para> 45ffe3c632Sopenharmony_ci /// This tokenizer is stateful, and only returns "useful" tokens - names, values etc. 46ffe3c632Sopenharmony_ci /// It does not create tokens for the separator between names and values, or for the comma 47ffe3c632Sopenharmony_ci /// between values. It validates the token stream as it goes - so callers can assume that the 48ffe3c632Sopenharmony_ci /// tokens it produces are appropriate. For example, it would never produce "start object, end array." 49ffe3c632Sopenharmony_ci /// </para> 50ffe3c632Sopenharmony_ci /// <para>Implementation details: the base class handles single token push-back and </para> 51ffe3c632Sopenharmony_ci /// <para>Not thread-safe.</para> 52ffe3c632Sopenharmony_ci /// </remarks> 53ffe3c632Sopenharmony_ci internal abstract class JsonTokenizer 54ffe3c632Sopenharmony_ci { 55ffe3c632Sopenharmony_ci private JsonToken bufferedToken; 56ffe3c632Sopenharmony_ci 57ffe3c632Sopenharmony_ci /// <summary> 58ffe3c632Sopenharmony_ci /// Creates a tokenizer that reads from the given text reader. 59ffe3c632Sopenharmony_ci /// </summary> 60ffe3c632Sopenharmony_ci internal static JsonTokenizer FromTextReader(TextReader reader) 61ffe3c632Sopenharmony_ci { 62ffe3c632Sopenharmony_ci return new JsonTextTokenizer(reader); 63ffe3c632Sopenharmony_ci } 64ffe3c632Sopenharmony_ci 65ffe3c632Sopenharmony_ci /// <summary> 66ffe3c632Sopenharmony_ci /// Creates a tokenizer that first replays the given list of tokens, then continues reading 67ffe3c632Sopenharmony_ci /// from another tokenizer. Note that if the returned tokenizer is "pushed back", that does not push back 68ffe3c632Sopenharmony_ci /// on the continuation tokenizer, or vice versa. Care should be taken when using this method - it was 69ffe3c632Sopenharmony_ci /// created for the sake of Any parsing. 70ffe3c632Sopenharmony_ci /// </summary> 71ffe3c632Sopenharmony_ci internal static JsonTokenizer FromReplayedTokens(IList<JsonToken> tokens, JsonTokenizer continuation) 72ffe3c632Sopenharmony_ci { 73ffe3c632Sopenharmony_ci return new JsonReplayTokenizer(tokens, continuation); 74ffe3c632Sopenharmony_ci } 75ffe3c632Sopenharmony_ci 76ffe3c632Sopenharmony_ci /// <summary> 77ffe3c632Sopenharmony_ci /// Returns the depth of the stack, purely in objects (not collections). 78ffe3c632Sopenharmony_ci /// Informally, this is the number of remaining unclosed '{' characters we have. 79ffe3c632Sopenharmony_ci /// </summary> 80ffe3c632Sopenharmony_ci internal int ObjectDepth { get; private set; } 81ffe3c632Sopenharmony_ci 82ffe3c632Sopenharmony_ci // TODO: Why do we allow a different token to be pushed back? It might be better to always remember the previous 83ffe3c632Sopenharmony_ci // token returned, and allow a parameterless Rewind() method (which could only be called once, just like the current PushBack). 84ffe3c632Sopenharmony_ci internal void PushBack(JsonToken token) 85ffe3c632Sopenharmony_ci { 86ffe3c632Sopenharmony_ci if (bufferedToken != null) 87ffe3c632Sopenharmony_ci { 88ffe3c632Sopenharmony_ci throw new InvalidOperationException("Can't push back twice"); 89ffe3c632Sopenharmony_ci } 90ffe3c632Sopenharmony_ci bufferedToken = token; 91ffe3c632Sopenharmony_ci if (token.Type == JsonToken.TokenType.StartObject) 92ffe3c632Sopenharmony_ci { 93ffe3c632Sopenharmony_ci ObjectDepth--; 94ffe3c632Sopenharmony_ci } 95ffe3c632Sopenharmony_ci else if (token.Type == JsonToken.TokenType.EndObject) 96ffe3c632Sopenharmony_ci { 97ffe3c632Sopenharmony_ci ObjectDepth++; 98ffe3c632Sopenharmony_ci } 99ffe3c632Sopenharmony_ci } 100ffe3c632Sopenharmony_ci 101ffe3c632Sopenharmony_ci /// <summary> 102ffe3c632Sopenharmony_ci /// Returns the next JSON token in the stream. An EndDocument token is returned to indicate the end of the stream, 103ffe3c632Sopenharmony_ci /// after which point <c>Next()</c> should not be called again. 104ffe3c632Sopenharmony_ci /// </summary> 105ffe3c632Sopenharmony_ci /// <remarks>This implementation provides single-token buffering, and calls <see cref="NextImpl"/> if there is no buffered token.</remarks> 106ffe3c632Sopenharmony_ci /// <returns>The next token in the stream. This is never null.</returns> 107ffe3c632Sopenharmony_ci /// <exception cref="InvalidOperationException">This method is called after an EndDocument token has been returned</exception> 108ffe3c632Sopenharmony_ci /// <exception cref="InvalidJsonException">The input text does not comply with RFC 7159</exception> 109ffe3c632Sopenharmony_ci internal JsonToken Next() 110ffe3c632Sopenharmony_ci { 111ffe3c632Sopenharmony_ci JsonToken tokenToReturn; 112ffe3c632Sopenharmony_ci if (bufferedToken != null) 113ffe3c632Sopenharmony_ci { 114ffe3c632Sopenharmony_ci tokenToReturn = bufferedToken; 115ffe3c632Sopenharmony_ci bufferedToken = null; 116ffe3c632Sopenharmony_ci } 117ffe3c632Sopenharmony_ci else 118ffe3c632Sopenharmony_ci { 119ffe3c632Sopenharmony_ci tokenToReturn = NextImpl(); 120ffe3c632Sopenharmony_ci } 121ffe3c632Sopenharmony_ci if (tokenToReturn.Type == JsonToken.TokenType.StartObject) 122ffe3c632Sopenharmony_ci { 123ffe3c632Sopenharmony_ci ObjectDepth++; 124ffe3c632Sopenharmony_ci } 125ffe3c632Sopenharmony_ci else if (tokenToReturn.Type == JsonToken.TokenType.EndObject) 126ffe3c632Sopenharmony_ci { 127ffe3c632Sopenharmony_ci ObjectDepth--; 128ffe3c632Sopenharmony_ci } 129ffe3c632Sopenharmony_ci return tokenToReturn; 130ffe3c632Sopenharmony_ci } 131ffe3c632Sopenharmony_ci 132ffe3c632Sopenharmony_ci /// <summary> 133ffe3c632Sopenharmony_ci /// Returns the next JSON token in the stream, when requested by the base class. (The <see cref="Next"/> method delegates 134ffe3c632Sopenharmony_ci /// to this if it doesn't have a buffered token.) 135ffe3c632Sopenharmony_ci /// </summary> 136ffe3c632Sopenharmony_ci /// <exception cref="InvalidOperationException">This method is called after an EndDocument token has been returned</exception> 137ffe3c632Sopenharmony_ci /// <exception cref="InvalidJsonException">The input text does not comply with RFC 7159</exception> 138ffe3c632Sopenharmony_ci protected abstract JsonToken NextImpl(); 139ffe3c632Sopenharmony_ci 140ffe3c632Sopenharmony_ci /// <summary> 141ffe3c632Sopenharmony_ci /// Skips the value we're about to read. This must only be called immediately after reading a property name. 142ffe3c632Sopenharmony_ci /// If the value is an object or an array, the complete object/array is skipped. 143ffe3c632Sopenharmony_ci /// </summary> 144ffe3c632Sopenharmony_ci internal void SkipValue() 145ffe3c632Sopenharmony_ci { 146ffe3c632Sopenharmony_ci // We'll assume that Next() makes sure that the end objects and end arrays are all valid. 147ffe3c632Sopenharmony_ci // All we care about is the total nesting depth we need to close. 148ffe3c632Sopenharmony_ci int depth = 0; 149ffe3c632Sopenharmony_ci 150ffe3c632Sopenharmony_ci // do/while rather than while loop so that we read at least one token. 151ffe3c632Sopenharmony_ci do 152ffe3c632Sopenharmony_ci { 153ffe3c632Sopenharmony_ci var token = Next(); 154ffe3c632Sopenharmony_ci switch (token.Type) 155ffe3c632Sopenharmony_ci { 156ffe3c632Sopenharmony_ci case JsonToken.TokenType.EndArray: 157ffe3c632Sopenharmony_ci case JsonToken.TokenType.EndObject: 158ffe3c632Sopenharmony_ci depth--; 159ffe3c632Sopenharmony_ci break; 160ffe3c632Sopenharmony_ci case JsonToken.TokenType.StartArray: 161ffe3c632Sopenharmony_ci case JsonToken.TokenType.StartObject: 162ffe3c632Sopenharmony_ci depth++; 163ffe3c632Sopenharmony_ci break; 164ffe3c632Sopenharmony_ci } 165ffe3c632Sopenharmony_ci } while (depth != 0); 166ffe3c632Sopenharmony_ci } 167ffe3c632Sopenharmony_ci 168ffe3c632Sopenharmony_ci /// <summary> 169ffe3c632Sopenharmony_ci /// Tokenizer which first exhausts a list of tokens, then consults another tokenizer. 170ffe3c632Sopenharmony_ci /// </summary> 171ffe3c632Sopenharmony_ci private class JsonReplayTokenizer : JsonTokenizer 172ffe3c632Sopenharmony_ci { 173ffe3c632Sopenharmony_ci private readonly IList<JsonToken> tokens; 174ffe3c632Sopenharmony_ci private readonly JsonTokenizer nextTokenizer; 175ffe3c632Sopenharmony_ci private int nextTokenIndex; 176ffe3c632Sopenharmony_ci 177ffe3c632Sopenharmony_ci internal JsonReplayTokenizer(IList<JsonToken> tokens, JsonTokenizer nextTokenizer) 178ffe3c632Sopenharmony_ci { 179ffe3c632Sopenharmony_ci this.tokens = tokens; 180ffe3c632Sopenharmony_ci this.nextTokenizer = nextTokenizer; 181ffe3c632Sopenharmony_ci } 182ffe3c632Sopenharmony_ci 183ffe3c632Sopenharmony_ci // FIXME: Object depth not maintained... 184ffe3c632Sopenharmony_ci protected override JsonToken NextImpl() 185ffe3c632Sopenharmony_ci { 186ffe3c632Sopenharmony_ci if (nextTokenIndex >= tokens.Count) 187ffe3c632Sopenharmony_ci { 188ffe3c632Sopenharmony_ci return nextTokenizer.Next(); 189ffe3c632Sopenharmony_ci } 190ffe3c632Sopenharmony_ci return tokens[nextTokenIndex++]; 191ffe3c632Sopenharmony_ci } 192ffe3c632Sopenharmony_ci } 193ffe3c632Sopenharmony_ci 194ffe3c632Sopenharmony_ci /// <summary> 195ffe3c632Sopenharmony_ci /// Tokenizer which does all the *real* work of parsing JSON. 196ffe3c632Sopenharmony_ci /// </summary> 197ffe3c632Sopenharmony_ci private sealed class JsonTextTokenizer : JsonTokenizer 198ffe3c632Sopenharmony_ci { 199ffe3c632Sopenharmony_ci // The set of states in which a value is valid next token. 200ffe3c632Sopenharmony_ci private static readonly State ValueStates = State.ArrayStart | State.ArrayAfterComma | State.ObjectAfterColon | State.StartOfDocument; 201ffe3c632Sopenharmony_ci 202ffe3c632Sopenharmony_ci private readonly Stack<ContainerType> containerStack = new Stack<ContainerType>(); 203ffe3c632Sopenharmony_ci private readonly PushBackReader reader; 204ffe3c632Sopenharmony_ci private State state; 205ffe3c632Sopenharmony_ci 206ffe3c632Sopenharmony_ci internal JsonTextTokenizer(TextReader reader) 207ffe3c632Sopenharmony_ci { 208ffe3c632Sopenharmony_ci this.reader = new PushBackReader(reader); 209ffe3c632Sopenharmony_ci state = State.StartOfDocument; 210ffe3c632Sopenharmony_ci containerStack.Push(ContainerType.Document); 211ffe3c632Sopenharmony_ci } 212ffe3c632Sopenharmony_ci 213ffe3c632Sopenharmony_ci /// <remarks> 214ffe3c632Sopenharmony_ci /// This method essentially just loops through characters skipping whitespace, validating and 215ffe3c632Sopenharmony_ci /// changing state (e.g. from ObjectBeforeColon to ObjectAfterColon) 216ffe3c632Sopenharmony_ci /// until it reaches something which will be a genuine token (e.g. a start object, or a value) at which point 217ffe3c632Sopenharmony_ci /// it returns the token. Although the method is large, it would be relatively hard to break down further... most 218ffe3c632Sopenharmony_ci /// of it is the large switch statement, which sometimes returns and sometimes doesn't. 219ffe3c632Sopenharmony_ci /// </remarks> 220ffe3c632Sopenharmony_ci protected override JsonToken NextImpl() 221ffe3c632Sopenharmony_ci { 222ffe3c632Sopenharmony_ci if (state == State.ReaderExhausted) 223ffe3c632Sopenharmony_ci { 224ffe3c632Sopenharmony_ci throw new InvalidOperationException("Next() called after end of document"); 225ffe3c632Sopenharmony_ci } 226ffe3c632Sopenharmony_ci while (true) 227ffe3c632Sopenharmony_ci { 228ffe3c632Sopenharmony_ci var next = reader.Read(); 229ffe3c632Sopenharmony_ci if (next == null) 230ffe3c632Sopenharmony_ci { 231ffe3c632Sopenharmony_ci ValidateState(State.ExpectedEndOfDocument, "Unexpected end of document in state: "); 232ffe3c632Sopenharmony_ci state = State.ReaderExhausted; 233ffe3c632Sopenharmony_ci return JsonToken.EndDocument; 234ffe3c632Sopenharmony_ci } 235ffe3c632Sopenharmony_ci switch (next.Value) 236ffe3c632Sopenharmony_ci { 237ffe3c632Sopenharmony_ci // Skip whitespace between tokens 238ffe3c632Sopenharmony_ci case ' ': 239ffe3c632Sopenharmony_ci case '\t': 240ffe3c632Sopenharmony_ci case '\r': 241ffe3c632Sopenharmony_ci case '\n': 242ffe3c632Sopenharmony_ci break; 243ffe3c632Sopenharmony_ci case ':': 244ffe3c632Sopenharmony_ci ValidateState(State.ObjectBeforeColon, "Invalid state to read a colon: "); 245ffe3c632Sopenharmony_ci state = State.ObjectAfterColon; 246ffe3c632Sopenharmony_ci break; 247ffe3c632Sopenharmony_ci case ',': 248ffe3c632Sopenharmony_ci ValidateState(State.ObjectAfterProperty | State.ArrayAfterValue, "Invalid state to read a comma: "); 249ffe3c632Sopenharmony_ci state = state == State.ObjectAfterProperty ? State.ObjectAfterComma : State.ArrayAfterComma; 250ffe3c632Sopenharmony_ci break; 251ffe3c632Sopenharmony_ci case '"': 252ffe3c632Sopenharmony_ci string stringValue = ReadString(); 253ffe3c632Sopenharmony_ci if ((state & (State.ObjectStart | State.ObjectAfterComma)) != 0) 254ffe3c632Sopenharmony_ci { 255ffe3c632Sopenharmony_ci state = State.ObjectBeforeColon; 256ffe3c632Sopenharmony_ci return JsonToken.Name(stringValue); 257ffe3c632Sopenharmony_ci } 258ffe3c632Sopenharmony_ci else 259ffe3c632Sopenharmony_ci { 260ffe3c632Sopenharmony_ci ValidateAndModifyStateForValue("Invalid state to read a double quote: "); 261ffe3c632Sopenharmony_ci return JsonToken.Value(stringValue); 262ffe3c632Sopenharmony_ci } 263ffe3c632Sopenharmony_ci case '{': 264ffe3c632Sopenharmony_ci ValidateState(ValueStates, "Invalid state to read an open brace: "); 265ffe3c632Sopenharmony_ci state = State.ObjectStart; 266ffe3c632Sopenharmony_ci containerStack.Push(ContainerType.Object); 267ffe3c632Sopenharmony_ci return JsonToken.StartObject; 268ffe3c632Sopenharmony_ci case '}': 269ffe3c632Sopenharmony_ci ValidateState(State.ObjectAfterProperty | State.ObjectStart, "Invalid state to read a close brace: "); 270ffe3c632Sopenharmony_ci PopContainer(); 271ffe3c632Sopenharmony_ci return JsonToken.EndObject; 272ffe3c632Sopenharmony_ci case '[': 273ffe3c632Sopenharmony_ci ValidateState(ValueStates, "Invalid state to read an open square bracket: "); 274ffe3c632Sopenharmony_ci state = State.ArrayStart; 275ffe3c632Sopenharmony_ci containerStack.Push(ContainerType.Array); 276ffe3c632Sopenharmony_ci return JsonToken.StartArray; 277ffe3c632Sopenharmony_ci case ']': 278ffe3c632Sopenharmony_ci ValidateState(State.ArrayAfterValue | State.ArrayStart, "Invalid state to read a close square bracket: "); 279ffe3c632Sopenharmony_ci PopContainer(); 280ffe3c632Sopenharmony_ci return JsonToken.EndArray; 281ffe3c632Sopenharmony_ci case 'n': // Start of null 282ffe3c632Sopenharmony_ci ConsumeLiteral("null"); 283ffe3c632Sopenharmony_ci ValidateAndModifyStateForValue("Invalid state to read a null literal: "); 284ffe3c632Sopenharmony_ci return JsonToken.Null; 285ffe3c632Sopenharmony_ci case 't': // Start of true 286ffe3c632Sopenharmony_ci ConsumeLiteral("true"); 287ffe3c632Sopenharmony_ci ValidateAndModifyStateForValue("Invalid state to read a true literal: "); 288ffe3c632Sopenharmony_ci return JsonToken.True; 289ffe3c632Sopenharmony_ci case 'f': // Start of false 290ffe3c632Sopenharmony_ci ConsumeLiteral("false"); 291ffe3c632Sopenharmony_ci ValidateAndModifyStateForValue("Invalid state to read a false literal: "); 292ffe3c632Sopenharmony_ci return JsonToken.False; 293ffe3c632Sopenharmony_ci case '-': // Start of a number 294ffe3c632Sopenharmony_ci case '0': 295ffe3c632Sopenharmony_ci case '1': 296ffe3c632Sopenharmony_ci case '2': 297ffe3c632Sopenharmony_ci case '3': 298ffe3c632Sopenharmony_ci case '4': 299ffe3c632Sopenharmony_ci case '5': 300ffe3c632Sopenharmony_ci case '6': 301ffe3c632Sopenharmony_ci case '7': 302ffe3c632Sopenharmony_ci case '8': 303ffe3c632Sopenharmony_ci case '9': 304ffe3c632Sopenharmony_ci double number = ReadNumber(next.Value); 305ffe3c632Sopenharmony_ci ValidateAndModifyStateForValue("Invalid state to read a number token: "); 306ffe3c632Sopenharmony_ci return JsonToken.Value(number); 307ffe3c632Sopenharmony_ci default: 308ffe3c632Sopenharmony_ci throw new InvalidJsonException("Invalid first character of token: " + next.Value); 309ffe3c632Sopenharmony_ci } 310ffe3c632Sopenharmony_ci } 311ffe3c632Sopenharmony_ci } 312ffe3c632Sopenharmony_ci 313ffe3c632Sopenharmony_ci private void ValidateState(State validStates, string errorPrefix) 314ffe3c632Sopenharmony_ci { 315ffe3c632Sopenharmony_ci if ((validStates & state) == 0) 316ffe3c632Sopenharmony_ci { 317ffe3c632Sopenharmony_ci throw reader.CreateException(errorPrefix + state); 318ffe3c632Sopenharmony_ci } 319ffe3c632Sopenharmony_ci } 320ffe3c632Sopenharmony_ci 321ffe3c632Sopenharmony_ci /// <summary> 322ffe3c632Sopenharmony_ci /// Reads a string token. It is assumed that the opening " has already been read. 323ffe3c632Sopenharmony_ci /// </summary> 324ffe3c632Sopenharmony_ci private string ReadString() 325ffe3c632Sopenharmony_ci { 326ffe3c632Sopenharmony_ci var value = new StringBuilder(); 327ffe3c632Sopenharmony_ci bool haveHighSurrogate = false; 328ffe3c632Sopenharmony_ci while (true) 329ffe3c632Sopenharmony_ci { 330ffe3c632Sopenharmony_ci char c = reader.ReadOrFail("Unexpected end of text while reading string"); 331ffe3c632Sopenharmony_ci if (c < ' ') 332ffe3c632Sopenharmony_ci { 333ffe3c632Sopenharmony_ci throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in string literal: U+{0:x4}", (int) c)); 334ffe3c632Sopenharmony_ci } 335ffe3c632Sopenharmony_ci if (c == '"') 336ffe3c632Sopenharmony_ci { 337ffe3c632Sopenharmony_ci if (haveHighSurrogate) 338ffe3c632Sopenharmony_ci { 339ffe3c632Sopenharmony_ci throw reader.CreateException("Invalid use of surrogate pair code units"); 340ffe3c632Sopenharmony_ci } 341ffe3c632Sopenharmony_ci return value.ToString(); 342ffe3c632Sopenharmony_ci } 343ffe3c632Sopenharmony_ci if (c == '\\') 344ffe3c632Sopenharmony_ci { 345ffe3c632Sopenharmony_ci c = ReadEscapedCharacter(); 346ffe3c632Sopenharmony_ci } 347ffe3c632Sopenharmony_ci // TODO: Consider only allowing surrogate pairs that are either both escaped, 348ffe3c632Sopenharmony_ci // or both not escaped. It would be a very odd text stream that contained a "lone" high surrogate 349ffe3c632Sopenharmony_ci // followed by an escaped low surrogate or vice versa... and that couldn't even be represented in UTF-8. 350ffe3c632Sopenharmony_ci if (haveHighSurrogate != char.IsLowSurrogate(c)) 351ffe3c632Sopenharmony_ci { 352ffe3c632Sopenharmony_ci throw reader.CreateException("Invalid use of surrogate pair code units"); 353ffe3c632Sopenharmony_ci } 354ffe3c632Sopenharmony_ci haveHighSurrogate = char.IsHighSurrogate(c); 355ffe3c632Sopenharmony_ci value.Append(c); 356ffe3c632Sopenharmony_ci } 357ffe3c632Sopenharmony_ci } 358ffe3c632Sopenharmony_ci 359ffe3c632Sopenharmony_ci /// <summary> 360ffe3c632Sopenharmony_ci /// Reads an escaped character. It is assumed that the leading backslash has already been read. 361ffe3c632Sopenharmony_ci /// </summary> 362ffe3c632Sopenharmony_ci private char ReadEscapedCharacter() 363ffe3c632Sopenharmony_ci { 364ffe3c632Sopenharmony_ci char c = reader.ReadOrFail("Unexpected end of text while reading character escape sequence"); 365ffe3c632Sopenharmony_ci switch (c) 366ffe3c632Sopenharmony_ci { 367ffe3c632Sopenharmony_ci case 'n': 368ffe3c632Sopenharmony_ci return '\n'; 369ffe3c632Sopenharmony_ci case '\\': 370ffe3c632Sopenharmony_ci return '\\'; 371ffe3c632Sopenharmony_ci case 'b': 372ffe3c632Sopenharmony_ci return '\b'; 373ffe3c632Sopenharmony_ci case 'f': 374ffe3c632Sopenharmony_ci return '\f'; 375ffe3c632Sopenharmony_ci case 'r': 376ffe3c632Sopenharmony_ci return '\r'; 377ffe3c632Sopenharmony_ci case 't': 378ffe3c632Sopenharmony_ci return '\t'; 379ffe3c632Sopenharmony_ci case '"': 380ffe3c632Sopenharmony_ci return '"'; 381ffe3c632Sopenharmony_ci case '/': 382ffe3c632Sopenharmony_ci return '/'; 383ffe3c632Sopenharmony_ci case 'u': 384ffe3c632Sopenharmony_ci return ReadUnicodeEscape(); 385ffe3c632Sopenharmony_ci default: 386ffe3c632Sopenharmony_ci throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in character escape sequence: U+{0:x4}", (int) c)); 387ffe3c632Sopenharmony_ci } 388ffe3c632Sopenharmony_ci } 389ffe3c632Sopenharmony_ci 390ffe3c632Sopenharmony_ci /// <summary> 391ffe3c632Sopenharmony_ci /// Reads an escaped Unicode 4-nybble hex sequence. It is assumed that the leading \u has already been read. 392ffe3c632Sopenharmony_ci /// </summary> 393ffe3c632Sopenharmony_ci private char ReadUnicodeEscape() 394ffe3c632Sopenharmony_ci { 395ffe3c632Sopenharmony_ci int result = 0; 396ffe3c632Sopenharmony_ci for (int i = 0; i < 4; i++) 397ffe3c632Sopenharmony_ci { 398ffe3c632Sopenharmony_ci char c = reader.ReadOrFail("Unexpected end of text while reading Unicode escape sequence"); 399ffe3c632Sopenharmony_ci int nybble; 400ffe3c632Sopenharmony_ci if (c >= '0' && c <= '9') 401ffe3c632Sopenharmony_ci { 402ffe3c632Sopenharmony_ci nybble = c - '0'; 403ffe3c632Sopenharmony_ci } 404ffe3c632Sopenharmony_ci else if (c >= 'a' && c <= 'f') 405ffe3c632Sopenharmony_ci { 406ffe3c632Sopenharmony_ci nybble = c - 'a' + 10; 407ffe3c632Sopenharmony_ci } 408ffe3c632Sopenharmony_ci else if (c >= 'A' && c <= 'F') 409ffe3c632Sopenharmony_ci { 410ffe3c632Sopenharmony_ci nybble = c - 'A' + 10; 411ffe3c632Sopenharmony_ci } 412ffe3c632Sopenharmony_ci else 413ffe3c632Sopenharmony_ci { 414ffe3c632Sopenharmony_ci throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in character escape sequence: U+{0:x4}", (int) c)); 415ffe3c632Sopenharmony_ci } 416ffe3c632Sopenharmony_ci result = (result << 4) + nybble; 417ffe3c632Sopenharmony_ci } 418ffe3c632Sopenharmony_ci return (char) result; 419ffe3c632Sopenharmony_ci } 420ffe3c632Sopenharmony_ci 421ffe3c632Sopenharmony_ci /// <summary> 422ffe3c632Sopenharmony_ci /// Consumes a text-only literal, throwing an exception if the read text doesn't match it. 423ffe3c632Sopenharmony_ci /// It is assumed that the first letter of the literal has already been read. 424ffe3c632Sopenharmony_ci /// </summary> 425ffe3c632Sopenharmony_ci private void ConsumeLiteral(string text) 426ffe3c632Sopenharmony_ci { 427ffe3c632Sopenharmony_ci for (int i = 1; i < text.Length; i++) 428ffe3c632Sopenharmony_ci { 429ffe3c632Sopenharmony_ci char? next = reader.Read(); 430ffe3c632Sopenharmony_ci if (next == null) 431ffe3c632Sopenharmony_ci { 432ffe3c632Sopenharmony_ci throw reader.CreateException("Unexpected end of text while reading literal token " + text); 433ffe3c632Sopenharmony_ci } 434ffe3c632Sopenharmony_ci if (next.Value != text[i]) 435ffe3c632Sopenharmony_ci { 436ffe3c632Sopenharmony_ci throw reader.CreateException("Unexpected character while reading literal token " + text); 437ffe3c632Sopenharmony_ci } 438ffe3c632Sopenharmony_ci } 439ffe3c632Sopenharmony_ci } 440ffe3c632Sopenharmony_ci 441ffe3c632Sopenharmony_ci private double ReadNumber(char initialCharacter) 442ffe3c632Sopenharmony_ci { 443ffe3c632Sopenharmony_ci StringBuilder builder = new StringBuilder(); 444ffe3c632Sopenharmony_ci if (initialCharacter == '-') 445ffe3c632Sopenharmony_ci { 446ffe3c632Sopenharmony_ci builder.Append("-"); 447ffe3c632Sopenharmony_ci } 448ffe3c632Sopenharmony_ci else 449ffe3c632Sopenharmony_ci { 450ffe3c632Sopenharmony_ci reader.PushBack(initialCharacter); 451ffe3c632Sopenharmony_ci } 452ffe3c632Sopenharmony_ci // Each method returns the character it read that doesn't belong in that part, 453ffe3c632Sopenharmony_ci // so we know what to do next, including pushing the character back at the end. 454ffe3c632Sopenharmony_ci // null is returned for "end of text". 455ffe3c632Sopenharmony_ci char? next = ReadInt(builder); 456ffe3c632Sopenharmony_ci if (next == '.') 457ffe3c632Sopenharmony_ci { 458ffe3c632Sopenharmony_ci next = ReadFrac(builder); 459ffe3c632Sopenharmony_ci } 460ffe3c632Sopenharmony_ci if (next == 'e' || next == 'E') 461ffe3c632Sopenharmony_ci { 462ffe3c632Sopenharmony_ci next = ReadExp(builder); 463ffe3c632Sopenharmony_ci } 464ffe3c632Sopenharmony_ci // If we read a character which wasn't part of the number, push it back so we can read it again 465ffe3c632Sopenharmony_ci // to parse the next token. 466ffe3c632Sopenharmony_ci if (next != null) 467ffe3c632Sopenharmony_ci { 468ffe3c632Sopenharmony_ci reader.PushBack(next.Value); 469ffe3c632Sopenharmony_ci } 470ffe3c632Sopenharmony_ci 471ffe3c632Sopenharmony_ci // TODO: What exception should we throw if the value can't be represented as a double? 472ffe3c632Sopenharmony_ci try 473ffe3c632Sopenharmony_ci { 474ffe3c632Sopenharmony_ci return double.Parse(builder.ToString(), 475ffe3c632Sopenharmony_ci NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, 476ffe3c632Sopenharmony_ci CultureInfo.InvariantCulture); 477ffe3c632Sopenharmony_ci } 478ffe3c632Sopenharmony_ci catch (OverflowException) 479ffe3c632Sopenharmony_ci { 480ffe3c632Sopenharmony_ci throw reader.CreateException("Numeric value out of range: " + builder); 481ffe3c632Sopenharmony_ci } 482ffe3c632Sopenharmony_ci } 483ffe3c632Sopenharmony_ci 484ffe3c632Sopenharmony_ci private char? ReadInt(StringBuilder builder) 485ffe3c632Sopenharmony_ci { 486ffe3c632Sopenharmony_ci char first = reader.ReadOrFail("Invalid numeric literal"); 487ffe3c632Sopenharmony_ci if (first < '0' || first > '9') 488ffe3c632Sopenharmony_ci { 489ffe3c632Sopenharmony_ci throw reader.CreateException("Invalid numeric literal"); 490ffe3c632Sopenharmony_ci } 491ffe3c632Sopenharmony_ci builder.Append(first); 492ffe3c632Sopenharmony_ci int digitCount; 493ffe3c632Sopenharmony_ci char? next = ConsumeDigits(builder, out digitCount); 494ffe3c632Sopenharmony_ci if (first == '0' && digitCount != 0) 495ffe3c632Sopenharmony_ci { 496ffe3c632Sopenharmony_ci throw reader.CreateException("Invalid numeric literal: leading 0 for non-zero value."); 497ffe3c632Sopenharmony_ci } 498ffe3c632Sopenharmony_ci return next; 499ffe3c632Sopenharmony_ci } 500ffe3c632Sopenharmony_ci 501ffe3c632Sopenharmony_ci private char? ReadFrac(StringBuilder builder) 502ffe3c632Sopenharmony_ci { 503ffe3c632Sopenharmony_ci builder.Append('.'); // Already consumed this 504ffe3c632Sopenharmony_ci int digitCount; 505ffe3c632Sopenharmony_ci char? next = ConsumeDigits(builder, out digitCount); 506ffe3c632Sopenharmony_ci if (digitCount == 0) 507ffe3c632Sopenharmony_ci { 508ffe3c632Sopenharmony_ci throw reader.CreateException("Invalid numeric literal: fraction with no trailing digits"); 509ffe3c632Sopenharmony_ci } 510ffe3c632Sopenharmony_ci return next; 511ffe3c632Sopenharmony_ci } 512ffe3c632Sopenharmony_ci 513ffe3c632Sopenharmony_ci private char? ReadExp(StringBuilder builder) 514ffe3c632Sopenharmony_ci { 515ffe3c632Sopenharmony_ci builder.Append('E'); // Already consumed this (or 'e') 516ffe3c632Sopenharmony_ci char? next = reader.Read(); 517ffe3c632Sopenharmony_ci if (next == null) 518ffe3c632Sopenharmony_ci { 519ffe3c632Sopenharmony_ci throw reader.CreateException("Invalid numeric literal: exponent with no trailing digits"); 520ffe3c632Sopenharmony_ci } 521ffe3c632Sopenharmony_ci if (next == '-' || next == '+') 522ffe3c632Sopenharmony_ci { 523ffe3c632Sopenharmony_ci builder.Append(next.Value); 524ffe3c632Sopenharmony_ci } 525ffe3c632Sopenharmony_ci else 526ffe3c632Sopenharmony_ci { 527ffe3c632Sopenharmony_ci reader.PushBack(next.Value); 528ffe3c632Sopenharmony_ci } 529ffe3c632Sopenharmony_ci int digitCount; 530ffe3c632Sopenharmony_ci next = ConsumeDigits(builder, out digitCount); 531ffe3c632Sopenharmony_ci if (digitCount == 0) 532ffe3c632Sopenharmony_ci { 533ffe3c632Sopenharmony_ci throw reader.CreateException("Invalid numeric literal: exponent without value"); 534ffe3c632Sopenharmony_ci } 535ffe3c632Sopenharmony_ci return next; 536ffe3c632Sopenharmony_ci } 537ffe3c632Sopenharmony_ci 538ffe3c632Sopenharmony_ci private char? ConsumeDigits(StringBuilder builder, out int count) 539ffe3c632Sopenharmony_ci { 540ffe3c632Sopenharmony_ci count = 0; 541ffe3c632Sopenharmony_ci while (true) 542ffe3c632Sopenharmony_ci { 543ffe3c632Sopenharmony_ci char? next = reader.Read(); 544ffe3c632Sopenharmony_ci if (next == null || next.Value < '0' || next.Value > '9') 545ffe3c632Sopenharmony_ci { 546ffe3c632Sopenharmony_ci return next; 547ffe3c632Sopenharmony_ci } 548ffe3c632Sopenharmony_ci count++; 549ffe3c632Sopenharmony_ci builder.Append(next.Value); 550ffe3c632Sopenharmony_ci } 551ffe3c632Sopenharmony_ci } 552ffe3c632Sopenharmony_ci 553ffe3c632Sopenharmony_ci /// <summary> 554ffe3c632Sopenharmony_ci /// Validates that we're in a valid state to read a value (using the given error prefix if necessary) 555ffe3c632Sopenharmony_ci /// and changes the state to the appropriate one, e.g. ObjectAfterColon to ObjectAfterProperty. 556ffe3c632Sopenharmony_ci /// </summary> 557ffe3c632Sopenharmony_ci private void ValidateAndModifyStateForValue(string errorPrefix) 558ffe3c632Sopenharmony_ci { 559ffe3c632Sopenharmony_ci ValidateState(ValueStates, errorPrefix); 560ffe3c632Sopenharmony_ci switch (state) 561ffe3c632Sopenharmony_ci { 562ffe3c632Sopenharmony_ci case State.StartOfDocument: 563ffe3c632Sopenharmony_ci state = State.ExpectedEndOfDocument; 564ffe3c632Sopenharmony_ci return; 565ffe3c632Sopenharmony_ci case State.ObjectAfterColon: 566ffe3c632Sopenharmony_ci state = State.ObjectAfterProperty; 567ffe3c632Sopenharmony_ci return; 568ffe3c632Sopenharmony_ci case State.ArrayStart: 569ffe3c632Sopenharmony_ci case State.ArrayAfterComma: 570ffe3c632Sopenharmony_ci state = State.ArrayAfterValue; 571ffe3c632Sopenharmony_ci return; 572ffe3c632Sopenharmony_ci default: 573ffe3c632Sopenharmony_ci throw new InvalidOperationException("ValidateAndModifyStateForValue does not handle all value states (and should)"); 574ffe3c632Sopenharmony_ci } 575ffe3c632Sopenharmony_ci } 576ffe3c632Sopenharmony_ci 577ffe3c632Sopenharmony_ci /// <summary> 578ffe3c632Sopenharmony_ci /// Pops the top-most container, and sets the state to the appropriate one for the end of a value 579ffe3c632Sopenharmony_ci /// in the parent container. 580ffe3c632Sopenharmony_ci /// </summary> 581ffe3c632Sopenharmony_ci private void PopContainer() 582ffe3c632Sopenharmony_ci { 583ffe3c632Sopenharmony_ci containerStack.Pop(); 584ffe3c632Sopenharmony_ci var parent = containerStack.Peek(); 585ffe3c632Sopenharmony_ci switch (parent) 586ffe3c632Sopenharmony_ci { 587ffe3c632Sopenharmony_ci case ContainerType.Object: 588ffe3c632Sopenharmony_ci state = State.ObjectAfterProperty; 589ffe3c632Sopenharmony_ci break; 590ffe3c632Sopenharmony_ci case ContainerType.Array: 591ffe3c632Sopenharmony_ci state = State.ArrayAfterValue; 592ffe3c632Sopenharmony_ci break; 593ffe3c632Sopenharmony_ci case ContainerType.Document: 594ffe3c632Sopenharmony_ci state = State.ExpectedEndOfDocument; 595ffe3c632Sopenharmony_ci break; 596ffe3c632Sopenharmony_ci default: 597ffe3c632Sopenharmony_ci throw new InvalidOperationException("Unexpected container type: " + parent); 598ffe3c632Sopenharmony_ci } 599ffe3c632Sopenharmony_ci } 600ffe3c632Sopenharmony_ci 601ffe3c632Sopenharmony_ci private enum ContainerType 602ffe3c632Sopenharmony_ci { 603ffe3c632Sopenharmony_ci Document, Object, Array 604ffe3c632Sopenharmony_ci } 605ffe3c632Sopenharmony_ci 606ffe3c632Sopenharmony_ci /// <summary> 607ffe3c632Sopenharmony_ci /// Possible states of the tokenizer. 608ffe3c632Sopenharmony_ci /// </summary> 609ffe3c632Sopenharmony_ci /// <remarks> 610ffe3c632Sopenharmony_ci /// <para>This is a flags enum purely so we can simply and efficiently represent a set of valid states 611ffe3c632Sopenharmony_ci /// for checking.</para> 612ffe3c632Sopenharmony_ci /// <para> 613ffe3c632Sopenharmony_ci /// Each is documented with an example, 614ffe3c632Sopenharmony_ci /// where ^ represents the current position within the text stream. The examples all use string values, 615ffe3c632Sopenharmony_ci /// but could be any value, including nested objects/arrays. 616ffe3c632Sopenharmony_ci /// The complete state of the tokenizer also includes a stack to indicate the contexts (arrays/objects). 617ffe3c632Sopenharmony_ci /// Any additional notional state of "AfterValue" indicates that a value has been completed, at which 618ffe3c632Sopenharmony_ci /// point there's an immediate transition to ExpectedEndOfDocument, ObjectAfterProperty or ArrayAfterValue. 619ffe3c632Sopenharmony_ci /// </para> 620ffe3c632Sopenharmony_ci /// <para> 621ffe3c632Sopenharmony_ci /// These states were derived manually by reading RFC 7159 carefully. 622ffe3c632Sopenharmony_ci /// </para> 623ffe3c632Sopenharmony_ci /// </remarks> 624ffe3c632Sopenharmony_ci [Flags] 625ffe3c632Sopenharmony_ci private enum State 626ffe3c632Sopenharmony_ci { 627ffe3c632Sopenharmony_ci /// <summary> 628ffe3c632Sopenharmony_ci /// ^ { "foo": "bar" } 629ffe3c632Sopenharmony_ci /// Before the value in a document. Next states: ObjectStart, ArrayStart, "AfterValue" 630ffe3c632Sopenharmony_ci /// </summary> 631ffe3c632Sopenharmony_ci StartOfDocument = 1 << 0, 632ffe3c632Sopenharmony_ci /// <summary> 633ffe3c632Sopenharmony_ci /// { "foo": "bar" } ^ 634ffe3c632Sopenharmony_ci /// After the value in a document. Next states: ReaderExhausted 635ffe3c632Sopenharmony_ci /// </summary> 636ffe3c632Sopenharmony_ci ExpectedEndOfDocument = 1 << 1, 637ffe3c632Sopenharmony_ci /// <summary> 638ffe3c632Sopenharmony_ci /// { "foo": "bar" } ^ (and already read to the end of the reader) 639ffe3c632Sopenharmony_ci /// Terminal state. 640ffe3c632Sopenharmony_ci /// </summary> 641ffe3c632Sopenharmony_ci ReaderExhausted = 1 << 2, 642ffe3c632Sopenharmony_ci /// <summary> 643ffe3c632Sopenharmony_ci /// { ^ "foo": "bar" } 644ffe3c632Sopenharmony_ci /// Before the *first* property in an object. 645ffe3c632Sopenharmony_ci /// Next states: 646ffe3c632Sopenharmony_ci /// "AfterValue" (empty object) 647ffe3c632Sopenharmony_ci /// ObjectBeforeColon (read a name) 648ffe3c632Sopenharmony_ci /// </summary> 649ffe3c632Sopenharmony_ci ObjectStart = 1 << 3, 650ffe3c632Sopenharmony_ci /// <summary> 651ffe3c632Sopenharmony_ci /// { "foo" ^ : "bar", "x": "y" } 652ffe3c632Sopenharmony_ci /// Next state: ObjectAfterColon 653ffe3c632Sopenharmony_ci /// </summary> 654ffe3c632Sopenharmony_ci ObjectBeforeColon = 1 << 4, 655ffe3c632Sopenharmony_ci /// <summary> 656ffe3c632Sopenharmony_ci /// { "foo" : ^ "bar", "x": "y" } 657ffe3c632Sopenharmony_ci /// Before any property other than the first in an object. 658ffe3c632Sopenharmony_ci /// (Equivalently: after any property in an object) 659ffe3c632Sopenharmony_ci /// Next states: 660ffe3c632Sopenharmony_ci /// "AfterValue" (value is simple) 661ffe3c632Sopenharmony_ci /// ObjectStart (value is object) 662ffe3c632Sopenharmony_ci /// ArrayStart (value is array) 663ffe3c632Sopenharmony_ci /// </summary> 664ffe3c632Sopenharmony_ci ObjectAfterColon = 1 << 5, 665ffe3c632Sopenharmony_ci /// <summary> 666ffe3c632Sopenharmony_ci /// { "foo" : "bar" ^ , "x" : "y" } 667ffe3c632Sopenharmony_ci /// At the end of a property, so expecting either a comma or end-of-object 668ffe3c632Sopenharmony_ci /// Next states: ObjectAfterComma or "AfterValue" 669ffe3c632Sopenharmony_ci /// </summary> 670ffe3c632Sopenharmony_ci ObjectAfterProperty = 1 << 6, 671ffe3c632Sopenharmony_ci /// <summary> 672ffe3c632Sopenharmony_ci /// { "foo":"bar", ^ "x":"y" } 673ffe3c632Sopenharmony_ci /// Read the comma after the previous property, so expecting another property. 674ffe3c632Sopenharmony_ci /// This is like ObjectStart, but closing brace isn't valid here 675ffe3c632Sopenharmony_ci /// Next state: ObjectBeforeColon. 676ffe3c632Sopenharmony_ci /// </summary> 677ffe3c632Sopenharmony_ci ObjectAfterComma = 1 << 7, 678ffe3c632Sopenharmony_ci /// <summary> 679ffe3c632Sopenharmony_ci /// [ ^ "foo", "bar" ] 680ffe3c632Sopenharmony_ci /// Before the *first* value in an array. 681ffe3c632Sopenharmony_ci /// Next states: 682ffe3c632Sopenharmony_ci /// "AfterValue" (read a value) 683ffe3c632Sopenharmony_ci /// "AfterValue" (end of array; will pop stack) 684ffe3c632Sopenharmony_ci /// </summary> 685ffe3c632Sopenharmony_ci ArrayStart = 1 << 8, 686ffe3c632Sopenharmony_ci /// <summary> 687ffe3c632Sopenharmony_ci /// [ "foo" ^ , "bar" ] 688ffe3c632Sopenharmony_ci /// After any value in an array, so expecting either a comma or end-of-array 689ffe3c632Sopenharmony_ci /// Next states: ArrayAfterComma or "AfterValue" 690ffe3c632Sopenharmony_ci /// </summary> 691ffe3c632Sopenharmony_ci ArrayAfterValue = 1 << 9, 692ffe3c632Sopenharmony_ci /// <summary> 693ffe3c632Sopenharmony_ci /// [ "foo", ^ "bar" ] 694ffe3c632Sopenharmony_ci /// After a comma in an array, so there *must* be another value (simple or complex). 695ffe3c632Sopenharmony_ci /// Next states: "AfterValue" (simple value), StartObject, StartArray 696ffe3c632Sopenharmony_ci /// </summary> 697ffe3c632Sopenharmony_ci ArrayAfterComma = 1 << 10 698ffe3c632Sopenharmony_ci } 699ffe3c632Sopenharmony_ci 700ffe3c632Sopenharmony_ci /// <summary> 701ffe3c632Sopenharmony_ci /// Wrapper around a text reader allowing small amounts of buffering and location handling. 702ffe3c632Sopenharmony_ci /// </summary> 703ffe3c632Sopenharmony_ci private class PushBackReader 704ffe3c632Sopenharmony_ci { 705ffe3c632Sopenharmony_ci // TODO: Add locations for errors etc. 706ffe3c632Sopenharmony_ci 707ffe3c632Sopenharmony_ci private readonly TextReader reader; 708ffe3c632Sopenharmony_ci 709ffe3c632Sopenharmony_ci internal PushBackReader(TextReader reader) 710ffe3c632Sopenharmony_ci { 711ffe3c632Sopenharmony_ci // TODO: Wrap the reader in a BufferedReader? 712ffe3c632Sopenharmony_ci this.reader = reader; 713ffe3c632Sopenharmony_ci } 714ffe3c632Sopenharmony_ci 715ffe3c632Sopenharmony_ci /// <summary> 716ffe3c632Sopenharmony_ci /// The buffered next character, if we have one. 717ffe3c632Sopenharmony_ci /// </summary> 718ffe3c632Sopenharmony_ci private char? nextChar; 719ffe3c632Sopenharmony_ci 720ffe3c632Sopenharmony_ci /// <summary> 721ffe3c632Sopenharmony_ci /// Returns the next character in the stream, or null if we have reached the end. 722ffe3c632Sopenharmony_ci /// </summary> 723ffe3c632Sopenharmony_ci /// <returns></returns> 724ffe3c632Sopenharmony_ci internal char? Read() 725ffe3c632Sopenharmony_ci { 726ffe3c632Sopenharmony_ci if (nextChar != null) 727ffe3c632Sopenharmony_ci { 728ffe3c632Sopenharmony_ci char? tmp = nextChar; 729ffe3c632Sopenharmony_ci nextChar = null; 730ffe3c632Sopenharmony_ci return tmp; 731ffe3c632Sopenharmony_ci } 732ffe3c632Sopenharmony_ci int next = reader.Read(); 733ffe3c632Sopenharmony_ci return next == -1 ? null : (char?) next; 734ffe3c632Sopenharmony_ci } 735ffe3c632Sopenharmony_ci 736ffe3c632Sopenharmony_ci internal char ReadOrFail(string messageOnFailure) 737ffe3c632Sopenharmony_ci { 738ffe3c632Sopenharmony_ci char? next = Read(); 739ffe3c632Sopenharmony_ci if (next == null) 740ffe3c632Sopenharmony_ci { 741ffe3c632Sopenharmony_ci throw CreateException(messageOnFailure); 742ffe3c632Sopenharmony_ci } 743ffe3c632Sopenharmony_ci return next.Value; 744ffe3c632Sopenharmony_ci } 745ffe3c632Sopenharmony_ci 746ffe3c632Sopenharmony_ci internal void PushBack(char c) 747ffe3c632Sopenharmony_ci { 748ffe3c632Sopenharmony_ci if (nextChar != null) 749ffe3c632Sopenharmony_ci { 750ffe3c632Sopenharmony_ci throw new InvalidOperationException("Cannot push back when already buffering a character"); 751ffe3c632Sopenharmony_ci } 752ffe3c632Sopenharmony_ci nextChar = c; 753ffe3c632Sopenharmony_ci } 754ffe3c632Sopenharmony_ci 755ffe3c632Sopenharmony_ci /// <summary> 756ffe3c632Sopenharmony_ci /// Creates a new exception appropriate for the current state of the reader. 757ffe3c632Sopenharmony_ci /// </summary> 758ffe3c632Sopenharmony_ci internal InvalidJsonException CreateException(string message) 759ffe3c632Sopenharmony_ci { 760ffe3c632Sopenharmony_ci // TODO: Keep track of and use the location. 761ffe3c632Sopenharmony_ci return new InvalidJsonException(message); 762ffe3c632Sopenharmony_ci } 763ffe3c632Sopenharmony_ci } 764ffe3c632Sopenharmony_ci } 765ffe3c632Sopenharmony_ci } 766ffe3c632Sopenharmony_ci} 767