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