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