1 module cerealed.cereal;
2 
3 public import cerealed.attrs;
4 import cerealed.traits;
5 import std.traits;
6 import std.conv;
7 import std.algorithm;
8 import std.range;
9 import std.typetuple;
10 
11 class CerealException: Exception {
12     this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) @safe pure {
13         super(msg, file, line, next);
14     }
15 }
16 
17 enum CerealType { WriteBytes, ReadBytes };
18 
19 void grain(C, T)(auto ref C cereal, ref T val) if(isCereal!C && is(T == ubyte)) {
20     cereal.grainUByte(val);
21 }
22 
23 //catch all signed numbers and forward to reinterpret
24 void grain(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && !is(T == enum) &&
25                                                         (isSigned!T || isBoolean!T ||
26                                                          is(T == char) || isFloatingPoint!T)) {
27     cereal.grainReinterpret(val);
28 }
29 
30 // If the type is an enum, get the unqualified base type and cast it to that.
31 void grain(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == enum)) {
32     alias Unqual!(OriginalType!(T)) BaseType;
33     cereal.grain( cast(BaseType)val );
34 }
35 
36 
37 void grain(C, T)(auto ref C cereal, ref T val) @trusted if(isCereal!C && is(T == wchar)) {
38     cereal.grain(*cast(ushort*)&val);
39 }
40 
41 void grain(C, T)(auto ref C cereal, ref T val) @trusted if(isCereal!C && is(T == dchar)) {
42     cereal.grain(*cast(uint*)&val);
43 }
44 
45 void grain(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == ushort)) {
46     ubyte valh = (val >> 8);
47     ubyte vall = val & 0xff;
48     cereal.grainUByte(valh);
49     cereal.grainUByte(vall);
50     val = (valh << 8) + vall;
51 }
52 
53 void grain(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == uint)) {
54     ubyte val0 = (val >> 24);
55     ubyte val1 = cast(ubyte)(val >> 16);
56     ubyte val2 = cast(ubyte)(val >> 8);
57     ubyte val3 = val & 0xff;
58     cereal.grainUByte(val0);
59     cereal.grainUByte(val1);
60     cereal.grainUByte(val2);
61     cereal.grainUByte(val3);
62     val = (val0 << 24) + (val1 << 16) + (val2 << 8) + val3;
63 }
64 
65 void grain(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == ulong)) {
66     T newVal;
67     for(int i = 0; i < T.sizeof; ++i) {
68         immutable shiftBy = 64 - (i + 1) * T.sizeof;
69         ubyte byteVal = (val >> shiftBy) & 0xff;
70         cereal.grainUByte(byteVal);
71         newVal |= (cast(T)byteVal << shiftBy);
72     }
73     val = newVal;
74 }
75 
76 enum hasByteElement(T) = is(Unqual!(ElementType!T): ubyte) && T.sizeof == 1;
77 
78 void grain(C, T, U = ushort)(auto ref C cereal, ref T val) @trusted if(isCerealiser!C &&
79                                                                        isInputRange!T && !isInfinite!T &&
80                                                                        !is(T == string) &&
81                                                                        !isStaticArray!T &&
82                                                                        !isAssociativeArray!T) {
83     enum hasLength = is(typeof(() { auto l = val.length; }));
84     static assert(hasLength, text("Only InputRanges with .length accepted, not the case for ",
85                                   fullyQualifiedName!T));
86     U length = cast(U)val.length;
87     assert(length == val.length, "overflow");
88     cereal.grain(length);
89 
90     static if(hasSlicing!(Unqual!T) && hasByteElement!T)
91         cereal.grainRaw(cast(ubyte[])val.array);
92     else
93         foreach(ref e; val) cereal.grain(e);
94 }
95 
96 void grain(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && isStaticArray!T) {
97     static if(hasByteElement!T)
98         cereal.grainRaw(cast(ubyte[])val);
99     else
100         foreach(ref e; val) cereal.grain(e);
101 }
102 
103 void grain(C, T, U = ushort)(auto ref C cereal, ref T val) @trusted if(isDecerealiser!C &&
104                                                                        !isStaticArray!T &&
105                                                                        isOutputRange!(T, ubyte)) {
106     U length = void;
107     cereal.grain(length);
108 
109     static if(isArray!T) {
110         decerealiseArrayImpl(cereal, val, length);
111     } else {
112         for(U i = 0; i < length; ++i) {
113             ubyte b = void;
114             cereal.grain(b);
115 
116             enum hasOpOpAssign = is(typeof(() { val ~= b; }));
117             static if(hasOpOpAssign) {
118                 val ~= b;
119             } else {
120                 val.put(b);
121             }
122         }
123     }
124 }
125 
126 private void decerealiseArrayImpl(C, T, U = ushort)(auto ref C cereal, ref T val, U length) @safe
127     if(is(T == E[], E)) {
128 
129     static if(hasByteElement!T) {
130         val = cereal.grainRaw(length).dup;
131     } else {
132         if(val.length != length) val.length = cast(uint)length;
133         assert(length == val.length, "overflow");
134 
135         foreach(ref e; val) cereal.grain(e);
136     }
137 }
138 
139 void grain(C, T, U = ushort)(auto ref C cereal, ref T val) @trusted if(isDecerealiser!C &&
140                                                                        !isOutputRange!(T, ubyte) &&
141                                                                        isDynamicArray!T && !is(T == string)) {
142     U length = void;
143     cereal.grain(length);
144     decerealiseArrayImpl(cereal, val, length);
145 }
146 
147 void grain(C, T, U = ushort)(auto ref C cereal, ref T val) @trusted if(isCereal!C && is(T == string)) {
148     U length = cast(U)val.length;
149     assert(length == val.length, "overflow");
150     cereal.grain(length);
151 
152     static if(isCerealiser!C)
153         cereal.grainRaw(cast(ubyte[])val);
154     else
155         val = cast(string)cereal.grainRaw(length);
156 }
157 
158 
159 void grain(C, T, U = ushort)(auto ref C cereal, ref T val) @trusted if(isCereal!C && isAssociativeArray!T) {
160     U length = cast(U)val.length;
161     assert(length == val.length, "overflow");
162     cereal.grain(length);
163     const keys = val.keys;
164 
165     for(U i = 0; i < length; ++i) {
166         KeyType!T k = keys.length ? keys[i] : KeyType!T.init;
167         auto v = keys.length ? val[k] : ValueType!T.init;
168 
169         cereal.grain(k);
170         cereal.grain(v);
171         val[k] = v;
172     }
173 }
174 
175 void grain(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && isPointer!T) {
176     import std.traits;
177     alias ValueType = PointerTarget!T;
178     static if(isDecerealiser!C) {
179         if(val is null) val = new ValueType;
180     }
181     cereal.grain(*val);
182 }
183 
184 private template canCall(C, T, string func) {
185     enum canCall = is(typeof(() { auto cer = C(); auto val = T.init; mixin("val." ~ func ~ "(cer);"); }));
186     static if(!canCall && __traits(hasMember, T, func)) {
187         pragma(msg, "Warning: '" ~ func ~
188                "' function defined for ", T, ", but does not compile for Cereal ", C);
189     }
190 }
191 
192 void grain(C, T)(auto ref C cereal, ref T val) @trusted if(isCereal!C && isAggregateType!T &&
193                                                            !isInputRange!T && !isOutputRange!(T, ubyte)) {
194     enum canAccept   = canCall!(C, T, "accept");
195     enum canPreBlit = canCall!(C, T, "preBlit");
196     enum canPostBlit = canCall!(C, T, "postBlit");
197 
198     static if(canAccept) { //custom serialisation
199         static assert(!canPostBlit && !canPreBlit, "Cannot define both accept and pre/postBlit");
200         val.accept(cereal);
201     } else { //normal serialisation, go through each member and possibly serialise
202         static if(canPreBlit) {
203             val.preBlit(cereal);
204         }
205 
206         cereal.grainAllMembers(val);
207         static if(canPostBlit) { //semi-custom serialisation, do post blit
208             val.postBlit(cereal);
209         }
210     }
211 }
212 
213 void grainAllMembers(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == struct)) {
214     cereal.grainAllMembersImpl!T(val);
215 }
216 
217 
218 void grainAllMembers(C, T)(auto ref C cereal, ref T val) @trusted if(isCereal!C && is(T == class)) {
219     static if(isCerealiser!C) {
220         assert(val !is null, "null value cannot be serialised");
221     }
222 
223     enum hasDefaultConstructor = is(typeof(() { val = new T; }));
224     static if(hasDefaultConstructor && isDecerealiser!C) {
225         if(val is null) val = new T;
226     } else {
227         assert(val !is null, text("Cannot deserialise into null value. ",
228                                   "Possible cause: no default constructor for ",
229                                   fullyQualifiedName!T, "."));
230     }
231 
232     cereal.grainClass(val);
233 }
234 
235 
236 void grainMemberWithAttr(string member, C, T)(auto ref C cereal, ref T val) @trusted if(isCereal!C) {
237     /**(De)serialises one member taking into account its attributes*/
238     enum noCerealIndex = staticIndexOf!(NoCereal, __traits(getAttributes,
239                                                            __traits(getMember, val, member)));
240     enum rawArrayIndex = staticIndexOf!(RawArray, __traits(getAttributes,
241                                                            __traits(getMember, val, member)));
242     //only serialise if the member doesn't have @NoCereal
243     static if(noCerealIndex == -1) {
244         alias bitsAttrs = Filter!(isABitsStruct, __traits(getAttributes,
245                                                       __traits(getMember, val, member)));
246         static assert(bitsAttrs.length == 0 || bitsAttrs.length == 1,
247                       "Too many Bits!N attributes!");
248 
249         alias arrayLengths = Filter!(isArrayLengthStruct,
250                                      __traits(getAttributes,
251                                               __traits(getMember, val, member)));
252         static assert(arrayLengths.length == 0 || arrayLengths.length == 1,
253                       "Too many ArrayLength attributes");
254 
255         alias lengthInBytes = Filter!(isLengthInBytesStruct,
256                                       __traits(getAttributes,
257                                                __traits(getMember, val, member)));
258         static assert(lengthInBytes.length == 0 || lengthInBytes.length == 1,
259                       "Too many LengthInBytes attributes");
260 
261         static if(bitsAttrs.length == 1) {
262 
263             grainWithBitsAttr!(member, bitsAttrs[0])(cereal, val);
264 
265         } else static if(rawArrayIndex != -1) {
266 
267             cereal.grainRawArray(__traits(getMember, val, member));
268 
269         } else static if(arrayLengths.length > 0) {
270 
271             grainWithArrayLengthAttr!(member, arrayLengths[0].member)(cereal, val);
272 
273         } else static if(lengthInBytes.length > 0) {
274 
275             grainWithLengthInBytesAttr!(member, lengthInBytes[0].member)(cereal, val);
276 
277         } else {
278 
279             cereal.grain(__traits(getMember, val, member));
280 
281         }
282     }
283 }
284 
285 private void grainWithBitsAttr(string member, alias bitsAttr, C, T)(
286     auto ref C cereal, ref T val) @safe if(isCereal!C) {
287 
288     enum numBits = getNumBits!(bitsAttr);
289     enum sizeInBits = __traits(getMember, val, member).sizeof * 8;
290     static assert(numBits <= sizeInBits,
291                   text(fullyQualifiedName!T, ".", member, " is ", sizeInBits,
292                        " bits long, which is not enough to store @Bits!", numBits));
293     cereal.grainBitsT(__traits(getMember, val, member), numBits);
294 }
295 
296 private void grainWithArrayLengthAttr(string member, string lengthMember, C, T)
297     (auto ref C cereal, ref T val) @safe if(isCereal!C) {
298 
299     checkArrayAttrType!member(cereal, val);
300 
301     static if(isCerealiser!C) {
302         cereal.grainRawArray(__traits(getMember, val, member));
303     } else {
304         immutable length = lengthOfArray!(member, lengthMember)(cereal, val);
305         alias E = ElementType!(typeof(__traits(getMember, val, member)));
306 
307         if(length * E.sizeof  > cereal.bytesLeft) {
308             throw new CerealException(text("@ArrayLength of ", length, " units of type ",
309                                            E.stringof,
310                                            " (", length * E.sizeof, " bytes) ",
311                                            "larger than remaining byte array (",
312                                            cereal.bytesLeft, " bytes)\n",
313                                           cereal.bytes));
314         }
315 
316         mixin(q{__traits(getMember, val, member).length = length;});
317 
318         foreach(ref e; __traits(getMember, val, member)) cereal.grain(e);
319     }
320 }
321 
322 private void grainWithLengthInBytesAttr(string member, string lengthMember, C, T)
323     (auto ref C cereal, ref T val) @safe if(isCereal!C) {
324 
325     checkArrayAttrType!member(cereal, val);
326 
327     static if(isCerealiser!C) {
328         cereal.grainRawArray(__traits(getMember, val, member));
329     } else {
330         immutable length = lengthOfArray!(member, lengthMember)(cereal, val); //error handling
331 
332         if(length > cereal.bytesLeft) {
333             alias E = ElementType!(typeof(__traits(getMember, val, member)));
334             throw new CerealException(text("@LengthInBytes of ", length, " bytes ",
335                                            "larger than remaining byte array (",
336                                            cereal.bytesLeft, " bytes)"));
337         }
338 
339         __traits(getMember, val, member).length = 0;
340 
341         long bytesLeft = length;
342         while(bytesLeft) {
343             auto origCerealBytesLeft = cereal.bytesLeft;
344             __traits(getMember, val, member).length++;
345             cereal.grain(__traits(getMember, val, member)[$ - 1]);
346             bytesLeft -= (origCerealBytesLeft - cereal.bytesLeft);
347         }
348     }
349 }
350 
351 private void checkArrayAttrType(string member, C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C) {
352     alias M = typeof(__traits(getMember, val, member));
353     static assert(is(M == E[], E),
354                   text("@ArrayLength and @LengthInBytes not valid for ", member,
355                        ": they can only be used on slices"));
356 }
357 
358 
359 private int lengthOfArray(string member, string lengthMember, C, T)(auto ref C cereal, ref T val)
360     @safe if(isCereal!C) {
361     int _tmpLen;
362     mixin(q{with(val) _tmpLen = cast(int)(} ~ lengthMember ~ q{);});
363 
364     if(_tmpLen < 0)
365         throw new CerealException(text("@LengthInBytes resulted in negative length ", _tmpLen));
366 
367     return _tmpLen;
368 }
369 
370 void grainRawArray(C, T)(auto ref C cereal, ref T[] val) @trusted if(isCereal!C) {
371     //can't use virtual functions due to template parameter
372     static if(isDecerealiser!C) {
373         val.length = 0;
374         while(cereal.bytesLeft()) {
375             val.length++;
376             cereal.grain(val[$ - 1]);
377         }
378     } else {
379         foreach(ref t; val) cereal.grain(t);
380     }
381 }
382 
383 
384 /**
385  * To be used when the length of the array is known at run-time based on the value
386  * of a part of byte stream.
387  */
388 void grainLengthedArray(C, T)(auto ref C cereal, ref T[] val, long length) {
389     val.length = cast(typeof(val.length))length;
390     foreach(ref t; val) cereal.grain(t);
391 }
392 
393 
394 package void grainClassImpl(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == class)) {
395     //do base classes first or else the order is wrong
396     cereal.grainBaseClasses(val);
397     cereal.grainAllMembersImpl!T(val);
398 }
399 
400 private void grainBitsT(C, T)(auto ref C cereal, ref T val, int bits) @safe if(isCereal!C) {
401     uint realVal = val;
402     cereal.grainBits(realVal, bits);
403     val = cast(T)realVal;
404 }
405 
406 private void grainReinterpret(C, T)(auto ref C cereal, ref T val) @trusted if(isCereal!C) {
407     auto ptr = cast(CerealPtrType!T)(&val);
408     cereal.grain(*ptr);
409 }
410 
411 private void grainBaseClasses(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == class)) {
412     foreach(base; BaseTypeTuple!T) {
413         cereal.grainAllMembersImpl!base(val);
414     }
415 }
416 
417 
418 private void grainAllMembersImpl(ActualType, C, ValType)(auto ref C cereal, ref ValType val) @trusted
419 if(isCereal!C) {
420     foreach(member; __traits(derivedMembers, ActualType)) {
421         //makes sure to only serialise members that make sense, i.e. data
422         enum isMemberVariable = is(typeof(() {
423                                            __traits(getMember, val, member) = __traits(getMember, val, member).init;
424                                        }));
425         static if(isMemberVariable) {
426             cereal.grainMemberWithAttr!member(val);
427         }
428     }
429 }
430 
431 private template CerealPtrType(T) {
432     static if(is(T == bool) || is(T == char)) {
433         alias ubyte* CerealPtrType;
434     } else static if(is(T == float)) {
435         alias uint* CerealPtrType;
436     } else static if(is(T == double)) {
437         alias ulong* CerealPtrType;
438     } else {
439        alias Unsigned!T* CerealPtrType;
440     }
441 }