1 /** 2 Examples: 3 -------------------------------- 4 struct Person { 5 string name; 6 ushort age; 7 string[] nicknames; 8 auto children = appender!(Person[])(); 9 void reset() { 10 name = null; 11 age = 0; 12 nicknames = null; 13 children.clear(); 14 } 15 void validate() { 16 if(name == null) throw new Exception("person is missing the 'name' tag"); 17 if(age == 0) throw new Exception("person is missing the 'age' tag"); 18 } 19 } 20 21 char[] sdl; 22 23 // ...read SDL into char[] sdl 24 25 Person p; 26 parseSdlInto!Person(sdl); 27 ---------------------------------- 28 29 30 31 32 */ 33 module more.sdlreflection; 34 35 import std..string; 36 import std.conv; 37 import std.typecons; 38 import std.traits; 39 import std.range; 40 import std.array; 41 import std.meta : AliasSeq; 42 43 import more.common; 44 import more.sdl; 45 46 version(unittest) 47 { 48 import std.stdio; 49 } 50 51 version = DebugSdlReflection; 52 53 string debugSdlReflection(string object, string member, string message, bool submessage = false) 54 { 55 return format("version(DebugSdlReflection) pragma(msg, \"[DebugSdlReflection] %s%s.%s %s\");", 56 submessage ? " " : "", object, member, message); 57 } 58 59 60 61 /// An enum of sdl reflection options to be used as custom attributes. 62 /// If an option requires arguments pass then in as an argument list 63 enum SdlReflection { 64 /// Indicates the SDL parser should ignore this member 65 ignore, 66 67 /// Prevents SDL from specifying a list type as individual tags. TODO provide example. 68 noSingularTags, 69 70 /// Only allows the list items to appear one at a time in the SDL using 71 /// the singular version of the list member name 72 onlySingularTags, 73 } 74 75 76 /// An SDL Reflection Attribute. 77 /// If singularName is not specified, then it will try to determine the singularName 78 /// by removing the ending 's' from the member name. 79 /// If the member name does not end in an 's' it will assert an error if noSingularTags 80 /// is not true. The singularName is used to determine what a tag-name should be when 81 /// someone wants to list elements of the array one at a time in the SDL. For example, if the 82 /// member is an array of strings called "names", the default singular will be "name". 83 struct SdlSingularName 84 { 85 string name; 86 } 87 88 private template containsFlag(SdlReflection flag, A...) { 89 static if (A.length == 0) { 90 enum containsFlag = false; 91 } else static if ( is( typeof(A[0]) == SdlReflection ) && A[0] == flag ) { 92 enum containsFlag = true; 93 } else { 94 enum containsFlag = containsFlag!(flag, A[1..$]); 95 } 96 } 97 private template singularName(structType, string memberString) { 98 alias singularName = singularNameRecursive!(memberString, __traits(getAttributes, __traits(getMember, structType, memberString))); 99 } 100 private template singularNameRecursive(string memberString, A...) { 101 static if(A.length == 0) { 102 static if(memberString[$-1] == 's') { 103 enum singularNameRecursive = memberString[0..$-1]; 104 } else { 105 enum singularNameRecursive = null; 106 } 107 } else static if( is( typeof(A[0]) == SdlSingularName ) ) { 108 enum singularNameRecursive = A[0].name; 109 } else { 110 enum singularNameRecursive = singularNameRecursive!(memberString, A[1..$]); 111 } 112 } 113 114 115 /+ 116 template isOutRange(R) { 117 enum isOutRange = is( typeof(__traits(getMember, R, "put")) == function ); 118 } 119 +/ 120 template AppenderElementType(T) { 121 static if( !__traits(hasMember, T, "data") ) { 122 alias AppenderElementType = void; 123 } else static if( is( ElementType!(typeof(__traits(getMember, T, "data"))) == void) ) { 124 alias AppenderElementType = void; 125 } else { 126 alias AppenderElementType = Unqual!(ElementType!(typeof(__traits(getMember, T, "data")))); 127 } 128 } 129 130 static assert(is( AppenderElementType!(Appender!(int[])) == int)); 131 static assert(is( AppenderElementType!(Appender!(string[])) == string)); 132 133 static assert(is( Appender!(AppenderElementType!(Appender!(int[]))[]) == Appender!(int[]))); 134 static assert(is( Appender!(AppenderElementType!(Appender!(string[]))[]) == Appender!(string[]))); 135 136 137 void parseSdlInto(T)(ref T obj, string sdl) 138 { 139 Tag tag; 140 tag.preserveSdlText = true; // Make sure that the sdl is not modified because it is a string 141 SdlWalker walker = SdlWalker(&tag, sdl); 142 143 debug writefln("[DEBUG] parseSdlInto: --> (Type=%s)", T.stringof); 144 parseSdlInto(obj, walker, sdl, 0); 145 debug writefln("[DEBUG] parseSdlInto: <--"); 146 } 147 void parseSdlInto(T)(ref T obj, char[] sdl) 148 { 149 Tag tag; 150 SdlWalker walker = SdlWalker(&tag, sdl); 151 152 debug writefln("[DEBUG] parseSdlInto: --> (Type=%s)", T.stringof); 153 parseSdlInto(obj, walker, sdl, 0); 154 debug writefln("[DEBUG] parseSdlInto: <--"); 155 } 156 void parseSdlInto(T)(ref T obj, ref SdlWalker walker, char[] sdl, size_t depth) if( is( T == struct) ) 157 { 158 Tag* tag = walker.tag; 159 160 if(depth > 0) { 161 // 162 // Process attributes and values 163 // 164 implement("object values and attributes"); 165 } 166 167 TAG_LOOP: 168 while(walker.pop(depth)) { 169 170 debug writefln("[DEBUG] parseSdlInto: at depth %s tag '%s'", tag.depth, tag.name); 171 172 foreach(memberIndex, copyOfMember; obj.tupleof) { 173 174 alias typeof(T.tupleof[memberIndex]) memberType; 175 enum memberString = T.tupleof[memberIndex].stringof; 176 177 //writefln("[DEBUG] tag '%s' checking member '%s %s'", tag.name, memberType.stringof, memberString); 178 179 alias AliasSeq!(__traits(getAttributes, T.tupleof[memberIndex])) memberAttributes; 180 alias ElementType!(memberType) memberElementType; 181 enum isAppender = is( memberType == Appender!(AppenderElementType!(memberType)[])); 182 183 static if(memberString == "this") { 184 185 mixin(debugSdlReflection(T.stringof, memberString, "ignored because 'this' is always ignored")); 186 187 } else static if(containsFlag!(SdlReflection.ignore, memberAttributes)) { 188 189 mixin(debugSdlReflection(T.stringof, memberString, "ignored from SdlReflection.ignore")); 190 191 } else static if( is( memberType == function) ) { 192 193 mixin(debugSdlReflection(T.stringof, memberString, "ignored because it is a function")); 194 195 } else static if( isAppender || ( !is( memberElementType == void ) && !isSomeString!(memberType) ) ) { 196 197 198 static if(isAppender) { 199 mixin(debugSdlReflection(T.stringof, memberString, "deserialized as a list, specifically an appender")); 200 201 template addValues(string memberName) { 202 void addValues() { 203 auto elementOffset = __traits(getMember, obj, memberString).data.length; 204 __traits(getMember, obj, memberString).reserve(elementOffset + tag.values.data.length); 205 AppenderElementType!(memberType) deserializedValue; 206 207 foreach(value; tag.values.data) { 208 if(!sdlLiteralToD!(AppenderElementType!(memberType))( value, deserializedValue)) { 209 throw new SdlParseException(tag.line, format("failed to convert '%s' to %s for appender %s.%s", 210 value, memberElementType.stringof, T.stringof, memberString) ); 211 } 212 __traits(getMember, obj, memberString).put(deserializedValue); 213 elementOffset++; 214 } 215 } 216 } 217 218 } else { 219 220 mixin(debugSdlReflection(T.stringof, memberString, "deserialized as a list, specifically an array")); 221 222 template addValues(string memberName) { 223 void addValues() { 224 auto elementOffset = __traits(getMember, obj, memberString).length; 225 226 __traits(getMember, obj, memberString).length += tag.values.data.length; 227 foreach(value; tag.values.data) { 228 if(!sdlLiteralToD!(memberElementType)( value, __traits(getMember, obj, memberString)[elementOffset] ) ) { 229 throw new SdlParseException(tag.line, format("failed to convert '%s' to %s for array member %s.%s", 230 value, memberElementType.stringof, T.stringof, memberString) ); 231 } 232 elementOffset++; 233 } 234 } 235 } 236 237 } 238 239 static if(containsFlag!(SdlReflection.onlySingularTags, memberAttributes)) { 240 mixin(debugSdlReflection(T.stringof, memberString, format("onlySingularTags so will not handle tags named '%s'", memberString), true)); 241 } else { 242 243 if(tag.name == memberString) { 244 245 tag.enforceNoAttributes(); 246 247 // 248 // Add tag values to the array 249 // 250 static if( !is( ElementType!(memberElementType) == void ) && !isSomeString!(memberElementType) ) { 251 252 implement("list of arrays"); 253 254 } else static if( isAssociativeArray!(memberElementType)) { 255 256 implement("list of assoc-arrays"); 257 258 } else static if( is ( isNested!( memberType ) ) ) { 259 260 implement("list of functions/structs/classes"); 261 262 } else { 263 264 if(tag.values.data.length > 0) { 265 addValues!(memberString); 266 } 267 268 } 269 270 271 if(tag.hasOpenBrace) { 272 273 size_t arrayDepth = tag.depth + 1; 274 while(walker.pop(arrayDepth)) { 275 276 tag.enforceNoAttributes(); 277 // Check if the tag can be converted to an array element 278 if(!tag.isAnonymous) { 279 throw new SdlParseException(tag.line, format("the child elements of array member %s.%s can only use anonymous tags, but found a tag with name '%s'", 280 T.stringof, memberString, tag.name)); 281 } 282 283 284 static if( !isSomeString!(memberElementType) && isArray!(memberElementType)) { 285 286 implement("using children for list of arrays"); 287 288 } else static if( isAssociativeArray!(memberElementType)) { 289 290 implement("using children for list of assoc-arrays"); 291 292 } else static if( is ( isNested!(memberType) ) ) { 293 294 implement("using children for list of functions/structs/classes"); 295 296 } else { 297 298 if(tag.values.data.length > 0) { 299 300 addValues!(memberString); 301 302 } 303 304 } 305 306 307 } 308 309 } 310 311 continue TAG_LOOP; 312 } 313 } 314 315 static if(containsFlag!(SdlReflection.noSingularTags, memberAttributes) ) { 316 mixin(debugSdlReflection(T.stringof, memberString, "does not handle singular tags", true)); 317 } else { 318 static if(singularName!(T, memberString) is null) { 319 static assert(0, format("Could not determine the singular name for %s.%s because it does not end with an 's'. Use @(SdlSingularName(\"name\") to specify one.", 320 T.stringof, memberString)); 321 } 322 323 mixin(debugSdlReflection(T.stringof, memberString, format("handles singular tags named '%s'", singularName!(T, memberString)), true)); 324 325 326 if(tag.name == singularName!(T, memberString)) { 327 328 tag.enforceNoAttributes(); 329 tag.enforceNoChildren(); 330 331 static if( isArray!(memberElementType) && 332 !isSomeString!(memberElementType) ) { 333 334 implement("singular list of arrays"); 335 336 } else static if( isAssociativeArray!(memberElementType)) { 337 338 implement("singular list of assoc-arrays"); 339 340 } else static if( is ( isNested!(memberType) ) ) { 341 342 implement("singular list of functions/structs/classes"); 343 344 } else { 345 346 static if ( isAppender ) { 347 AppenderElementType!(memberType) value; 348 } else { 349 memberElementType value; 350 } 351 352 tag.getOneValue(value); 353 __traits(getMember, obj, memberString) ~= value; 354 debug writefln("[DEBUG] parseSdlInto: %s.%s was appended with '%s'", T.stringof, memberString, __traits(getMember, obj, memberString)); 355 continue TAG_LOOP; 356 357 } 358 359 } 360 361 } 362 363 // END OF HANDLING OUTPUT RANGES 364 365 } else static if( isAssociativeArray!(memberType)) { 366 367 mixin(debugSdlReflection(T.stringof, memberString, "deserialized as an associative array")); 368 implement("associative arrays"); 369 370 } else static if( is (isNested!(memberType))) { 371 372 mixin(debugSdlReflection(T.stringof, memberString, "deserialized as an object")); 373 implement("sub function/struct/class"); 374 375 } else { 376 377 mixin(debugSdlReflection(T.stringof, memberString, "deserialized as a single value")); 378 379 if(tag.name == memberString) { 380 tag.enforceNoAttributes(); 381 tag.enforceNoChildren(); 382 tag.getOneValue(__traits(getMember, obj, memberString)); 383 debug writefln("[DEBUG] parseSdlInto: set %s.%s to '%s'", T.stringof, memberString, __traits(getMember, obj, memberString)); 384 continue TAG_LOOP; 385 } 386 387 388 } 389 390 } 391 392 tag.throwIsUnknown(); 393 } 394 395 } 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 /+ 416 string enumerateTraits(string fmt, string value) { 417 string code = ""; 418 code != "if( format(fmt, format("__traits( 419 } 420 +/ 421 422 423 unittest 424 { 425 mixin(scopedTest!("SdlReflection")); 426 427 void testParseType(T)(bool copySdl, string sdlText, T expectedType) 428 { 429 T parsedType; 430 431 try { 432 433 parseSdlInto!T(parsedType, setupSdlText(sdlText, copySdl)); 434 435 } catch(Exception e) { 436 writefln("the following sdl threw an unexpected exception: %s", sdlText); 437 writeln(e); 438 assert(0); 439 } 440 441 stdout.flush(); 442 if(expectedType != parsedType) { 443 writefln("Expected: %s", expectedType); 444 writefln(" but got: %s", parsedType); 445 assert(0); 446 } 447 448 } 449 450 struct TypeWithAppender 451 { 452 auto values = appender!(int[])(); 453 this(int[] values...) { 454 foreach(value; values) { 455 this.values.put(value); 456 } 457 } 458 } 459 testParseType(false,` 460 values 1 2 3 461 values { 462 4 5 6 7 463 8 9 10 11 464 } 465 value 12 466 value 13 467 `, TypeWithAppender(1,2,3,4,5,6,7,8,9,10,11,12,13)); 468 469 struct PackageInfo 470 { 471 @(SdlReflection.ignore) 472 string ignoreThisMember; 473 474 string name; 475 private string privateName; 476 uint packageID; 477 478 uint[] randomUints; 479 480 string[] authors; 481 482 @(SdlReflection.noSingularTags) 483 string[] sourceFiles; 484 485 486 @(SdlSingularName("dependency")) 487 string[string][] dependencies; 488 489 @(SdlReflection.onlySingularTags) 490 @(SdlSingularName("a-float")) 491 float[] myFloats; 492 493 494 495 void reset() { 496 name = null; 497 packageID = 0; 498 randomUints = null; 499 authors = null; 500 sourceFiles = null; 501 dependencies = null; 502 } 503 } 504 505 506 507 /+ 508 testParseType(false, ` 509 510 `, Appender!(int[])()); 511 +/ 512 513 testParseType(false, ` 514 name "vibe-d" 515 privateName "secret" 516 packageID 1023 517 randomUints 1 2 3 4 518 `, PackageInfo(null, "vibe-d", "secret", 1023, [1,2,3,4])); 519 520 testParseType(false, ` 521 randomUint 1 522 randomUints 2 3 4 5 523 randomUints { 524 99 8291 525 83992 526 } 527 randomUint 9983`, PackageInfo(null, null, null, 0, [1,2,3,4,5,99,8291,83992,9983])); 528 529 testParseType(false, ` 530 authors "Jimbo" 531 authors "Spencer" "Dylan" 532 authors { 533 "Jay" 534 "Amy" "Steven" 535 } 536 author "SingleAuthor" 537 `, PackageInfo(null, null, null, 0, null, ["Jimbo", "Spencer", "Dylan", "Jay", "Amy", "Steven", "SingleAuthor"])); 538 539 540 testParseType(false,` 541 a-float 0 542 // a-float 1 2 # should be an error 543 // a-float # should be an error 544 // myFloats 1 2 3 # should be an error 545 a-float 2.3829 546 a-float -192 547 `, PackageInfo(null, null, null, 0, null, null, null, null, [0, 2.3829, -192])); 548 549 550 551 552 } 553