1 /** SDLang data format serialization support for vibe.data.serialization.
2 */
3 module vibe.data.sdl;
4 
5 import vibe.data.serialization;
6 import vibe.data.json;
7 import sdlang;
8 import std.datetime : Date, DateTime, SysTime, TimeOfDay, UTC;
9 import std.meta : allSatisfy, staticIndexOf;
10 import std.traits : Unqual, ValueType, hasUDA, isNumeric, isBoolean, isArray, isAssociativeArray;
11 import core.time : Duration, days, hours, seconds;
12 
13 
14 ///
15 unittest {
16 	import std.conv : to;
17 
18 	static struct S {
19 		bool b;
20 		int i;
21 		float f;
22 		double d;
23 		string str;
24 		ubyte[] data;
25 		SysTime sysTime;
26 		DateTimeFrac dateTime;
27 		Date date;
28 		Duration dur;
29 	}
30 
31 	S s = {
32 		b : true,
33 		i: 12,
34 		f: 0.5f,
35 		d: 0.5,
36 		str: "foo",
37 		data: [1, 2, 3],
38 		sysTime: SysTime(DateTime(Date(2016, 12, 10), TimeOfDay(9, 30, 23)), UTC()),
39 		dateTime: DateTimeFrac(DateTime(Date(2016, 12, 10), TimeOfDay(9, 30, 23))),
40 		date: Date(2016, 12, 10),
41 		dur: 2.days + 2.hours + 2.seconds
42 	};
43 
44 	auto res = serializeSDLang(s);
45 	assert(res.toSDLDocument() == "b true\ni 12\nf 0.5F\nd 0.5D\nstr \"foo\"\ndata [AQID]\n"
46 		~ "sysTime 2016/12/10 09:30:23-UTC\ndateTime 2016/12/10 09:30:23\ndate 2016/12/10\n"
47 		~ "dur 2d:02:00:02\n", [res.toSDLDocument()].to!string);
48 
49 	auto t = deserializeSDLang!S(res);
50 	assert(s == t);
51 }
52 
53 
54 ///
55 unittest {
56 	static struct U {
57 		@sdlAttribute int att1;
58 		string content1;
59 	}
60 
61 	static struct T {
62 		@sdlAttribute int att1;
63 		@sdlAttribute string att2;
64 		@sdlValue string content;
65 	}
66 
67 	static struct S {
68 		string[string] dict;
69 		U[] arr;
70 		T[] arr1;
71 		@sdlSingle T[] arr2;
72 		int[] iarr;
73 	}
74 
75 	S s = {
76 		dict : ["a": "foo", "b": "bar"],
77 		arr : [U(1, "x"), U(2, "y")],
78 		arr1 : [T(1, "a", "x"), T(2, "b", "y")],
79 		arr2 : [T(1, "a", "x"), T(2, "b", "y")],
80 		iarr : [1, 2, 3]
81 	};
82 
83 	auto res = serializeSDLang(s);
84 	assert(res.toSDLDocument() ==
85 `dict {
86 	"b" "bar"
87 	"a" "foo"
88 }
89 arr {
90 	entry att1=1 {
91 		content1 "x"
92 	}
93 	entry att1=2 {
94 		content1 "y"
95 	}
96 }
97 arr1 {
98 	entry "x" att1=1 att2="a"
99 	entry "y" att1=2 att2="b"
100 }
101 arr2 "x" att1=1 att2="a"
102 arr2 "y" att1=2 att2="b"
103 iarr 1 2 3
104 `, res.toSDLDocument());
105 
106 	S t = deserialize!(SDLangSerializer, S)(res);
107 	import std.conv : to;
108 	assert(s == t, t.to!string);
109 }
110 
111 /** Serializes a value as an SDLang document.
112 */
113 Tag serializeSDLang(T, alias Policy = DefaultPolicy)(T value)
114 {
115 	return serializeWithPolicy!(SDLangSerializer, Policy)(value, new Tag(null, null));
116 }
117 
118 /** Deseriailzes a value from an SDLang document.
119 */
120 T deserializeSDLang(T, alias Policy = DefaultPolicy)(Tag sdl)
121 {
122 	return deserializeWithPolicy!(SDLangSerializer, Policy, T)(sdl);
123 }
124 
125 
126 ///
127 struct SDLangSerializer {
128 @trusted:
129 	enum isSDLBasicType(T) =
130 		isNumeric!T ||
131 		isBoolean!T ||
132 		is(T == string) ||
133 		is(T == ubyte[]) ||
134 		is(T == SysTime) ||
135 		is(T == DateTime) ||
136 		is(T == DateTimeFrac) ||
137 		is(T == Date) ||
138 		is(T == Duration) ||
139 		is(T == typeof(null)) ||
140 		isSDLSerializable!T;
141 
142 	enum isSupportedValueType(T) = isSDLBasicType!T || is(T == Tag);
143 
144 	private enum Loc { subNodes, attribute, values }
145 
146 	private static struct StackEntry {
147 		Tag tag;
148 		Attribute attribute;
149 		Loc loc;
150 		size_t valIdx;
151 		bool hasIdentKeys, isArrayEntry;
152 		string singleArrayName;
153 	}
154 
155 	private {
156 		StackEntry[] m_stack;
157 	}
158 
159 	this(Tag data) { pushTag(data); }
160 
161 	@disable this(this);
162 
163 	//
164 	// serialization
165 	//
166 	Tag getSerializedResult() { return m_stack[0].tag; }
167 
168 	void beginWriteDictionary(Traits)()
169 	{
170 		current.hasIdentKeys = !isAssociativeArray!(Traits.Type);
171 	}
172 	void endWriteDictionary(Traits)() {}
173 	void beginWriteDictionaryEntry(ElementTraits)(string name) {
174 		assert(m_stack.length > 0);
175 		assert(isAssociativeArray!(ElementTraits.ContainerType) || name.length > 0);
176 		static if (containsValue!(SDLSingleAttribute, ElementTraits.Attributes)) {
177 			current.singleArrayName = name;
178 			current.loc = Loc.subNodes;
179 		} else static if (isSDLBasicType!(ElementTraits.Type) && containsValue!(SDLAttributeAttribute, ElementTraits.Attributes)) {
180 			current.attribute = new Attribute(null, name, Value(null));
181 			current.tag.add(current.attribute);
182 			current.loc = Loc.attribute;
183 		} else static if (isSDLBasicType!(ElementTraits.Type) && containsValue!(SDLValueAttribute, ElementTraits.Attributes)) {
184 			current.loc = Loc.values;
185 		} else {
186 			if (current.hasIdentKeys) pushTag(name);
187 			else pushTag(null, () @trusted { return Value(name); } ());
188 			current.singleArrayName = "entry";
189 			current.loc = Loc.values;
190 		}
191 	}
192 	void endWriteDictionaryEntry(ElementTraits)(string name) {
193 		static if (containsValue!(SDLSingleAttribute, ElementTraits.Attributes)) {}
194 		else static if (isSDLBasicType!(ElementTraits.Type) && containsValue!(SDLAttributeAttribute, ElementTraits.Attributes)) {}
195 		else static if (isSDLBasicType!(ElementTraits.Type) && containsValue!(SDLValueAttribute, ElementTraits.Attributes)) {}
196 		else pop();
197 	}
198 
199 
200 	void beginWriteArray(Traits)(size_t)
201 		if (isValueArray!(Traits.Type))
202 	{
203 		current.loc = Loc.values;
204 	}
205 	void endWriteArray(Traits)() if (isValueArray!(Traits.Type)) {}
206 	void beginWriteArray(Traits)(size_t) if (!isValueArray!(Traits.Type))
207 	{
208 		current.loc = Loc.subNodes;
209 	}
210 	void endWriteArray(Traits)() if (!isValueArray!(Traits.Type)) {}
211 	void beginWriteArrayEntry(ElementTraits)(size_t idx)
212 	{
213 		if (current.loc == Loc.subNodes) {
214 			static if (containsValue!(SDLSingleAttribute, ElementTraits.ContainerAttributes)) {
215 				if (idx > 0 && m_stack.length > 1) {
216 					auto name = current.tag.name;
217 					assert(name.length > 0);
218 					pop();
219 					pushTag(name);
220 					return;
221 				}
222 			}
223 
224 			assert(current.singleArrayName.length > 0);
225 			pushTag(current.singleArrayName);
226 			current.isArrayEntry = true;
227 		}
228 	}
229 	void endWriteArrayEntry(ElementTraits)(size_t)
230 	{
231 		if (current.isArrayEntry) pop();
232 	}
233 
234 	 void writeValue(Traits, T)(in T value) @trusted
235 		if (!is(T == Tag))
236 	{
237 		import std.traits : isIntegral;
238 
239 		static if (isSDLSerializable!T) writeValue(value.toSDL());
240 		else {
241 			Value val;
242 			static if (is(T == DateTime)) val = DateTimeFrac(value);
243 			else {
244 				Unqual!T uval;
245 				static if (is(typeof(uval = value))) uval = value;
246 				else uval = value.dup;
247 				static if (isIntegral!T) {
248 					static if (T.sizeof <= int.sizeof) val = cast(int)uval;
249 					else val = uval;
250 				} else val = uval;
251 			}
252 			
253 			final switch (current.loc) {
254 				case Loc.attribute: current.attribute.value = val; break;
255 				case Loc.values: current.tag.add(val); break;
256 				case Loc.subNodes: current.tag.add(new Tag(null, null, [val])); break;
257 			}
258 		}
259 	}
260 
261 	void writeValue(Traits, T)(Tag value) if (is(T == Tag)) { current.tag.add(value); }
262 	void writeValue(Traits, T)(in Json Tag) if (is(T == Tag)) { current.tag.add(value.clone); }
263 
264 	//
265 	// deserialization
266 	//
267 	void readDictionary(Traits)(scope void delegate(string) @safe field_handler)
268 		if (isAssociativeArray!(Traits.Type))
269 	{
270 		foreach (st; current.tag.tags) {
271 			pushTag(st);
272 			current.valIdx = 1;
273 			current.loc = Loc.values;
274 			auto n = st.values[0].get!string;
275 			field_handler(n);
276 			pop();
277 		}
278 	}
279 
280 	void readDictionary(Traits)(scope void delegate(string) @safe field_handler)
281 		if (!isAssociativeArray!(Traits.Type))
282 	{
283 		import std.meta : AliasSeq;
284 		import std.traits : FieldNameTuple, hasUDA;
285 		foreach (att; current.tag.attributes) {
286 			current.loc = Loc.attribute;
287 			current.attribute = att;
288 			field_handler(att.name);
289 		}
290 		size_t first_idx = 0; // FIXME: 1 for non-ident keys
291 		foreach (st; current.tag.tags) {
292 			pushTag(st);
293 			current.loc = Loc.values;
294 			current.valIdx = 0;
295 			field_handler(st.name);
296 			pop();
297 		}
298 		static if (is(Traits.Type == struct) || is(Traits.Type == class)) {
299 			current.valIdx = 0;
300 			foreach (fname; FieldNameTuple!(Traits.Type)) {
301 				alias F = AliasSeq!(__traits(getMember, Traits.Type, fname));
302 				static if (hasUDAValue!(F[0], SDLValueAttribute)) {
303 					current.loc = Loc.values;
304 					if (current.valIdx >= current.tag.values.length) {
305 						import std.format : format;
306 						throw new Exception(format("Line %s: Missing value number %s (%s) for '%s'.", current.tag.location.line+1, current.valIdx+1, fname, current.tag.name)); // TODO: show line number
307 					}
308 
309 					field_handler(__traits(identifier, F[0]));
310 					current.valIdx++;
311 				}
312 			}
313 		}
314 	}
315 
316 	void beginReadDictionaryEntry(Traits)(string name)
317 	{
318 	}
319 
320 	void endReadDictionaryEntry(Traits)(string name)
321 	{
322 	}
323 
324 	void readArray(Traits)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback)
325 		if (containsValue!(SDLSingleAttribute, Traits.Attributes))
326 	{
327 		// FIXME: ensure that this is only called once and not for each array entry tag
328 		auto name = current.tag.name;
329 		foreach (t; current.tag.parent.tags) {
330 			if (t.name == name) {
331 				pushTag(t);
332 				entry_callback();
333 				pop();
334 			}
335 		}
336 	}
337 
338 	void readArray(Traits)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback)
339 		if (!containsValue!(SDLSingleAttribute, Traits.Attributes) && isValueArray!(Traits.Type))
340 	{
341 		current.loc = Loc.values;
342 		current.valIdx = 0;
343 		size_callback(current.tag.values.length);
344 		while (current.valIdx < current.tag.values.length) {
345 			entry_callback();
346 			current.valIdx++;
347 		}
348 	}
349 
350 	void readArray(Traits)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback)
351 		if (!containsValue!(SDLSingleAttribute, Traits.Attributes) && !isValueArray!(Traits.Type))
352 	{
353 		size_callback(current.tag.tags.length);
354 		foreach (st; current.tag.tags) {
355 			pushTag(st);
356 			entry_callback();
357 			pop();
358 		}
359 	}
360 
361 	void beginReadArrayEntry(Traits)(size_t idx)
362 	{
363 	}
364 
365 	void endReadArrayEntry(Traits)(size_t idx)
366 	{
367 	}
368 
369 	T readValue(Traits, T)()
370 	{
371 		import std.conv : to;
372 		import std.traits : isIntegral;
373 
374 		auto val = getCurrentValue();
375 		static if (isIntegral!T) {
376 			static if (T.sizeof <= int.sizeof) return val.get!int.to!T;
377 			else return val.get!long;
378 		} else return val.get!T;
379 	}
380 
381 	bool tryReadNull(Traits)()
382 	{
383 		auto val = getCurrentValue();
384 		if (!val.hasValue) return false;
385 		return val.peek!(typeof(null)) !is null;
386 	}
387 
388 	private @property ref inout(StackEntry) current() inout { return m_stack[$-1]; }
389 
390 	private void pushTag(string name) { pushTag(new Tag(current.tag, null, name)); }
391 	private void pushTag(string name, Value value) { pushTag(new Tag(current.tag, null, name, [value])); }
392 	private void pushTag(Tag tag)
393 	{
394 		StackEntry se;
395 		se.tag = tag;
396 		se.valIdx = tag.values.length;
397 		m_stack ~= se;
398 	}
399 
400 	private void pushAttribute(string name)
401 	{
402 		StackEntry se;
403 		se.attribute = new Attribute(null, name, Value.init);
404 		se.tag = current.tag;
405 		se.loc = Loc.attribute;
406 		current.tag.add(se.attribute);
407 		m_stack ~= se;
408 	}
409 
410 	private void pop()
411 	{
412 		assert(m_stack.length > 1, "Popping last element!?");
413 		m_stack.length--;
414 		m_stack.assumeSafeAppend();
415 	}
416 
417 
418 	private Value getCurrentValue()
419 	{
420 		final switch (current.loc) {
421 			case Loc.attribute: return current.attribute.value;
422 			case Loc.values:
423 				if (current.valIdx < current.tag.values.length)
424 					return current.tag.values[current.valIdx];
425 				return Value.init;
426 			case Loc.subNodes:
427 				if (current.tag.values.length > 0)
428 					return current.tag.values[0];
429 				return Value.init;
430 		}
431 	}
432 
433 	private template isValueDictionary(T) {
434 		static if (isAssociativeArray!T)
435 			enum isValueDictionary = isSDLBasicType!(ValueType!T);
436 		else static if (is(T == struct) || is(T == class))
437 			enum isValueDictionary = anySatisfy!(isValueField, T.tupleof) && allSatisfy!(isValueOrAttributeField, T.tupleof);
438 		else enum isValueDictionary = false;
439 	}
440 
441 	private template isValueArray(T) {
442 		static if (isArray!T)
443 			enum isValueArray = isSDLBasicType!(typeof(T.init[0]));
444 		else enum isValueArray = false;
445 	}
446 }
447 
448 /** Forces a value to be serialized as an SDL attribute.
449 
450 	This can be applied to plain values in aggregate members and will cause
451 	the vaule to be seriailzed as an attribute of the parent tag instead of
452 	as a child tag.
453 */
454 @property SDLAttributeAttribute sdlAttribute() { return SDLAttributeAttribute.init; }
455 
456 ///
457 unittest {
458 	struct S {
459 		int foo;
460 		@sdlAttribute int bar;
461 	}
462 
463 	struct T {
464 		S s;
465 	}
466 
467 	assert(
468 		serializeSDLang(T(S(1, 2))).toSDLDocument() ==
469 		"s bar=2 {\n" ~
470 		"\tfoo 1\n" ~
471 		"}\n"
472 	);
473 }
474 
475 
476 /** Forces a value to be serialized as an SDL value.
477 
478 	This can be applied to plain values in aggregate members and will cause
479 	the vaule to be seriailzed as a value of the parent tag instead of
480 	as a child tag.
481 */
482 @property SDLValueAttribute sdlValue() { return SDLValueAttribute.init; }
483 
484 ///
485 unittest {
486 	struct S {
487 		int foo;
488 		@sdlValue int bar;
489 	}
490 
491 	struct T {
492 		S s;
493 	}
494 
495 	assert(
496 		serializeSDLang(T(S(1, 2))).toSDLDocument() ==
497 		"s 2 {\n" ~
498 		"\tfoo 1\n" ~
499 		"}\n"
500 	);
501 }
502 
503 
504 /** Causes an array to be serialized as a plain sequence of entry tags instead
505 	of wrapping them in a separate parent tag.
506 */
507 @property SDLSingleAttribute sdlSingle() { return SDLSingleAttribute.init; }
508 
509 ///
510 unittest {
511 	struct S {
512 		@sdlAttribute int foo;
513 		@sdlAttribute int bar;
514 	}
515 
516 	struct T {
517 		S[] arr1;
518 		@sdlSingle S[] arr2;
519 	}
520 
521 	assert(
522 		serializeSDLang(T([S(1, 2), S(3, 4)], [S(5, 6), S(7, 8)])).toSDLDocument() ==
523 		"arr1 {\n" ~
524 		"\tentry foo=1 bar=2\n" ~
525 		"\tentry foo=3 bar=4\n" ~
526 		"}\n" ~
527 		"arr2 foo=5 bar=6\n" ~
528 		"arr2 foo=7 bar=8\n"
529 	);
530 }
531 
532 enum isSDLSerializable(T) = is(typeof(T.init.toSDL()) == Tag) && is(typeof(T.fromSDL(new Tag())) == T);
533 enum isValueField(alias F) = hasUDAValue!(F, SDLValueAttribute);
534 enum isValueOrAttributeField(alias F) = hasUDAValue!(F, SDLValueAttribute) || hasUDAValue!(F, SDLAttributeAttribute);
535 
536 /// private
537 private struct SDLAttributeAttribute {}
538 /// private
539 private struct SDLSingleAttribute {}
540 /// private
541 private struct SDLValueAttribute {}
542 
543 private enum hasUDAValue(alias DECL, T) = containsValue!(T, __traits(getAttributes, DECL));
544 private template containsValue(T, V...) {
545 	static if (V.length > 0)
546 		enum containsValue = is(typeof(V[0]) == T) || containsValue!(T, V[1 .. $]);
547 	else enum containsValue = false;
548 }
549 
550 unittest {
551 	struct S {
552 		@sdlSingle int single;
553 		@sdlValue int value;
554 		@sdlAttribute int attribute;
555 	}
556 
557 	static assert(hasUDAValue!(S.single, SDLSingleAttribute));
558 	static assert(!hasUDAValue!(S.single, SDLValueAttribute));
559 	static assert(!hasUDAValue!(S.single, SDLAttributeAttribute));
560 	static assert(!hasUDAValue!(S.value, SDLSingleAttribute));
561 	static assert(hasUDAValue!(S.value, SDLValueAttribute));
562 	static assert(!hasUDAValue!(S.value, SDLAttributeAttribute));
563 	static assert(!hasUDAValue!(S.attribute, SDLSingleAttribute));
564 	static assert(!hasUDAValue!(S.attribute, SDLValueAttribute));
565 	static assert(hasUDAValue!(S.attribute, SDLAttributeAttribute));
566 }
567 
568 unittest {
569 	struct S {
570 		short s;
571 		long l;
572 		int i;
573 		float f;
574 		double d;
575 	}
576 	auto s = S(-30000, -80000000000, -2000000000, 0.5f, 0.5);
577 	Tag serialized = s.serializeSDLang();
578 	assert(serialized.toSDLDocument() == "s -30000\nl -80000000000L\ni -2000000000\nf 0.5F\nd 0.5D\n", serialized.toSDLDocument());
579 	assert(serialized.deserializeSDLang!S() == s);
580 }