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