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