private/runtime/Parser/JsonParser.cs

using System;
using System.Collections.Generic;
using System.IO;

namespace Carbon.Json.Parser
{
    public class JsonParser : IDisposable
    {
        private readonly TokenReader reader;

        public JsonParser(TextReader reader)
            : this(new SourceReader(reader)) { }

        internal JsonParser(SourceReader sourceReader)
        {
            if (sourceReader == null)
                throw new ArgumentNullException(nameof(sourceReader));

            this.reader = new TokenReader(new JsonTokenizer(sourceReader));

            this.reader.Next(); // Start with the first token
        }

        public IEnumerable<JsonNode> ReadNodes()
        {
            JsonNode node;

            while ((node = ReadNode()) != null) yield return node;
        }

        public JsonNode ReadNode()
        {
            if (reader.Current.Kind == TokenKind.Eof || reader.Current.IsTerminator)
            {
                return null;
            }

            switch (reader.Current.Kind)
            {
                case TokenKind.LeftBrace : return ReadObject(); // {
                case TokenKind.LeftBracket : return ReadArray(); // [

                default: throw new ParserException($"Expected '{{' or '['. Was {reader.Current}.");
            }
        }

        private JsonNode ReadFieldValue()
        {
            // Boolean, Date, Null, Number, String, Uri
            if (reader.Current.IsLiteral)
            {
                return ReadLiteral();
            }
            else
            {
                switch (reader.Current.Kind)
                {
                    case TokenKind.LeftBracket: return ReadArray();
                    case TokenKind.LeftBrace : return ReadObject();

                    default: throw new ParserException($"Unexpected token reading field value. Was {reader.Current}.");
                }
            }
        }

        private JsonNode ReadLiteral()
        {
            var literal = reader.Current;

            reader.Next(); // Read the literal token

            switch (literal.Kind)
            {
                case TokenKind.Boolean : return JsonBoolean.Parse(literal.Value);
                case TokenKind.Null : return XNull.Instance;
                case TokenKind.Number : return new JsonNumber(literal.Value);
                case TokenKind.String : return new JsonString(literal.Value);

                default: throw new ParserException($"Unexpected token reading literal. Was {literal}.");
            }
        }

        public JsonObject ReadObject()
        {
            reader.Ensure(TokenKind.LeftBrace, "object");

            reader.Next(); // Read '{' (Object start)

            var jsonObject = new JsonObject();

            // Read the object's fields until we reach the end of the object ('}')
            while (reader.Current.Kind != TokenKind.RightBrace)
            {
                if (reader.Current.Kind == TokenKind.Comma)
                {
                    reader.Next(); // Read ',' (Seperator)
                }

                // Ensure we have a field name
                reader.Ensure(TokenKind.String, "Expected field name");

                var field = ReadField();

                jsonObject.Add(field.Key, field.Value);
            }

            reader.Next(); // Read '}' (Object end)

            return jsonObject;
        }


        // TODO: Use ValueTuple in C#7
        private KeyValuePair<string, JsonNode> ReadField()
        {
            var fieldName = reader.Current.Value;

            reader.Next(); // Read the field name

            reader.Ensure(TokenKind.Colon, "field");

            reader.Next(); // Read ':' (Field value indicator)

            return new KeyValuePair<string, JsonNode>(fieldName, ReadFieldValue());
        }


        public JsonArray ReadArray()
        {
            reader.Ensure(TokenKind.LeftBracket, "array");

            var array = new XNodeArray();

            reader.Next(); // Read the '[' (Array start)

            // Read the array's items
            while (reader.Current.Kind != TokenKind.RightBracket)
            {
                if (reader.Current.Kind == TokenKind.Comma)
                {
                    reader.Next(); // Read the ',' (Seperator)
                }

                if (reader.Current.IsLiteral)
                {
                    array.Add(ReadLiteral()); // Boolean, Date, Number, Null, String, Uri
                }
                else if (reader.Current.Kind == TokenKind.LeftBracket)
                {
                    array.Add(ReadArray()); // Array
                }
                else if (reader.Current.Kind == TokenKind.LeftBrace)
                {
                    array.Add(ReadObject()); // Object
                }
                else
                {
                    throw new ParserException($"Expected comma, literal, or object. Was {reader.Current}.");
                }
            }

            reader.Next(); // Read the ']' (Array end)

            return array;
        }

        #region IDisposable

        public void Dispose()
        {
            reader.Dispose();
        }

        #endregion
    }
}