1 /** Serialization. 2 * 3 * Test: dmd -version=show -preview=dip1000 -preview=in -vcolumns -d -I.. -i -debug -g -checkaction=context -allinst -unittest -main -run serialization.d 4 * Test: ldmd2 -d -fsanitize=address -I.. -i -debug -g -checkaction=context -allinst -unittest -main -run serialization.d 5 * Debug: ldmd2 -d -fsanitize=address -I.. -i -debug -g -checkaction=context -allinst -unittest -main serialization.d && lldb serialization 6 * 7 * TODO: Detect and pack consecutive bitfields using new `__traits(isBitfield, T)` 8 * 9 * TODO: Give compile-time error message when trying to serialize `void*` but not `void[]` 10 * 11 * TODO: Allocator supoprt 12 * 13 * TODO: Disable (de)serialization of nested types via `!__traits(isNested, T)` 14 * 15 * TODO: Support serialization of cycles and remove `Code.failureCycle` and `sc`. 16 * 17 * TODO: Use direct field setting for T only when __traits(isPOD, T) is true 18 otherwise use __traits(getOverloads, T, "__ctor"). 19 Try to use this to generalize (de)serialization of `std.json.JSONValue` 20 to a type-agnostic logic inside inside generic main generic `serializeRaw` 21 and `deserializeRaw`. 22 * 23 * TODO: Support bit-blitting of unions of only non-pointer fields. 24 * 25 * TODO: Only disable union's that contain any pointers. 26 * Detect using __traits, std.traits or gc_traits.d. 27 * 28 * TODO: Avoid call to `new` when deserializing arrays of immutable elements 29 * (and perhaps classes) when `Sink` element type `E` is immutable. 30 * 31 * TODO: Exercise `JSONValue` (de)serialization with `nxt.sampling`. 32 * 33 * TODO: Optimize (de)serialization when `__traits(hasIndirections)` is avaiable and 34 `__traits(hasIndirections, T)` is false and `enablesSlicing` is set. 35 */ 36 module nxt.serialization; 37 38 import nxt.visiting : Addresses; 39 import nxt.dip_traits : hasPreviewBitfields; 40 41 version = serialization_json_test; 42 43 /++ Serialization format. 44 +/ 45 @safe struct Format { 46 /++ Flag that integral types are packed via variable length encoding (VLE). 47 +/ 48 bool packIntegrals = false; 49 50 /++ Flag that scalar types are serialized in native (platform-dependent) byte-order. 51 Reason for settings this is usually to gain speed. 52 +/ 53 bool useNativeByteOrder = false; 54 55 /++ Returns: `true` iff `this` enables array slices of a scalar type 56 to be read/written without a loop, resulting in higher performance. 57 +/ 58 @property bool enablesSlicing() const pure nothrow @nogc 59 => (!packIntegrals && useNativeByteOrder); 60 } 61 62 /++ Status. 63 Converts to `bool true` for failures to simplify control flow in 64 (de)serialization functions. 65 +/ 66 struct Status { 67 /++ Status code. +/ 68 enum Code { 69 successful = 0, 70 failure = 1, 71 failureCycle = 2, 72 } 73 Code code; 74 alias code this; 75 bool opCast(T : bool)() const nothrow @nogc => code != Code.successful; 76 } 77 78 /++ Code (unit) type. 79 +/ 80 alias CodeUnitType = ubyte; 81 82 /++ Raw (binary) serialize `arg` to `sink` in format `fmt`. 83 TODO: Predict `initialAddrsCapacity` in callers. 84 +/ 85 Status serializeRaw(T, Sink)(scope ref Sink sink, in T arg, in Format fmt = Format.init, in size_t initialAddrsCapacity = 0) { 86 scope Addresses addrs; 87 () @trusted { 88 addrs.reserve(initialAddrsCapacity); // .reserve should be @trusted here 89 }(); 90 return serializeRaw_!(T, Sink)(sink, arg, addrs, fmt); 91 } 92 93 private Status serializeRaw_(T, Sink)(scope ref Sink sink, in T arg, scope ref Addresses addrs, in Format fmt = Format.init) { 94 alias E = typeof(Sink.init[][0]); // code unit (element) type 95 static assert(__traits(isUnsigned, E), 96 "Non-unsigned sink code unit (element) type " ~ E.stringof); 97 static assert(!is(T == union), 98 "Cannot serialize union type `" ~ T.stringof ~ "`"); 99 static if (is(T == struct) || is(T == union) || is(T == class)) { 100 Status serializeFields() { 101 import std.traits : FieldNameTuple; 102 foreach (fieldName; FieldNameTuple!T) 103 if (const st = serializeRaw_(sink, __traits(getMember, arg, fieldName), addrs, fmt)) 104 return st; 105 return Status(Status.Code.successful); 106 } 107 } 108 static if (is(T : __vector(U[N]), U, size_t N)) { // must come before isArithmetic 109 foreach (const ref elt; arg) 110 if (const st = serializeRaw_(sink, elt, addrs, fmt)) { return st; } 111 } else static if (__traits(isArithmetic, T)) { 112 static if (__traits(isIntegral, T)) { 113 static if (__traits(isUnsigned, T)) { 114 if (fmt.packIntegrals) { 115 if (arg < unsignedPrefixSentinel) { 116 const tmp = cast(E)arg; 117 assert(tmp != unsignedPrefixSentinel); 118 sink ~= tmp; // pack in single code unit 119 return Status(Status.Code.successful); 120 } 121 else 122 sink ~= unsignedPrefixSentinel; 123 } 124 } else { // isSigned 125 if (fmt.packIntegrals) { 126 if (arg >= byte.min+1 && arg <= byte.max) { 127 const tmp = cast(byte)arg; // pack in single code unit 128 assert(tmp != signedPrefixSentinel); 129 () @trusted { sink ~= ((cast(E*)&tmp)[0 .. 1]); }(); 130 return Status(Status.Code.successful); 131 } 132 else 133 sink ~= signedPrefixSentinel; // signed prefix 134 } 135 } 136 } else static if (__traits(isFloating, T) && is(T : real)) { 137 /+ TODO: pack small values +/ 138 } 139 static if (T.sizeof <= 8 && canSwapEndianness!(T)) { 140 import std.bitmanip : nativeToBigEndian; 141 if (!fmt.useNativeByteOrder) { 142 sink ~= arg.nativeToBigEndian[]; 143 return Status(Status.Code.successful); 144 } 145 } 146 // `T` for `T.sizeof == 1` or `T` being `real`: 147 () @trusted { sink ~= ((cast(E*)&arg)[0 .. T.sizeof]); }(); 148 } else static if (is(T == struct) || is(T == union)) { 149 static if (is(typeof(T.init[]) == U[], U)) { // hasSlicing 150 if (const st = serializeRaw_(sink, arg[], addrs, fmt)) { return st; } 151 } else { 152 if (const st = serializeFields()) { return st; } 153 } 154 } else static if (is(T == class) || is(T == U*, U)) { // isAddress 155 const bool isNull = arg is null; 156 if (const st = serializeRaw_(sink, isNull, addrs, fmt)) { return st; } 157 if (isNull) 158 return Status(Status.Code.successful); 159 import nxt.algorithm.searching : canFind; 160 void* addr; 161 () @trusted { addr = cast(void*)arg; }(); 162 if (addrs.canFind(addr)) { 163 // dbg("Cycle detected at `" ~ T.stringof ~ "`"); 164 return Status(Status.Code.failureCycle); 165 } 166 () @trusted { addrs ~= addr; }(); // `addrs`.lifetime <= `arg`.lifetime 167 static if (is(T == class)) { 168 if (const st = serializeFields()) { return st; } 169 } else { 170 if (const st = serializeRaw_(sink, *arg, addrs, fmt)) { return st; } 171 } 172 } else static if (is(T U : U[])) { // isArray 173 static if (!__traits(isStaticArray, T)) { 174 if (const st = serializeRaw_(sink, arg.length, addrs, fmt)) { return st; } 175 } 176 static if (__traits(isScalar, U)) { 177 if (fmt.enablesSlicing) { 178 () @trusted { sink ~= ((cast(E*)arg.ptr)[0 .. arg.length*U.sizeof]); }(); 179 return Status(Status.Code.successful); 180 } 181 } 182 static if (is(immutable U == immutable void)) { 183 ubyte[] raw; 184 () @trusted { raw = cast(typeof(raw))arg; }(); 185 if (const st = serializeRaw_(sink, raw, addrs, fmt)) { return st; } 186 } else { 187 foreach (const ref elt; arg) 188 if (const st = serializeRaw_(sink, elt, addrs, fmt)) { return st; } 189 } 190 } else static if (__traits(isAssociativeArray, T)) { 191 if (const st = serializeRaw_(sink, arg.length, addrs, fmt)) { return st; } 192 foreach (const ref elt; arg.byKeyValue) { 193 if (const st = serializeRaw_(sink, elt.key, addrs, fmt)) { return st; } 194 if (const st = serializeRaw_(sink, elt.value, addrs, fmt)) { return st; } 195 } 196 } else 197 static assert(0, "Cannot serialize `arg` of type `" ~ T.stringof ~ "`"); 198 return Status(Status.Code.successful); 199 } 200 201 /++ Raw (binary) deserialize `arg` from `sink` in format `fmt`. 202 +/ 203 Status deserializeRaw(T, Sink)(scope ref Sink sink, ref T arg, in Format fmt = Format.init) { 204 alias E = typeof(Sink.init[][0]); // code unit (element) type 205 static assert(__traits(isUnsigned, E), 206 "Non-unsigned sink code unit (element) type " ~ E.stringof); 207 static assert(!is(T == union), 208 "Cannot deserialize union type `" ~ T.stringof ~ "`"); 209 static if (__traits(hasMember, Sink, "data") && 210 is(immutable typeof(sink.data) == immutable E[])) { 211 auto data = sink.data; // l-value to pass by ref 212 const st_ = deserializeRaw!(T)(data, arg, fmt); // Appender arg => arg.data 213 sink = Sink(data); /+ TODO: avoid this because it allocates +/ 214 return st_; 215 } 216 import std.traits : Unqual; 217 Unqual!T* argP; 218 () @trusted { argP = cast(Unqual!T*)&arg; }(); 219 static if (is(T == struct) || is(T == union) || is(T == class)) { 220 import std.traits : FieldNameTuple; 221 Status deserializeFields() { 222 foreach (fieldName; FieldNameTuple!T) { 223 /+ TODO: maybe use *argP here instead: +/ 224 if (const st = deserializeRaw(sink, __traits(getMember, arg, fieldName), fmt)) { return st; } 225 } 226 return Status(Status.Code.successful); 227 } 228 } 229 static if (is(T : __vector(U[N]), U, size_t N)) { // must come before isArithmetic 230 foreach (const ref elt; arg) 231 if (const st = deserializeRaw(sink, elt, fmt)) { return st; } 232 } else static if (__traits(isArithmetic, T)) { 233 static if (__traits(isIntegral, T)) { 234 if (fmt.packIntegrals) { 235 auto tmp = sink.frontPop!(E)(); 236 static if (__traits(isUnsigned, T)) { 237 if (tmp != unsignedPrefixSentinel) { 238 *argP = cast(T)tmp; 239 return Status(Status.Code.successful); 240 } 241 } else { 242 if (tmp != signedPrefixSentinel) { 243 () @trusted {*argP = cast(T)*cast(byte*)&tmp;}(); // reinterpret 244 return Status(Status.Code.successful); 245 } 246 } 247 } 248 } else static if (__traits(isFloating, T) && is(T : real)) { 249 /+ TODO: unpack small values +/ 250 } 251 static if (T.sizeof <= 8 && canSwapEndianness!(T)) { 252 if (!fmt.useNativeByteOrder) { 253 *argP = sink.frontPopSwapEndian!(T)(); 254 return Status(Status.Code.successful); 255 } 256 } 257 // `T` for `T.sizeof == 1` or `T` being `real`: 258 *argP = sink.frontPop!(T)(); 259 } else static if (is(T == struct) || is(T == union)) { 260 static if (is(typeof(T.init[]) == U[], U)) { // hasSlicing 261 U[] tmp; // T was serialized via `T.opSlice` 262 if (const st = deserializeRaw(sink, tmp, fmt)) { return st; } 263 arg = T(tmp); 264 } else { 265 if (const st = deserializeFields()) { return st; } 266 } 267 } else static if (is(T == class) || is(T == U*, U)) { // isAddress 268 bool isNull; 269 if (const st = deserializeRaw(sink, isNull, fmt)) { return st; } 270 if (isNull) { 271 arg = null; 272 return Status(Status.Code.successful); 273 } 274 static if (is(T == class)) { 275 if (arg is null) { 276 alias ctors = typeof(__traits(getOverloads, TestClass, "__ctor")); 277 static assert(ctors.length <= 1, "Cannot deserialize `arg` of type `" ~ T.stringof ~ "` as it has multiple constructors"); 278 static if (ctors.length == 1) { 279 import std.traits : ParameterTypeTuple; 280 alias CtorParams = ParameterTypeTuple!(ctors[0]); 281 CtorParams params; 282 // pragma(msg, __FILE__, "(", __LINE__, ",1): Debug: ", CtorParams); 283 /+ TODO: Somehow deserialize `CtorParams` and pass them to constructor probably when compiler has native support for passing tuples to functions. +/ 284 static if (is(typeof(() @safe pure { return new T(params); }))) { 285 arg = new T(); 286 } else { 287 static assert(0, "Cannot deserialize `arg` of type `" ~ T.stringof ~ "` as it has no default constructor"); 288 } 289 } else { 290 static if (is(typeof(() @safe pure { return new T(); }))) { 291 arg = new T(); 292 } else { 293 static assert(0, "Cannot deserialize `arg` of type `" ~ T.stringof ~ "` as it has no default constructor"); 294 } 295 } 296 } 297 if (const st = deserializeFields()) { return st; } 298 } else { 299 if (arg is null) 300 arg = new typeof(*T.init); 301 if (const st = deserializeRaw(sink, *arg, fmt)) { return st; } 302 } 303 } else static if (is(T U : U[])) { // isArray 304 static if (!__traits(isStaticArray, T)) { 305 typeof(T.init.length) length; 306 if (const st = deserializeRaw(sink, length, fmt)) { return st; } 307 /+ TODO: avoid allocation if `E` is `immutable` and `U` is `immutable` and both have .sizeof 1: +/ 308 arg.length = length; // allocates. TODO: use allocator 309 } 310 static if (__traits(isScalar, U)) { 311 if (fmt.enablesSlicing) { 312 () @trusted { arg = (cast(U*)sink[].ptr)[0 .. arg.length]; }(); 313 sink = cast(Sink)(sink[][arg.length * U.sizeof .. $]); 314 return Status(Status.Code.successful); 315 } 316 } 317 foreach (ref elt; arg) 318 if (const st = deserializeRaw(sink, elt, fmt)) { return st; } 319 } else static if (__traits(isAssociativeArray, T)) { 320 typeof(T.init.length) length; 321 if (const st = deserializeRaw(sink, length, fmt)) { return st; } 322 /+ TODO: isMap: arg.capacity = length; or arg.reserve(length); +/ 323 foreach (_; 0 .. length) { 324 /* WARNING: `key` and `value` must not be put in outer scope as 325 that will lead to keys being overwritten. */ 326 typeof(T.init.keys[0]) key; 327 typeof(T.init.values[0]) value; 328 if (const st = deserializeRaw(sink, key, fmt)) { return st; } 329 if (const st = deserializeRaw(sink, value, fmt)) { return st; } 330 arg[key] = value; 331 } 332 } else 333 static assert(0, "Cannot deserialize `arg` of type `" ~ T.stringof ~ "`"); 334 return Status(Status.Code.successful); 335 } 336 337 private static immutable CodeUnitType unsignedPrefixSentinel = 0b_1111_1111; 338 private static immutable CodeUnitType signedPrefixSentinel = 0b_1000_0000; 339 340 private T frontPop(T, Sink)(ref Sink sink) in(T.sizeof <= sink[].length) { 341 T* ptr; 342 () @trusted { ptr = (cast(T*)sink[][0 .. T.sizeof]); }(); 343 typeof(return) result = *ptr; /+ TODO: unaligned access +/ 344 sink = cast(Sink)(sink[][T.sizeof .. $]); 345 return result; 346 } 347 348 private T frontPopSwapEndian(T, Sink)(ref Sink sink) if (T.sizeof >= 2) { 349 enum sz = T.sizeof; 350 import std.bitmanip : bigEndianToNative; 351 typeof(return) result = sink[][0 .. sz].bigEndianToNative!(T, sz); /+ TODO: unaligned access +/ 352 sink = cast(Sink)(sink[][sz .. $]); 353 return result; 354 } 355 356 /++ Is true iff `T` has swappable endianness (byte-order). +/ 357 private enum canSwapEndianness(T) = (T.sizeof >= 2 && T.sizeof <= 8 && __traits(isArithmetic, T)); 358 359 /// enum both sink type to trigger instantiation 360 version (none) 361 @safe pure nothrow unittest { 362 foreach (const packIntegrals; [false, true]) { 363 foreach (const useNativeByteOrder; [false, true]) { 364 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 365 static foreach (Sink; AliasSeq!(ArraySink, AppenderSink)) {{ // trigger instantiation 366 Sink sink; 367 alias T = void[]; 368 T t = [1,2,3,4]; 369 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 370 // assert(sink[].length == (packIntegrals ? 1 : T.sizeof)); 371 // T u; 372 // assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 373 // assert(sink[].length == 0); 374 // assert(t == u); 375 }} 376 } 377 } 378 } 379 380 /// enum both sink type to trigger instantiation 381 @safe pure nothrow unittest { 382 foreach (const packIntegrals; [false, true]) { 383 foreach (const useNativeByteOrder; [false, true]) { 384 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 385 static foreach (Sink; AliasSeq!(ArraySink, AppenderSink)) {{ // trigger instantiation 386 Sink sink; 387 alias T = TestEnum; 388 T t; 389 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 390 assert(sink[].length == (packIntegrals ? 1 : T.sizeof)); 391 T u; 392 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 393 assert(sink[].length == 0); 394 assert(t == u); 395 }} 396 } 397 } 398 } 399 400 /// empty {struct|union} 401 @safe pure nothrow unittest { 402 foreach (const packIntegrals; [false, true]) { 403 foreach (const useNativeByteOrder; [false, true]) { 404 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 405 AppenderSink sink; 406 struct S {} 407 struct U {} 408 static foreach (T; AliasSeq!(S, U)) {{ 409 T t; 410 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 411 assert(sink[].length == 0); 412 T u; 413 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 414 assert(sink[].length == 0); 415 static if (!is(T == class)) { 416 assert(t == u); 417 } 418 }} 419 } 420 } 421 } 422 423 /// class type 424 @safe pure nothrow unittest { 425 foreach (const packIntegrals; [false, true]) { 426 foreach (const useNativeByteOrder; [false, true]) { 427 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 428 AppenderSink sink; 429 alias T = TestClass; 430 T t = new T(11,22); 431 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 432 assert(sink[].length != 0); 433 if (fmt.packIntegrals) 434 assert(sink[] == [0, 11, 22]); 435 T u; 436 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 437 assert(sink[].length == 0); 438 assert(t.tupleof == u.tupleof); 439 } 440 } 441 } 442 443 /// cycle-struct type 444 version (none) /+ TODO: activate +/ 445 @trusted unittest { 446 foreach (const packIntegrals; [false, true]) { 447 foreach (const useNativeByteOrder; [false, true]) { 448 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 449 AppenderSink sink; 450 alias T = CycleStruct; 451 T t = new T(); 452 t.x = 42; 453 () @trusted { 454 t.parent = &t; 455 }(); 456 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 457 assert(sink[].length != 0); 458 if (fmt.packIntegrals) 459 assert(sink == [0, 11, 22]); 460 T u; 461 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 462 assert(sink[].length == 0); 463 assert(t.tupleof == u.tupleof); 464 } 465 } 466 } 467 468 /// cycle-class type 469 @trusted unittest { 470 foreach (const packIntegrals; [false, true]) { 471 foreach (const useNativeByteOrder; [false, true]) { 472 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 473 AppenderSink sink; 474 alias T = CycleClass; 475 T t = new T(42); 476 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.failureCycle)); 477 assert(sink[].length != 0); 478 /+ TODO: activate when cycles are supported: +/ 479 // T u; 480 // assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 481 // assert(sink[].length == 0); 482 // assert(t.tupleof == u.tupleof); 483 } 484 } 485 } 486 487 /// struct with static field 488 @safe pure nothrow unittest { 489 foreach (const packIntegrals; [false, true]) { 490 foreach (const useNativeByteOrder; [false, true]) { 491 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 492 AppenderSink sink; 493 struct T { static int _; } 494 T t; 495 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 496 assert(sink[].length == 0); 497 T u; 498 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 499 assert(sink[].length == 0); 500 assert(t == u); 501 } 502 } 503 } 504 505 /// struct with immutable field 506 @safe pure nothrow unittest { 507 foreach (const packIntegrals; [false, true]) { 508 foreach (const useNativeByteOrder; [false, true]) { 509 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 510 AppenderSink sink; 511 struct T { immutable int _; } 512 T t; 513 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 514 assert(sink[].length == (packIntegrals ? 1 : T.sizeof)); 515 T u; 516 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 517 assert(sink[].length == 0); 518 assert(t == u); 519 } 520 } 521 } 522 523 /// struct with bitfields 524 @safe pure nothrow unittest { 525 static if (hasPreviewBitfields) { 526 foreach (const packIntegrals; [false, true]) { 527 foreach (const useNativeByteOrder; [false, true]) { 528 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 529 AppenderSink sink; 530 alias W = ubyte; 531 struct T { W b_0_2 : 2; W b_2_6 : 6; } 532 static assert(T.sizeof == W.sizeof); 533 T t; 534 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 535 assert(sink[].length == (packIntegrals ? 2 * W.sizeof : 2*T.sizeof)); 536 T u; 537 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 538 assert(sink[].length == 0); 539 assert(t == u); 540 } 541 } 542 } 543 } 544 545 /// {char|wchar|dchar} 546 @safe pure nothrow unittest { 547 foreach (const packIntegrals; [false, true]) { 548 foreach (const useNativeByteOrder; [false, true]) { 549 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 550 AppenderSink sink; 551 static foreach (T; CharTypes) {{ 552 foreach (const T t; 0 .. 127+1) { 553 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 554 assert(sink[].length == (packIntegrals ? 1 : T.sizeof)); 555 T u; 556 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 557 assert(sink[].length == 0); 558 assert(t == u); 559 } 560 }} 561 } 562 } 563 } 564 565 /// {char|wchar|dchar}[] 566 @safe pure nothrow unittest { 567 foreach (const packIntegrals; [false, true]) { 568 foreach (const useNativeByteOrder; [false, true]) { 569 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 570 AppenderSink sink; 571 import std.meta : AliasSeq; 572 static foreach (E; CharTypes) {{ 573 alias T = E[]; 574 T t; 575 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 576 assert(sink[].length != 0); 577 T u; 578 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 579 assert(sink[].length == 0); 580 assert(t == u); 581 }} 582 } 583 } 584 } 585 586 /// signed integral 587 @safe pure nothrow unittest { 588 foreach (const packIntegrals; [false, true]) { 589 foreach (const useNativeByteOrder; [false, true]) { 590 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 591 AppenderSink sink; 592 import std.meta : AliasSeq; 593 static foreach (T; AliasSeq!(byte, short, int, long)) {{ 594 foreach (const T t; byte.min+1 .. byte.max+1) { 595 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 596 assert(sink[].length == (packIntegrals ? 1 : T.sizeof)); 597 T u; 598 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 599 assert(sink[].length == 0); 600 assert(t == u); 601 } 602 }} 603 } 604 } 605 } 606 607 /// core.simd vector types 608 @safe pure nothrow unittest { 609 foreach (const packIntegrals; [false, true]) { 610 foreach (const useNativeByteOrder; [false, true]) { 611 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 612 AppenderSink sink; 613 import std.meta : AliasSeq; 614 import core.simd; 615 /+ TODO: support more core.simd types +/ 616 static foreach (T; AliasSeq!(byte16, float4, double2)) {{ 617 T t = 0; 618 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 619 assert(sink[].length == T.sizeof); 620 T u; 621 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 622 assert(sink[].length == 0); 623 assert(t[] == u[]); 624 }} 625 } 626 } 627 } 628 629 /// unsigned integral 630 @safe pure nothrow unittest { 631 foreach (const packIntegrals; [false, true]) { 632 foreach (const useNativeByteOrder; [false, true]) { 633 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 634 AppenderSink sink; 635 import std.meta : AliasSeq; 636 static foreach (T; AliasSeq!(ubyte, ushort, uint, ulong)) {{ 637 foreach (const T t; 0 .. ubyte.max) { 638 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 639 assert(sink[].length == (packIntegrals ? 1 : T.sizeof)); 640 T u; 641 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 642 assert(sink[].length == 0); 643 assert(t == u); 644 } 645 }} 646 } 647 } 648 } 649 650 /// floating point 651 @safe pure nothrow unittest { 652 foreach (const packIntegrals; [false, true]) { 653 foreach (const useNativeByteOrder; [false, true]) { 654 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 655 AppenderSink sink; 656 import std.meta : AliasSeq; 657 static foreach (T; AliasSeq!(float, double, real)) {{ 658 foreach (const T t; 0 .. ubyte.max) { 659 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 660 assert(sink[].length == (packIntegrals ? T.sizeof : T.sizeof)); 661 T u; 662 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 663 assert(sink[].length == 0); 664 assert(t == u); 665 } 666 }} 667 } 668 } 669 } 670 671 /// integral pointer 672 @trusted pure nothrow unittest { 673 foreach (const packIntegrals; [false, true]) { 674 foreach (const useNativeByteOrder; [false, true]) { 675 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 676 AppenderSink sink; 677 import std.meta : AliasSeq; 678 static foreach (E; AliasSeq!(uint, ulong)) {{ 679 E val = 42; 680 struct T { E* p1, p2; } 681 T t = T(null,&val); 682 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 683 assert(sink[].length != 0); 684 T u; 685 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 686 assert(sink[].length == 0); 687 assert(t.p1 is u.p1); 688 assert(*t.p2 == *u.p2); 689 }} 690 } 691 } 692 } 693 694 /// static array 695 @safe pure nothrow unittest { 696 foreach (const packIntegrals; [false, true]) { 697 foreach (const useNativeByteOrder; [false, true]) { 698 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 699 AppenderSink sink; 700 enum n = 3; 701 alias T = int[n]; 702 T t = [11,22,33]; 703 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 704 assert(sink[].length != 0); 705 T u; 706 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 707 assert(sink[].length == 0); 708 assert(t == u); 709 } 710 } 711 } 712 713 /// dynamic array 714 @safe pure nothrow unittest { 715 foreach (const packIntegrals; [false, true]) { 716 foreach (const useNativeByteOrder; [false, true]) { 717 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 718 AppenderSink sink; 719 alias T = int[]; 720 T t = [11,22,33]; 721 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 722 assert(sink[].length == (fmt.packIntegrals ? (1 + t.length) : 20)); 723 T u; 724 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 725 assert(sink[].length == 0); 726 727 assert(t == u); 728 } 729 } 730 } 731 732 /// associative array 733 @safe pure nothrow unittest { 734 foreach (const packIntegrals; [false, true]) { 735 foreach (const useNativeByteOrder; [false, true]) { 736 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 737 AppenderSink sink; 738 alias T = int[int]; 739 T t = [1: 1, 2: 2]; 740 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 741 assert(sink[].length != 0); 742 T u; 743 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 744 assert(sink[].length == 0); 745 assert(t == u); 746 } 747 } 748 } 749 750 /// associative array 751 @safe pure nothrow unittest { 752 foreach (const packIntegrals; [false, true]) { 753 foreach (const useNativeByteOrder; [false, true]) { 754 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 755 AppenderSink sink; 756 alias T = int[string]; 757 T t = ["1": 3, "2": 4]; 758 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 759 assert(sink[].length != 0); 760 T u = T.init; 761 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 762 assert(sink[].length == 0); 763 assert(t == u); 764 } 765 } 766 } 767 768 /// empty `std.array.Appender` via slicing 769 @safe pure nothrow unittest { 770 foreach (const packIntegrals; [false, true]) { 771 foreach (const useNativeByteOrder; [false, true]) { 772 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 773 import std.array : Appender; 774 AppenderSink sink; 775 alias A = int[]; 776 alias T = Appender!(A); 777 T t = []; 778 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 779 assert(sink[].length == (fmt.packIntegrals ? 1 : 8) + t.data.length); 780 T u; 781 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 782 assert(sink[].length == 0); 783 assert(t[] == u[]); 784 } 785 } 786 } 787 788 /// populated `std.array.Appender` via slicing 789 @safe pure nothrow unittest { 790 foreach (const packIntegrals; [false, true]) { 791 foreach (const useNativeByteOrder; [false, true]) { 792 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 793 import std.array : Appender; 794 AppenderSink sink; 795 alias A = int[]; 796 alias T = Appender!(A); 797 A a = [11,22,33]; 798 T t = a; 799 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 800 assert(sink[].length == (fmt.packIntegrals ? 4 : 20)); 801 AppenderSink asink; 802 assert(!asink.serializeRaw(a, fmt)); 803 assert(asink[].length == (fmt.packIntegrals ? 4 : 20)); 804 assert(sink[] == asink[]); 805 T u; 806 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 807 assert(sink[].length == 0); 808 assert(t[] == u[]); 809 } 810 } 811 } 812 813 /// aggregate type 814 @safe pure nothrow unittest { 815 foreach (const packIntegrals; [false, true]) { 816 foreach (const useNativeByteOrder; [false, true]) { 817 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 818 AppenderSink sink; 819 struct P { int x, y; int* p1, p2; char[3] ch3; char ch; wchar wc; dchar dc; int[int] aa; } 820 struct T { int x, y; long l; float f; double d; real r; bool b1, b2; P p; } 821 T t = T(0x01234567,0x76543210, 0x01234567_01234567, 3.14,3.14,3.14, false,true, 822 P(2,3, null,null, "abc", 'a', 'b', 'c', [11:-11,-22:22,-33:-33])); 823 assert(sink.serializeRaw(t, fmt) == Status(Status.Code.successful)); 824 T u; 825 assert(sink.deserializeRaw(u, fmt) == Status(Status.Code.successful)); 826 assert(sink[].length == 0); 827 assert(t == u); 828 } 829 } 830 } 831 832 version (serialization_json_test) 833 private import std.json : JSONValue, JSONType, parseJSON; 834 835 /++ Raw (binary) serialize `JSONValue arg` to `sink` in format `fmt`. +/ 836 version (serialization_json_test) 837 private Status serializeRaw_(T : JSONValue, AppenderSink)(scope ref AppenderSink sink, in T arg, scope ref Addresses addrs, in Format fmt = Format.init) { 838 if (const st = serializeRaw_(sink, arg.type, addrs, fmt)) { return st; } 839 final switch (arg.type) { 840 case JSONType.integer: 841 return serializeRaw_(sink, arg.integer, addrs, fmt); 842 case JSONType.uinteger: 843 return serializeRaw_(sink, arg.uinteger, addrs, fmt); 844 case JSONType.float_: 845 return serializeRaw_(sink, arg.floating, addrs, fmt); 846 case JSONType..string: 847 return serializeRaw_(sink, arg.str, addrs, fmt); 848 case JSONType.object: 849 return serializeRaw_(sink, arg.object, addrs, fmt); 850 case JSONType.array: 851 return serializeRaw_(sink, arg.array, addrs, fmt); 852 case JSONType.true_: 853 case JSONType.false_: 854 case JSONType.null_: 855 return Status(Status.Code.successful); 856 } 857 } 858 859 /++ Raw (binary) deserialize `JSONValue arg` from `sink` in format `fmt`. +/ 860 version (serialization_json_test) 861 Status deserializeRaw(T : JSONValue, AppenderSink)(scope ref AppenderSink sink, ref T arg, in Format fmt = Format.init) { 862 JSONType type; 863 if (const st = deserializeRaw(sink, type, fmt)) { return st; } 864 typeof(return) st; 865 final switch (type) { 866 case JSONType.integer: 867 typeof(arg.integer) value; 868 st = deserializeRaw(sink, value, fmt); 869 if (st == Status(Status.Code.successful)) arg = JSONValue(value); 870 break; 871 case JSONType.uinteger: 872 typeof(arg.uinteger) value; 873 st = deserializeRaw(sink, value, fmt); 874 if (st == Status(Status.Code.successful)) arg = JSONValue(value); 875 break; 876 case JSONType.float_: 877 typeof(arg.floating) value; 878 st = deserializeRaw(sink, value, fmt); 879 if (st == Status(Status.Code.successful)) arg = JSONValue(value); 880 break; 881 case JSONType..string: 882 typeof(arg.str) value; 883 st = deserializeRaw(sink, value, fmt); 884 if (st == Status(Status.Code.successful)) arg = JSONValue(value); 885 break; 886 case JSONType.object: 887 typeof(arg.object) value; 888 st = deserializeRaw(sink, value, fmt); 889 if (st == Status(Status.Code.successful)) arg = JSONValue(value); 890 break; 891 case JSONType.array: 892 typeof(arg.array) value; 893 st = deserializeRaw(sink, value, fmt); 894 if (st == Status(Status.Code.successful)) arg = JSONValue(value); 895 break; 896 case JSONType.true_: 897 arg = true; 898 st = Status(Status.Code.successful); 899 break; 900 case JSONType.false_: 901 arg = false; 902 st = Status(Status.Code.successful); 903 break; 904 case JSONType.null_: 905 arg = null; 906 st = Status(Status.Code.successful); 907 break; 908 } 909 return st; 910 } 911 912 // { "optional": true } 913 version (serialization_json_test) 914 @trusted unittest { 915 foreach (const s; [`false`, 916 `true`, 917 `null`, 918 `{}`, 919 `12`, 920 `"x"`, 921 `[1,2]`, 922 `[1,2,[3,4,5,"x","y"],"a",null]`, 923 `[1,3.14,"x",null]`, 924 `{ "optional":false }`, 925 `{ "optional":true }`, 926 `{ "optional":null }`, 927 `{ "a":"a", }`, 928 `{ "a":"a", "a":"a", }`, 929 `{ "a":1, "a":2, }`, 930 `{ "":1, "":2, }`, 931 `{ "":1, "b":2, }`, 932 `{ "a":1, "":2, }`, 933 `{ "a":1, "b":2, }`, 934 /+ TODO: this fails: readLargeFile, +/ 935 ]) { 936 foreach (const packIntegrals; [false, true]) { 937 foreach (const useNativeByteOrder; [false, true]) { 938 const fmt = Format(packIntegrals: packIntegrals, useNativeByteOrder: useNativeByteOrder); 939 AppenderSink sink; 940 alias T = JSONValue; 941 const T t = s.parseJSON(); 942 assert(sink.serializeRaw!(JSONValue)(t, fmt) == Status(Status.Code.successful)); 943 assert(sink[].length != 0); 944 T u; 945 assert(sink.deserializeRaw!(JSONValue)(u, fmt) == Status(Status.Code.successful)); 946 assert(sink[].length == 0); 947 assert(t == u); 948 } 949 } 950 } 951 } 952 953 version (none) 954 version (serialization_json_test) 955 private @system string readLargeFile() { 956 import std.path : expandTilde; 957 import std.file : readText; 958 return "~/Downloads/large-file.json".expandTilde.readText; 959 } 960 961 version (unittest) { 962 import std.array : Appender; 963 import std.meta : AliasSeq; 964 private alias ArraySink = CodeUnitType[]; 965 private alias AppenderSink = Appender!(ArraySink); 966 private alias CharTypes = AliasSeq!(char, wchar, dchar); 967 private enum TestEnum { first, second, third } 968 private class TestClass { 969 int a, b; 970 this(int a = 0, int b = 0) @safe pure nothrow @nogc { 971 this.a = a; 972 this.b = b; 973 } 974 } 975 private class CycleClass { 976 this(int x = 0) @safe pure nothrow @nogc { 977 this.x = x; 978 this.parent = this; // self-reference 979 } 980 int x; 981 CycleClass parent; 982 } 983 private class CycleStruct { 984 int x; 985 CycleStruct* parent; 986 } 987 import std.traits : ParameterTypeTuple; 988 alias ConstructorParams = ParameterTypeTuple!(typeof(__traits(getOverloads, TestClass, "__ctor")[0])); 989 static assert(is(ConstructorParams == AliasSeq!(int, int))); 990 debug import nxt.debugio; 991 }