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 }