1 /// Serialize a D type to a message buffer.
2 module serialize;
3 
4 import std.conv, std.traits, std.outbuffer, std.string;
5 
6 import leb128, common, oneof;
7 
8 /// Serialize top-level, either array, associative array or struct.
9 /// For aggregate types like structs, serialize each member.
10 /// Return: serialized data as `OutBuffer`
11 auto toMsgBuffer(MsgBufferType E = MsgBufferType.Var, T)(const ref T val) {
12 	return toMsgBuffer!(E, T)(val, new OutBuffer);
13 }	// toMsgBuffer()
14 
15 /// Serialise an integer to `buf`.
16 pragma(inline, true)
17 void serializeInt(MsgBufferType E = MsgBufferType.Var, T)(const T val, OutBuffer buf) {
18 	static if (E == MsgBufferType.Flat) {
19 		buf.alignSize(T.alignof);
20 		buf.write(to!T(val));
21 	} else {
22 		toLEB128(buf, to!T(val));
23 	}
24 }	// serializeInt()
25 
26 /// Serialize a single value, either a string, a floating point value or a scalar, e.g.
27 /// int, bool, long, etc. If it is neither, then forward to top-level serialize.
28 /// Used to serialize rvalues, where no ref is possible.
29 auto serializeValue(MsgBufferType E = MsgBufferType.Var, T)(const T val, OutBuffer buf) {
30 	return serializeValue!(E, T)(val, buf);
31 }
32 
33 /// Serialize a single value, either a string, a floating point value or a scalar, e.g.
34 /// int, bool, long, etc. If it is neither, then forward to top-level serialize.
35 auto serializeValue(MsgBufferType E = MsgBufferType.Var, T)(const ref T val, OutBuffer buf) {
36 	static if (__traits(hasMember, T, "toMsgBuf")) {
37 		val.toMsgBuf!E(buf);
38 	} else static if (isSomeString!(T)) {
39 		serializeInt!(E)(to!uint(val.length), buf);
40 		buf.write(val);
41 	} else static if (isBoolean!(T)) {
42 		static assert(ubyte.sizeof == bool.sizeof);
43 		buf.write(to!ubyte(val));
44 	} else static if (is(T == enum)) {
45 		static if (__traits(compiles, to!int(EnumMembers!T[0]))) {
46 			static if (E == MsgBufferType.Flat) {
47 				buf.alignSize(T.alignof);
48 				buf.write(to!int(val));
49 			} else {
50 				toLEB128(buf, to!int(val));
51 			}
52 		} else {
53 			static assert(0, "only integral enums supported for enum " ~ T.stringof);
54 		}
55 	} else static if (isScalarType!(T) || isFloatingPoint!(T)) {
56 		static if (!isFloatingPoint!(T) && T.sizeof > 1 && E == MsgBufferType.Var) {
57 			toLEB128!T(buf, val);
58 		} else {
59 			buf.alignSize(T.alignof);
60 			buf.write(val);
61 		}
62 	} else static if (isArray!(T)) {
63 		serializeInt!(E)(to!uint(val.length), buf);
64 		static if (isScalarType!(typeof(val[0])) || isFloatingPoint!(typeof(val[0]))) {
65 			// Performance optimisation for scalar/floating point arrays
66 			static if (!isFloatingPoint!(typeof(val[0])) && typeof(val[0]).sizeof > 1 && E == MsgBufferType.Var) {
67 				toLEB128!T(buf, val);
68 			} else {
69 				buf.reserve(val.length * typeof(val[0]).sizeof);
70 				buf.alignSize(T.alignof);
71 				buf.write(cast(ubyte[])val);
72 			}
73 		} else {
74 			static foreach (k; val)
75 				serializeValue!(E, typeof(k))(k, buf);
76 		}
77 	} else static if (isAssociativeArray!(T)) {
78 		// TODO: the performance for AAs is underwhelming, maybe there is a way to make "forbidden"
79 		// optimisations, given the implementation details of AAs here:
80 		// https://github.com/dlang/dmd/blob/7f4620f4e1fe29641b28648c5c4c93d9fafdf90f/src/dmd/backend/aarray.d (dmd)
81 		// or
82 		// https://github.com/ldc-developers/ldc/blob/196a9eb9ad6374c0837588588f36715a3cf8e302/dmd/root/aav.d (ldc2)
83 		// However, this might not work for all compilers, so maybe there is another way I have not seen yet...
84 		serializeInt!(E)(to!uint(val.length), buf);
85 		foreach (k; val.keys) {
86 			serializeValue!(E, typeof(k))(k, buf);
87 			serializeValue!(E, typeof(val[k]))(val[k], buf);
88 		}
89 	} else static if (is(T == struct) && T.stringof.startsWith("Oneof")) {
90 		bool hasValue;
91 		static foreach (idx, s; T.tupleof) {{
92 			alias OneofMemberType = typeof(s);
93 			enum OneofMemberName = __traits(identifier, s);
94 			static if (OneofMemberName.startsWith("___data_field")) {
95 				if (has!(OneofMemberType)(val)) {
96 					immutable oneOfValue = get!(OneofMemberType)(val);
97 					serializeInt!(E)(to!ubyte(idx+1), buf);
98 					serializeValue!(E, OneofMemberType)(oneOfValue, buf);
99 					hasValue = true;
100 				}
101 			}
102 		}}
103 		if (!hasValue) {
104 			// Oneof was empty.
105 			serializeInt!(E)(to!ubyte(0), buf);
106 		}
107 	} else {
108 		toMsgBuffer!(E, T)(val, buf);
109 	}
110 }	// serializeValue()
111 
112 /// Serialize top-level, either array, associative array or struct.
113 /// For aggregate types like structs, serialize each member.
114 /// Return: serialized data as `OutBuffer`
115 auto toMsgBuffer(MsgBufferType E = MsgBufferType.Var, T)(const ref T val, OutBuffer buf) {
116 	static if (__traits(hasMember, T, "toMsgBuf")) {
117 		val.toMsgBuf!E(buf);
118 	} else static if (isAggregateType!(T) && !is(T == class)) {
119 		static foreach (v; T.tupleof) {
120 			static if (__traits(identifier, v) != "this") // ignore "alias this"
121 				serializeValue!(E, typeof(v))(mixin("val." ~ __traits(identifier, v)), buf);
122 		}
123 	} else static if (isArray!(T) || isAssociativeArray!(T)) {
124 		serializeValue!(E, T)(val, buf);
125 	} else {
126 		static assert(0, "expected struct or array, not " ~ T.stringof);
127 	}
128 	return buf;
129 }	// toMsgBuffer()