diff --git a/Dtsod/DtsodV21.cs b/Dtsod/DtsodV21.cs index f49b3a6..5201e19 100644 --- a/Dtsod/DtsodV21.cs +++ b/Dtsod/DtsodV21.cs @@ -310,7 +310,7 @@ public class DtsodV21 : Dictionary, IDtsod value = stringValue.Remove(stringValue.Length - 1).ToLong(); break; default: - value = stringValue.ToShort(); + value = stringValue.ToInt(); break; } break; diff --git a/Dtsod/DtsodV22.cs b/Dtsod/DtsodV22.cs index 65f39c2..5aa112b 100644 --- a/Dtsod/DtsodV22.cs +++ b/Dtsod/DtsodV22.cs @@ -345,7 +345,7 @@ public class DtsodV22 : Dictionary, IDtsod break; default: type = ValueTypes.Int; - value = stringValue.ToShort(); + value = stringValue.ToInt(); break; } break; diff --git a/Dtsod/DtsodV23.cs b/Dtsod/DtsodV23.cs new file mode 100644 index 0000000..dddab05 --- /dev/null +++ b/Dtsod/DtsodV23.cs @@ -0,0 +1,308 @@ +using System.Globalization; + +namespace DTLib.Dtsod; + +// v23 +// в процессе создания v30 появились идеи по улучшению 20-ой серии +// новый парсер (опять) +// улучшена сериализация и десериализация листов + +public class DtsodV23 : DtsodDict, IDtsod +{ + + public DtsodVersion Version { get; } = DtsodVersion.V30; + public IDictionary ToDictionary() => this; + + public DtsodV23() : base() => UpdateLazy(); + public DtsodV23(IDictionary dict) : base(dict) => UpdateLazy(); + public DtsodV23(string serialized) : this() => Append(Deserialize(serialized)); + + static DtsodV23 Deserialize(string text) + { + char c; + int i = -1; // ++i в ReadName + StringBuilder b = new(); + Dictionary output = new(); + bool partOfDollarList = false; + for (; i < text.Length; i++) + { + string name = ReadName(); + dynamic value = ReadValue(out bool _); + if (partOfDollarList) + { + if (!output.TryGetValue(name, out var dollarList)) + { + dollarList = new List(); + output.Add(name, dollarList); + } + dollarList.Add(value); + } + else output.Add(name, value); + } + return new DtsodV23(output); + + string ReadName() + { + while (i < text.Length - 1) + { + c = text[++i]; + switch (c) + { + case ' ': + case '\t': + case '\r': + case '\n': + break; + case '#': + SkipComment(); + break; + case ':': + string _name = b.ToString(); + b.Clear(); + return _name; + case '$': + partOfDollarList = true; + break; + case '=': + case '"': + case '\'': + case ';': + case '[': + case ']': + case '{': + case '}': + throw new Exception($"DtsodV23.Deserialize() error: unexpected {c}"); + default: + b.Append(c); + break; + } + } + + throw new Exception("DtsodV23.Deserialize.ReadName() error: end of text\ntext:\n" + text); + } + + dynamic ReadValue(out bool endOfList) + { + endOfList = false; + + void ReadString() + { + while ((c != '"' && c != '\'') || (text[i - 1] == '\\' && text[i - 2] != '\\')) + { + b.Append(c); + if (++i >= text.Length) throw new Exception("DtsodV23.Deserialize() error: end of text\ntext:\n" + text); + c = text[i]; + } + b.Append('"'); + } + + DtsodV23 ReadDtsod() + { + short bracketBalance = 1; + c = text[++i]; //пропускает начальный символ '{' + while (bracketBalance != 0) + { + switch (c) + { + case ' ': + case '\t': + case '\r': + case '\n': + break; + case '#': + SkipComment(); + break; + case '{': + bracketBalance++; + b.Append(c); + break; + case '}': + bracketBalance--; + if (bracketBalance != 0) + b.Append(c); + break; + case '"': + case '\'': + ReadString(); + break; + default: + b.Append(c); + break; + } + + if (++i >= text.Length) throw new Exception("DtsodV23.Deserialize() error: end of text\ntext:\n" + text); + c = text[i]; + } + + b.Clear(); + return Deserialize(b.ToString()); + } + + List ReadList() + { + List list = new(); + while (true) + { + list.Add(ReadValue(out bool _eol)); + if (_eol) break; + } + + b.Clear(); + return list; + } + + dynamic ParseValue(string value_str) + { + switch (value_str) + { + case "true": + case "false": + return value_str.ToBool(); + case "null": + return null; + default: + if (value_str.Contains('"')) + return value_str.Substring(1, value_str.Length - 2); + else if (value_str.Contains('\'')) + return value_str[1]; + else switch (value_str[value_str.Length - 1]) + { + case 's': + return value_str[value_str.Length - 2] == 'u' + ? value_str.Remove(value_str.Length - 2).ToUShort() + : value_str.Remove(value_str.Length - 1).ToShort(); + case 'u': + return value_str.Remove(value_str.Length - 1).ToUInt(); + case 'i': + return value_str[value_str.Length - 2] == 'u' + ? value_str.Remove(value_str.Length - 2).ToUInt() + : value_str.Remove(value_str.Length - 1).ToInt(); + case 'l': + return value_str[value_str.Length - 2] == 'u' + ? value_str.Remove(value_str.Length - 2).ToULong() + : value_str.Remove(value_str.Length - 1).ToLong(); + case 'b': + return value_str[value_str.Length - 2] == 's' + ? value_str.Remove(value_str.Length - 2).ToSByte() + : value_str.Remove(value_str.Length - 1).ToByte(); + case 'f': + return value_str.Remove(value_str.Length - 1).ToFloat(); + case 'e': + return value_str[value_str.Length - 2] == 'd' + ? value_str.Remove(value_str.Length - 2).ToDecimal() + : throw new Exception("can't parse value:" + value_str); + default: + return value_str.Contains('.') + ? value_str.ToDouble() + : value_str.ToInt(); + } + }; + } + + while (i < text.Length - 1) + { + c = text[++i]; + switch (c) + { + case ' ': + case '\t': + case '\r': + case '\n': + break; + case '#': + SkipComment(); + break; + case '"': + case '\'': + ReadString(); + break; + case ';': + case ',': + string str = b.ToString(); + b.Clear(); + return ParseValue(str); + case '[': + return ReadList(); + case ']': + endOfList = true; + break; + case '{': + return ReadDtsod(); + case '=': + case ':': + case '}': + case '$': + throw new Exception($"DtsodV23.Deserialize() error: unexpected {c}"); + default: + b.Append(c); + break; + } + } + + throw new Exception("DtsodV23.Deserialize.ReadValue() error: end of text\ntext:\n" + text); + } + + void SkipComment() + { + while (text[i] != '\n') + if (++i >= text.Length) throw new Exception("DtsodV23.Deserialize() error: end of text\ntext:\n" + text); + } + } + + internal static readonly Dictionary> TypeSerializeFuncs = new() + { + { typeof(bool), (val, b) => b.Append(val.ToString()) }, + { typeof(char), (val, b) => b.Append('\'').Append(val).Append('\'') }, + { typeof(string), (val, b) => b.Append('"').Append(val).Append('"') }, + { typeof(byte), (val, b) => b.Append(val.ToString()).Append('b') }, + { typeof(sbyte), (val, b) => b.Append(val.ToString()).Append("sb") }, + { typeof(short), (val, b) => b.Append(val.ToString()).Append('s') }, + { typeof(ushort), (val, b) => b.Append(val.ToString()).Append("us") }, + { typeof(int), (val, b) => b.Append(val.ToString()) }, + { typeof(uint), (val, b) => b.Append(val.ToString()).Append("ui") }, + { typeof(long), (val, b) => b.Append(val.ToString()).Append('l') }, + { typeof(ulong), (val, b) => b.Append(val.ToString()).Append("ul") }, + { typeof(float), (val, b) => b.Append(val.ToString(CultureInfo.InvariantCulture)).Append('f') }, + { typeof(double), (val, b) => b.Append(val.ToString(CultureInfo.InvariantCulture)) }, + { typeof(decimal), (val, b) => b.Append(val.ToString(CultureInfo.InvariantCulture)).Append("de") } + }; + short tabscount = -1; + protected StringBuilder Serialize(IDictionary dtsod, StringBuilder b = null) + { + tabscount++; + if (b is null) b = new StringBuilder(); + foreach (var pair in dtsod) + { + b.Append('\t', tabscount).Append(pair.Key).Append(": "); + SerializeType(pair.Value); + b.Append(";\n"); + + void SerializeType(dynamic value) + { + if (value is IList _list) + { + b.Append('['); + foreach (object el in _list) + { + SerializeType(el); + b.Append(','); + } + b.Remove(b.Length - 1, 1).Append(']'); + } + else if (value is IDictionary _dict) + { + b.Append('{'); + Serialize(value, b); + b.Append('}'); + } + else b.Append(TypeSerializeFuncs[value.GetType()].Invoke(value, b)); + } + } + tabscount--; + return b; + } + + protected Lazy serialized; + protected void UpdateLazy() => serialized = new(() => Serialize(this).ToString()); + public override string ToString() => serialized.Value; +} diff --git a/Dtsod/DtsodVersion.cs b/Dtsod/DtsodVersion.cs index 9ba4090..b36d454 100644 --- a/Dtsod/DtsodVersion.cs +++ b/Dtsod/DtsodVersion.cs @@ -4,5 +4,6 @@ public enum DtsodVersion : byte { V21 = 21, V22 = 22, + V23 = 23, V30 = 30 } diff --git a/Dtsod/DtsodVersionConverter.cs b/Dtsod/DtsodVersionConverter.cs index a14264f..81b2685 100644 --- a/Dtsod/DtsodVersionConverter.cs +++ b/Dtsod/DtsodVersionConverter.cs @@ -7,6 +7,7 @@ public static class DtsodVersionConverter { DtsodVersion.V21 => new DtsodV21(src.ToDictionary()), DtsodVersion.V22 => throw new NotImplementedException("Converting dtsods to V22 isn't implemented"), + DtsodVersion.V23 => new DtsodV23(src.ToDictionary()), DtsodVersion.V30 => new DtsodV30(src.ToDictionary()), _ => throw new Exception($"DtsodVersionConverter.Convert() error: unknown target version <{targetVersion}>"), }; diff --git a/Dtsod/V30/DtsodV30.cs b/Dtsod/V30/DtsodV30.cs index c5e7181..82fd6ad 100644 --- a/Dtsod/V30/DtsodV30.cs +++ b/Dtsod/V30/DtsodV30.cs @@ -1,5 +1,4 @@ using System.Globalization; -using System.Linq; namespace DTLib.Dtsod; @@ -13,10 +12,6 @@ public class DtsodV30 : DtsodDict, IDtsod public DtsodV30(IDictionary dict) : base(dict) => UpdateLazy(); public DtsodV30(string serialized) : this() => Append(Deserialize(serialized)); -#if DEBUG - static void DebugLog(params string[] msg) => PublicLog.Log(msg); -#endif - static IDictionary Deserialize(string text) { char c; @@ -27,7 +22,7 @@ public class DtsodV30 : DtsodDict, IDtsod Type ReadType() { - while (i < text.Length) + while (i < text.Length - 1) { c = text[++i]; switch (c) @@ -43,9 +38,10 @@ public class DtsodV30 : DtsodDict, IDtsod case ':': string _type = b.ToString(); b.Clear(); - return TypeHelper.TypeFromString(_type); + return TypeHelper.Instance.TypeFromString(_type); case '=': case '"': + case '\'': case ';': case '[': case ']': @@ -63,7 +59,7 @@ public class DtsodV30 : DtsodDict, IDtsod string ReadName() { - while (i < text.Length) + while (i < text.Length - 1) { c = text[++i]; switch (c) @@ -82,6 +78,7 @@ public class DtsodV30 : DtsodDict, IDtsod return _name; case ':': case '"': + case '\'': case ';': case '[': case ']': @@ -97,12 +94,12 @@ public class DtsodV30 : DtsodDict, IDtsod throw new Exception("DtsodV30.Deserialize.ReadName() error: end of text\ntext:\n" + text); } - object[] ReadValue() + object ReadValue(ref bool endoflist) { void ReadString() { c = text[++i]; //пропускает начальный символ '"' - while (c != '"' || (text[i - 1] == '\\' && text[i - 2] != '\\')) + while ((c != '"' && c != '\'') || (text[i - 1] == '\\' && text[i - 2] != '\\')) { b.Append(c); if (++i >= text.Length) throw new Exception("DtsodV30.Deserialize() error: end of text\ntext:\n" + text); @@ -110,13 +107,13 @@ public class DtsodV30 : DtsodDict, IDtsod } } - bool endoflist = false; // выставляется в цикле в ReadValue() List ReadList() { - List list = new(); - while (!endoflist) - list.Add(CreateInstance(ReadType(), ReadValue())); - endoflist = false; + List list = new(); + bool _endoflist = false; + while (!_endoflist) + list.Add(CreateInstance(ReadType(), ReadValue(ref _endoflist))); + b.Clear(); return list; } @@ -146,6 +143,7 @@ public class DtsodV30 : DtsodDict, IDtsod b.Append(c); break; case '"': + case '\'': b.Append('"'); ReadString(); b.Append('"'); @@ -159,10 +157,11 @@ public class DtsodV30 : DtsodDict, IDtsod c = text[i]; } + b.Clear(); return Deserialize(b.ToString()); } - while (i < text.Length) + while (i < text.Length-1) { c = text[++i]; switch (c) @@ -176,29 +175,21 @@ public class DtsodV30 : DtsodDict, IDtsod SkipComment(); break; case '"': + case '\'': ReadString(); break; case ';': // один параметр case ',': // для листов string str = b.ToString(); b.Clear(); - // hardcoded "null" value - return str == "null" ? (new object[] { null }) : (new object[] { str }); + return str == "null" ? null : str; case '[': - { - object[] _value = ReadList().ToArray(); - b.Clear(); - return _value; - } + return ReadList(); case ']': endoflist = true; goto case ','; case '{': - { - object[] _value = new object[] { ReadDictionary() }; - b.Clear(); - return _value; - } + return ReadDictionary(); case '=': case ':': case '}': @@ -218,25 +209,42 @@ public class DtsodV30 : DtsodDict, IDtsod if (++i >= text.Length) throw new Exception("DtsodV30.Deserialize() error: end of text\ntext:\n" + text); } - object CreateInstance(Type type, object[] ctor_args) + object CreateInstance(Type type, object ctor_arg) { - if (TypeHelper.BaseTypeConstructors.TryGetValue(type, out Func ctor)) - return (object)ctor.Invoke((string)ctor_args[0]); + if (TypeHelper.Instance.BaseTypeConstructors.TryGetValue(type, out Func ctor)) + return (object)ctor.Invoke((string)ctor_arg); else if (type.CustomAttributes.Any(a => a.AttributeType == typeof(DtsodSerializableAttribute))) - return Activator.CreateInstance(type, ctor_args); - else throw new Exception($"type {type.AssemblyQualifiedName} doesn't have DtsodSerializableAttribute"); + return Activator.CreateInstance(type, ((IDictionary)ctor_arg).Values.ToArray()); + else if (typeof(ICollection).IsAssignableFrom(type)) + { + var method_As = typeof(TypeHelper).GetMethod("As", + System.Reflection.BindingFlags.Static | + System.Reflection.BindingFlags.NonPublic) + .MakeGenericMethod(type.GetGenericArguments()[0]); + object collection = type.GetConstructor(Type.EmptyTypes).Invoke(null); + var method_Add = type.GetMethod("Add"); + Log(method_Add.Name); + foreach (object el in (IEnumerable)ctor_arg) + { + var pel = method_As.Invoke(null, new object[] { el }); + method_Add.Invoke(collection, new object[] { pel }); + } + return collection; + } + else throw new Exception($"can't create instance of {type.FullName}"); } Dictionary output = new(); Type type; string name; - object[] value; + object value; for (; i < text.Length; i++) { type = ReadType(); name = ReadName(); - value = ReadValue(); + bool _ = false; + value = ReadValue(ref _); output.Add(name, CreateInstance(type, value)); } @@ -252,22 +260,32 @@ public class DtsodV30 : DtsodDict, IDtsod StringBuilder b = new(); foreach (KeyValuePair pair in dtsod) { - Type type = pair.Value.GetType(); - b.Append(TypeHelper.TypeToString(type)).Append(':') - .Append(pair.Key).Append('='); - if (TypeHelper.BaseTypeNames.ContainsKey(type)) + + } + + void SerializeObject(string name, dynamic inst) + { + Type type = inst.GetType(); + b.Append(TypeHelper.Instance.TypeToString(type)).Append(':') + .Append(name).Append('='); + if (TypeHelper.Instance.BaseTypeNames.ContainsKey(type)) { if (type == typeof(decimal) || type == typeof(double) || type == typeof(float)) - b.Append(pair.Value.ToString(CultureInfo.InvariantCulture)); - else b.Append(pair.Value.ToString()); + b.Append(inst.ToString(CultureInfo.InvariantCulture)); + else b.Append(inst.ToString()); } else if (typeof(IDictionary).IsAssignableFrom(type)) - b.Append("\n{\n").Append(Serialize(pair.Value, tabsCount++)).Append("};\n"); - else + b.Append("\n{\n").Append(Serialize(inst, tabsCount++)).Append("};\n"); + else if (type.CustomAttributes.Any(a => a.AttributeType == typeof(DtsodSerializableAttribute))) { - type.GetProperties().Where(p => p.CustomAttributes.Any(a => a.AttributeType == typeof(DtsodSerializableAttribute))); + var props = type.GetProperties().Where(p => p.CustomAttributes.Any(a => a.AttributeType == typeof(DtsodSerializableAttribute))); + foreach (var prop in props) + { + var propval = prop.GetValue(inst); + } } + else throw new Exception($"can't serialize type {type.FullName}"); } return b.ToString(); diff --git a/Dtsod/V30/TypeHelper.cs b/Dtsod/V30/TypeHelper.cs index 88b1d7b..af580ee 100644 --- a/Dtsod/V30/TypeHelper.cs +++ b/Dtsod/V30/TypeHelper.cs @@ -1,8 +1,11 @@ namespace DTLib.Dtsod; -public static class TypeHelper +public class TypeHelper { - public static readonly Dictionary> BaseTypeConstructors = new() + static Lazy _inst = new(); + public static TypeHelper Instance => _inst.Value; + + internal readonly Dictionary> BaseTypeConstructors = new() { { typeof(bool), (inp) => inp.ToBool() }, { typeof(char), (inp) => inp.ToChar() }, @@ -20,7 +23,7 @@ public static class TypeHelper { typeof(decimal), (inp) => inp.ToDecimal() } }; - public static Dictionary BaseTypeNames = new() + internal Dictionary BaseTypeNames = new() { { typeof(bool), "bool" }, { typeof(char), "char" }, @@ -38,12 +41,55 @@ public static class TypeHelper { typeof(decimal), "decimal" } }; - public static string TypeToString(Type t) => + private DtsodDict ST_extensions = new() + { + { "List", typeof(List) }, + { "List", typeof(List) }, + { "List", typeof(List) }, + { "List", typeof(List) }, + { "List", typeof(List) }, + { "List", typeof(List) }, + { "List", typeof(List) }, + { "List", typeof(List) }, + { "List", typeof(List) }, + { "List", typeof(List) }, + { "List", typeof(List) }, + { "List", typeof(List) }, + { "List", typeof(List) }, + { "List", typeof(List) }, + }; + private DtsodDict TS_extensions = new() + { + { typeof(List), "List" }, + { typeof(List), "List" }, + { typeof(List), "List" }, + { typeof(List), "List" }, + { typeof(List), "List" }, + { typeof(List), "List" }, + { typeof(List), "List" }, + { typeof(List), "List" }, + { typeof(List), "List" }, + { typeof(List), "List" }, + { typeof(List), "List" }, + { typeof(List), "List" }, + { typeof(List), "List" }, + { typeof(List), "List" }, + }; + + public TypeHelper Extend(string name, Type type) + { + ST_extensions.Add(name, type); + TS_extensions.Add(type, name); + return this; + } + + public string TypeToString(Type t) => BaseTypeNames.TryGetValue(t, out string name) ? name - : t.AssemblyQualifiedName; - - public static Type TypeFromString(string str) => str switch + : TS_extensions.TryGetValue(t, out name) + ? name + : t.FullName; + public Type TypeFromString(string str) => str switch { "bool" => typeof(bool), "char" => typeof(char), @@ -59,8 +105,10 @@ public static class TypeHelper "float" => typeof(float), "double" => typeof(double), "decimal" => typeof(decimal), - _ => Type.GetType(str, false) ?? - throw new Exception($"DtsodV30.Deserialize.ParseType() error: type {str} doesn't exists") + _ => ST_extensions.TryGetValue(str, out var t) + ? t + : Type.GetType(str, false) + ?? throw new Exception($"DtsodV30.Deserialize.ParseType() error: type {str} doesn't exists") }; - + static internal T As(object inst) where T : class => inst as T; }