1 ///
2 module sbin.deserialize;
3 
4 import std.format;
5 
6 import sbin.type;
7 import sbin.vluint;
8 import sbin.zigzag;
9 import sbin.exception;
10 import sbin.repr;
11 
12 /++ Deserialize part of input ragne to `Target` value
13 
14     Params:
15         range = input range with serialized data (not saved before work)
16         target = reference to result object
17  +/
18 void sbinDeserializePart(RH=EmptyReprHandler, R, string file=__FILE__, size_t line=__LINE__, Target...)
19     (ref R range, ref Target target) if (isInputRange!R && is(Unqual!(ElementType!R) == ubyte) && isReprHandler!RH)
20 {
21     import sbin.util.stack : Stack;
22 
23     static struct FieldName
24     {
25         string name;
26         ptrdiff_t index = -1;
27 
28         string toString() const
29         {
30             if (index != -1) return format!"%s[%d]"(name, index);
31             else return name;
32         }
33     }
34 
35     alias FNStack = Stack!(FieldName, 8);
36 
37     FNStack fNameStack;
38 
39     static struct WrapRng
40     {
41         R* rng;
42         size_t* count;
43         FNStack* fstack;
44 
45         string field() const
46         {
47             import std.algorithm: map, joiner;
48             import std.array : array;
49             import std.conv : to;
50             return fstack.getData().map!(a=>a.toString()).joiner(".").array.to!string;
51         }
52 
53         string type;
54         size_t vcnt;
55         size_t vexp;
56 
57         ubyte front() @property
58         {
59             enforce (!(*rng).empty, new SBinDeserializeEmptyRangeException(
60                         Target.stringof, field(), type, vcnt, vexp, *count));
61             return (*rng).front;
62         }
63 
64         void popFront()
65         {
66             (*rng).popFront();
67             (*count)++;
68         }
69 
70         bool empty() @property { return (*rng).empty; }
71     }
72 
73     void setRngFields(ref WrapRng rng, string type, size_t vcnt, size_t vexp)
74     { rng.type = type; rng.vcnt = vcnt; rng.vexp = vexp; }
75 
76     struct StackHolder
77     {
78         this(string name, ptrdiff_t idx=-1)
79         { fNameStack.push(FieldName(name, idx)); }
80         ~this() { fNameStack.pop(); }
81     }
82 
83     ubyte pop(ref WrapRng rng, string field, size_t idx, string type, size_t vcnt, size_t vexp)
84     {
85         const _ = StackHolder(field, idx);
86         setRngFields(rng, type, vcnt, vexp);
87         auto ret = rng.front();
88         rng.popFront();
89         return ret;
90     }
91 
92     void impl(T)(ref WrapRng r, ref T trg, string field, ptrdiff_t idx=-1)
93     {
94         const __ = StackHolder(field, idx);
95 
96         static if (hasRepr!(RH, T))
97         {
98             serializeRepr!(RH, T) repr;
99             impl(r, repr, "repr");
100             trg = RH.fromRepr(repr);
101         }
102         else static if (is(T == enum))
103         {
104             immutable EM = [EnumMembers!T];
105             alias ENT = EnumNumType!T;
106             ubyte[ENT.sizeof] tmp;
107             foreach (i, ref v; tmp)
108                 v = pop(r, "byte", i, T.stringof, i, ENT.sizeof);
109             trg = EM[tmp.unpack!ENT];
110         }
111         else static if (is(T == vluint))
112         {
113             setRngFields(r, "vluint", 0, 10);
114             trg = vluint(readVLUInt(r));
115         }
116         else static if (is(T == vlint))
117         {
118             setRngFields(r, "vlint", 0, 10);
119             trg = vlint(zzDecode(readVLUInt(r)));
120         }
121         else static if (is(T : double) || is(T : long))
122         {
123             ubyte[T.sizeof] tmp;
124             foreach (i, ref v; tmp)
125                 v = pop(r, "byte", i, T.stringof, i, T.sizeof);
126             trg = tmp.unpack!T;
127         }
128         else static if (isVoidArray!T)
129         {
130             static if (isDynamicArray!T)
131             {
132                 auto _ = StackHolder("length");
133                 setRngFields(r, "vluint", 0, 10);
134                 const l = cast(size_t)readVLUInt(r);
135                 if (trg.length != l) trg.length = l;
136             }
137 
138             auto tmp = (() @trusted => cast(ubyte[])trg[])();
139             foreach (i, ref v; tmp) impl(r, v, "elem", i);
140         }
141         else static if (isStaticArray!T)
142         {
143             foreach (i, ref v; trg) impl(r, v, "elem", i);
144         }
145         else static if (isSomeString!T)
146         {
147             const len = (() {
148                 auto _ = StackHolder("length");
149                 setRngFields(r, "vluint", 0, 10);
150                 return cast(size_t)readVLUInt(r);
151             })();
152             auto tmp = new ubyte[](len);
153             foreach (i, ref v; tmp)
154                 v = pop(r, "elem", i, T.stringof, i, len);
155             trg = (() @trusted => cast(T)tmp)();
156         }
157         else static if (isDynamicArray!T)
158         {
159             const len = (()
160             {
161                 auto _ = StackHolder("length");
162                 setRngFields(r, "vluint", 0, 10);
163                 return cast(size_t)readVLUInt(r);
164             })();
165             if (trg.length != len) trg.length = len;
166             foreach (i, ref v; trg) impl(r, v, "elem", i);
167         }
168         else static if (isAssociativeArray!T)
169         {
170             const len = ((){
171                 auto _ = StackHolder("length");
172                 setRngFields(r, "vluint", 0, 10);
173                 return cast(size_t)readVLUInt(r);
174             })();
175 
176             (() @trusted => trg.clear())();
177 
178             foreach (i; 0 .. len)
179             {
180                 KeyType!T k;
181                 ValueType!T v;
182                 impl(r, k, "key", i);
183                 impl(r, v, "value", i);
184                 trg[k] = v;
185             }
186 
187             (() @trusted => trg.rehash())();
188         }
189         else static if (isTagged!(T).any)
190         {
191             import std.algorithm.mutation : move;
192 
193             TaggedTagType!T tag;
194             impl(r, tag, "tag");
195 
196             FS: final switch (tag)
197             {
198                 static foreach (t; getTaggedAllTags!T)
199                 {
200                     case t:
201                         TaggedTypeByTag!(T, t) tmp;
202 
203                         // do not try deserialize null for nullable type
204                         static if (!is(typeof(tmp) == typeof(null)))
205                             impl(r, tmp, "value");
206 
207                         static if (__traits(compiles, trg = tmp)) trg = tmp;
208                         else
209                         {
210                             static if (isTagged!(T).isSumType)
211                                 trg = T(move(tmp));
212                             else
213                                 trg.set!t(move(tmp));
214                         }
215 
216                         break FS;
217                 }
218             }
219         }
220         else static if (hasCustomRepr!(T, RH))
221         {
222             ReturnType!(trg.sbinCustomRepr) tmp;
223             impl(r, tmp, "customRepr");
224             // for @safe sbinDeserialize sbinFromCustomRepr must be @trusted or @safe
225             trg = T.sbinFromCustomRepr(tmp);
226         }
227         else static if (is(T == struct))
228         {
229             version (allowRawUnions)
230             {
231                 import std : Nullable;
232                 static if (is(T == Nullable!A, A))
233                     pragma(msg, file, "(", cast(int)line, "): ", "\033[33mWarning:\033[0m ",
234                         T, " deserialize as union, use NullableAsSumTypeRH for proper deserialize!");
235             }
236 
237             import std.traits : hasUDA;
238             foreach (i, ref v; trg.tupleof)
239                 static if (!hasUDA!(T.tupleof[i], sbinSkip))
240                     impl(r, v, __traits(identifier, trg.tupleof[i]));
241         }
242         else static if (is(T == union))
243         {
244             version (allowRawUnions)
245             {
246                 auto tmp = (() @trusted => cast(ubyte[])((cast(void*)&trg)[0..T.sizeof]))();
247                 foreach (i, ref v; tmp)
248                     v = pop(r, "byte", i, T.stringof, i, T.sizeof);
249             }
250             else
251                 static assert(0, "raw unions are not allowed, for allow build "~
252                                     "with configuration 'allow-raw-unions'");
253         }
254         else static assert(0, "unsupported type: " ~ T.stringof);
255     }
256 
257     size_t cnt;
258 
259     auto wr = (() @trusted => WrapRng(&range, &cnt, &fNameStack))();
260 
261     static if (Target.length == 1)
262         impl(wr, target[0], "root");
263     else foreach (i, ref v; target)
264         impl(wr, v, "root", i);
265 }
266 
267 /++ Deserialize part of input range to `Target` value
268 
269     Params:
270         range = input range with serialized data (not saved before work)
271 
272     Returns:
273         deserialized value
274  +/
275 Target sbinDeserializePart(RH=EmptyReprHandler, Target, R, string file=__FILE__, size_t line=__LINE__)
276     (ref R range) if (isReprHandler!RH)
277 {
278     Unqual!Target ret;
279     sbinDeserializePart!(RH, R, file, line, Target)(range, ret);
280     return ret;
281 }
282 
283 /// ditto
284 Target sbinDeserializePart(Target, R, string file=__FILE__, size_t line=__LINE__)(ref R range)
285 {
286     Unqual!Target ret;
287     sbinDeserializePart!(EmptyReprHandler, R, file, line, Target)(range, ret);
288     return ret;
289 }
290 
291 /++ Deserialize `Target` value
292 
293     Params:
294         range = input range with serialized data (not saved before work)
295 
296     Returns:
297         deserialized value
298  +/
299 Target sbinDeserialize(RH=EmptyReprHandler, Target, R, string file=__FILE__, size_t line=__LINE__)
300     (R range) if (isReprHandler!RH)
301 {
302     Unqual!Target ret;
303     sbinDeserialize!(RH)(range, ret);
304     return ret;
305 }
306 
307 /// ditto
308 Target sbinDeserialize(Target, R, string file=__FILE__, size_t line=__LINE__)(R range)
309 {
310     Unqual!Target ret;
311     sbinDeserialize!(EmptyReprHandler, R, file, line)(range, ret);
312     return ret;
313 }
314 
315 /++ Deserialize `Target` value
316 
317     Params:
318         range = input range with serialized data (not saved before work)
319         target = reference to result object
320 
321     Throws:
322         SBinDeserializeException if range isn't empty after deseriazlie
323  +/
324 void sbinDeserialize(RH=EmptyReprHandler, R, string file=__FILE__, size_t line=__LINE__, Target...)
325     (R range, ref Target target) if (isReprHandler!RH)
326 {
327     sbinDeserializePart!(RH, R, file, line)(range, target);
328 
329     enforce(range.empty, new SBinDeserializeException(
330         format("input range not empty after full '%s' deserialize", Target.stringof)));
331 }