diff --git a/index.d b/index.d index 2d544fa4..cc4042dd 100644 --- a/index.d +++ b/index.d @@ -19,6 +19,8 @@ $(BOOKTABLE , $(TR $(TDNW $(MREF mir,small_string)) $(TD Generic Small Strings)) $(TR $(TDNW $(MREF mir,array,allocation)) $(TD `std.array` reworked for Mir)) $(TR $(TDNW $(MREF mir,range)) $(TD Ranges)) + $(TR $(TDNW $(MREF mir,serde)) $(TD Utilities for serialization libraries )) + $(TR $(TDNW $(MREF mir,reflection)) $(TD Compile time reflection utilities )) $(LEADINGROW Date and time) $(TR $(TDNW $(MREF mir,date)) $(TD Fast BetterC Date type with Boost ABI and mangling compatability)) $(LEADINGROW NDarrays and Algorithms) diff --git a/meson.build b/meson.build index 4cbd77cd..bb6f8423 100644 --- a/meson.build +++ b/meson.build @@ -63,6 +63,8 @@ sources_list = [ 'mir/rc/package', 'mir/rc/ptr', 'mir/rc/slim_ptr', + 'mir/reflection', + 'mir/serde', 'mir/series', 'mir/small_array', 'mir/small_string', diff --git a/source/mir/reflection.d b/source/mir/reflection.d new file mode 100644 index 00000000..ce04ff5d --- /dev/null +++ b/source/mir/reflection.d @@ -0,0 +1,400 @@ +/++ ++/ +module mir.reflection; + +import std.meta; +import std.traits: Parameters, isSomeFunction, FunctionAttribute, functionAttributes; + +/++ +Checks if member is field. ++/ +template isField(T, string member) +{ + T aggregate; + enum bool isField = __traits(compiles, __traits(getMember, aggregate, member).offsetof); +} + +/// +version(mir_test) +unittest +{ + struct D + { + int gi; + } + + struct I + { + int f; + + D base; + alias base this; + + void gi(double ) @property {} + void gi(uint ) @property {} + } + + struct S + { + int d; + + I i; + alias i this; + + int gm() @property {return 0;} + int gc() const @property {return 0;} + void gs(int) @property {} + } + + static assert(!isField!(S, "gi")); + static assert(!isField!(S, "gs")); + static assert(!isField!(S, "gc")); + static assert(!isField!(S, "gm")); + static assert(!isField!(S, "gi")); + static assert(isField!(S, "d")); + static assert(isField!(S, "f")); + static assert(isField!(S, "i")); +} + +/// with classes +version(mir_test) +unittest +{ + class I + { + int f; + + void gi(double ) @property {} + void gi(uint ) @property {} + } + + class S + { + int d; + + I i; + alias i this; + + int gm() @property {return 0;} + int gc() const @property {return 0;} + void gs(int) @property {} + } + + static assert(!isField!(S, "gi")); + static assert(!isField!(S, "gs")); + static assert(!isField!(S, "gc")); + static assert(!isField!(S, "gm")); + static assert(isField!(S, "d")); + static assert(isField!(S, "f")); + static assert(isField!(S, "i")); +} + +/++ +Checks if member is property. ++/ +template isProperty(T, string member) +{ + T aggregate; + + static if (__traits(compiles, isSomeFunction!(__traits(getMember, aggregate, member)))) + { + static if (isSomeFunction!(__traits(getMember, aggregate, member))) + { + enum bool isProperty = isPropertyImpl!(__traits(getMember, aggregate, member)); + } + else + { + enum bool isProperty = false; + } + } + else + enum bool isProperty = false; +} + +/// +version(mir_test) +unittest +{ + struct D + { + int y; + + void gf(double ) @property {} + void gf(uint ) @property {} + } + + struct I + { + int f; + + D base; + alias base this; + + void gi(double ) @property {} + void gi(uint ) @property {} + } + + struct S + { + int d; + + I i; + alias i this; + + int gm() @property {return 0;} + int gc() const @property {return 0;} + void gs(int) @property {} + } + + static assert(isProperty!(S, "gf")); + static assert(isProperty!(S, "gi")); + static assert(isProperty!(S, "gs")); + static assert(isProperty!(S, "gc")); + static assert(isProperty!(S, "gm")); + static assert(!isProperty!(S, "d")); + static assert(!isProperty!(S, "f")); + static assert(!isProperty!(S, "y")); +} + +/++ +Returns: list of the setter properties. + +Note: The implementation ignores templates. ++/ +template getSetters(T, string member) +{ + alias getSetters = Filter!(hasSingleArgument, Filter!(isPropertyImpl, __traits(getOverloads, T, member))); +} + +/// +version(mir_test) +unittest +{ + struct I + { + int f; + + void gi(double ) @property {} + void gi(uint ) @property {} + } + + struct S + { + int d; + + I i; + alias i this; + + int gm() @property {return 0;} + int gc() const @property {return 0;} + void gs(int) @property {} + } + + static assert(getSetters!(S, "gi").length == 2); + static assert(getSetters!(S, "gs").length == 1); + static assert(getSetters!(S, "gc").length == 0); + static assert(getSetters!(S, "gm").length == 0); + static assert(getSetters!(S, "d").length == 0); + static assert(getSetters!(S, "f").length == 0); +} + +/++ +Returns: list of the serializable (public getters) members. ++/ +enum string[] SerializableMembers(T) = [Filter!(ApplyLeft!(Serializable, T), FieldsAndProperties!T)]; + +/// +version(mir_test) +unittest +{ + struct D + { + int y; + + int gf() @property {return 0;} + } + + struct I + { + int f; + + D base; + alias base this; + + int gi() @property {return 0;} + } + + struct S + { + int d; + + package int p; + + int gm() @property {return 0;} + + private int q; + + I i; + alias i this; + + int gc() const @property {return 0;} + void gs(int) @property {} + } + + static assert(SerializableMembers!S == ["y", "gf", "f", "gi", "d", "gm", "gc"]); + static assert(SerializableMembers!(const S) == ["y", "f", "d", "gc"]); +} + +/++ +Returns: list of the deserializable (public setters) members. ++/ +enum string[] DeserializableMembers(T) = [Filter!(ApplyLeft!(Deserializable, T), FieldsAndProperties!T)]; + +/// +version(mir_test) +unittest +{ + struct I + { + int f; + void ga(int) @property {} + } + + struct S + { + int d; + package int p; + + int gm() @property {return 0;} + void gm(int) @property {} + + private int q; + + I i; + alias i this; + + + void gc(int, int) @property {} + void gc(int) @property {} + } + + S s; + // s.gc(0); + + static assert (DeserializableMembers!S == ["f", "ga", "d", "gm", "gc"]); + static assert (DeserializableMembers!(const S) == []); +} + +// This trait defines what members should be serialized - +// public members that are either readable and writable or getter properties +private template Serializable(T, string member) +{ + static if (!isPublic!(T, member)) + enum Serializable = false; + else + enum Serializable = isReadable!(T, member); // any readable is good +} + +private enum bool hasSingleArgument(alias fun) = Parameters!fun.length == 1; +private enum bool hasZeroArguments(alias fun) = Parameters!fun.length == 0; + +// This trait defines what members should be serialized - +// public members that are either readable and writable or setter properties +private template Deserializable(T, string member) +{ + static if (!isPublic!(T, member)) + enum Deserializable = false; + else + static if (isReadableAndWritable!(T, member)) + enum Deserializable = true; + else + static if (getSetters!(T, member).length == 1) + enum Deserializable = is(typeof((ref T val){ __traits(getMember, val, member) = Parameters!(getSetters!(T, member)[0])[0].init; })); + else + enum Deserializable = false; +} + +private enum FieldsAndProperties(T) = Reverse!(NoDuplicates!(Reverse!(FieldsAndPropertiesImpl!T))); + +private template FieldsAndPropertiesImpl(T) +{ + alias isProperty = ApplyLeft!(.isProperty, T); + alias isField = ApplyLeft!(.isField, T); + static if (__traits(getAliasThis, T).length) + { + T aggregate; + alias baseMembers = FieldsAndPropertiesImpl!(typeof(__traits(getMember, aggregate, __traits(getAliasThis, T)))); + alias members = Erase!(__traits(getAliasThis, T)[0], __traits(allMembers, T)); + alias FieldsAndPropertiesImpl = AliasSeq!(baseMembers, Filter!(isField, members), Filter!(isProperty, members)); + + } + else + { + alias members = __traits(allMembers, T); + alias FieldsAndPropertiesImpl = AliasSeq!(Filter!(isField, members), Filter!(isProperty, members)); + } +} + +// check if the member is readable +private template isReadable(T, string member) +{ + T aggregate; + enum bool isReadable = __traits(compiles, { static fun(T)(auto ref T t) {} fun(__traits(getMember, aggregate, member)); }); +} + +// check if the member is readable/writeble? +private template isReadableAndWritable(T, string member) +{ + T aggregate; + enum bool isReadableAndWritable = __traits(compiles, __traits(getMember, aggregate, member) = __traits(getMember, aggregate, member)); +} + +private template isPublic(T, string member) +{ + T aggregate; + enum bool isPublic = !__traits(getProtection, __traits(getMember, aggregate, member)).privateOrPackage; +} + +// check if the member is property +private template isSetter(T, string member) +{ + T aggregate; + static if (__traits(compiles, isSomeFunction!(__traits(getMember, aggregate, member)))) + { + static if (isSomeFunction!(__traits(getMember, aggregate, member))) + { + enum bool isSetter = getSetters!(T, member).length > 0;; + } + else + { + enum bool isSetter = false; + } + } + else + enum bool isSetter = false; +} + +private template isGetter(T, string member) +{ + T aggregate; + static if (__traits(compiles, isSomeFunction!(__traits(getMember, aggregate, member)))) + { + static if (isSomeFunction!(__traits(getMember, aggregate, member))) + { + enum bool isGetter = Filter!(hasZeroArguments, Filter!(isPropertyImpl, __traits(getOverloads, T, member))).length == 1; + } + else + { + enum bool isGetter = false; + } + } + else + enum bool isGetter = false; +} + +private enum bool isPropertyImpl(alias member) = (functionAttributes!member & FunctionAttribute.property) != 0; + +private bool privateOrPackage()(string protection) +{ + return protection == "private" || protection == "package"; +} diff --git a/source/mir/serde.d b/source/mir/serde.d new file mode 100644 index 00000000..0e71df0a --- /dev/null +++ b/source/mir/serde.d @@ -0,0 +1,256 @@ +module mir.serde; + +import std.traits: getUDAs, hasUDA; + +private template getUDA(alias symbol, alias attribute) +{ + private alias all = getUDAs!(symbol, attribute); + static assert(all.length == 1, "Exactly one " ~ attribute.stringof ~ " attribute is allowed"); + enum getUDA = all[0]; +} + +/++ +Attribute for key overloading during Serialization and Deserialization. +The first argument overloads the key value during serialization unless `serdeKeyOut` is given. ++/ +struct serdeKeys +{ + /// + immutable(char[])[] keys; + +@safe pure nothrow: + /// + this(string[] keys...) { this.keys = keys.idup; } + /// + @disable this(); +} + +/++ +Attribute for key overloading during serialization. ++/ +struct serdeKeyOut +{ + /// + string key; + +@safe pure nothrow @nogc: + /// + this(string key) { this.key = key; } +} + +/++ ++/ +template getSerdeKeysIn(alias symbol) +{ + static if (hasUDA!(symbol, serdeIgnore) || hasUDA!(symbol, serdeIgnoreIn)) + enum immutable(char[])[] getSerdeKeysIn = null; + else + static if (hasUDA!(symbol, serdeKeys)) + enum immutable(char[])[] getSerdeKeysIn = getUDA!(symbol, serdeKeys).keys; + else + enum immutable(char[])[] getSerdeKeysIn = [__traits(identifier, symbol)]; +} + +/// +version(mir_test) +unittest +{ + struct S + { + int f; + + @serdeKeys("D", "t") + int d; + + @serdeIgnore + int i; + + @serdeIgnoreIn + int ii; + + @serdeIgnoreOut + int io; + + void p(int) @property {} + } + + static assert(getSerdeKeysIn!(S.f) == ["f"]); + static assert(getSerdeKeysIn!(S.d) == ["D", "t"]); + static assert(getSerdeKeysIn!(S.i) == null); + static assert(getSerdeKeysIn!(S.ii) == null); + static assert(getSerdeKeysIn!(S.io) == ["io"]); + static assert(getSerdeKeysIn!(S.p) == ["p"]); +} + +/++ ++/ +template serdeGetKeyOut(alias symbol) +{ + static if (hasUDA!(symbol, serdeIgnore) || hasUDA!(symbol, serdeIgnoreOut)) + enum string serdeGetKeyOut = null; + else + static if (hasUDA!(symbol, serdeKeyOut)) + enum string serdeGetKeyOut = getUDA!(symbol, serdeKeyOut).key; + else + static if (hasUDA!(symbol, serdeKeys)) + enum string serdeGetKeyOut = getUDA!(symbol, serdeKeys).keys[0]; + else + enum string serdeGetKeyOut = __traits(identifier, symbol); +} + +/// +version(mir_test) +unittest +{ + struct S + { + int f; + + @serdeKeys("D", "t") + int d; + + @serdeIgnore + int i; + + @serdeIgnoreIn + int ii; + + @serdeIgnoreOut + int io; + + @serdeKeys("P") + @serdeKeyOut("") + void p(int) @property {} + } + + static assert(serdeGetKeyOut!(S.f) == "f"); + static assert(serdeGetKeyOut!(S.d) == "D"); + static assert(serdeGetKeyOut!(S.i) is null); + static assert(serdeGetKeyOut!(S.ii) == "ii"); + static assert(serdeGetKeyOut!(S.io) is null); + static assert(serdeGetKeyOut!(S.p) !is null); + static assert(serdeGetKeyOut!(S.p) == ""); +} + +/++ +Attribute to ignore field. ++/ +enum serdeIgnore; + +/++ +Attribute to ignore field during deserialization. ++/ +enum serdeIgnoreIn; + +/++ +Attribute to ignore field during serialization. ++/ +enum serdeIgnoreOut; + +/++ +Attribute to ignore a field during deserialization when equals to its default value. +Do not use it on void initialized fields or aggregates with void initialized fields, recursively. ++/ +enum serdeIgnoreDefault; + +/// +version(mir_test) +unittest +{ + struct S + { + @serdeIgnoreDefault + double d = 0; // skips field if 0 during deserialization + } + + import std.traits: hasUDA; + + static assert(hasUDA!(S.d, serdeIgnoreDefault)); +} + +/++ ++/ + +/++ +Serialization proxy. ++/ +struct serdeProxy(T); + +/// +version(mir_test) +unittest +{ + import mir.small_string; + + struct S + { + @serdeProxy!(SmallString!32) + double d; + } + + import std.traits: hasUDA, getUDAs; + + static assert(hasUDA!(S.d, serdeProxy)); + static assert(hasUDA!(S.d, serdeProxy!(SmallString!32))); + static assert(getUDAs!(S.d, serdeProxy).length == 1); +} + +/++ +Can be applied only to fields that can be constructed from strings. +Does not allocate new data when deserializeing. Raw data is used for strings instead of new memory allocation. +Use this attributes only for strings that would not be used after the input data deallocation. ++/ +enum serdeScopeStringProxy; + +/++ +Attributes to out conditional ignore field during serialization. + +The predicate should be aplied to the aggregate type itself, not to the member. ++/ +struct serdeIgnoreOutIf(alias pred); + +/++ +Allows to use flexible deserialization rules such as conversion from input string to numeric types. ++/ +enum serdeFlexible; + +/++ +Allows serialize / deserialize fields like arrays. + +A range or a container should be iterable for serialization. +Following code should compile: +------ +foreach(ref value; yourRangeOrContainer) +{ + ... +} +------ + +`put(value)` method is used for deserialization. + +See_also: $(MREF serdeIgnoreOut), $(MREF serdeIgnoreIn) ++/ +enum serdeLikeList; + +/++ +Allows serialize / deserialize fields like objects. + +Object should have `opApply` method to allow serialization. +Following code should compile: +------ +foreach(key, value; yourObject) +{ + ... +} +------ +Object should have only one `opApply` method with 2 argument to allow automatic value type deduction. + +`opIndexAssign` or `opIndex` is used for deserialization to support required syntax: +----- +yourObject["key"] = value; +----- +Multiple value types is supported for deserialization. + +See_also: $(MREF serdeIgnoreOut), $(MREF serdeIgnoreIn) ++/ +enum serdeLikeStruct;