1 /**
2     Some additional generic types.
3 
4     Copyright: © 2019 Arne Ludwig <arne.ludwig@posteo.de>
5     License: Subject to the terms of the MIT license, as written in the
6              included LICENSE file.
7     Authors: Arne Ludwig <arne.ludwig@posteo.de>
8 */
9 module dalicious.typecons;
10 
11 import std.traits;
12 
13 
14 /// A statically allocated array with up to `maxElements` elements.
15 struct BoundedArray(E, size_t maxElements_)
16 {
17     import std.meta : allSatisfy;
18 
19     private static enum isBaseType(T) = is(T : E);
20 
21 
22     /// An alias to the element type.
23     static alias ElementType = E;
24 
25 
26     /// The maximum number of elements this bounded array can hold.
27     static enum maxElements = maxElements_;
28 
29 
30     private E[maxElements] _elements;
31     private size_t _length;
32 
33 
34     this(E[] values) @nogc
35     {
36         this._length = values.length;
37         this._elements[0 .. values.length] = values[];
38     }
39 
40     static if (isCopyable!(const(E)))
41         this(const E[] values) const @nogc
42         {
43             this._length = values.length;
44             this._elements[0 .. values.length] = values[];
45         }
46 
47     this(V...)(V values) @nogc if (V.length > 0 && V.length <= maxElements && allSatisfy!(isBaseType, V))
48     {
49         this._length = V.length;
50         static foreach (i; 0 .. V.length)
51             this._elements[i] = values[i];
52     }
53 
54     unittest
55     {
56         alias BArray = BoundedArray!(int, 3);
57 
58         BArray arr0;
59         auto arr1 = BArray(1);
60         auto arr2 = BArray(1, 2);
61         auto arr3 = BArray(1, 2, 3);
62 
63         assert(arr0._length == 0);
64         assert(arr0._elements == [0, 0, 0]);
65         assert(arr1._length == 1);
66         assert(arr1._elements == [1, 0, 0]);
67         assert(arr2._length == 2);
68         assert(arr2._elements == [1, 2, 0]);
69         assert(arr3._length == 3);
70         assert(arr3._elements == [1, 2, 3]);
71     }
72 
73 
74     ///
75     @property size_t length() pure const nothrow @safe
76     {
77         return _length;
78     }
79 
80     /// ditto
81     alias opDollar = length;
82 
83     unittest
84     {
85         alias BArray = BoundedArray!(int, 3);
86 
87         BArray arr0;
88         auto arr1 = BArray(1);
89         auto arr2 = BArray(1, 2);
90         auto arr3 = BArray(1, 2, 3);
91 
92         assert(arr0.length == 0);
93         assert(arr1.length == 1);
94         assert(arr2.length == 2);
95         assert(arr3.length == 3);
96     }
97 
98 
99     ///
100     inout(E[]) opIndex() inout pure nothrow @safe
101     {
102         return _elements[0 .. _length];
103     }
104 
105     unittest
106     {
107         alias BArray = BoundedArray!(int, 3);
108 
109         BArray arr0;
110         auto arr1 = BArray(1);
111         auto arr2 = BArray(1, 2);
112         auto arr3 = BArray(1, 2, 3);
113 
114         assert(arr0[] == []);
115         assert(arr1[] == [1]);
116         assert(arr2[] == [1, 2]);
117         assert(arr3[] == [1, 2, 3]);
118     }
119 
120 
121     ///
122     auto ref inout(E) opIndex(size_t idx) inout pure @safe
123     {
124         return this[][idx];
125     }
126 
127     unittest
128     {
129         import core.exception : RangeError;
130         import std.exception : assertThrown;
131 
132         alias BArray = BoundedArray!(int, 3);
133 
134         BArray arr0;
135         auto arr1 = BArray(1);
136         auto arr2 = BArray(1, 2);
137         auto arr3 = BArray(1, 2, 3);
138 
139         assertThrown!RangeError(arr0[0]);
140         assert(arr1[0] == 1);
141         assertThrown!RangeError(arr1[1]);
142         assert(arr2[0] == 1);
143         assert(arr2[1] == 2);
144         assertThrown!RangeError(arr2[2]);
145         assert(arr3[0] == 1);
146         assert(arr3[1] == 2);
147         assert(arr3[2] == 3);
148         assertThrown!RangeError(arr3[3]);
149     }
150 
151     unittest
152     {
153         alias BArray = BoundedArray!(int, 3);
154 
155         auto arr = BArray(1, 2, 3);
156 
157         arr[0] = 3;
158         arr[1] = 2;
159         arr[2] = 1;
160 
161         assert(arr == BArray(3, 2, 1));
162 
163         arr[0] += 1;
164         arr[1] += 1;
165         arr[2] += 1;
166 
167         assert(arr == BArray(4, 3, 2));
168     }
169 
170 
171     ///
172     typeof(this) opIndex(size_t[2] bounds) @safe
173     {
174         return typeof(return)(this._elements[bounds[0] .. bounds[1]]);
175     }
176 
177     /// ditto
178     static if (isCopyable!(const(E)))
179         const(typeof(this)) opIndex(size_t[2] bounds) const @safe
180         {
181             return typeof(return)(this._elements[bounds[0] .. bounds[1]]);
182         }
183 
184     unittest
185     {
186         import core.exception : RangeError;
187         import std.exception : assertThrown;
188 
189         alias BArray = BoundedArray!(int, 3);
190 
191         auto arr = BArray(1, 2, 3);
192 
193         assert(arr[0 .. 0] == BArray());
194         assert(arr[1 .. 1] == BArray());
195         assert(arr[2 .. 2] == BArray());
196         assert(arr[0 .. 1] == BArray(1));
197         assert(arr[1 .. 2] == BArray(2));
198         assert(arr[2 .. 3] == BArray(3));
199         assert(arr[0 .. 2] == BArray(1, 2));
200         assert(arr[1 .. 3] == BArray(2, 3));
201         assert(arr[0 .. 3] == arr);
202 
203         assertThrown!RangeError(arr[0 .. 4] == arr);
204         assertThrown!RangeError(arr[3 .. 0] == arr);
205     }
206 
207 
208     size_t[2] opSlice(size_t dim)(size_t from, size_t to) const @safe if (dim == 0)
209     {
210         return [from, to];
211     }
212 
213 
214     ///
215     typeof(this) opOpAssign(string op)(ElementType element) @safe if (op == "~")
216     {
217         this._elements[this.length] = element;
218         this._length += 1;
219 
220         return this;
221     }
222 
223     /// ditto
224     typeof(this) opOpAssign(string op)(typeof(this) other) @safe if (op == "~")
225     {
226         auto catLength = this.length + other.length;
227         this._elements[this.length .. catLength] = other[];
228         this._length = catLength;
229 
230         return this;
231     }
232 
233     unittest
234     {
235         import core.exception : RangeError;
236         import std.exception :
237             assertNotThrown,
238             assertThrown;
239 
240         alias BArray = BoundedArray!(int, 3);
241 
242         auto arr1 = BArray(1);
243 
244         arr1 ~= 2;
245         arr1 ~= 3;
246 
247         assert(arr1 == BArray(1, 2, 3));
248         assertThrown!RangeError(arr1 ~= 4);
249 
250         auto arr2 = BArray(1);
251         arr2 ~= BArray(2, 3);
252 
253         assert(arr2 == BArray(1, 2, 3));
254         assertNotThrown!RangeError(arr2 ~= BArray());
255         assertThrown!RangeError(arr2 ~= BArray(4));
256     }
257 
258 
259     ///
260     typeof(this) opBinary(string op)(ElementType element) @safe if (op == "~")
261     {
262         typeof(this) copy = this;
263 
264         return copy ~= element;
265     }
266 
267     /// ditto
268     typeof(this) opBinary(string op)(typeof(this) other) @safe if (op == "~")
269     {
270         typeof(this) copy = this;
271 
272         return copy ~= other;
273     }
274 
275     unittest
276     {
277         import core.exception : RangeError;
278         import std.exception :
279             assertNotThrown,
280             assertThrown;
281 
282         alias BArray = BoundedArray!(int, 3);
283 
284         auto arr = BArray(1);
285 
286         assert(arr ~ 2 == BArray(1, 2));
287         assert(arr == BArray(1));
288 
289         assert(arr ~ BArray(2, 3) == BArray(1, 2, 3));
290         assert(arr == BArray(1));
291         assertThrown!RangeError(arr ~ BArray(2, 3, 4));
292         assertNotThrown!RangeError(BArray(2, 3, 4) ~ BArray());
293     }
294 
295 
296     /// Range primitives for a random access range.
297     void popFront() pure @safe
298     {
299         assert(!empty, "Attempting to popFront an empty " ~ typeof(this).stringof);
300 
301         this = this[1 .. $];
302     }
303 
304     ///
305     @property ref ElementType front() pure @safe
306     {
307         assert(!empty, "Attempting to fetch the front of an empty " ~ typeof(this).stringof);
308 
309         return this[0];
310     }
311 
312     ///
313     void popBack() pure @safe
314     {
315         assert(!empty, "Attempting to popBack an empty " ~ typeof(this).stringof);
316 
317         this = this[0 .. $ - 1];
318     }
319 
320     ///
321     @property ref ElementType back() pure @safe
322     {
323         assert(!empty, "Attempting to fetch the back of an empty " ~ typeof(this).stringof);
324 
325         return this[$ - 1];
326     }
327 
328     ///
329     @property bool empty() const pure nothrow @safe
330     {
331         return length == 0;
332     }
333 
334     ///
335     @property typeof(this) save() pure nothrow @safe
336     {
337         return this;
338     }
339 
340     unittest
341     {
342         import std.algorithm : minElement;
343         import std.range : dropBackOne;
344         import std.range.primitives;
345 
346         alias BArray = BoundedArray!(int, 3);
347 
348         static assert(isInputRange!BArray);
349         static assert(isForwardRange!BArray);
350         static assert(isBidirectionalRange!BArray);
351         static assert(isRandomAccessRange!BArray);
352         static assert(hasMobileElements!BArray);
353         static assert(hasSwappableElements!BArray);
354         static assert(hasAssignableElements!BArray);
355         static assert(hasLvalueElements!BArray);
356         static assert(hasLength!BArray);
357         static assert(hasSlicing!BArray);
358 
359         // Test popFront, front and empy
360         assert(minElement(BArray(1, 2, 3)) == 1);
361         // Test popBack, back and empy
362         assert(dropBackOne(BArray(1, 2, 3)).back == 2);
363 
364         // Test save
365         auto arr = BArray(1, 2, 3);
366         arr.save.popFront();
367         assert(arr.front == 1);
368     }
369 
370 
371     import vibe.data.json : Json;
372 
373     /// Convert from/to Json.
374     Json toJson() const @safe
375     {
376         import vibe.data.json : serializeToJson;
377 
378         return serializeToJson(this[]);
379     }
380 
381     /// ditto
382     static typeof(this) fromJson(Json src) @safe
383     {
384         import std.exception : enforce;
385         import std.json : JSONException;
386         import std.range : enumerate;
387         import vibe.data.json : deserializeJson;
388 
389         typeof(this) result;
390 
391         enforce!JSONException(
392             src.length <= maxElements,
393             "could not parse JSON: " ~ typeof(this).stringof ~
394             " must contain <= " ~ maxElements.stringof ~ " elements",
395         );
396 
397         result._length = src.length;
398         foreach (i, element; src.byValue().enumerate)
399             result[i] = deserializeJson!ElementType(element);
400 
401         return result;
402     }
403 
404     unittest
405     {
406         import std.exception : assertThrown;
407         import std.json : JSONException;
408         import vibe.data.json :
409             deserializeJson,
410             serializeToJson;
411 
412         alias BArray = BoundedArray!(int, 3);
413 
414         BArray arr = BArray(1, 2, 3);
415 
416         assert(serializeToJson(arr) == serializeToJson([1, 2, 3]));
417         assert(deserializeJson!BArray(serializeToJson(arr)) == arr);
418 
419         auto tooLong = serializeToJson([1, 2, 3, 4]);
420         assertThrown!JSONException(deserializeJson!BArray(tooLong));
421         auto mismatchedElementType = serializeToJson(["1", "2", "3", "4"]);
422         assertThrown!JSONException(deserializeJson!BArray(mismatchedElementType));
423     }
424 
425 
426     version (Have_sbin)
427     {
428         void sbinCustomSerialize(R)(ref R r) const
429         {
430             import sbin : sbinSerialize;
431 
432             sbinSerialize(r, _length);
433             foreach (ref element; this[])
434                 sbinSerialize(r, element);
435         }
436 
437         static void sbinCustomDeserialize(R)(ref R r, ref typeof(this) array)
438         {
439             import sbin : sbinDeserializePart;
440 
441             sbinDeserializePart(r, array._length);
442             foreach (i; 0 .. array._length)
443                 sbinDeserializePart(r, array._elements[i]);
444         }
445 
446         unittest
447         {
448             import sbin;
449             import std.array : appender;
450 
451             alias BArray = BoundedArray!(int, 3);
452 
453             auto array = BArray([1, 2]);
454 
455             auto buffer = appender!(ubyte[]);
456 
457             sbinSerialize(buffer, array);
458 
459             auto binaryData = buffer.data;
460             auto recoveredArray = sbinDeserialize!BArray(binaryData);
461 
462             assert(recoveredArray == array);
463         }
464     }
465 }
466 
467 unittest
468 {
469     import vibe.data.json :
470         deserializeJson,
471         serializeToJson;
472 
473     struct Complex
474     {
475         int foo;
476     }
477 
478     alias ComplexArray = BoundedArray!(Complex, 3);
479 
480     auto arr = ComplexArray(Complex(1), Complex(2), Complex(3));
481 
482     assert(deserializeJson!ComplexArray(serializeToJson(arr)) == arr);
483 }
484 
485 unittest
486 {
487     BoundedArray!(int, 3) triggerUnitTests;
488 }
489 
490 
491 /// Create a tuple of `variables` using their native names. Mostly works with
492 /// symbols but not with values (see example).
493 auto tupleOf(variables...)()
494 {
495     import std.meta;
496     import std.typecons;
497 
498     enum symbolName(alias sym) = sym.stringof;
499     alias variableNames = staticMap!(symbolName, variables);
500 
501     return tuple!(variableNames)(variables);
502 }
503 
504 ///
505 unittest
506 {
507     import std.typecons;
508 
509     auto a = 1;
510     auto b = false;
511     auto c = "foobar";
512     auto tup = tupleOf!(a, b, c);
513 
514     assert(tup == tuple!("a", "b", "c")(a, b, c));
515 }
516 
517 
518 
519 /// Does not work with values.
520 unittest
521 {
522     import std.typecons;
523 
524     auto a = 1;
525     auto b = false;
526 
527     static assert(!is(tupleOf!(a, b, 42)));
528     // compiler error:
529     //     std.typecons.Tuple!(int, "a", bool, "b", int, "42")` does not match
530     //     template declaration `Tuple(Specs...)`
531 }
532 
533