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 }