1 module boilerplate.util;
2 
3 import std.meta;
4 import std.range : iota;
5 import std.traits;
6 
7 enum needToDup(T) = isArray!(T) && !DeepConst!(T);
8 
9 enum DeepConst(T) = __traits(compiles, (const T x) { T y = x; });
10 
11 @("needToDup correctly handles common types")
12 @nogc nothrow pure @safe unittest
13 {
14     int integerField;
15     int[] integerArrayField;
16 
17     static assert(!needToDup!(typeof(integerField)));
18     static assert(needToDup!(typeof(integerArrayField)));
19 }
20 
21 @("needToDup correctly handles const types")
22 @nogc nothrow pure @safe unittest
23 {
24     const(int)[] constIntegerArrayField;
25     string stringField;
26 
27     static assert(!needToDup!(typeof(constIntegerArrayField)));
28     static assert(!needToDup!(typeof(stringField)));
29 }
30 
31 @("doesn't add write-only properties to NormalMembers")
32 unittest
33 {
34     struct Test
35     {
36         @property void foo(int i) { }
37         mixin GenNormalMemberTuple;
38         static assert(is(NormalMemberTuple == AliasSeq!()),
39             "write-only properties should not appear in NormalMembers because they have no type"
40         );
41     }
42 }
43 
44 @("doesn't add read properties to NormalMembers if includeFunctions is false")
45 unittest
46 {
47     struct Test
48     {
49         @property int foo() { return 0; }
50         int bar() { return 0; }
51         mixin GenNormalMemberTuple;
52         static assert(is(NormalMemberTuple == AliasSeq!()),
53             "read properties should not appear in NormalMembers if includeFunctions is false"
54         );
55     }
56 }
57 
58 /**
59  * Generate AliasSeq of "normal" members - ie. no templates, no alias, no enum, only fields
60  * (and functions if includeFunctions is true).
61  */
62 mixin template GenNormalMemberTuple(bool includeFunctions = false)
63 {
64     import boilerplate.util : GenNormalMembersCheck, GenNormalMembersImpl;
65     import std.meta : AliasSeq;
66 
67     mixin(`alias NormalMemberTuple = ` ~ GenNormalMembersImpl([__traits(derivedMembers, typeof(this))],
68         mixin(GenNormalMembersCheck([__traits(derivedMembers, typeof(this))], includeFunctions))) ~ `;`);
69 }
70 
71 string GenNormalMembersCheck(string[] members, bool includeFunctions)
72 {
73     import std.format : format;
74     import std.string : join;
75 
76     string code = "[";
77     foreach (i, member; members)
78     {
79         if (i > 0)
80         {
81             code ~= ", "; // don't .map.join because this is compile performance critical code
82         }
83 
84         if (member != "this")
85         {
86             string check = `__traits(compiles, &typeof(this).init.` ~ member ~ `)`
87                 ~ ` && __traits(compiles, typeof(typeof(this).init.` ~ member ~ `))`;
88 
89             if (!includeFunctions)
90             {
91                 check ~= ` && !is(typeof(typeof(this).` ~ member ~ `) == function)`
92                     ~ ` && !is(typeof(&typeof(this).init.` ~ member ~ `) == delegate)`;
93             }
94 
95             code ~= check;
96         }
97         else
98         {
99             code ~= `false`;
100         }
101     }
102     code ~= "]";
103 
104     return code;
105 }
106 
107 string GenNormalMembersImpl(string[] members, bool[] compiles)
108 {
109     import std.string : join;
110 
111     string[] names;
112 
113     foreach (i, member; members)
114     {
115         if (member != "this" && compiles[i])
116         {
117             names ~= "\"" ~ member ~ "\"";
118         }
119     }
120 
121     return "AliasSeq!(" ~ names.join(", ") ~ ")";
122 }
123 
124 template getOverloadLike(Aggregate, string Name, Type)
125 {
126     alias Overloads = AliasSeq!(__traits(getOverloads, Aggregate, Name));
127     enum FunctionMatchesType(alias Fun) = is(typeof(Fun) == Type);
128     alias MatchingOverloads = Filter!(FunctionMatchesType, Overloads);
129 
130     static assert(MatchingOverloads.length == 1);
131 
132     alias getOverloadLike = MatchingOverloads[0];
133 }
134 
135 template udaIndex(alias attr, attributes...)
136 {
137     enum udaIndex = helper();
138 
139     ptrdiff_t helper()
140     {
141         if (!__ctfe)
142         {
143             return 0;
144         }
145         static if (attributes.length)
146         {
147             foreach (i, attrib; attributes)
148             {
149                 enum lastAttrib = i == attributes.length - 1;
150 
151                 static if (__traits(isTemplate, attr))
152                 {
153                     static if (__traits(isSame, attrib, attr))
154                     {
155                         return i;
156                     }
157                     else static if (is(attrib: attr!Args, Args...))
158                     {
159                         return i;
160                     }
161                     else static if (lastAttrib)
162                     {
163                         return -1;
164                     }
165                 }
166                 else static if (__traits(compiles, is(typeof(attrib) == typeof(attr)) && attrib == attr))
167                 {
168                     static if (is(typeof(attrib) == typeof(attr)) && attrib == attr)
169                     {
170                         return i;
171                     }
172                     else static if (lastAttrib)
173                     {
174                         return -1;
175                     }
176                 }
177                 else static if (__traits(compiles, typeof(attrib)) && __traits(compiles, is(typeof(attrib) == attr)))
178                 {
179                     static if (is(typeof(attrib) == attr))
180                     {
181                         return i;
182                     }
183                     else static if (lastAttrib)
184                     {
185                         return -1;
186                     }
187                 }
188                 else static if (__traits(compiles, is(attrib == attr)))
189                 {
190                     static if (is(attrib == attr))
191                     {
192                         return i;
193                     }
194                     else static if (lastAttrib)
195                     {
196                         return -1;
197                     }
198                 }
199                 else static if (lastAttrib)
200                 {
201                     return -1;
202                 }
203             }
204         }
205         else
206         {
207             return -1;
208         }
209     }
210 }
211 
212 string isStatic(string field)
213 {
214     return `__traits(getOverloads, typeof(this), "` ~ field ~ `").length == 0`
215       ~ ` && __traits(compiles, &this.` ~ field ~ `)`;
216 }
217 
218 string isUnsafe(string field)
219 {
220     return isStatic(field) ~ ` && !__traits(compiles, () @safe { return this.` ~ field ~ `; })`;
221 }
222 
223 // a stable, simple O(n) sort optimal for a small number of sort keys
224 T[] bucketSort(T)(T[] inputArray, size_t delegate(T) rankfn)
225 {
226     import std.algorithm : joiner;
227     import std.range : array;
228 
229     T[][] buckets;
230 
231     foreach (element; inputArray)
232     {
233         auto rank = rankfn(element);
234 
235         if (rank >= buckets.length)
236         {
237             buckets.length = rank + 1;
238         }
239 
240         buckets[rank] ~= element;
241     }
242 
243     return buckets.joiner.array;
244 }
245 
246 void sinkWrite(T)(scope void delegate(const(char)[]) sink, ref bool comma, string fmt, T arg)
247 {
248 	static if (__traits(compiles, { import config.string : toString; }))
249 	{
250 		import config.string : customToString = toString;
251 	}
252 	else
253 	{
254 		void customToString(T)()
255 		if (false)
256 		{
257 		}
258 	}
259 
260     import std.datetime : SysTime;
261     import std.format : formattedWrite;
262     import std.typecons : Nullable;
263 
264     alias PlainT = typeof(cast() arg);
265 
266     enum isNullable = is(PlainT: Nullable!Args, Args...);
267 
268     static if (isNullable)
269     {
270         if (!arg.isNull)
271         {
272             sinkWrite(sink, comma, fmt, arg.get);
273         }
274         return;
275     }
276     else
277     {
278         static if (is(PlainT == SysTime))
279         {
280             if (arg == SysTime.init) // crashes on toString
281             {
282                 return;
283             }
284         }
285 
286         if (comma)
287         {
288             sink(", ");
289         }
290 
291         comma = true;
292 
293         static if (__traits(compiles, customToString(arg, sink)))
294         {
295             struct TypeWrapper
296             {
297                 void toString(scope void delegate(const(char)[]) sink) const
298                 {
299                     customToString(arg, sink);
300                 }
301             }
302             sink.formattedWrite(fmt, TypeWrapper());
303         }
304         else
305         {
306             sink.formattedWrite(fmt, arg);
307         }
308     }
309 }
310 
311 private string quote(string text)
312 {
313     import std.string : replace;
314 
315     return `"` ~ text.replace(`\`, `\\`).replace(`"`, `\"`) ~ `"`;
316 }
317 
318 private string genFormatFunctionImpl(string text)
319 {
320     import std.algorithm : findSplit;
321     import std.exception : enforce;
322     import std.format : format;
323     import std.range : empty;
324     import std.string : join;
325 
326     string[] fragments;
327 
328     string remainder = text;
329 
330     while (true)
331     {
332         auto splitLeft = remainder.findSplit("%(");
333 
334         if (splitLeft[1].empty)
335         {
336             break;
337         }
338 
339         auto splitRight = splitLeft[2].findSplit(")");
340 
341         enforce(!splitRight[1].empty, format!"Closing paren not found in '%s'"(remainder));
342         remainder = splitRight[2];
343 
344         fragments ~= quote(splitLeft[0]);
345         fragments ~= splitRight[0];
346     }
347     fragments ~= quote(remainder);
348 
349     return `string values(T)(T arg)
350     {
351         with (arg)
352         {
353             return ` ~ fragments.join(" ~ ") ~ `;
354         }
355     }`;
356 }
357 
358 public template formatNamed(string text)
359 {
360     mixin(genFormatFunctionImpl(text));
361 }
362 
363 ///
364 @("formatNamed replaces named keys with given values")
365 unittest
366 {
367     import std.typecons : tuple;
368     import unit_threaded.should;
369 
370     formatNamed!("Hello %(second) World %(first)%(second)!")
371         .values(tuple!("first", "second")("3", "5"))
372         .shouldEqual("Hello 5 World 35!");
373 }
374 
375 public T[] reorder(T)(T[] source, size_t[] newOrder)
376 in
377 {
378     import std.algorithm : sort;
379     import std.range : array, iota;
380 
381     // newOrder must be a permutation of source indices
382     assert(newOrder.dup.sort.array == source.length.iota.array);
383 }
384 body
385 {
386     import std.algorithm : map;
387     import std.range : array;
388 
389     return newOrder.map!(i => source[i]).array;
390 }
391 
392 @("reorder returns reordered array")
393 unittest
394 {
395     import unit_threaded.should;
396 
397     [1, 2, 3].reorder([0, 2, 1]).shouldEqual([1, 3, 2]);
398 }
399 
400 // TODO replace with Nullable once pr 19037 is merged
401 public struct Optional(T)
402 {
403     import std.typecons : Nullable;
404 
405     // workaround: types in union are not destructed
406     union DontCallDestructor { SafeUnqual!T t; }
407 
408     // workaround: types in struct are memcpied in move/moveEmplace, bypassing constness
409     struct UseMemcpyMove { DontCallDestructor u; }
410 
411     private UseMemcpyMove value = UseMemcpyMove.init;
412 
413     public bool isNull = true;
414 
415     public this(T value)
416     {
417         this.value = UseMemcpyMove(DontCallDestructor(value));
418         this.isNull = false;
419     }
420 
421     // This method should only be called from Builder.value! Builder fields are semantically write-only.
422     public inout(T) _get() inout
423     in
424     {
425         assert(!this.isNull);
426     }
427     do
428     {
429         return this.value.u.t;
430     }
431 
432     public void opAssign(T value)
433     {
434         import std.algorithm : moveEmplace, move;
435 
436         auto valueCopy = UseMemcpyMove(DontCallDestructor(value));
437 
438         if (this.isNull)
439         {
440             moveEmplace(valueCopy, this.value);
441 
442             this.isNull = false;
443         }
444         else
445         {
446             move(valueCopy, this.value);
447         }
448     }
449 
450     public void opOpAssign(string op, RHS)(RHS rhs)
451     if (__traits(compiles, mixin("T.init " ~ op ~ " RHS.init")))
452     {
453         if (this.isNull)
454         {
455             this = T.init;
456         }
457         mixin("this = this._get " ~ op ~ " rhs;");
458     }
459 
460     static if (is(T: Nullable!Arg, Arg))
461     {
462         public void opAssign(Arg value)
463         {
464             this = T(value);
465         }
466     }
467 
468     static if (is(T == struct) && hasElaborateDestructor!T)
469     {
470         ~this()
471         {
472             if (!this.isNull)
473             {
474                 destroy(this.value.u.t);
475             }
476         }
477     }
478 }
479 
480 ///
481 unittest
482 {
483     Optional!(int[]) intArrayOptional;
484 
485     assert(intArrayOptional.isNull);
486 
487     intArrayOptional ~= 5;
488 
489     assert(!intArrayOptional.isNull);
490     assert(intArrayOptional._get == [5]);
491 
492     intArrayOptional ~= 6;
493 
494     assert(intArrayOptional._get == [5, 6]);
495 }
496 
497 private template SafeUnqual(T)
498 {
499     static if (__traits(compiles, (T t) { Unqual!T ut = t; }))
500     {
501         alias SafeUnqual = Unqual!T;
502     }
503     else
504     {
505         alias SafeUnqual = T;
506     }
507 }
508 
509 public string removeTrailingUnderline(string name)
510 {
511     import std.string : endsWith;
512 
513     return name.endsWith("_") ? name[0 .. $ - 1] : name;
514 }