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