view Implab/Formats/JSON/JSONParser.cs @ 187:dd4a3590f9c6 ref20160224

Reworked cancelation handling, if the cancel handler isn't specified the OperationCanceledException will be handled by the error handler Any unhandled OperationCanceledException will cause the promise cancelation
author cin
date Tue, 19 Apr 2016 17:35:20 +0300
parents c32688129f14
children 7d07503621fe
line wrap: on
line source

using System;
using System.Diagnostics;
using System.IO;
using Implab.Automaton;
using Implab.Automaton.RegularExpressions;
using System.Linq;
using Implab.Components;
using System.Collections.Generic;

namespace Implab.Formats.JSON {
    /// <summary>
    /// Pull парсер JSON данных.
    /// </summary>
    /// <remarks>
    /// Следует отметить отдельную интерпретацию свойства <see cref="Level"/>,
    /// оно означает текущий уровень вложенности объектов, однако закрывающий
    /// элемент объекта и массива имеет уровень меньше, чем сам объект.
    /// <code>
    /// { // Level = 1
    ///     "name" : "Peter", // Level = 1
    ///     "address" : { // Level = 2
    ///         city : "Stern" // Level = 2
    ///     } // Level = 1
    /// } // Level = 0
    /// </code>
    /// </remarks>
    public class JSONParser : Disposable {

        enum MemberContext {
            MemberName,
            MemberValue
        }

        #region Parser rules
        struct ParserContext {
            readonly int[,] m_dfa;
            int m_state;

            readonly JSONElementContext m_elementContext;

            public ParserContext(int[,] dfa, int state, JSONElementContext context) {
                m_dfa = dfa;
                m_state = state;
                m_elementContext = context;
            }

            public bool Move(JsonTokenType token) {
                var next = m_dfa[m_state, (int)token];
                if (next == AutomatonConst.UNREACHABLE_STATE)
                    return false;
                m_state = next;
                return true;
            }

            public JSONElementContext ElementContext {
                get { return m_elementContext; }
            }
        }

        static readonly ParserContext _jsonContext;
        static readonly ParserContext _objectContext;
        static readonly ParserContext _arrayContext;

        static JSONParser() {

            var valueExpression = MakeToken(JsonTokenType.BeginArray, JsonTokenType.BeginObject, JsonTokenType.Literal, JsonTokenType.Number, JsonTokenType.String);
            var memberExpression = MakeToken(JsonTokenType.String).Cat(MakeToken(JsonTokenType.NameSeparator)).Cat(valueExpression);

            var objectExpression = memberExpression
                .Cat(
                    MakeToken(JsonTokenType.ValueSeparator)
                    .Cat(memberExpression)
                    .EClosure()
                )
                .Optional()
                .Cat(MakeToken(JsonTokenType.EndObject))
                .End();
            
            var arrayExpression = valueExpression
                .Cat(
                    MakeToken(JsonTokenType.ValueSeparator)
                    .Cat(valueExpression)
                    .EClosure()
                )
                .Optional()
                .Cat(MakeToken(JsonTokenType.EndArray))
                .End();

            var jsonExpression = valueExpression.End();

            _jsonContext = CreateParserContext(jsonExpression, JSONElementContext.None);
            _objectContext = CreateParserContext(objectExpression, JSONElementContext.Object);
            _arrayContext = CreateParserContext(arrayExpression, JSONElementContext.Array);
        }

        static Token MakeToken(params JsonTokenType[] input) {
            return Token.New( input.Select(t => (int)t).ToArray() );
        }

        static ParserContext CreateParserContext(Token expr, JSONElementContext context) {
            
            var dfa = new DFATable();
            var builder = new RegularExpressionVisitor(dfa);
            expr.Accept(builder);
            builder.BuildDFA();

            return new ParserContext(dfa.CreateTransitionTable(), dfa.InitialState, context);
        }

        #endregion

        readonly JSONScanner m_scanner;
        MemberContext m_memberContext;

        JSONElementType m_elementType;
        object m_elementValue;
        string m_memberName = String.Empty;

        Stack<ParserContext> m_stack = new Stack<ParserContext>();
        ParserContext m_context = _jsonContext;

        /// <summary>
        /// Создает новый парсер на основе строки, содержащей JSON
        /// </summary>
        /// <param name="text"></param>
        public JSONParser(string text) {
            Safe.ArgumentNotEmpty(text, "text");
            m_scanner = new JSONScanner(text);
        }

