1 module hunt.serialization.Specify;
2 
3 import std.traits;
4 import std.range;
5 import hunt.serialization.BinarySerializer;
6 import hunt.serialization.BinaryDeserializer;
7 
8 template PtrType(T) {
9     static if (is(T == bool) || is(T == char)) {
10         alias PtrType = ubyte*;
11     } else static if (is(T == float)) {
12         alias PtrType = uint*;
13     } else static if (is(T == double)) {
14         alias PtrType = ulong*;
15     } else {
16         alias PtrType = Unsigned!T*;
17     }
18 }
19 
20 enum NULL = [110, 117, 108, 108];
21 
22 void specify(C, T)(auto ref C obj, ref T val) if (is(T == wchar)) {
23     specify(obj, *cast(ushort*)&val);
24 }
25 
26 void specify(C, T)(auto ref C obj, ref T val) if (is(T == dchar)) {
27     specify(obj, *cast(uint*)&val);
28 }
29 
30 void specify(C, T)(auto ref C obj, ref T val) if (is(T == ushort)) {
31     ubyte valh = (val >> 8);
32     ubyte vall = val & 0xff;
33     obj.putUbyte(valh);
34     obj.putUbyte(vall);
35     val = (valh << 8) + vall;
36 }
37 
38 void specify(C, T)(auto ref C obj, ref T val) if (is(T == uint)) {
39     ubyte val0 = (val >> 24);
40     ubyte val1 = cast(ubyte)(val >> 16);
41     ubyte val2 = cast(ubyte)(val >> 8);
42     ubyte val3 = val & 0xff;
43     obj.putUbyte(val0);
44     obj.putUbyte(val1);
45     obj.putUbyte(val2);
46     obj.putUbyte(val3);
47     val = (val0 << 24) + (val1 << 16) + (val2 << 8) + val3;
48 }
49 
50 void specify(C, T)(auto ref C obj, ref T val) if (is(T == ulong)) {
51     T newVal;
52     for (int i = 0; i < T.sizeof; ++i) {
53         immutable shiftBy = 64 - (i + 1) * T.sizeof;
54         ubyte byteVal = (val >> shiftBy) & 0xff;
55         obj.putUbyte(byteVal);
56         newVal |= (cast(T) byteVal << shiftBy);
57     }
58     val = newVal;
59 }
60 
61 void specify(C, T)(auto ref C obj, ref T val) if (isStaticArray!T) {
62     static if (is(Unqual!(ElementType!T) : ubyte) && T.sizeof == 1) {
63         obj.putRaw(cast(ubyte[]) val);
64     } else {
65         foreach (ref v; val) {
66             specify(obj, v);
67         }
68     }
69 }
70 
71 void specify(C, T)(auto ref C obj, ref T val) if (is(T == string)) {
72     ushort len = cast(ushort) val.length;
73     specify(obj, len);
74 
75     static if (is(C == BinarySerializer)) {
76         obj.putRaw(cast(ubyte[]) val);
77     } else {
78         val = cast(string) obj.putRaw(len).idup;
79     }
80 }
81 
82 void specify(U, C, T)(auto ref C obj, ref T val) if(is(T == string)) {
83     U length = cast(U)val.length;
84     assert(length == val.length, "overflow");
85     specify(obj,length);
86 
87     static if(is(C == BinarySerializer))
88         obj.putRaw(cast(ubyte[])val);
89     else
90         val = cast(string) obj.putRaw(length).idup;
91 }
92 
93 void specify(C, T)(auto ref C obj, ref T val) if (isAssociativeArray!T) {
94     ushort length = cast(ushort) val.length;
95     specify(obj, length);
96     const keys = val.keys;
97     for (ushort i = 0; i < length; ++i) {
98         KeyType!T k = keys.length ? keys[i] : KeyType!T.init;
99         auto v = keys.length ? val[k] : ValueType!T.init;
100 
101         specify(obj, k);
102         specify(obj, v);
103         val[k] = v;
104     }
105 }
106 
107 void specify(C, T)(auto ref C obj, ref T val) if (isPointer!T) {
108     alias ValueType = PointerTarget!T;
109     specify(obj, *val);
110 }
111 
112 //ubyte
113 void specify(C, T)(auto ref C obj, ref T val) if (is(T == ubyte)) {
114     obj.putUbyte(val);
115 }
116 
117 void specify(C, T)(auto ref C obj, ref T val)
118         if (!is(T == enum) && (isSigned!T || isBoolean!T || is(T == char) || isFloatingPoint!T)) {
119     specifyPtr(obj, val);
120 }
121 
122 //ENUM
123 void specify(C, T)(auto ref C obj, ref T val) if (is(T == enum)) {
124     specify(obj, cast(Unqual!(OriginalType!(T))) val);
125 }
126 
127 void specify(C, T)(auto ref C obj, ref T val)
128         if (is(C == BinarySerializer) && isInputRange!T && !isInfinite!T
129             && !is(T == string) && !isStaticArray!T && !isAssociativeArray!T) {
130     enum hasLength = is(typeof(() { auto l = val.length; }));
131     ushort length = cast(ushort) val.length;
132     specify(obj, length);
133 
134     static if (hasSlicing!(Unqual!T) && is(Unqual!(ElementType!T) : ubyte) && T.sizeof == 1) {
135         obj.putRaw(cast(ubyte[]) val.array);
136     } else {
137         foreach (ref v; val) {
138             specify(obj, v);
139         }
140     }
141 }
142 
143 void specify(C, T)(auto ref C obj, ref T val)
144         if (isAggregateType!T && !isInputRange!T && !isOutputRange!(T, ubyte)) {
145     loopMembers(obj, val);
146 }
147 
148 void specify(C, T)(auto ref C obj, ref T val)
149         if (isDecerealiser!C && !isOutputRange!(T, ubyte) && isDynamicArray!T && !is(T == string)) {
150     ushort length;
151 
152     specify(obj, length);
153     decerealiseArrayImpl(obj, val, length);
154 }
155 
156 void decerealiseArrayImpl(C, T, U)(auto ref C obj, ref T val, U length)
157         if (is(T == E[], E) && isDecerealiser!C) {
158 
159     ulong neededBytes(T)(ulong length) {
160         alias E = ElementType!T;
161         static if (isScalarType!E)
162             return length * E.sizeof;
163         else static if (isInputRange!E)
164             return neededBytes!E(length);
165         else
166             return 0;
167     }
168 
169     immutable needed = neededBytes!T(length);
170 
171     static if (is(Unqual!(ElementType!T) : ubyte) && T.sizeof == 1) {
172         val = obj.putRaw(length).dup;
173     } else {
174         if (val.length != length)
175             val.length = cast(uint) length;
176         foreach (ref e; val)
177             obj.specify(e);
178     }
179 }
180 
181 void specifyPtr(C, T)(auto ref C obj, ref T val) {
182     auto ptr = cast(PtrType!T)(&val);
183     specify(obj, *ptr);
184 }
185 
186 void loopMembers(C, T)(auto ref C obj, ref T val) if (is(T == struct)) {
187     loopMembersImpl!T(obj, val);
188 }
189 
190 void loopMembers(C, T)(auto ref C obj, ref T val) if (is(T == class)) {
191 
192     static if (is(C == BinarySerializer)) {
193         if (val is null) {
194             obj.putRaw(NULL);
195             return;
196         }
197         //assert(val !is null, "null value cannot be serialised");
198     }
199 
200     static if (is(C == BinaryDeserializer)) {
201         if (obj.isNullObj) {
202             val = null;
203             return;
204         }
205     }
206 
207     static if (is(typeof(() { val = new T; }))) {
208         if (val is null)
209             val = new T;
210     } else {
211     }
212 
213     obj.putClass(val);
214 }
215 
216 void loopMembersImpl(T, C, VT)(auto ref C obj, ref VT val) {
217     foreach (member; __traits(derivedMembers, T)) {
218         enum isMemberVariable = is(typeof(() {
219                     __traits(getMember, val, member) = __traits(getMember, val, member).init;
220                 }));
221         static if (isMemberVariable) {
222             specifyAggregateMember!member(obj, val);
223         }
224     }
225 }
226 
227 void specifyAggregateMember(string member, C, T)(auto ref C obj, ref T val) {
228     import std.meta : staticIndexOf;
229 
230     enum NoCereal;
231     enum noCerealIndex = staticIndexOf!(NoCereal, __traits(getAttributes,
232                 __traits(getMember, val, member)));
233     static if (noCerealIndex == -1) {
234         specifyMember!member(obj, val);
235     }
236 }
237 
238 void specifyMember(string member, C, T)(auto ref C obj, ref T val) {
239     specify(obj, __traits(getMember, val, member));
240 }
241 
242 void specifyBaseClass(C, T)(auto ref C obj, ref T val) if (is(T == class)) {
243     foreach (base; BaseTypeTuple!T) {
244         loopMembersImpl!base(obj, val);
245     }
246 }
247 
248 void specifyClass(C, T)(auto ref C obj, ref T val) if (is(T == class)) {
249     specifyBaseClass(obj, val);
250     loopMembersImpl!T(obj, val);
251 }
252 
253 void checkDecerealiser(T)() {
254     //static assert(T.type == CerealType.ReadBytes);
255     auto dec = T();
256     ulong bl = dec.bytesLeft;
257 }
258 
259 enum isDecerealiser(T) = (is(T == BinaryDeserializer) && is(typeof(checkDecerealiser!T)));