1 /**
2  * Automatic struct/class comparison/hashing/serialization
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 <ae@cy.md>
12  */
13 
14 module ae.utils.autodata;
15 
16 import ae.utils.digest;
17 import std.traits;
18 public import std.conv;
19 
20 /// Returns a string mixin for processing the field with the name `name`.
21 string addAutoField(string name, bool reverseSort = false)
22 {
23 	return `mixin(typeof(handler).getMixin!(typeof(` ~ name ~ `), "` ~ name ~ `", ` ~ (reverseSort ? "true" : "false") ~`));`;
24 }
25 
26 /// Automatically implements `opCmp`, `opEquals`, and `toHash` using `processData`
27 template AutoCompare()
28 {
29 	///
30 	static if (is(typeof(this)==class))
31 	{
32 		alias typeof(this) _AutoDataTypeReference;
33 		alias Object _AutoDataOtherTypeReference;
34 
35 		override hash_t toHash() const { try { return _AutoDataHash(); } catch(object.Exception e) { assert(0, e.msg); } }
36 		override bool opEquals(Object o) const { return _AutoDataEquals(o); }
37 		override int opCmp(Object o) const { return _AutoDataCmp(o); }
38 	}
39 	else // struct
40 	{
41 		alias const(typeof(this)*) _AutoDataTypeReference;
42 		alias const(typeof(this)*) _AutoDataOtherTypeReference;
43 
44 		hash_t toHash() const { return _AutoDataHash(); }
45 		bool opEquals(ref const typeof(this) s) const { return _AutoDataEquals(&s); }
46 		int opCmp(ref const typeof(this) s) const { return _AutoDataCmp(&s); }
47 	}
48 
49 	@trusted private hash_t _AutoDataHash() const
50 	{
51 		_HashDataHandler handler;
52 		handler.hasher.Begin();
53 		processData!(void, q{}, q{})(handler);
54 		return handler.hasher.End();
55 	}
56 
57 	private bool _AutoDataEquals(_AutoDataOtherTypeReference other) const
58 	{
59 		auto handler = _EqualsDataHandler!_AutoDataTypeReference(cast(_AutoDataTypeReference) other);
60 		if (handler.other is null)
61 			return false;
62 		return processData!(bool, q{auto _AutoDataOther = handler.other;}, q{return true;})(handler);
63 	}
64 
65 	private int _AutoDataCmp(_AutoDataOtherTypeReference other) const
66 	{
67 		auto handler = _CmpDataHandler!_AutoDataTypeReference(cast(_AutoDataTypeReference) other);
68 		if (handler.other is null)
69 			return false;
70 		return processData!(int, q{auto _AutoDataOther = handler.other;}, "return 0;")(handler);
71 	}
72 }
73 
74 /// Automatically implements `toString` using `processData`
75 template AutoToString()
76 {
77 	///
78 	static if (is(typeof(this)==class))
79 		override string toString() const { return _AutoDataToString(); }
80 	else // struct
81 		string toString() const { return _AutoDataToString(); }
82 
83 	string _AutoDataToString() const
84 	{
85 		_ToStringDataHandler handler;
86 		return processData!(string, "string _AutoDataResult;", "return _AutoDataResult;")(handler);
87 	}
88 }
89 
90 /// Automatically implements `processData` which processes all fields.
91 template ProcessAllData()
92 {
93 	///
94 	R processData(R, string prolog, string epilog, H)(ref H handler) const
95 	{
96 		mixin(prolog);
97 		foreach (i, T; this.tupleof)
98 			mixin(addAutoField(this.tupleof[i].stringof[5..$])); // remove "this."
99 		mixin(epilog);
100 	}
101 }
102 
103 /// For data handlers that only need to look at the raw data (currently only _HashDataHandler)
104 template _RawDataHandlerWrapper()
105 {
106 	template getMixin(T, string name, bool reverseSort)
107 	{
108 		enum getMixin = getMixinRecursive!(T, "this." ~ name, "");
109 	}
110 
111 	template getMixinRecursive(T, string name, string loopDepth)
112 	{
113 		static if (is(T U : U[]))
114 			enum getMixinRecursive =
115 				"{ bool _AutoDataNullTest = " ~ name ~ " is null; " ~ getRawMixin!("&_AutoDataNullTest", "bool.sizeof") ~ "}" ~
116 				(!hasAliasing!(U) ?
117 					getRawMixin!(name ~ ".ptr", name ~ ".length")
118 				:
119 					"foreach (ref _AutoDataArrayItem" ~ loopDepth ~ "; " ~ name ~ ") {" ~ getMixinRecursive!(U, "_AutoDataArrayItem" ~ loopDepth, loopDepth~"Item") ~ "}"
120 				);
121 		else
122 		static if (!hasAliasing!(T))
123 			enum getMixinRecursive = getRawMixin!("&" ~ name, name ~ ".sizeof");
124 		else
125 		static if (is(T==struct))
126 			enum getMixinRecursive = name ~ ".processData!(void, ``, ``)(handler);";
127 		else
128 		static if (is(T==class))
129 			enum getMixinRecursive = "if ("~name~" !is null) " ~ name ~ ".processData!(void, ``, ``)(handler);";
130 		else
131 			static assert(0, "Don't know how to process type: " ~ T.stringof);
132 	}
133 }
134 
135 struct _HashDataHandler
136 {
137 	mixin _RawDataHandlerWrapper;
138 
139 	MurmurHash2A hasher;
140 
141 	template getRawMixin(string ptr, string len)
142 	{
143 		enum getRawMixin = "handler.hasher.Add(" ~ ptr ~ ", to!int(" ~ len ~ "));";
144 	}
145 }
146 
147 struct _EqualsDataHandler(O)
148 {
149 	O other;
150 
151 	template nullCheck(T, string name)
152 	{
153 		static if (is(typeof(T.init is null)))
154 			enum nullCheck = "if ((this." ~ name ~ " is null) != (_AutoDataOther." ~ name ~ " is null)) return false;";
155 		else
156 			enum nullCheck = "";
157 	}
158 
159 	template getMixin(T, string name, bool reverseSort)
160 	{
161 		enum getMixin = nullCheck!(T, name) ~ "if (this." ~ name ~ " != _AutoDataOther." ~ name ~ ") return false;";
162 	}
163 }
164 
165 struct _CmpDataHandler(O)
166 {
167 	O other;
168 
169 	template getMixin(T, string name, bool reverseSort)
170 	{
171 		enum getMixin = getMixinComposite!(T, name, reverseSort).code;
172 	}
173 
174 	template nullCheck(T, string name, string reverseStr)
175 	{
176 		static if (is(typeof(T.init is null)))
177 			enum nullCheck = "
178 				if (this."~name~" is null && _AutoDataOther."~name~" is null)
179 					{ /* skip */ }
180 				else
181 				if (this."~name~" is null && _AutoDataOther."~name~" !is null)
182 					return " ~ reverseStr ~ "(-1);
183 				else
184 				if (this."~name~" !is null && _AutoDataOther."~name~" is null)
185 					return " ~ reverseStr ~ "( 1);
186 				else";
187 		else
188 			enum nullCheck = "";
189 	}
190 
191 	template getMixinComposite(T, string name, bool reverseSort)
192 	{
193 		enum reverseStr = reverseSort ? "-" : "";
194 		static if (is(T U : U[]))
195 			enum arrCode = "{ int _AutoDataCmp = cast(int)(this." ~ name ~ " !is null) - cast(int)(_AutoDataOther." ~ name ~ " !is null); if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }";
196 		else
197 			enum arrCode = "";
198 
199 		static if (is(T == string) && is(std..string.cmp))
200 			enum dataCode = "{ int _AutoDataCmp = std.string.cmp(this." ~ name ~ ", _AutoDataOther." ~ name ~ "); if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }";
201 		else
202 		static if (is(T == int))
203 			enum dataCode = "{ int _AutoDataCmp = this." ~ name ~ " - _AutoDataOther." ~ name ~ "; if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }"; // TODO: use long?
204 		else
205 		static if (is(typeof(T.opCmp)))
206 			enum dataCode = nullCheck!(T, name, reverseStr)
207 			              ~ "{ int _AutoDataCmp = this." ~ name ~ ".opCmp(_AutoDataOther." ~ name ~ "); if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }";
208 		else
209 			enum dataCode = "if (this." ~ name ~ " < _AutoDataOther." ~ name ~ ") return " ~ reverseStr ~ "(-1);" ~
210 			                "if (this." ~ name ~ " > _AutoDataOther." ~ name ~ ") return " ~ reverseStr ~ "( 1);";
211 		enum code = arrCode ~ dataCode;
212 	}
213 }
214 
215 struct _ToStringDataHandler
216 {
217 	template getMixinSingle(T, string name)
218 	{
219 /*
220 		enum getMixinSingle = "
221 				static if (is(typeof(_AutoDataResult ~= " ~ name ~ ".toString())))
222 					_AutoDataResult ~= " ~ name ~ ".toString();
223 				else
224 					_AutoDataResult ~= to!string(" ~ name ~ ");
225 		";
226 */
227 		static if (is(typeof(T.init.toString())))
228 			enum getMixinSingle = "_AutoDataResult ~= " ~ name ~ ".toString();";
229 		else
230 			enum getMixinSingle = "_AutoDataResult ~= to!string(" ~ name ~ ");";
231 	}
232 
233 	template getMixinBody(T, string name)
234 	{
235 		// TODO: arrays of arrays
236 		static if (is(T U : U[]))
237 		{
238 			enum getMixinBody = "
239 				_AutoDataResult ~= ` [ `;
240 				foreach (_AutoDataArrayIndex, _AutoDataArrayItem; " ~ name ~ ")
241 				{
242 					if (_AutoDataArrayIndex) _AutoDataResult ~= ` , `;
243 					" ~ getMixinSingle!(U, "_AutoDataArrayItem") ~ "
244 				}
245 				_AutoDataResult ~= ` ] `;
246 			";
247 		}
248 		else
249 			enum getMixinBody = getMixinSingle!(T, name);
250 	}
251 
252 	template getMixin(T, string name, bool reverseSort)
253 	{
254 		enum getMixin =
255 			"_AutoDataResult ~= `" ~ name ~ " = `;" ~
256 			getMixinBody!(T, name) ~
257 			"_AutoDataResult ~= ` `;";
258 	}
259 }