|
|
using Implab;
|
|
|
using Implab.Parsing;
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Diagnostics;
|
|
|
using System.IO;
|
|
|
using System.Linq;
|
|
|
using System.Text;
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
namespace Implab.JSON {
|
|
|
/// <summary>
|
|
|
/// internal
|
|
|
/// </summary>
|
|
|
public struct JSONParserContext {
|
|
|
public string memberName;
|
|
|
public JSONElementContext elementContext;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Pull парсер JSON данных.
|
|
|
/// </summary>
|
|
|
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 jsonExpression = Token.New(JsonTokenType.BeginObject, JsonTokenType.BeginArray).Tag(0);
|
|
|
|
|
|
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);
|
|
|
|
|
|
_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;
|
|
|
} else {
|
|
|
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);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|