1 /**
2  * Type serializer and deserializer.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.sd.sd;
15 
16 import std.conv;
17 import std.format;
18 import std.string;
19 import std.traits;
20 
21 import ae.utils.meta;
22 import ae.utils.text;
23 
24 /// Serialization source which serializes a given object.
25 struct Serializer
26 {
27 	static template Impl(alias anchor)
28 	{
29 		static void read(Sink, T)(Sink sink, auto ref T v)
30 		{
31 			static if (is(typeof(v is null)))
32 				if (v is null)
33 				{
34 					sink.handleNull();
35 					return;
36 				}
37 
38 			static if (is(T == bool))
39 				sink.handleBoolean(v);
40 			else
41 			static if (is(T : ulong))
42 			{
43 				char[decimalSize!T] buf = void;
44 				sink.handleNumeric(toDec(v, buf));
45 			}
46 			else
47 			static if (isNumeric!T) // floating point
48 			{
49 				import ae.utils.textout;
50 
51 				static char[64] arr;
52 				auto buf = StringBuffer(arr);
53 				formattedWrite(&buf, "%s", v);
54 				sink.handleNumeric(buf.get());
55 			}
56 			else
57 			static if (is(T == struct))
58 			{
59 				auto reader = StructReader!T(v.reference);
60 				sink.handleObject(boundFunctorOf!(StructReader!T.read)(&reader));
61 			}
62 			else
63 			static if (is(T V : V[K], K))
64 			{
65 				alias Reader = AAReader!(T, K, V);
66 				auto reader = Reader(v);
67 				sink.handleObject(boundFunctorOf!(Reader.read)(&reader));
68 			}
69 			else
70 			static if (is(T : string))
71 				sink.handleString(v);
72 			else
73 			static if (is(T U : U[]))
74 			{
75 				alias Reader = ArrayReader!T;
76 				auto reader = Reader(v);
77 				sink.handleArray(boundFunctorOf!(Reader.readArray)(&reader));
78 			}
79 			else
80 				static assert(false, "Don't know how to serialize " ~ T.stringof);
81 		}
82 
83 		static struct StructReader(T)
84 		{
85 			RefType!T p;
86 			void read(Sink)(Sink sink)
87 			{
88 				foreach (i, ref field; p.dereference.tupleof)
89 				{
90 					import std.array : split;
91 					enum name = p.dereference.tupleof[i].stringof.split(".")[$-1];
92 
93 					alias ValueReader = Reader!(typeof(field));
94 					auto reader = ValueReader(&field);
95 					sink.handleField(unboundFunctorOf!(stringReader!name), boundFunctorOf!(ValueReader.readValue)(&reader));
96 				}
97 			}
98 		}
99 
100 		static struct AAReader(T, K, V)
101 		{
102 			T aa;
103 			void read(Sink)(Sink sink)
104 			{
105 				foreach (K k, ref V v; aa)
106 				{
107 					alias KeyReader   = Reader!K;
108 					auto keyReader   = KeyReader  (&k);
109 					alias ValueReader = Reader!V;
110 					auto valueReader = ValueReader(&v);
111 					sink.handleField(
112 						boundFunctorOf!(KeyReader  .readValue)(&keyReader  ),
113 						boundFunctorOf!(ValueReader.readValue)(&valueReader),
114 					);
115 				}
116 			}
117 		}
118 
119 		static struct ArrayReader(T)
120 		{
121 			T arr;
122 			void readArray(Sink)(Sink sink)
123 			{
124 				foreach (ref v; arr)
125 					read(sink, v);
126 			}
127 		}
128 
129 		static template stringReader(string name)
130 		{
131 			static void stringReader(Sink)(Sink sink)
132 			{
133 				sink.handleString(name);
134 			}
135 		}
136 
137 		static struct Reader(T)
138 		{
139 			T* p;
140 
141 			void readValue(Sink)(Sink sink)
142 			{
143 				read(sink, *p);
144 			}
145 		}
146 	}
147 }
148 
149 /// Serialization sink which deserializes into a given type.
150 template Deserializer(alias anchor)
151 {
152 	alias C = immutable(char); // TODO
153 
154 	mixin template SinkHandlers(T)
155 	{
156 		template unparseable(string inputType)
157 		{
158 			void unparseable(Reader)(Reader reader)
159 			{
160 				throw new Exception("Can't parse %s from %s".format(T.stringof, inputType));
161 			}
162 		}
163 
164 		void handleString(S)(S s)
165 		{
166 			static if (is(typeof(s.to!T)))
167 			{
168 				T v = to!T(s);
169 				handleValue(v);
170 			}
171 			else
172 				throw new Exception("Can't parse %s from %s".format(T.stringof, S.stringof));
173 		}
174 
175 		static if (is(T : C[]))
176 			void handleStringFragments(Reader)(Reader reader)
177 			{
178 				static struct FragmentSink
179 				{
180 					C[] buf;
181 
182 					void handleStringFragment(CC)(CC[] s)
183 					{
184 						buf ~= s;
185 					}
186 				}
187 				FragmentSink sink;
188 				reader(&sink);
189 				handleValue(sink.buf);
190 			}
191 		else
192 			alias handleStringFragments = unparseable!"string fragments";
193 
194 		static if (is(T U : U[]))
195 			void handleArray(Reader)(Reader reader)
196 			{
197 				ArraySink!U sink;
198 				reader(&sink);
199 				handleValue(sink.arr);
200 			}
201 		else
202 			alias handleArray = unparseable!"array";
203 
204 		static if (is(T V : V[K], K))
205 			void handleObject(Reader)(Reader reader)
206 			{
207 				static struct FieldSink
208 				{
209 					T aa;
210 
211 					void handleField(NameReader, ValueReader)(NameReader nameReader, ValueReader valueReader)
212 					{
213 						K k;
214 						V v;
215 						nameReader (makeSink!K(&k));
216 						valueReader(makeSink!V(&v));
217 						aa[k] = v;
218 					}
219 				}
220 
221 				FieldSink sink;
222 				reader(&sink);
223 				handleValue(sink.aa);
224 			}
225 		else
226 		static if (is(T == struct))
227 		{
228 			void handleObject(Reader)(Reader reader)
229 			{
230 				static struct FieldSink
231 				{
232 					T s;
233 
234 					void handleField(NameReader, ValueReader)(NameReader nameReader, ValueReader valueReader)
235 					{
236 						alias N = const(C)[];
237 						N name;
238 						nameReader(makeSink!N(&name));
239 
240 						// TODO: generate switch
241 						foreach (i, field; s.tupleof)
242 						{
243 							// TODO: Name customization UDAs
244 							enum fieldName = to!N(__traits(identifier, s.tupleof[i]));
245 							if (name == fieldName)
246 							{
247 								alias V = typeof(field);
248 								valueReader(makeSink!V(&s.tupleof[i]));
249 								return;
250 							}
251 						}
252 						throw new Exception("Unknown field %s".format(name));
253 					}
254 				}
255 
256 				FieldSink sink;
257 				reader(&sink);
258 				handleValue(sink.s);
259 			}
260 		}
261 		else
262 			alias handleObject = unparseable!"object";
263 
264 		void handleNull()
265 		{
266 			static if (is(typeof({T v = null;})))
267 			{
268 				T v = null;
269 				handleValue(v);
270 			}
271 			else
272 				throw new Exception("Can't parse %s from %s".format(T.stringof, "null"));
273 		}
274 
275 		void handleBoolean(bool v)
276 		{
277 			static if (is(T : bool))
278 				handleValue(v);
279 			else
280 				throw new Exception("Can't parse %s from %s".format(T.stringof, "boolean"));
281 		}
282 
283 		void handleNumeric(CC)(CC[] v)
284 		{
285 			static if (is(typeof(to!T(v))))
286 			{
287 				T t = to!T(v);
288 				handleValue(t);
289 			}
290 			else
291 				throw new Exception("Can't parse %s from %s".format(T.stringof, "numeric"));
292 		}
293 	}
294 
295 	static struct ArraySink(T)
296 	{
297 		T[] arr;
298 
299 		void handleValue(ref T v) { arr ~= v; }
300 
301 		mixin SinkHandlers!T;
302 	}
303 
304 	static auto makeSink(T)(T* p)
305 	{
306 		static if (is(typeof(p.isSerializationSink)))
307 			return p;
308 		else
309 		{
310 			static struct Sink
311 			{
312 				T* p;
313 
314 				// TODO: avoid redundant copying for large types
315 				void handleValue(ref T v) { *p = v; }
316 
317 				auto traverse(CC, Reader)(CC[] name, Reader reader)
318 				{
319 					static if (is(T K : V[K], V))
320 					{
321 						auto key = name.to!K();
322 						auto pv = key in *p;
323 						if (!pv)
324 						{
325 							(*p)[key] = V.init;
326 							pv = key in *p;
327 						}
328 						return reader(makeSink(pv));
329 					}
330 					else
331 					static if (is(T == struct))
332 					{
333 						static immutable T dummy; // https://d.puremagic.com/issues/show_bug.cgi?id=12319
334 						foreach (i, ref field; p.tupleof)
335 						{
336 							// TODO: Name customization UDAs
337 							enum fieldName = to!(CC[])(__traits(identifier, dummy.tupleof[i]));
338 							if (name == fieldName)
339 								return reader(makeSink(&field));
340 						}
341 						throw new Exception("No such field in %s: %s".format(T.stringof, name));
342 					}
343 					else
344 					{
345 						if (false) // coerce return value
346 							return reader(this);
347 						else
348 							throw new Exception("Can't traverse %s".format(T.stringof));
349 					}
350 				}
351 
352 				mixin SinkHandlers!T;
353 			}
354 
355 			return Sink(p);
356 		}
357 	}
358 }
359 
360 alias Deserializer!Object.makeSink deserializer;