        /// <summary>
        /// Создает новый экземпляр парсера, на основе текстового потока.
        /// </summary>
        /// <param name="reader">Текстовый поток.</param>
        public JSONParser(TextReader reader) {
            Safe.ArgumentNotNull(reader, "reader");
            m_scanner = new JSONScanner(reader);
        }

        public int Level {
            get { return m_stack.Count; }
        }

        /// <summary>
        /// Тип текущего элемента на котором стоит парсер.
        /// </summary>
        public JSONElementType ElementType {
            get { return m_elementType; }
        }

        /// <summary>
        /// Имя элемента - имя свойства родительского контейнера. Для элементов массивов и корневого всегда
        /// пустая строка.
        /// </summary>
        public string ElementName {
            get { return m_memberName; }
        }

        /// <summary>
        /// Значение элемента. Только для элементов типа <see cref="JSONElementType.Value"/>, для остальных <c>null</c>
        /// </summary>
        public object ElementValue {
            get { return m_elementValue; }
        }

        /// <summary>
        /// Читает слеюудущий объект из потока
        /// </summary>
        /// <returns><c>true</c> - операция чтения прошла успешно, <c>false</c> - конец данных</returns>
        public bool Read() {
            object tokenValue;
            JsonTokenType tokenType;

            m_memberName = String.Empty;

            while (m_scanner.ReadToken(out tokenValue, out tokenType)) {
                if(!m_context.Move(tokenType))
                    UnexpectedToken(tokenValue, tokenType);

                switch (tokenType) {
                    case JsonTokenType.BeginObject:
                        m_stack.Push(m_context);
                        m_context = _objectContext;

                        m_elementValue = null;
                        m_memberContext = MemberContext.MemberName;
                        m_elementType = JSONElementType.BeginObject;
                        return true;
                    case JsonTokenType.EndObject:
                        if (m_stack.Count == 0)
                            UnexpectedToken(tokenValue, tokenType);
                        m_context = m_stack.Pop();

                        m_elementValue = null;
                        m_elementType = JSONElementType.EndObject;
                        return true;
                    case JsonTokenType.BeginArray:
                        m_stack.Push(m_context);
                        m_context = _arrayContext;

                        m_elementValue = null;
                        m_memberContext = MemberContext.MemberValue;
                        m_elementType = JSONElementType.BeginArray;
                        return true;
                    case JsonTokenType.EndArray:
                        if (m_stack.Count == 0)
                            UnexpectedToken(tokenValue, tokenType);
                        m_context = m_stack.Pop();

                        m_elementValue = null;
                        m_elementType = JSONElementType.EndArray;
                        return true;
                    case JsonTokenType.String:
                        if (m_memberContext == MemberContext.MemberName) {
                            m_memberName = (string)tokenValue;
                            break;
                        }
                        m_elementType = JSONElementType.Value;
                        m_elementValue = tokenValue;
                        return true;
                    case JsonTokenType.Number:
                        m_elementType = JSONElementType.Value;
                        m_elementValue = tokenValue;
                        return true;
                    case JsonTokenType.Literal:
                        m_elementType = JSONElementType.Value;
                        m_elementValue = ParseLiteral((string)tokenValue);
                        return true;
                    case JsonTokenType.NameSeparator:
                        m_memberContext = MemberContext.MemberValue;
                        break;
                    case JsonTokenType.ValueSeparator:
                        m_memberContext = m_context.ElementContext == JSONElementContext.Object ? MemberContext.MemberName : MemberContext.MemberValue;
                        break;
                    default:
                        UnexpectedToken(tokenValue, tokenType);
                        break;
                }
            }
            if (m_context.ElementContext != JSONElementContext.None)
                throw new ParserException("Unexpedted end of data");

            EOF = true;

            return false;
        }

        object ParseLiteral(string literal) {
            switch (literal) {
                case "null":
                    return null;
                case "false":
                    return false;
                case "true":
                    return true;
                default:
                    UnexpectedToken(literal, JsonTokenType.Literal);
                    return null; // avoid compliler error
            }
        }

        void UnexpectedToken(object value, JsonTokenType tokenType) {
            throw new ParserException(String.Format("Unexpected token {0}: '{1}'", tokenType, value));
        }


        /// <summary>
        /// Признак конца потока
        /// </summary>
        public bool EOF {
            get;
            private set;
        }

        protected override void Dispose(bool disposing) {
            if (disposing)
                Safe.Dispose(m_scanner);
        }

        /// <summary>
        /// Переходит в конец текущего объекта.
        /// </summary>
        public void SeekElementEnd() {
            var level = Level - 1;

            Debug.Assert(level >= 0);

            while (Level != level)
                Read();
        }
    }

}