root/trunk/src/semitwist/util/mixins.d

Revision 226, 14.1 kB (checked in by Abscissa, 1 year ago)

util.mixins: Fixed: For getterLazy, 'writeAccess' should apply to the _*_gen() function, too.

  • Property svn:eol-style set to native
Line 
1 // SemiTwist Library
2 // Written in the D programming language.
3
4 module semitwist.util.mixins;
5
6 import std.traits;
7 import std.stdio;
8 import std.conv;
9
10 import semitwist.util.all;
11
12 /++
13 Useful in constructors for DRY.
14
15 Usage:
16 ----
17 mixin(initMember("someVar"));
18 mixin(initMember("a", "b", "c"));
19 ----
20
21 Turns Into:
22 ----
23 this.someVar = someVar;
24 this.a = a;
25 this.b = b;
26 this.c = c;
27 ----
28 +/
29 string initMember(string[] vars...)
30 {
31     return initMemberX("this.%s = %s", vars);
32 }
33
34 /++
35 Generic version of initMember.
36
37 Usage:
38 ----
39 mixin(initMemberX("foo1.%s = foo2.%s", "someVar"));
40 mixin(initMemberX("this._%s = foo.%s", "a", "b", "c"));
41 ----
42
43 Turns Into:
44 ----
45 foo1.someVar = foo2.someVar;
46 this._a = foo.a;
47 this._b = foo.b;
48 this._c = foo.c;
49 ----
50 +/
51 string initMemberX(string str, string[] vars...)
52 {
53     //enum string initMemberX = ctfe_subMapJoin!string(str~";\n", "%s", /+templateArgsToStrings!(+/vars/+)+/);
54     return ctfe_subMapJoin!string(str~";\n", "%s", vars);
55 }
56
57 /++
58 Useful in copy constructors for DRY.
59
60 Usage:
61 ----
62 class myClass
63 {
64     // Declarations of 'someVar', 'a1', 'b', and 'c' here.
65     this(myClass copyOf)
66     {
67         mixin(initMemberFrom("copyOf", "someVar"));
68         mixin(initMemberFrom("copyOf", "a1", "b", "c"));
69     }
70 }
71 ----
72
73 Turns Into:
74 ----
75 class myClass
76 {
77     // Declarations of 'someVar', 'a1', 'b', and 'c' here.
78     this(myClass copyOf)
79     {
80         this.someVar = copyOf.someVar;
81         this.a1 = copyOf.a1;
82         this.b = copyOf.b;
83         this.c = copyOf.c;
84     }
85 }
86 ----
87 +/
88 string initMemberFrom(string from, string[] vars...)
89 {
90     return initMemberX("this.%s = "~from~".%s", vars);
91 }
92
93 string initMemberTo(string to, string[] vars...)
94 {
95     return initMemberX(to~".%s = %s", vars);
96 }
97
98 string initFrom(string from, string[] vars...)
99 {
100     return initMemberX("%s = "~from~".%s", vars);
101 }
102
103 /++
104 A DRY way to display an expression and its value to Stdout.
105
106 Usage:
107
108 ----
109 int myVar=100;
110 mixin(traceVal!("myVar"));
111 mixin(traceVal!("   myVar-1 "));
112 mixin(traceVal!("min(4,7)", "max(4,7)")); // from tango.math.Math
113 ----
114
115 Turns Into:
116
117 ----
118 int myVar=100;
119 writefln("%s: %s", "myVar", myVar);
120 writefln("%s: %s", "   myVar-1 ",   myVar-1 );
121 writefln("%s: %s", "min(4,7)", min(4,7));
122 writefln("%s: %s", "max(4,7)", max(4,7));
123 ----
124
125 Outputs:
126
127 ----
128 myVar: 100
129    myVar-1 : 99
130 min(4,7): 4
131 max(4,7): 7
132 ----
133 +/
134
135 //TODO: Add ability to specify format (binary, hex, etc)
136 //TODO: Make nameLength work by using format (at runtime)
137 //      on data passed to writefln
138 //      (ie, align name/value)
139 //TODO: Messes up on "ctfe_repeat_test_日本語3"
140 template traceVal(values...)
141 {
142     enum traceVal = traceVal!(false, values);
143 }
144
145 template traceVal(bool useNewline, values...)
146 {
147     static if(values.length == 0)
148         enum traceVal = "";
149     else
150     {
151         enum traceVal =
152             "writefln(\"%s:"~(useNewline?"\\n":" ")~"%s\", "~values[0].stringof~", "~unescapeDDQS(values[0].stringof)~");"
153             ~ traceVal!(useNewline, values[1..$]);
154     }
155 }
156
157 /++
158 Easy way to output file/line. Useful for debugging.
159
160 Usage:
161
162 ----
163 mixin(trace!());
164 funcSuspectedOfCrashing1_notTheRealCause()
165 mixin(trace!("--EASY TO VISUALLY GREP--"));
166 funcSuspectedOfCrashing2_isTheRealCause()
167 mixin(trace!());
168 ----
169
170 Turns Into:
171
172 ----
173 writefln("%s(%s): trace", __FILE__, __LINE__); stdout.flush();
174 funcSuspectedOfCrashing1_notTheRealCause()
175 writefln("%s%s(%s): trace", "--EASY TO VISUALLY GREP--", __FILE__, __LINE__); stdout.flush();
176 funcSuspectedOfCrashing2_isTheRealCause()
177 writefln("%s(%s): trace", __FILE__, __LINE__); stdout.flush();
178 ----
179
180 Example Output:
181
182 ----
183 C:\path\file.d(1): trace
184 --EASY TO VISUALLY GREP--: C:\path\file.d(3): trace
185 {segfault!}
186 ----
187 +/
188 template trace(string prefix="")
189 {
190     static if(prefix=="")
191         enum trace =
192             `writefln("%s(%s): trace", __FILE__, __LINE__); stdout.flush();`;
193     else
194         enum trace =
195             `writefln("%s: %s(%s): trace", `~prefix.stringof~`, __FILE__, __LINE__); stdout.flush();`;
196 }
197
198 /++
199 Wraps a string mixin and displays the string at compile-time. Useful for debugging.
200
201 Usage:
202
203 ----
204 template defineFloat(string name)
205 { enum defineFloat = "float "~name~";"; }
206 string defineInt(string name, string value)
207 { return "int "~name~"="~value";"; }
208
209 mixin(traceMixin!("defineFloat!", `"myFloat"`));
210 mixin(traceMixin!("defineInt!", `"myInt", 5`));
211 ----
212
213 Turns Into:
214
215 ----
216 template defineFloat(string name)
217 { enum defineFloat = "float "~name~";"; }
218 string defineInt(string name, string value)
219 { return "int "~name~"="~value";"; }
220
221 float myFloat;
222 pragma(msg, "defineFloat:\n float myFloat;");
223 int myInt=5;
224 pragma(msg, "defineInt:\n int myInt=5;");
225 ----
226
227 Compiler Output:
228
229 ----
230 defineFloat:
231 float myFloat;
232 defineInt:
233 int myInt=5;
234 ----
235 +/
236
237 template traceMixin(string name, string args)
238 {
239     enum traceMixin =
240         `pragma(msg, "` ~ name ~ `: \n"~`~name~`(`~args~`));`~"\n"~
241         "mixin("~name~"("~args~"));\n";
242 }
243
244 /++
245 Compile-time version of traceVal.
246
247 Only works for string values right now.
248
249 Usage:
250
251 ----
252 enum fooStr = "Hi";
253 enum fooStr2 = "Hi2";
254 mixin(traceValCT!("fooStr", "fooStr2"));
255 mixin(traceValCT!(`fooStr~" Joe"`));
256
257 template fooTmpl
258 {
259     enum fooTempl = "Hello World";
260     mixin(traceValCT!(true, "fooTempl"));
261 }
262 ----
263
264 Turns Into:
265
266 ----
267 enum fooStr = "Hi";
268 enum fooStr2 = "Hi2";
269 pragma(msg, "fooStr: " ~ (fooStr));
270 pragma(msg, "fooStr2: " ~ (fooStr2));
271 pragma(msg, "fooStr~\" Joe\""~": " ~ (fooStr~" Joe"));
272
273 template fooTmpl
274 {
275     enum fooTempl = "Hello World";
276     pragma(msg, "fooTempl:\n" ~ (fooTempl));
277 }
278 ----
279
280 Compiler Output:
281
282 ----
283 fooStr: Hi
284 fooStr2: Hi2
285 fooStr~" Joe": Hi Joe
286 fooTempl:
287 Hello World
288 ----
289 +/
290
291 template traceValCT(values...)
292 {
293     enum traceValCT = traceValCT!(false, values);
294 }
295
296 template traceValCT(bool useNewline, values...)
297 {
298     static if(values.length == 0)
299     {
300         enum traceValCT = "";
301     }
302     else
303     {
304         enum traceValCT =
305             "pragma(msg, "~escapeDDQS(values[0])~"~\":"~(useNewline? "\\n":" ")~"\" ~ ("~values[0]~"));\n"~
306             traceValCT!(useNewline, values[1..$]);
307
308         //pragma(msg, "traceValCT: " ~ traceValCT);
309     }
310 }
311
312 /// Part of a workaround for DMD Issues #5029 and #5030
313 string fixAATypeName(string str)
314 {
315     enum prefix = "AssociativeArray!(";
316     if(str.length > prefix.length && str[0..prefix.length] == prefix)
317     {
318         auto strippedStr = str[prefix.length..$-1];
319         int nestLevel=0;
320         size_t i;
321         bool done=false;
322         for(i=0; !done && i < strippedStr.length; i++)
323         {
324             switch(strippedStr[i])
325             {
326             case '(': nestLevel++; break;
327             case ')': nestLevel--; break;
328             case ',':
329                 if(nestLevel==0)
330                     done=true;
331                 break;
332             default: break;
333             }
334         }
335         i--;
336         auto typeKey = strippedStr[0..i];
337         auto typeVal = strippedStr[i+1..$];
338        
339         return typeVal~"["~typeKey~"]";
340     }
341     else
342         return str;
343 }
344
345 /++
346 Useful in class/struct declarations for DRY.
347
348 Generates a public getter, private setter, and a hidden private var.
349
350 Usage:
351
352 ----
353 mixin(getter!(int, "myVar"));
354 mixin(getter!("protected", float, "someFloat", 2.5));
355 mixin(getter!(string, "str"));
356 ----
357
358 Turns Into:
359
360 ----
361 private int _myVar;
362 @property private int myVar(int _NEW_VAL_)
363 {
364     _myVar = _NEW_VAL_;
365     return _myVar;
366 }
367 @property public int myVar()
368 {
369     return _myVar;
370 }
371
372 protected float _someFloat = 2.5;
373 @property protected float someFloat(float _NEW_VAL_)
374 {
375     _someFloat = _NEW_VAL_;
376     return _someFloat;
377 }
378 @property public float someFloat()
379 {
380     return _someFloat;
381 }
382
383 private string _str;
384 @property private string str(string _NEW_VAL_)
385 {
386     _str = _NEW_VAL_;
387     return _str;
388 }
389 @property public string str()
390 {
391     return _str;
392 }
393 ----
394 +/
395 template getter(varType, string name, varType initialValue=varType.init)
396 {
397     static if(is(varType.init))
398         enum getter = getter!("private", varType, name, initialValue);
399     else
400         enum getter = getter!("private", varType, name);
401 }
402
403 template getter(string writeAccess, varType, string name, varType initialValue=varType.init)
404 {
405     static if(is(varType.init))
406     {
407         enum getter =
408             writeAccess~" "~fixAATypeName(varType.stringof)~" _"~name~(initialValue.stringof == varType.init.stringof ? "" : "=" ~ initialValue.stringof)~";\n"~
409             "@property "~writeAccess~" "~fixAATypeName(varType.stringof)~" "~name~"("~fixAATypeName(varType.stringof)~" _NEW_VAL_) {_"~name~"=_NEW_VAL_;return _"~name~";}\n"~
410             "@property public "~fixAATypeName(varType.stringof)~" "~name~"() {return _"~name~";}\n";
411     }
412     else
413     {
414         enum getter =
415             writeAccess~" "~fixAATypeName(varType.stringof)~" _"~name~";\n"~
416             "@property "~writeAccess~" "~fixAATypeName(varType.stringof)~" "~name~"("~fixAATypeName(varType.stringof)~" _NEW_VAL_) {_"~name~"=_NEW_VAL_;return _"~name~";}\n"~
417             "@property public "~fixAATypeName(varType.stringof)~" "~name~"() {return _"~name~";}\n";
418     }
419 }
420
421 /++
422 Similar to "getter", but for values that are to be lazily generated and cached.
423 This is useful for values that are complex to generate, not always used, and
424 either never or infrequently change.
425
426 The first time the getter is called, the generator function you have provided
427 is run, and it's return value is cached and returned. On subsequent calls to
428 the getter, the cached value is returned without the generator function being
429 called. The cache can be cleared, thus forcing the value to be re-generated
430 upon the next getter call, by setting "_myVarName_cached" to false.
431
432 Example use-case: If you have a property created by getter() and want
433 to change the "get" from a trivial "return _blah" to a more involved function,
434 you will most likely just simply switch from getter to getterLazy.
435
436 Additional Info:
437
438 If you don't want the value to ever be cached, just set "_myVarName_cached"
439 to false within your provided generator function.
440
441 Usage:
442
443 ----
444 mixin(getterLazy!(int, "myVar", `
445     // Ordinarily, this function would be more complex
446     return 7;
447 `));
448
449 mixin(getterLazy!("protected", int, "myVar2", `return 7;`));
450
451 mixin(getterLazy!(string, "str"));
452 private string _str_gen()
453 {
454     return "Hello";
455 }
456 ----
457
458 Turns Into:
459
460 ----
461 private int _myVar;
462 private bool _myVar_cached = false;
463 @property public int myVar() {
464     if(!_myVar_cached) {
465         _myVar_cached = true;
466         _myVar = _myVar_gen();
467     }
468     return _myVar;
469 }
470 private int _myVar_gen()
471 {
472     // Ordinarily, this function would be more complex
473     return 7;
474 }
475
476 protected int _myVar2;
477 protected bool _myVar2_cached = false;
478 @property public int myVar2() {
479     if(!_myVar2_cached) {
480         _myVar2_cached = true;
481         _myVar2 = _myVar2_gen();
482     }
483     return _myVar2;
484 }
485 protected int _myVar2_gen()
486 {
487     return 7;
488 }
489
490 private string _str;
491 private bool _str_cached = false;
492 @property public string str() {
493     if(!_str_cached) {
494         _str_cached = true;
495         _str = customGenFunc();
496     }
497     return _str;
498 }
499 private string customGenFunc()
500 {
501     return "Hello";
502 }
503
504 ----
505 +/
506 //TODO? Merge with getter if reasonably possible
507 template getterLazy(varType, string name, string genFunc="")
508 {
509     enum getterLazy = getterLazy!("private", varType, name, genFunc);
510 }
511
512 template getterLazy(string writeAccess, varType, string name, string genFunc="")
513 {
514     enum getterLazy =
515         "\n"~
516         ((genFunc=="")?
517             "static if(!is(typeof(_"~name~"_gen)==function))\n"~
518             `   static assert(false, "'getterLazy!(`~fixAATypeName(varType.stringof)~`, \"`~name~`\")' requires function '`~fixAATypeName(varType.stringof)~` _`~name~`_gen()' to be defined");`~"\n"
519
520             // Blocked by DMD Bug #2885
521             //"static if(!is(ReturnTypeOf!(_"~name~"_gen):"~varType.stringof~"))\n"~
522             //` static assert(false, "'getterLazy!(`~varType.stringof~`, \"`~name~`\")' requires function '_`~name~`_gen' to return type '`~varType.stringof~`' (or a compatible type), not type '"~ReturnTypeOf!(_`~name~`_gen).stringof~"'");`~"\n"~
523
524             // Forward reference issues prevent this too
525             //"static if(!ParameterTupleOf!(_line_gen).length==0)\n"~
526             //` static assert(false, "'getterLazy!(`~varType.stringof~`, \"`~name~`\")' requires an overload of function '_`~name~`_gen' that takes no arguments");`~"\n"~
527         :"")~
528
529         writeAccess~" "~varType.stringof~" _"~name~";\n"~
530         writeAccess~" bool _"~name~"_cached = false;\n"~
531         "@property public "~varType.stringof~" "~name~"() {\n"~
532         "   if(!_"~name~"_cached) {\n"~
533         "       _"~name~"_cached = true;\n"~
534         "       _"~name~" = _"~name~"_gen();\n"~
535         "   }\n"~
536         "   return _"~name~";\n"~
537         "}\n"~
538         ((genFunc=="")?"":
539             writeAccess~" "~varType.stringof~" _"~name~"_gen()\n"~
540             "{\n"~
541             genFunc~
542             "}\n"
543         );
544 }
545
546 /++
547 OBSOLETE IN D2
548
549 Inserts a compile-time check that ensures a given type is a character type.
550 (ie, char, wchar, or dchar)
551
552 Usage:
553
554 ----
555 void funcForStringsOnly(T)(T[] str)
556 {
557     //Note, the second param is optional
558     mixin(ensureCharType!("T", "funcForStringsOnly"));
559     //Do stuff
560     return str;
561 }
562 funcForStringsOnly("hello"); // Ok
563 funcForStringsOnly([cast(int)1,2,3]); // Compile error
564 ----
565
566 Turns Into:
567
568 ----
569 void funcForStringsOnly(T)(T[] str)
570 {
571     static assert(
572         is(T==char) || is(T==wchar) || is(T==dchar),
573         "From 'funcForStringsOnly': 'T' must be char, wchar or dchar, not '"~T.stringof~"'"
574     );`;
575     //Do stuff
576     return str;
577 }
578 funcForStringsOnly("hello"); // Ok
579 funcForStringsOnly([cast(int)1,2,3]); // Compile error
580 ----
581
582 Compiler Output:
583
584 ----
585 Error: static assert  "From 'funcForStringsOnly': 'T' must be char, wchar or dchar, not 'int'"
586 ----
587 +/
588
589 /+template ensureCharType(string nameOfT, string nameOfCaller="")
590 {
591     enum ensureCharType =
592         `static assert(`~"\n"~
593         `   is(`~nameOfT~`==char) || is(`~nameOfT~`==wchar) || is(`~nameOfT~`==dchar),`~"\n"~
594         `   "`~(nameOfCaller==""?"":"From '"~nameOfCaller~"': ")~`'`~nameOfT~`' must be char, wchar or dchar, not '"~`~nameOfT~`.stringof~"'"`~"\n"~
595         `);`;
596 }+/
597
598 //TODO: Document genEnum
599 public string genEnum(string name, string[] values)
600 {
601     return
602         "enum "~name~" {"~values.ctfe_join(", ")~"}\n"~
603         "enum uint "~name~"_length = "~to!string(values.length)~";\n"~
604         _genEnumToString(name, values)~
605         _genStringToEnum(name, values);
606 }
607
608 // The function this generates could probably be improved.
609 public string _genEnumToString(string enumName, string[] enumValues)
610 {
611     string ret = "";
612    
613     foreach(string enumValue; enumValues)
614         ret ~= "    if(value=="~enumName~"."~enumValue~") return \""~enumValue~"\";\n";
615    
616     ret =
617         "string enum"~enumName~"ToString("~enumName~" value)\n"~
618         "{\n"~
619         ret~
620         `    throw new Exception("Internal Error: Unhandled value in enum`~enumName~`ToString");`~"\n"~
621         "}\n";
622    
623     return ret;
624 }
625
626 // The function this generates could probably be improved.
627 public string _genStringToEnum(string enumName, string[] enumValues)
628 {
629     string ret = "";
630    
631     foreach(string enumValue; enumValues)
632         ret ~= "    if(value==\""~enumValue~"\") return "~enumName~"."~enumValue~";\n";
633    
634     ret =
635         enumName~" stringToEnum"~enumName~"(string value)\n"~
636         "{\n"~
637         ret~
638         `    throw new Exception("'"~value~"' is not a valid value for '`~enumName~`'");`~"\n"~
639         "}\n";
640    
641     return ret;
642 }
Note: See TracBrowser for help on using the browser.