1 /++ 2 A declarative file/stream loader/saver. You define structs with a handful of annotations, this read and writes them to/from files. 3 +/ 4 module arsd.declarativeloader; 5 6 import std.range; 7 8 /// 9 enum BigEndian; 10 /// 11 enum LittleEndian; 12 /// @VariableLength indicates the value is saved in a MIDI like format 13 enum VariableLength; 14 /// @NumBytes!Field or @NumElements!Field controls length of embedded arrays 15 struct NumBytes(alias field) {} 16 /// ditto 17 struct NumElements(alias field) {} 18 /// @Tagged!Field indicates a tagged union. Each struct within should have @Tag(X) which is a value of Field 19 struct Tagged(alias field) {} 20 /// ditto 21 auto Tag(T)(T t) { 22 return TagStruct!T(t); 23 } 24 /// For example `@presentIf("version >= 2") int addedInVersion2;` 25 struct presentIf { string code; } 26 27 28 struct TagStruct(T) { T t; } 29 struct MustBeStruct(T) { T t; } 30 /// The marked field is not in the actual file 31 enum NotSaved; 32 /// Insists the field must be a certain value, like for magic numbers 33 auto MustBe(T)(T t) { 34 return MustBeStruct!T(t); 35 } 36 37 static bool fieldSaved(alias a)() { 38 bool saved; 39 static if(is(typeof(a.offsetof))) { 40 saved = true; 41 static foreach(attr; __traits(getAttributes, a)) 42 static if(is(attr == NotSaved)) 43 saved = false; 44 } 45 return saved; 46 } 47 48 static bool bigEndian(alias a)(bool def) { 49 bool be = def; 50 static foreach(attr; __traits(getAttributes, a)) { 51 static if(is(attr == BigEndian)) 52 be = true; 53 else static if(is(attr == LittleEndian)) 54 be = false; 55 } 56 return be; 57 } 58 59 static auto getTag(alias a)() { 60 static foreach(attr; __traits(getAttributes, a)) { 61 static if(is(typeof(attr) == TagStruct!T, T)) { 62 return attr.t; 63 } 64 } 65 assert(0); 66 } 67 68 union N(ty) { 69 ty member; 70 ubyte[ty.sizeof] bytes; 71 } 72 73 static bool fieldPresent(alias field, T)(T t) { 74 bool p = true; 75 static foreach(attr; __traits(getAttributes, field)) { 76 static if(is(typeof(attr) == presentIf)) { 77 bool p2 = false; 78 with(t) p2 = mixin(attr.code); 79 p = p && p2; 80 } 81 } 82 return p; 83 } 84 85 /// input range of ubytes... 86 int loadFrom(T, Range)(ref T t, auto ref Range r, bool assumeBigEndian = false) { 87 int bytesConsumed; 88 string currentItem; 89 90 import std.conv; 91 try { 92 93 ubyte next() { 94 if(r.empty) 95 throw new Exception(T.stringof ~ "." ~ currentItem ~ " trouble " ~ to!string(t)); 96 auto bfr = r.front; 97 r.popFront; 98 bytesConsumed++; 99 return bfr; 100 } 101 102 bool endianness = bigEndian!T(assumeBigEndian); 103 static foreach(memberName; __traits(allMembers, T)) {{ 104 currentItem = memberName; 105 static if(is(typeof(__traits(getMember, T, memberName)))) { 106 alias f = __traits(getMember, T, memberName); 107 alias ty = typeof(f); 108 static if(fieldSaved!f) 109 if(fieldPresent!f(t)) { 110 endianness = bigEndian!f(endianness); 111 // FIXME VariableLength 112 static if(is(ty : ulong) || is(ty : double)) { 113 N!ty n; 114 if(endianness) { 115 foreach(i; 0 .. ty.sizeof) { 116 version(BigEndian) 117 n.bytes[i] = next(); 118 else 119 n.bytes[$ - 1 - i] = next(); 120 } 121 } else { 122 foreach(i; 0 .. ty.sizeof) { 123 version(BigEndian) 124 n.bytes[$ - 1 - i] = next(); 125 else 126 n.bytes[i] = next(); 127 } 128 } 129 130 // FIXME: MustBe 131 132 __traits(getMember, t, memberName) = n.member; 133 } else static if(is(ty == struct)) { 134 bytesConsumed += loadFrom(__traits(getMember, t, memberName), r, endianness); 135 } else static if(is(ty == union)) { 136 static foreach(attr; __traits(getAttributes, ty)) 137 static if(is(attr == Tagged!Field, alias Field)) 138 enum tagField = __traits(identifier, Field); 139 static assert(is(typeof(tagField)), "Unions need a Tagged UDA on the union type (not the member) indicating the field that identifies the union"); 140 141 auto tag = __traits(getMember, t, tagField); 142 // find the child of the union matching the tag... 143 bool found = false; 144 static foreach(um; __traits(allMembers, ty)) { 145 if(tag == getTag!(__traits(getMember, ty, um))) { 146 bytesConsumed += loadFrom(__traits(getMember, __traits(getMember, t, memberName), um), r, endianness); 147 found = true; 148 } 149 } 150 if(!found) { 151 import std.format; 152 throw new Exception(format("found unknown union tag %s at %s", tag, t)); 153 } 154 } else static if(is(ty == E[], E)) { 155 static foreach(attr; __traits(getAttributes, f)) { 156 static if(is(attr == NumBytes!Field, alias Field)) 157 ulong numBytesRemaining = __traits(getMember, t, __traits(identifier, Field)); 158 else static if(is(attr == NumElements!Field, alias Field)) { 159 ulong numElementsRemaining = __traits(getMember, t, __traits(identifier, Field)); 160 } 161 } 162 163 static if(is(typeof(numBytesRemaining))) { 164 static if(is(E : const(ubyte)) || is(E : const(char))) { 165 while(numBytesRemaining) { 166 __traits(getMember, t, memberName) ~= next; 167 numBytesRemaining--; 168 } 169 } else { 170 while(numBytesRemaining) { 171 E piece; 172 auto by = loadFrom(e, r, endianness); 173 numBytesRemaining -= by; 174 bytesConsumed += by; 175 __traits(getMember, t, memberName) ~= piece; 176 } 177 } 178 } else static if(is(typeof(numElementsRemaining))) { 179 static if(is(E : const(ubyte)) || is(E : const(char))) { 180 while(numElementsRemaining) { 181 __traits(getMember, t, memberName) ~= next; 182 numElementsRemaining--; 183 } 184 } else static if(is(E : const(ushort))) { 185 while(numElementsRemaining) { 186 ushort n; 187 n = next << 8; 188 n |= next; 189 // FIXME all of this filth 190 __traits(getMember, t, memberName) ~= n; 191 numElementsRemaining--; 192 } 193 } else { 194 while(numElementsRemaining) { 195 //import std.stdio; writeln(memberName); 196 E piece; 197 auto by = loadFrom(piece, r, endianness); 198 numElementsRemaining--; 199 200 // such a filthy hack, needed for Java's mistake though :( 201 static if(__traits(compiles, piece.takesTwoSlots())) { 202 if(piece.takesTwoSlots()) { 203 __traits(getMember, t, memberName) ~= piece; 204 numElementsRemaining--; 205 } 206 } 207 208 bytesConsumed += by; 209 __traits(getMember, t, memberName) ~= piece; 210 } 211 } 212 } else static assert(0, "no way to identify length... " ~ memberName); 213 214 } else static assert(0, ty.stringof); 215 } 216 } 217 }} 218 219 } catch(Exception e) { 220 throw new Exception(T.stringof ~ "." ~ currentItem ~ " trouble " ~ to!string(t), e.file, e.line, e); 221 } 222 223 return bytesConsumed; 224 } 225 226 int saveTo(T, Range)(ref T t, ref Range r, bool assumeBigEndian = false) { 227 int bytesWritten; 228 string currentItem; 229 230 import std.conv; 231 try { 232 233 void write(ubyte b) { 234 bytesWritten++; 235 static if(is(Range == ubyte[])) 236 r ~= b; 237 else 238 r.put(b); 239 } 240 241 bool endianness = bigEndian!T(assumeBigEndian); 242 static foreach(memberName; __traits(allMembers, T)) {{ 243 currentItem = memberName; 244 static if(is(typeof(__traits(getMember, T, memberName)))) { 245 alias f = __traits(getMember, T, memberName); 246 alias ty = typeof(f); 247 static if(fieldSaved!f) 248 if(fieldPresent!f(t)) { 249 endianness = bigEndian!f(endianness); 250 // FIXME VariableLength 251 static if(is(ty : ulong) || is(ty : double)) { 252 N!ty n; 253 n.member = __traits(getMember, t, memberName); 254 if(endianness) { 255 foreach(i; 0 .. ty.sizeof) { 256 version(BigEndian) 257 write(n.bytes[i]); 258 else 259 write(n.bytes[$ - 1 - i]); 260 } 261 } else { 262 foreach(i; 0 .. ty.sizeof) { 263 version(BigEndian) 264 write(n.bytes[$ - 1 - i]); 265 else 266 write(n.bytes[i]); 267 } 268 } 269 270 // FIXME: MustBe 271 } else static if(is(ty == struct)) { 272 bytesWritten += saveTo(__traits(getMember, t, memberName), r, endianness); 273 } else static if(is(ty == union)) { 274 static foreach(attr; __traits(getAttributes, ty)) 275 static if(is(attr == Tagged!Field, alias Field)) 276 enum tagField = __traits(identifier, Field); 277 static assert(is(typeof(tagField)), "Unions need a Tagged UDA on the union type (not the member) indicating the field that identifies the union"); 278 279 auto tag = __traits(getMember, t, tagField); 280 // find the child of the union matching the tag... 281 bool found = false; 282 static foreach(um; __traits(allMembers, ty)) { 283 if(tag == getTag!(__traits(getMember, ty, um))) { 284 bytesWritten += saveTo(__traits(getMember, __traits(getMember, t, memberName), um), r, endianness); 285 found = true; 286 } 287 } 288 if(!found) { 289 import std.format; 290 throw new Exception(format("found unknown union tag %s at %s", tag, t)); 291 } 292 } else static if(is(ty == E[], E)) { 293 294 // the numBytesRemaining / numElementsRemaining thing here ASSUMING the 295 // arrays are already the correct size. the struct itself could invariant that maybe 296 297 foreach(item; __traits(getMember, t, memberName)) { 298 static if(is(typeof(item) == struct)) { 299 bytesWritten += saveTo(item, r, endianness); 300 } else { 301 static struct dummy { 302 typeof(item) i; 303 } 304 dummy d = dummy(item); 305 bytesWritten += saveTo(d, r, endianness); 306 } 307 } 308 309 } else static assert(0, ty.stringof); 310 } 311 } 312 }} 313 314 } catch(Exception e) { 315 throw new Exception(T.stringof ~ "." ~ currentItem ~ " save trouble " ~ to!string(t), e.file, e.line, e); 316 } 317 318 return bytesWritten; 319 } 320 321 unittest { 322 static struct A { 323 int a; 324 @presentIf("a > 5") int b; 325 int c; 326 @NumElements!c ubyte[] d; 327 } 328 329 A a; 330 a.loadFrom(cast(ubyte[]) [1, 1, 0, 0, 7, 0, 0, 0, 3, 0, 0, 0, 6, 7, 8]); 331 332 assert(a.a == 257); 333 assert(a.b == 7); 334 assert(a.c == 3); 335 assert(a.d == [6,7,8]); 336 337 a = A.init; 338 339 a.loadFrom(cast(ubyte[]) [0, 0, 0, 0, 7, 0, 0, 0,1,2,3,4,5,6,7]); 340 assert(a.b == 0); 341 assert(a.c == 7); 342 assert(a.d == [1,2,3,4,5,6,7]); 343 344 a.a = 44; 345 a.c = 3; 346 a.d = [5,4,3]; 347 348 ubyte[] saved; 349 350 a.saveTo(saved); 351 352 A b; 353 b.loadFrom(saved); 354 355 assert(a == b); 356 }