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 }