1 module utile.binary; 2 import std, std.typetuple, utile.misc, utile.except, utile.binary.helpers; 3 4 public import utile.binary.attrs, utile.binary.streams; 5 6 size_t writeLength(T)(in T value, string file = __FILE__, uint line = __LINE__) 7 { 8 return Serializer!LengthCalcStream().write(value, true, file, line).stream.written; 9 } 10 11 void serializeFile(T)(string name, in T value, string file = __FILE__, uint line = __LINE__) 12 { 13 scope mm = new MmFile(name, MmFile.Mode.readWriteNew, writeLength(value, file, line), null); 14 15 mm[].Serializer!MemoryStream.write(value, true, file, line); 16 } 17 18 T deserializeFile(T)(string name, string file = __FILE__, uint line = __LINE__) 19 { 20 scope mm = new MmFile(name); 21 22 return mm[].Serializer!MemoryStream 23 .read!T(true, file, line); 24 } 25 26 ubyte[] serializeMem(T)(in T value, string file = __FILE__, uint line = __LINE__) 27 { 28 return Serializer!AppendStream().write(value, true, file, line).stream.data; 29 } 30 31 T deserializeMem(T)(in void[] data, bool ensureFullyParsed = true, string file = __FILE__, uint line = __LINE__) 32 { 33 return data.Serializer!MemoryStream 34 .read!T(ensureFullyParsed, file, line); 35 } 36 37 struct Serializer(Stream) 38 { 39 this(A...)(auto ref A args) 40 { 41 stream = Stream(args); 42 } 43 44 T read(T)(bool ensureFullyParsed = true, string file = __FILE__, uint line = __LINE__) if (is(T == struct)) 45 { 46 T value; 47 48 auto s = SerializerImpl!(Stream, T)(&value, &stream, file, line); 49 s.process!false(value, value); 50 51 ensureFullyParsed && stream.length && throwError!`%u bytes were not parsed`(file, line, stream.length); 52 return value; 53 } 54 55 ref write(T)(in T value, bool ensureNoSpaceLeft = true, string file = __FILE__, uint line = __LINE__) if (is(T == struct)) 56 { 57 auto s = SerializerImpl!(Stream, const(T))(&value, &stream, file, line); 58 s.process!true(value, value); 59 60 ensureNoSpaceLeft && stream.length && throwError!`%u bytes were not occupied`(file, line, stream.length); 61 return this; 62 } 63 64 Stream stream; 65 } 66 67 private: 68 69 struct SerializerImpl(Stream, I) 70 { 71 void process(bool Writing, T, P)(ref T data, ref P parent) 72 { 73 _depth = 1; 74 _names[0] = Unqual!T.stringof; 75 76 doProcess!Writing(data, parent); 77 } 78 79 I* input; 80 Stream* stream; 81 82 string file; 83 uint line; 84 private: 85 pragma(inline, false) 86 { 87 @property variableName() const => _names[0 .. _depth].join('.'); 88 89 bool commonError(string msg) => throwError!"can't %s %s variable"(file, line, msg, variableName); 90 91 bool errorRead() => commonError(`read`); 92 bool errorWrite() => commonError(`write`); 93 94 bool errorRSkip() => commonError(`skip when reading`); 95 bool errorWSkip() => commonError(`skip when writing`); 96 97 bool errorCheck(T)(const(T)* tmp, const(T)* p) 98 { 99 return throwError!"variable %s mismatch(%s when %s expected)"(file, line, variableName, *tmp, *p); 100 } 101 102 bool errorValid(T)(T * p) => throwError!"variable %s has invalid value %s"(file, line, variableName, * p); 103 } 104 105 pragma(inline, true) void doProcess(bool Writing, T, P)(ref T data, ref P parent) 106 { 107 _depth++; 108 scope (exit) 109 _depth--; 110 111 enum Reading = !Writing; 112 auto evaluateData = tuple!(`input`, `parent`, `that`, `stream`)(input, &parent, &data, stream); 113 114 alias Fields = aliasSeqOf!(fieldsToProcess!T()); 115 alias processElem = (ref a) => doProcess!Writing(a, data); 116 117 foreach (name; Fields) 118 { 119 _names[_depth - 1] = name; 120 121 enum Elem = T.stringof ~ '.' ~ name; 122 enum Unserializable = `don't know how to process ` ~ Elem; 123 124 alias attrs = AliasSeq!(__traits(getAttributes, __traits(getMember, T, name))); 125 126 static assert(allSatisfy!(isAttrValid, attrs), Elem ~ ` has unknown attributes`); 127 128 auto p = &__traits(getMember, data, name); 129 alias R = typeof(*p); 130 131 { 132 alias skip = templateParamFor!(Skip, attrs); 133 134 static if (!is(skip == void)) 135 { 136 size_t cnt = skip(evaluateData); 137 138 static if (Writing) 139 stream.wskip(cnt) || errorWSkip; 140 else 141 stream.rskip(cnt) || errorRSkip; 142 } 143 } 144 145 { 146 alias ignore = templateParamFor!(IgnoreIf, attrs); 147 148 static if (!is(ignore == void)) 149 { 150 if (ignore(evaluateData)) 151 { 152 static if (Reading) 153 { 154 alias def = templateParamFor!(Default, attrs); 155 156 static if (!is(def == void)) 157 *p = def(evaluateData); 158 } 159 160 continue; 161 } 162 } 163 } 164 165 static if (Reading) 166 { 167 static if (is(R == immutable)) 168 { 169 Unqual!R tmp; 170 auto varPtr = &tmp; 171 } 172 else 173 alias varPtr = p; 174 } 175 176 static if (isDataSimple!R) 177 { 178 static if (Writing) 179 stream.write(toByte(*p)) || errorWrite; 180 else 181 stream.read(toByte(*varPtr)) || errorRead; 182 } 183 else static if (isAssociativeArray!R) 184 { 185 struct Pair 186 { 187 Unqual!(KeyType!R) key; 188 Unqual!(ValueType!R) value; 189 } 190 191 struct AA 192 { 193 mixin(`@(` ~ [attrs].to!string[1 .. $ - 1] ~ `) Pair[] ` ~ name ~ `;`); 194 } 195 196 AA aa; 197 auto arr = &aa.tupleof[0]; 198 199 static if (Writing) 200 *arr = p.byKeyValue.map!(a => Pair(a.key, a.value)).array; 201 202 processElem(aa); 203 204 static if (Reading) 205 *p = map!(a => tuple(a.tupleof))(*arr).assocArray; 206 } 207 else static if (isArray!R) 208 { 209 alias E = ElementEncodingType!R; 210 enum isElemSimple = isDataSimple!E; 211 212 static assert(isElemSimple || is(E == struct), Unserializable); 213 214 alias LenAttr = templateParamFor!(ArrayLength, attrs); 215 enum isZeroTerminated = staticIndexOf!(ZeroTerminated, attrs) >= 0; 216 217 static if (is(LenAttr == void)) 218 { 219 enum isRest = staticIndexOf!(ToTheEnd, attrs) >= 0; 220 221 static if (isRest) 222 static assert(name == Fields[$ - 1], Elem ~ ` is not the last field`); 223 } 224 else 225 { 226 static if (isType!LenAttr) 227 { 228 static assert(isUnsigned!LenAttr, `length must be a function or an unsigned type for ` ~ Elem); 229 230 LenAttr elemsCnt; 231 232 static if (Writing) 233 { 234 assert(p.length <= LenAttr.max); 235 236 elemsCnt = cast(LenAttr)p.length; 237 stream.write(elemsCnt.toByte) || errorWrite; 238 } 239 else 240 stream.read(elemsCnt.toByte) || errorRead; 241 242 enum isRest = false; 243 } 244 else 245 { 246 const size_t elemsCnt = LenAttr(evaluateData); 247 248 static if (Writing && !isZeroTerminated) 249 assert(p.length == elemsCnt); 250 251 enum isRest = false; 252 } 253 } 254 255 enum isStr = isSomeString!R; 256 enum isLen = is(typeof(elemsCnt)); 257 enum isDyn = isDynamicArray!R; 258 259 enum processAsString = isStr && !(isLen || isRest) || isZeroTerminated; 260 261 static if (processAsString) 262 { 263 static assert(isUnsigned!E || isSomeChar!E, `only unsigned elements are allowed for string ` ~ Elem); 264 } 265 else 266 { 267 static if (isDyn) 268 static assert(isStr || isLen || isRest, `length is unknown for ` ~ Elem); 269 else 270 static assert(!(isLen || isRest), `specifying length is not allowed for a static array ` ~ Elem); 271 } 272 273 static if (isElemSimple) 274 { 275 static if (Writing) 276 { 277 static if (processAsString) 278 { 279 debug 280 { 281 assert(all!(a => !!a)(*p), `zero is found in zero-terminated string ` ~ Elem); 282 283 static if (isLen) 284 assert(p.length <= elemsCnt, `no space left in the buffer for string ` ~ Elem); 285 } 286 } 287 288 stream.write(toByte(*p)) || errorWrite; 289 290 static if (processAsString) 291 { 292 static if (isLen) 293 { 294 if (p.length == elemsCnt) 295 continue; 296 } 297 298 E terminator = 0; 299 stream.write(terminator.toByte) || errorWrite; 300 301 static if (isLen) 302 stream.wskip(elemsCnt - p.length - 1) || errorWSkip; 303 } 304 } 305 else 306 { 307 static if (processAsString) 308 { 309 static if (!isLen) 310 auto elemsCnt = size_t.max; 311 312 stream.readstr(*varPtr, elemsCnt) || errorRead; 313 314 static if (isLen) 315 { 316 if (varPtr.length != elemsCnt) 317 stream.rskip(elemsCnt - p.length - 1) || errorRSkip; 318 } 319 } 320 else 321 { 322 ubyte[] arr; 323 324 static if (isRest) 325 { 326 stream.length % E.sizeof && errorRead; 327 stream.read(arr, stream.length) || errorRead; 328 } 329 else 330 stream.read(arr, elemsCnt * E.sizeof) || errorRead; 331 332 *varPtr = (cast(E*)arr.ptr)[0 .. arr.length / E.sizeof]; 333 } 334 } 335 } 336 else 337 { 338 static if (Writing) 339 { 340 foreach (ref v; *p) 341 processElem(v); 342 } 343 else 344 { 345 static if (isRest) 346 { 347 while (stream.length) 348 { 349 E v; 350 processElem(v); 351 *varPtr ~= v; 352 } 353 } 354 else 355 { 356 static if (isDyn) 357 { 358 foreach (_; 0 .. elemsCnt) 359 { 360 E v; 361 processElem(v); 362 *varPtr ~= v; 363 } 364 } 365 else 366 foreach (ref v; *varPtr) 367 processElem(v); 368 } 369 } 370 } 371 } 372 else static if (isPointer!R) 373 { 374 alias E = PointerTarget!R; 375 static assert(is(E == struct), Unserializable); 376 377 bool processPointer; 378 379 static if (Writing) 380 { 381 processPointer = !!*p; 382 stream.write(processPointer.toByte) || errorWrite; 383 } 384 else 385 stream.read(processPointer.toByte) || errorRead; 386 387 if (processPointer) 388 { 389 static if (Reading) 390 { 391 *varPtr = new E; 392 processElem(**varPtr); 393 } 394 else 395 processElem(**p); 396 } 397 } 398 else 399 { 400 static assert(is(R == struct), Unserializable); 401 402 processElem(*p); 403 } 404 405 static if (Reading) 406 { 407 static if (is(typeof(tmp))) 408 { 409 tmp == *p || errorCheck(&tmp, p); 410 } 411 412 alias validate = templateParamFor!(Validate, attrs); 413 414 static if (!is(validate == void)) 415 { 416 validate(evaluateData) || errorValid(p); 417 } 418 } 419 } 420 } 421 422 template templateParamFor(alias C, A...) 423 { 424 static if (A.length) 425 { 426 alias T = A[0]; 427 428 static if (__traits(isSame, TemplateOf!T, C)) 429 { 430 alias templateParamFor = TemplateArgsOf!T[0]; 431 } 432 else 433 alias templateParamFor = templateParamFor!(C, A[1 .. $]); 434 } 435 else 436 alias templateParamFor = void; 437 } 438 439 enum isAttrValid(T) = is(T : SerializerAttr); 440 441 uint _depth; 442 string[64] _names; 443 }