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