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) @safe pure { 13 super(msg); 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); 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)")); 313 } 314 315 mixin(q{__traits(getMember, val, member).length = length;}); 316 317 foreach(ref e; __traits(getMember, val, member)) cereal.grain(e); 318 } 319 } 320 321 private void grainWithLengthInBytesAttr(string member, string lengthMember, C, T) 322 (auto ref C cereal, ref T val) @safe if(isCereal!C) { 323 324 checkArrayAttrType!member(cereal, val); 325 326 static if(isCerealiser!C) { 327 cereal.grainRawArray(__traits(getMember, val, member)); 328 } else { 329 immutable length = lengthOfArray!(member, lengthMember)(cereal, val); //error handling 330 331 if(length > cereal.bytesLeft) { 332 alias E = ElementType!(typeof(__traits(getMember, val, member))); 333 throw new CerealException(text("@LengthInBytes of ", length, " bytes ", 334 "larger than remaining byte array (", 335 cereal.bytesLeft, " bytes)")); 336 } 337 338 __traits(getMember, val, member).length = 0; 339 340 while(cereal.bytesLeft) { 341 __traits(getMember, val, member).length++; 342 cereal.grain(__traits(getMember, val, member)[$ - 1]); 343 } 344 } 345 } 346 347 private void checkArrayAttrType(string member, C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C) { 348 alias M = typeof(__traits(getMember, val, member)); 349 static assert(is(M == E[], E), 350 text("@ArrayLength and @LengthInBytes not valid for ", member, 351 ": they can only be used on slices")); 352 } 353 354 355 private int lengthOfArray(string member, string lengthMember, C, T)(auto ref C cereal, ref T val) 356 @safe if(isCereal!C) { 357 int _tmpLen; 358 mixin(q{with(val) _tmpLen = cast(int)(} ~ lengthMember ~ q{);}); 359 360 if(_tmpLen < 0) 361 throw new CerealException(text("@LengthInBytes resulted in negative length ", _tmpLen)); 362 363 return _tmpLen; 364 } 365 366 void grainRawArray(C, T)(auto ref C cereal, ref T[] val) @trusted if(isCereal!C) { 367 //can't use virtual functions due to template parameter 368 static if(isDecerealiser!C) { 369 val.length = 0; 370 while(cereal.bytesLeft()) { 371 val.length++; 372 cereal.grain(val[$ - 1]); 373 } 374 } else { 375 foreach(ref t; val) cereal.grain(t); 376 } 377 } 378 379 380 /** 381 * To be used when the length of the array is known at run-time based on the value 382 * of a part of byte stream. 383 */ 384 void grainLengthedArray(C, T)(auto ref C cereal, ref T[] val, long length) { 385 val.length = cast(typeof(val.length))length; 386 foreach(ref t; val) cereal.grain(t); 387 } 388 389 390 package void grainClassImpl(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == class)) { 391 //do base classes first or else the order is wrong 392 cereal.grainBaseClasses(val); 393 cereal.grainAllMembersImpl!T(val); 394 } 395 396 private void grainBitsT(C, T)(auto ref C cereal, ref T val, int bits) @safe if(isCereal!C) { 397 uint realVal = val; 398 cereal.grainBits(realVal, bits); 399 val = cast(T)realVal; 400 } 401 402 private void grainReinterpret(C, T)(auto ref C cereal, ref T val) @trusted if(isCereal!C) { 403 auto ptr = cast(CerealPtrType!T)(&val); 404 cereal.grain(*ptr); 405 } 406 407 private void grainBaseClasses(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == class)) { 408 foreach(base; BaseTypeTuple!T) { 409 cereal.grainAllMembersImpl!base(val); 410 } 411 } 412 413 414 private void grainAllMembersImpl(ActualType, C, ValType)(auto ref C cereal, ref ValType val) @trusted 415 if(isCereal!C) { 416 foreach(member; __traits(derivedMembers, ActualType)) { 417 //makes sure to only serialise members that make sense, i.e. data 418 enum isMemberVariable = is(typeof(() { 419 __traits(getMember, val, member) = __traits(getMember, val, member).init; 420 })); 421 static if(isMemberVariable) { 422 cereal.grainMemberWithAttr!member(val); 423 } 424 } 425 } 426 427 private template CerealPtrType(T) { 428 static if(is(T == bool) || is(T == char)) { 429 alias ubyte* CerealPtrType; 430 } else static if(is(T == float)) { 431 alias uint* CerealPtrType; 432 } else static if(is(T == double)) { 433 alias ulong* CerealPtrType; 434 } else { 435 alias Unsigned!T* CerealPtrType; 436 } 437 }