1 module unit_threaded.randomized.gen;
2 
3 template from(string moduleName) {
4     mixin("import from = " ~ moduleName ~ ";");
5 }
6 
7 
8 /* Return $(D true) if the passed $(D T) is a $(D Gen) struct.
9 
10 A $(D Gen!T) is something that implicitly converts to $(D T), has a method
11 called $(D gen) that is accepting a $(D ref Random).
12 
13 This module already brings Gens for numeric types, strings and ascii strings.
14 
15 If a function needs to be benchmarked that has a parameter of custom type a
16 custom $(D Gen) is required.
17 */
18 template isGen(T)
19 {
20     static if (is(T : Gen!(S), S...))
21         enum isGen = true;
22     else
23         enum isGen = false;
24 }
25 
26 ///
27 unittest
28 {
29     static assert(!isGen!int);
30     static assert(isGen!(Gen!(int, 0, 10)));
31 }
32 
33 private template minimum(T) {
34     import std.traits: isIntegral, isFloatingPoint, isSomeChar;
35     static if(isIntegral!T || isSomeChar!T)
36         enum minimum = T.min;
37     else static if (isFloatingPoint!T)
38         enum mininum = T.min_normal;
39     else
40         enum minimum = T.init;
41 }
42 
43 private template maximum(T) {
44     import std.traits: isNumeric;
45     static if(isNumeric!T)
46         enum maximum = T.max;
47     else
48         enum maximum = T.init;
49 }
50 
51 /** A $(D Gen) type that generates numeric values between the values of the
52 template parameter $(D low) and $(D high).
53 */
54 mixin template GenNumeric(T, T low, T high) {
55 
56     import std.random: Random;
57 
58     static assert(is(typeof(() {
59         T[] res = frontLoaded();
60     })), "GenNumeric needs a function frontLoaded returning " ~ T.stringof ~ "[]");
61 
62     alias Value = T;
63 
64     T value;
65 
66     T gen(ref Random gen)
67     {
68         import std.random: uniform;
69 
70         static assert(low <= high);
71 
72         this.value = _index < frontLoaded.length
73             ? frontLoaded[_index++]
74             : uniform!("[]")(low, high, gen);
75 
76         return this.value;
77     }
78 
79     ref T opCall()
80     {
81         return this.value;
82     }
83 
84     void toString(scope void delegate(const(char)[]) sink) @trusted
85     {
86         import std.format : formattedWrite;
87         import std.traits: isFloatingPoint;
88 
89         static if (isFloatingPoint!T)
90         {
91             static if (low == T.min_normal && high == T.max)
92             {
93                 formattedWrite(sink, "'%s'", this.value);
94             }
95         }
96         else static if (low == T.min && high == T.max)
97         {
98             formattedWrite(sink, "'%s'", this.value);
99         }
100         else
101         {
102             formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value,
103                 low, high);
104         }
105     }
106 
107     alias opCall this;
108 
109 
110     private int _index;
111 }
112 
113 /** A $(D Gen) type that generates numeric values between the values of the
114 template parameter $(D low) and $(D high).
115 */
116 struct Gen(T, T low = minimum!T, T high = maximum!T) if (from!"std.traits".isIntegral!T)
117 {
118     private static T[] frontLoaded() @safe pure nothrow {
119         import std.algorithm: filter;
120         import std.array: array;
121         T[] values = [0, 1, T.min, T.max];
122         return values.filter!(a => a >= low && a <= high).array;
123     }
124 
125     mixin GenNumeric!(T, low, high);
126 }
127 
128 struct Gen(T, T low = 0, T high = 6.022E23) if(from!"std.traits".isFloatingPoint!T) {
129      private static T[] frontLoaded() @safe pure nothrow {
130         import std.algorithm: filter;
131         import std.array: array;
132          T[] values = [0, T.epsilon, T.min_normal, high];
133          return values.filter!(a => a >= low && a <= high).array;
134     }
135 
136     mixin GenNumeric!(T, low, high);
137 }
138 
139 
140 /** A $(D Gen) type that generates ASCII strings with a number of
141 characters that is between template parameter $(D low) and $(D high).
142 
143 If $(D low) and $(D high) are very close together, this might return
144 values that are too short. They should differ by at least three for
145 char strings, one for wstrings, and zero for dstrings.
146 */
147 struct Gen(T, size_t low = 0, size_t high = 32) if (from!"std.traits".isSomeString!T)
148 {
149     static const dchar[] charset;
150     import std.random: Random, uniform;
151 
152     static immutable size_t numCharsInCharSet;
153     alias Value = T;
154 
155     T value;
156     static this()
157     {
158         import std.array : array;
159         import std.uni : unicode;
160         import std.format : format;
161         import std.range : chain, iota;
162         import std.algorithm : map, joiner;
163         import std.conv : to;
164         import std.utf : count;
165 
166         charset = chain(
167                 // \t and \n
168                 iota(0x09, 0x0B),
169                 // \r
170                 iota(0x0D, 0x0E),
171                 // ' ' through '~'; next is DEL
172                 iota(0x20, 0x7F),
173                 // Vulgar fractions, punctuation, letters with accents, Greek
174                 iota(0xA1, 0x377),
175                 // More Greek
176                 iota(0x37A, 0x37F),
177                 iota(0x384, 0x38A),
178                 iota(0x38C, 0x38C),
179                 iota(0x38E, 0x3A1),
180                 // Greek, Cyrillic, a bit of Armenian
181                 iota(0x3A3, 0x52F),
182                 // Armenian
183                 iota(0x531, 0x556),
184                 iota(0x559, 0x55F),
185                 // Arabic
186                 iota(0xFBD3, 0xFD3F),
187                 iota(0xFD50, 0xFD8F),
188                 iota(0xFD92, 0xFDC7),
189                 // Linear B, included because it's a high character set
190                 iota(0x1003C, 0x1003D),
191                 iota(0x1003F, 0x1004D),
192                 iota(0x10050, 0x1005D),
193                 iota(0x10080, 0x100FA),
194                 // Emoji
195                 iota(0x1F300, 0x1F6D4)
196             )
197             .map!(a => cast(dchar)a)
198             .array;
199         numCharsInCharSet = charset.length;
200     }
201 
202     T gen(ref Random gen)
203     {
204         static assert(low <= high);
205         import std.range.primitives : ElementType;
206         import std.array : appender;
207         import std.utf : encode;
208 
209         if(_index < frontLoaded.length) {
210             value = frontLoaded[_index++];
211             return value;
212         }
213 
214         auto app = appender!T();
215         app.reserve(high);
216         size_t numElems = uniform!("[]")(low, high, gen);
217         static if ((ElementType!T).sizeof == 1)
218         {
219             char[4] buf;
220         }
221         else static if ((ElementType!T).sizeof == 2)
222         {
223             wchar[2] buf;
224         }
225         else
226         {
227             dchar[1] buf;
228         }
229 
230         size_t appLength = 0;
231         while (appLength < numElems)
232         {
233             size_t charIndex = uniform!("[)")(0, charset.length, gen);
234             auto len = encode(buf, charset[charIndex]);
235             appLength += len;
236             if (appLength > high) break;
237             app.put(buf[0..len]);
238         }
239 
240         this.value = app.data;
241         return this.value;
242     }
243 
244     ref T opCall()
245     {
246         return this.value;
247     }
248 
249     void toString(scope void delegate(const(char)[]) sink)
250     {
251         import std.format : formattedWrite;
252 
253         static if (low == 0 && high == 32)
254         {
255             formattedWrite(sink, "'%s'", this.value);
256         }
257         else
258         {
259             formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value,
260                            low, high);
261         }
262     }
263 
264     alias opCall this;
265 
266 private:
267 
268     int _index;
269 
270     T[] frontLoaded() @safe pure nothrow const {
271         import std.algorithm: filter;
272         import std.array: array;
273         T[] values = ["", "a", "é"];
274         return values.filter!(a => a.length >= low && a.length <= high).array;
275     }
276 }
277 
278 
279 /// DITTO This random $(D string)s only consisting of ASCII character
280 struct GenASCIIString(size_t low = 1, size_t high = 32)
281 {
282     import std.random: Random;
283 
284     static string charSet;
285     static immutable size_t numCharsInCharSet;
286 
287     string value;
288 
289     static this()
290     {
291         import std.array : array;
292         import std.uni : unicode;
293         import std.format : format;
294         import std.range : chain, iota;
295         import std.algorithm : map, joiner;
296                 import std.conv : to;
297                 import std.utf : byDchar, count;
298 
299         GenASCIIString!(low, high).charSet = to!string(chain(iota(0x21,
300             0x7E).map!(a => to!char(cast(dchar) a)).array));
301 
302         GenASCIIString!(low, high).numCharsInCharSet = count(charSet);
303     }
304 
305     string gen(ref Random gen)
306     {
307         import std.array : appender;
308         import std.random: uniform;
309 
310         if(_index < frontLoaded.length) {
311             value = frontLoaded[_index++];
312             return value;
313         }
314 
315         auto app = appender!string();
316         app.reserve(high);
317         size_t numElems = uniform!("[]")(low, high, gen);
318 
319         for (size_t i = 0; i < numElems; ++i)
320         {
321             size_t toSelect = uniform!("[)")(0, numCharsInCharSet, gen);
322             app.put(charSet[toSelect]);
323         }
324 
325         this.value = app.data;
326         return this.value;
327     }
328 
329     ref string opCall()
330     {
331         return this.value;
332     }
333 
334     void toString(scope void delegate(const(char)[]) sink)
335     {
336         import std.format : formattedWrite;
337 
338         static if (low == 0 && high == 32)
339         {
340             formattedWrite(sink, "'%s'", this.value);
341         }
342         else
343         {
344             formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value,
345                 low, high);
346         }
347     }
348 
349     alias opCall this;
350 
351 private:
352 
353     int _index;
354 
355     string[] frontLoaded() @safe pure nothrow const {
356         return ["", "a"];
357     }
358 }
359 
360 
361 
362 struct Gen(T, size_t low = 1, size_t high = 1024)
363     if(from!"std.range.primitives".isInputRange!T && !from!"std.traits".isSomeString!T)
364 {
365 
366     import std.traits: Unqual, isIntegral, isFloatingPoint;
367     import std.range: ElementType;
368     import std.random: Random;
369 
370     alias Value = T;
371     alias E = Unqual!(ElementType!T);
372 
373     T value;
374     Gen!E elementGen;
375 
376     T gen(ref Random rnd) {
377         value = _index < frontLoaded.length
378             ? frontLoaded[_index++]
379             : genArray(rnd);
380         return value;
381     }
382 
383     alias value this;
384 
385 private:
386 
387     size_t _index;
388      //these values are always generated
389     T[] frontLoaded() @safe nothrow {
390         T[] ret = [[]];
391         return ret;
392     }
393 
394     T genArray(ref Random rnd) {
395         import std.array: appender;
396         import std.random: uniform;
397 
398         immutable length = uniform(low, high, rnd);
399         auto app = appender!T;
400         app.reserve(length);
401         foreach(i; 0 .. length) {
402             app.put(elementGen.gen(rnd));
403         }
404 
405         return app.data;
406     }
407 }
408 
409 static assert(isGen!(Gen!(int[])));
410 
411 
412 struct Gen(T) if(is(T == bool)) {
413     import std.random: Random;
414 
415     bool value;
416     alias value this;
417 
418     bool gen(ref Random rnd) @safe {
419         import std.random: uniform;
420         value = [false, true][uniform(0, 2, rnd)];
421         return value;
422     }
423 }
424 
425 
426 struct Gen(T, T low = minimum!T, T high = maximum!T) if (from!"std.traits".isSomeChar!T)
427 {
428     private static T[] frontLoaded() @safe pure nothrow { return []; }
429     mixin GenNumeric!(T, low, high);
430 }
431 
432 
433 private template AggregateTuple(T...) {
434     import unit_threaded.randomized.random: ParameterToGen;
435     import std.meta: staticMap;
436     alias AggregateTuple = staticMap!(ParameterToGen, T);
437 }
438 
439 struct Gen(T) if(from!"std.traits".isAggregateType!T) {
440 
441     import std.traits: Fields;
442     import std.random: Random;
443 
444     AggregateTuple!(Fields!T) generators;
445 
446     alias Value = T;
447     Value value;
448 
449     T gen(ref Random rnd) @safe {
450         static if(is(T == class))
451             if(value is null)
452                 value = new T;
453 
454         foreach(i, ref g; generators) {
455             value.tupleof[i] = g.gen(rnd);
456         }
457 
458         return value;
459     }
460 
461     inout(T) opCall() inout {
462         return this.value;
463     }
464 
465     alias opCall this;
466 
467 }