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