Mercurial > pub > ImplabNet
diff Implab/Formats/JSON/JSONParser.cs @ 163:419aa51b04fd ref20160224
JSON moved to Formats namespace
Working in RegularDFA
author | cin |
---|---|
date | Wed, 24 Feb 2016 20:12:52 +0300 |
parents | |
children | e227e78d72e4 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Formats/JSON/JSONParser.cs Wed Feb 24 20:12:52 2016 +0300 @@ -0,0 +1,277 @@ +using Implab.Parsing; +using System; +using System.Diagnostics; +using System.IO; + +namespace Implab.JSON { + /// <summary> + /// internal + /// </summary> + public struct JSONParserContext { + public string memberName; + public JSONElementContext elementContext; + } + + /// <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 : DFAutomaton<JSONParserContext>, IDisposable { + + enum MemberContext { + MemberName, + MemberValue + } + + static readonly EnumAlphabet<JsonTokenType> _alphabet = EnumAlphabet<JsonTokenType>.FullAlphabet; + static readonly DFAStateDescriptior[] _jsonDFA; + static readonly DFAStateDescriptior[] _objectDFA; + static readonly DFAStateDescriptior[] _arrayDFA; + + static JSONParser() { + + + var valueExpression = Token.New(JsonTokenType.BeginArray, JsonTokenType.BeginObject, JsonTokenType.Literal, JsonTokenType.Number, JsonTokenType.String); + var memberExpression = Token.New(JsonTokenType.String).Cat(Token.New(JsonTokenType.NameSeparator)).Cat(valueExpression); + + var objectExpression = memberExpression + .Cat( + Token.New(JsonTokenType.ValueSeparator) + .Cat(memberExpression) + .EClosure() + ) + .Optional() + .Cat(Token.New(JsonTokenType.EndObject)) + .Tag(0); + var arrayExpression = valueExpression + .Cat( + Token.New(JsonTokenType.ValueSeparator) + .Cat(valueExpression) + .EClosure() + ) + .Optional() + .Cat(Token.New(JsonTokenType.EndArray)) + .Tag(0); + + var jsonExpression = valueExpression.Tag(0); + + _jsonDFA = BuildDFA(jsonExpression).States; + _objectDFA = BuildDFA(objectExpression).States; + _arrayDFA = BuildDFA(arrayExpression).States; + } + + static EDFADefinition<JsonTokenType> BuildDFA(Token expr) { + var builder = new DFABuilder(); + var dfa = new EDFADefinition<JsonTokenType>(_alphabet); + expr.Accept(builder); + + builder.BuildDFA(dfa); + return dfa; + } + + JSONScanner m_scanner; + MemberContext m_memberContext; + + JSONElementType m_elementType; + object m_elementValue; + + /// <summary> + /// Создает новый парсер на основе строки, содержащей JSON + /// </summary> + /// <param name="text"></param> + public JSONParser(string text) + : base(_jsonDFA, INITIAL_STATE, new JSONParserContext { elementContext = JSONElementContext.None, memberName = String.Empty }) { + Safe.ArgumentNotEmpty(text, "text"); + m_scanner = new JSONScanner(); + m_scanner.Feed(text.ToCharArray()); + } + + /// <summary> + /// Создает новый экземпляр парсера, на основе текстового потока. + /// </summary> + /// <param name="reader">Текстовый поток.</param> + /// <param name="dispose">Признак того, что парсер должен конролировать время жизни входного потока.</param> + public JSONParser(TextReader reader, bool dispose) + : base(_jsonDFA, INITIAL_STATE, new JSONParserContext { elementContext = JSONElementContext.None, memberName = String.Empty }) { + Safe.ArgumentNotNull(reader, "reader"); + m_scanner = new JSONScanner(); + m_scanner.Feed(reader, dispose); + } + + /// <summary> + /// Тип текущего элемента на котором стоит парсер. + /// </summary> + public JSONElementType ElementType { + get { return m_elementType; } + } + + /// <summary> + /// Имя элемента - имя свойства родительского контейнера. Для элементов массивов и корневого всегда + /// пустая строка. + /// </summary> + public string ElementName { + get { return m_context.info.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() { + if (m_context.current == UNREACHEBLE_STATE) + throw new InvalidOperationException("The parser is in invalid state"); + object tokenValue; + JsonTokenType tokenType; + m_context.info.memberName = String.Empty; + while (m_scanner.ReadToken(out tokenValue, out tokenType)) { + Move((int)tokenType); + if (m_context.current == UNREACHEBLE_STATE) + UnexpectedToken(tokenValue, tokenType); + switch (tokenType) { + case JsonTokenType.BeginObject: + Switch( + _objectDFA, + INITIAL_STATE, + new JSONParserContext { + memberName = m_context.info.memberName, + elementContext = JSONElementContext.Object + } + ); + m_elementValue = null; + m_memberContext = MemberContext.MemberName; + m_elementType = JSONElementType.BeginObject; + return true; + case JsonTokenType.EndObject: + Restore(); + m_elementValue = null; + m_elementType = JSONElementType.EndObject; + return true; + case JsonTokenType.BeginArray: + Switch( + _arrayDFA, + INITIAL_STATE, + new JSONParserContext { + memberName = m_context.info.memberName, + elementContext = JSONElementContext.Array + } + ); + m_elementValue = null; + m_memberContext = MemberContext.MemberValue; + m_elementType = JSONElementType.BeginArray; + return true; + case JsonTokenType.EndArray: + Restore(); + m_elementValue = null; + m_elementType = JSONElementType.EndArray; + return true; + case JsonTokenType.String: + if (m_memberContext == MemberContext.MemberName) { + m_context.info.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.info.elementContext == JSONElementContext.Object ? MemberContext.MemberName : MemberContext.MemberValue; + break; + default: + UnexpectedToken(tokenValue, tokenType); + break; + } + } + if (m_context.info.elementContext != JSONElementContext.None) + throw new ParserException("Unexpedted end of data"); + 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 { + return m_scanner.EOF; + } + } + + protected virtual void Dispose(bool disposing) { + if (disposing) { + m_scanner.Dispose(); + } + } + + /// <summary> + /// Освобождает парсер и связанный с ним сканнер. + /// </summary> + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~JSONParser() { + Dispose(false); + } + + /// <summary> + /// Переходит в конец текущего объекта. + /// </summary> + public void SeekElementEnd() { + var level = Level - 1; + + Debug.Assert(level >= 0); + + while (Level != level) + Read(); + } + } + +}