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