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         /// Skips the value we're about to read. This must only be called immediately after reading a property name.
142         /// If the value is an object or an array, the complete object/array is skipped.
143         /// </summary>
SkipValue()144         internal void SkipValue()
145         {
146             // We'll assume that Next() makes sure that the end objects and end arrays are all valid.
147             // All we care about is the total nesting depth we need to close.
148             int depth = 0;
149 
150             // do/while rather than while loop so that we read at least one token.
151             do
152             {
153                 var token = Next();
154                 switch (token.Type)
155                 {
156                     case JsonToken.TokenType.EndArray:
157                     case JsonToken.TokenType.EndObject:
158                         depth--;
159                         break;
160                     case JsonToken.TokenType.StartArray:
161                     case JsonToken.TokenType.StartObject:
162                         depth++;
163                         break;
164                 }
165             } while (depth != 0);
166         }
167 
168         /// <summary>
169         /// Tokenizer which first exhausts a list of tokens, then consults another tokenizer.
170         /// </summary>
171         private class JsonReplayTokenizer : JsonTokenizer
172         {
173             private readonly IList<JsonToken> tokens;
174             private readonly JsonTokenizer nextTokenizer;
175             private int nextTokenIndex;
176 
JsonReplayTokenizer(IList<JsonToken> tokens, JsonTokenizer nextTokenizer)177             internal JsonReplayTokenizer(IList<JsonToken> tokens, JsonTokenizer nextTokenizer)
178             {
179                 this.tokens = tokens;
180                 this.nextTokenizer = nextTokenizer;
181             }
182 
183             // FIXME: Object depth not maintained...
NextImpl()184             protected override JsonToken NextImpl()
185             {
186                 if (nextTokenIndex >= tokens.Count)
187                 {
188                     return nextTokenizer.Next();
189                 }
190                 return tokens[nextTokenIndex++];
191             }
192         }
193 
194         /// <summary>
195         /// Tokenizer which does all the *real* work of parsing JSON.
196         /// </summary>
197         private sealed class JsonTextTokenizer : JsonTokenizer
198         {
199             // The set of states in which a value is valid next token.
200             private static readonly State ValueStates = State.ArrayStart | State.ArrayAfterComma | State.ObjectAfterColon | State.StartOfDocument;
201 
202             private readonly Stack<ContainerType> containerStack = new Stack<ContainerType>();
203             private readonly PushBackReader reader;
204             private State state;
205 
JsonTextTokenizer(TextReader reader)206             internal JsonTextTokenizer(TextReader reader)
207             {
208                 this.reader = new PushBackReader(reader);
209                 state = State.StartOfDocument;
210                 containerStack.Push(ContainerType.Document);
211             }
212 
213             /// <remarks>
214             /// This method essentially just loops through characters skipping whitespace, validating and
215             /// changing state (e.g. from ObjectBeforeColon to ObjectAfterColon)
216             /// until it reaches something which will be a genuine token (e.g. a start object, or a value) at which point
217             /// it returns the token. Although the method is large, it would be relatively hard to break down further... most
218             /// of it is the large switch statement, which sometimes returns and sometimes doesn't.
219             /// </remarks>
NextImpl()220             protected override JsonToken NextImpl()
221             {
222                 if (state == State.ReaderExhausted)
223                 {
224                     throw new InvalidOperationException("Next() called after end of document");
225                 }
226                 while (true)
227                 {
228                     var next = reader.Read();
229                     if (next == null)
230                     {
231                         ValidateState(State.ExpectedEndOfDocument, "Unexpected end of document in state: ");
232                         state = State.ReaderExhausted;
233                         return JsonToken.EndDocument;
234                     }
235                     switch (next.Value)
236                     {
237                         // Skip whitespace between tokens
238                         case ' ':
239                         case '\t':
240                         case '\r':
241                         case '\n':
242                             break;
243                         case ':':
244                             ValidateState(State.ObjectBeforeColon, "Invalid state to read a colon: ");
245                             state = State.ObjectAfterColon;
246                             break;
247                         case ',':
248                             ValidateState(State.ObjectAfterProperty | State.ArrayAfterValue, "Invalid state to read a comma: ");
249                             state = state == State.ObjectAfterProperty ? State.ObjectAfterComma : State.ArrayAfterComma;
250                             break;
251                         case '"':
252                             string stringValue = ReadString();
253                             if ((state & (State.ObjectStart | State.ObjectAfterComma)) != 0)
254                             {
255                                 state = State.ObjectBeforeColon;
256                                 return JsonToken.Name(stringValue);
257                             }
258                             else
259                             {
260                                 ValidateAndModifyStateForValue("Invalid state to read a double quote: ");
261                                 return JsonToken.Value(stringValue);
262                             }
263                         case '{':
264                             ValidateState(ValueStates, "Invalid state to read an open brace: ");
265                             state = State.ObjectStart;
266                             containerStack.Push(ContainerType.Object);
267                             return JsonToken.StartObject;
268                         case '}':
269                             ValidateState(State.ObjectAfterProperty | State.ObjectStart, "Invalid state to read a close brace: ");
270                             PopContainer();
271                             return JsonToken.EndObject;
272                         case '[':
273                             ValidateState(ValueStates, "Invalid state to read an open square bracket: ");
274                             state = State.ArrayStart;
275                             containerStack.Push(ContainerType.Array);
276                             return JsonToken.StartArray;
277                         case ']':
278                             ValidateState(State.ArrayAfterValue | State.ArrayStart, "Invalid state to read a close square bracket: ");
279                             PopContainer();
280                             return JsonToken.EndArray;
281                         case 'n': // Start of null
282                             ConsumeLiteral("null");
283                             ValidateAndModifyStateForValue("Invalid state to read a null literal: ");
284                             return JsonToken.Null;
285                         case 't': // Start of true
286                             ConsumeLiteral("true");
287                             ValidateAndModifyStateForValue("Invalid state to read a true literal: ");
288                             return JsonToken.True;
289                         case 'f': // Start of false
290                             ConsumeLiteral("false");
291                             ValidateAndModifyStateForValue("Invalid state to read a false literal: ");
292                             return JsonToken.False;
293                         case '-': // Start of a number
294                         case '0':
295                         case '1':
296                         case '2':
297                         case '3':
298                         case '4':
299                         case '5':
300                         case '6':
301                         case '7':
302                         case '8':
303                         case '9':
304                             double number = ReadNumber(next.Value);
305                             ValidateAndModifyStateForValue("Invalid state to read a number token: ");
306                             return JsonToken.Value(number);
307                         default:
308                             throw new InvalidJsonException("Invalid first character of token: " + next.Value);
309                     }
310                 }
311             }
312 
ValidateState(State validStates, string errorPrefix)313             private void ValidateState(State validStates, string errorPrefix)
314             {
315                 if ((validStates & state) == 0)
316                 {
317                     throw reader.CreateException(errorPrefix + state);
318                 }
319             }
320 
321             /// <summary>
322             /// Reads a string token. It is assumed that the opening " has already been read.
323             /// </summary>
ReadString()324             private string ReadString()
325             {
326                 var value = new StringBuilder();
327                 bool haveHighSurrogate = false;
328                 while (true)
329                 {
330                     char c = reader.ReadOrFail("Unexpected end of text while reading string");
331                     if (c < ' ')
332                     {
333                         throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in string literal: U+{0:x4}", (int) c));
334                     }
335                     if (c == '"')
336                     {
337                         if (haveHighSurrogate)
338                         {
339                             throw reader.CreateException("Invalid use of surrogate pair code units");
340                         }
341                         return value.ToString();
342                     }
343                     if (c == '\\')
344                     {
345                         c = ReadEscapedCharacter();
346                     }
347                     // TODO: Consider only allowing surrogate pairs that are either both escaped,
348                     // or both not escaped. It would be a very odd text stream that contained a "lone" high surrogate
349                     // followed by an escaped low surrogate or vice versa... and that couldn't even be represented in UTF-8.
350                     if (haveHighSurrogate != char.IsLowSurrogate(c))
351                     {
352                         throw reader.CreateException("Invalid use of surrogate pair code units");
353                     }
354                     haveHighSurrogate = char.IsHighSurrogate(c);
355                     value.Append(c);
356                 }
357             }
358 
359             /// <summary>
360             /// Reads an escaped character. It is assumed that the leading backslash has already been read.
361             /// </summary>
ReadEscapedCharacter()362             private char ReadEscapedCharacter()
363             {
364                 char c = reader.ReadOrFail("Unexpected end of text while reading character escape sequence");
365                 switch (c)
366                 {
367                     case 'n':
368                         return '\n';
369                     case '\\':
370                         return '\\';
371                     case 'b':
372                         return '\b';
373                     case 'f':
374                         return '\f';
375                     case 'r':
376                         return '\r';
377                     case 't':
378                         return '\t';
379                     case '"':
380                         return '"';
381                     case '/':
382                         return '/';
383                     case 'u':
384                         return ReadUnicodeEscape();
385                     default:
386                         throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in character escape sequence: U+{0:x4}", (int) c));
387                 }
388             }
389 
390             /// <summary>
391             /// Reads an escaped Unicode 4-nybble hex sequence. It is assumed that the leading \u has already been read.
392             /// </summary>
ReadUnicodeEscape()393             private char ReadUnicodeEscape()
394             {
395                 int result = 0;
396                 for (int i = 0; i < 4; i++)
397                 {
398                     char c = reader.ReadOrFail("Unexpected end of text while reading Unicode escape sequence");
399                     int nybble;
400                     if (c >= '0' && c <= '9')
401                     {
402                         nybble = c - '0';
403                     }
404                     else if (c >= 'a' && c <= 'f')
405                     {
406                         nybble = c - 'a' + 10;
407                     }
408                     else if (c >= 'A' && c <= 'F')
409                     {
410                         nybble = c - 'A' + 10;
411                     }
412                     else
413                     {
414                         throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in character escape sequence: U+{0:x4}", (int) c));
415                     }
416                     result = (result << 4) + nybble;
417                 }
418                 return (char) result;
419             }
420 
421             /// <summary>
422             /// Consumes a text-only literal, throwing an exception if the read text doesn't match it.
423             /// It is assumed that the first letter of the literal has already been read.
424             /// </summary>
ConsumeLiteral(string text)425             private void ConsumeLiteral(string text)
426             {
427                 for (int i = 1; i < text.Length; i++)
428                 {
429                     char? next = reader.Read();
430                     if (next == null)
431                     {
432                         throw reader.CreateException("Unexpected end of text while reading literal token " + text);
433                     }
434                     if (next.Value != text[i])
435                     {
436                         throw reader.CreateException("Unexpected character while reading literal token " + text);
437                     }
438                 }
439             }
440 
ReadNumber(char initialCharacter)441             private double ReadNumber(char initialCharacter)
442             {
443                 StringBuilder builder = new StringBuilder();
444                 if (initialCharacter == '-')
445                 {
446                     builder.Append("-");
447                 }
448                 else
449                 {
450                     reader.PushBack(initialCharacter);
451                 }
452                 // Each method returns the character it read that doesn't belong in that part,
453                 // so we know what to do next, including pushing the character back at the end.
454                 // null is returned for "end of text".
455                 char? next = ReadInt(builder);
456                 if (next == '.')
457                 {
458                     next = ReadFrac(builder);
459                 }
460                 if (next == 'e' || next == 'E')
461                 {
462                     next = ReadExp(builder);
463                 }
464                 // If we read a character which wasn't part of the number, push it back so we can read it again
465                 // to parse the next token.
466                 if (next != null)
467                 {
468                     reader.PushBack(next.Value);
469                 }
470 
471                 // TODO: What exception should we throw if the value can't be represented as a double?
472                 try
473                 {
474                     return double.Parse(builder.ToString(),
475                         NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent,
476                         CultureInfo.InvariantCulture);
477                 }
478                 catch (OverflowException)
479                 {
480                     throw reader.CreateException("Numeric value out of range: " + builder);
481                 }
482             }
483 
ReadInt(StringBuilder builder)484             private char? ReadInt(StringBuilder builder)
485             {
486                 char first = reader.ReadOrFail("Invalid numeric literal");
487                 if (first < '0' || first > '9')
488                 {
489                     throw reader.CreateException("Invalid numeric literal");
490                 }
491                 builder.Append(first);
492                 int digitCount;
493                 char? next = ConsumeDigits(builder, out digitCount);
494                 if (first == '0' && digitCount != 0)
495                 {
496                     throw reader.CreateException("Invalid numeric literal: leading 0 for non-zero value.");
497                 }
498                 return next;
499             }
500 
ReadFrac(StringBuilder builder)501             private char? ReadFrac(StringBuilder builder)
502             {
503                 builder.Append('.'); // Already consumed this
504                 int digitCount;
505                 char? next = ConsumeDigits(builder, out digitCount);
506                 if (digitCount == 0)
507                 {
508                     throw reader.CreateException("Invalid numeric literal: fraction with no trailing digits");
509                 }
510                 return next;
511             }
512 
ReadExp(StringBuilder builder)513             private char? ReadExp(StringBuilder builder)
514             {
515                 builder.Append('E'); // Already consumed this (or 'e')
516                 char? next = reader.Read();
517                 if (next == null)
518                 {
519                     throw reader.CreateException("Invalid numeric literal: exponent with no trailing digits");
520                 }
521                 if (next == '-' || next == '+')
522                 {
523                     builder.Append(next.Value);
524                 }
525                 else
526                 {
527                     reader.PushBack(next.Value);
528                 }
529                 int digitCount;
530                 next = ConsumeDigits(builder, out digitCount);
531                 if (digitCount == 0)
532                 {
533                     throw reader.CreateException("Invalid numeric literal: exponent without value");
534                 }
535                 return next;
536             }
537 
ConsumeDigits(StringBuilder builder, out int count)538             private char? ConsumeDigits(StringBuilder builder, out int count)
539             {
540                 count = 0;
541                 while (true)
542                 {
543                     char? next = reader.Read();
544                     if (next == null || next.Value < '0' || next.Value > '9')
545                     {
546                         return next;
547                     }
548                     count++;
549                     builder.Append(next.Value);
550                 }
551             }
552 
553             /// <summary>
554             /// Validates that we're in a valid state to read a value (using the given error prefix if necessary)
555             /// and changes the state to the appropriate one, e.g. ObjectAfterColon to ObjectAfterProperty.
556             /// </summary>
ValidateAndModifyStateForValue(string errorPrefix)557             private void ValidateAndModifyStateForValue(string errorPrefix)
558             {
559                 ValidateState(ValueStates, errorPrefix);
560                 switch (state)
561                 {
562                     case State.StartOfDocument:
563                         state = State.ExpectedEndOfDocument;
564                         return;
565                     case State.ObjectAfterColon:
566                         state = State.ObjectAfterProperty;
567                         return;
568                     case State.ArrayStart:
569                     case State.ArrayAfterComma:
570                         state = State.ArrayAfterValue;
571                         return;
572                     default:
573                         throw new InvalidOperationException("ValidateAndModifyStateForValue does not handle all value states (and should)");
574                 }
575             }
576 
577             /// <summary>
578             /// Pops the top-most container, and sets the state to the appropriate one for the end of a value
579             /// in the parent container.
580             /// </summary>
PopContainer()581             private void PopContainer()
582             {
583                 containerStack.Pop();
584                 var parent = containerStack.Peek();
585                 switch (parent)
586                 {
587                     case ContainerType.Object:
588                         state = State.ObjectAfterProperty;
589                         break;
590                     case ContainerType.Array:
591                         state = State.ArrayAfterValue;
592                         break;
593                     case ContainerType.Document:
594                         state = State.ExpectedEndOfDocument;
595                         break;
596                     default:
597                         throw new InvalidOperationException("Unexpected container type: " + parent);
598                 }
599             }
600 
601             private enum ContainerType
602             {
603                 Document, Object, Array
604             }
605 
606             /// <summary>
607             /// Possible states of the tokenizer.
608             /// </summary>
609             /// <remarks>
610             /// <para>This is a flags enum purely so we can simply and efficiently represent a set of valid states
611             /// for checking.</para>
612             /// <para>
613             /// Each is documented with an example,
614             /// where ^ represents the current position within the text stream. The examples all use string values,
615             /// but could be any value, including nested objects/arrays.
616             /// The complete state of the tokenizer also includes a stack to indicate the contexts (arrays/objects).
617             /// Any additional notional state of "AfterValue" indicates that a value has been completed, at which
618             /// point there's an immediate transition to ExpectedEndOfDocument,  ObjectAfterProperty or ArrayAfterValue.
619             /// </para>
620             /// <para>
621             /// These states were derived manually by reading RFC 7159 carefully.
622             /// </para>
623             /// </remarks>
624             [Flags]
625             private enum State
626             {
627                 /// <summary>
628                 /// ^ { "foo": "bar" }
629                 /// Before the value in a document. Next states: ObjectStart, ArrayStart, "AfterValue"
630                 /// </summary>
631                 StartOfDocument = 1 << 0,
632                 /// <summary>
633                 /// { "foo": "bar" } ^
634                 /// After the value in a document. Next states: ReaderExhausted
635                 /// </summary>
636                 ExpectedEndOfDocument = 1 << 1,
637                 /// <summary>
638                 /// { "foo": "bar" } ^ (and already read to the end of the reader)
639                 /// Terminal state.
640                 /// </summary>
641                 ReaderExhausted = 1 << 2,
642                 /// <summary>
643                 /// { ^ "foo": "bar" }
644                 /// Before the *first* property in an object.
645                 /// Next states:
646                 /// "AfterValue" (empty object)
647                 /// ObjectBeforeColon (read a name)
648                 /// </summary>
649                 ObjectStart = 1 << 3,
650                 /// <summary>
651                 /// { "foo" ^ : "bar", "x": "y" }
652                 /// Next state: ObjectAfterColon
653                 /// </summary>
654                 ObjectBeforeColon = 1 << 4,
655                 /// <summary>
656                 /// { "foo" : ^ "bar", "x": "y" }
657                 /// Before any property other than the first in an object.
658                 /// (Equivalently: after any property in an object)
659                 /// Next states:
660                 /// "AfterValue" (value is simple)
661                 /// ObjectStart (value is object)
662                 /// ArrayStart (value is array)
663                 /// </summary>
664                 ObjectAfterColon = 1 << 5,
665                 /// <summary>
666                 /// { "foo" : "bar" ^ , "x" : "y" }
667                 /// At the end of a property, so expecting either a comma or end-of-object
668                 /// Next states: ObjectAfterComma or "AfterValue"
669                 /// </summary>
670                 ObjectAfterProperty = 1 << 6,
671                 /// <summary>
672                 /// { "foo":"bar", ^ "x":"y" }
673                 /// Read the comma after the previous property, so expecting another property.
674                 /// This is like ObjectStart, but closing brace isn't valid here
675                 /// Next state: ObjectBeforeColon.
676                 /// </summary>
677                 ObjectAfterComma = 1 << 7,
678                 /// <summary>
679                 /// [ ^ "foo", "bar" ]
680                 /// Before the *first* value in an array.
681                 /// Next states:
682                 /// "AfterValue" (read a value)
683                 /// "AfterValue" (end of array; will pop stack)
684                 /// </summary>
685                 ArrayStart = 1 << 8,
686                 /// <summary>
687                 /// [ "foo" ^ , "bar" ]
688                 /// After any value in an array, so expecting either a comma or end-of-array
689                 /// Next states: ArrayAfterComma or "AfterValue"
690                 /// </summary>
691                 ArrayAfterValue = 1 << 9,
692                 /// <summary>
693                 /// [ "foo", ^ "bar" ]
694                 /// After a comma in an array, so there *must* be another value (simple or complex).
695                 /// Next states: "AfterValue" (simple value), StartObject, StartArray
696                 /// </summary>
697                 ArrayAfterComma = 1 << 10
698             }
699 
700             /// <summary>
701             /// Wrapper around a text reader allowing small amounts of buffering and location handling.
702             /// </summary>
703             private class PushBackReader
704             {
705                 // TODO: Add locations for errors etc.
706 
707                 private readonly TextReader reader;
708 
PushBackReader(TextReader reader)709                 internal PushBackReader(TextReader reader)
710                 {
711                     // TODO: Wrap the reader in a BufferedReader?
712                     this.reader = reader;
713                 }
714 
715                 /// <summary>
716                 /// The buffered next character, if we have one.
717                 /// </summary>
718                 private char? nextChar;
719 
720                 /// <summary>
721                 /// Returns the next character in the stream, or null if we have reached the end.
722                 /// </summary>
723                 /// <returns></returns>
Read()724                 internal char? Read()
725                 {
726                     if (nextChar != null)
727                     {
728                         char? tmp = nextChar;
729                         nextChar = null;
730                         return tmp;
731                     }
732                     int next = reader.Read();
733                     return next == -1 ? null : (char?) next;
734                 }
735 
ReadOrFail(string messageOnFailure)736                 internal char ReadOrFail(string messageOnFailure)
737                 {
738                     char? next = Read();
739                     if (next == null)
740                     {
741                         throw CreateException(messageOnFailure);
742                     }
743                     return next.Value;
744                 }
745 
PushBack(char c)746                 internal void PushBack(char c)
747                 {
748                     if (nextChar != null)
749                     {
750                         throw new InvalidOperationException("Cannot push back when already buffering a character");
751                     }
752                     nextChar = c;
753                 }
754 
755                 /// <summary>
756                 /// Creates a new exception appropriate for the current state of the reader.
757                 /// </summary>
CreateException(string message)758                 internal InvalidJsonException CreateException(string message)
759                 {
760                     // TODO: Keep track of and use the location.
761                     return new InvalidJsonException(message);
762                 }
763             }
764         }
765     }
766 }
767