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 }