1 /++
2 $(H1 Small Array)
3 
4 The module contains self-contained generic small array implementaton.
5 
6 $(LREF SmallArray) supports ASDF - Json Serialisation Library.
7 
8 Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments
9 Authors: Ilya Yaroshenko
10 +/
11 module mir.small_array;
12 
13 import mir.serde: serdeProxy;
14 
15 private static immutable errorMsg = "Cannot create SmallArray: input range exceeds maximum allowed length.";
16 version(D_Exceptions)
17     private static immutable exception = new Exception(errorMsg);
18 
19 ///
20 template SmallArray(T, uint maxLength)
21     if (maxLength)
22 {
23     import std.traits: Unqual, isIterable, isImplicitlyConvertible;
24 
25     static if (isImplicitlyConvertible!(const T, T))
26         alias V = const T;
27     else
28         alias V = T;
29 
30     ///
31     @serdeProxy!(V[])
32     struct SmallArray
33     {
34         uint _length;
35         T[maxLength] _data;
36 
37         ///
38         alias serdeKeysProxy = T;
39 
40     @safe pure @nogc:
41 
42         /// Constructor
43         this(typeof(null))
44         {
45         }
46 
47         /// ditto
48         this(V[] array)
49         {
50             this.opAssign(array);
51         }
52 
53         /// ditto
54         this(const SmallArray array) nothrow
55         {
56             this.opAssign(array);
57         }
58 
59         /// ditto
60         this(uint n)(const SmallArray!(T, n) array)
61         {
62             this.opAssign(array);
63         }
64 
65         /// ditto
66         this(uint n)(ref const SmallArray!(T, n) array)
67         {
68             this.opAssign(array);
69         }
70 
71         /// ditto
72         this(Range)(auto ref Range array)
73             if (isIterable!Range)
74         {
75             foreach(ref c; array)
76             {
77                 if (_length > _data.length)
78                 {
79                     version(D_Exceptions) throw exception;
80                     else assert(0, errorMsg);
81                 }
82                 _data[_length++] = c;
83             }
84         }
85 
86         /// `=` operator
87         ref typeof(this) opAssign(typeof(null)) return
88         {
89             _length = 0;
90             _data = T.init;
91             return this;
92         }
93 
94         /// ditto
95         ref typeof(this) opAssign(V[] array) return @trusted
96         {
97             if (array.length > maxLength)
98             {
99                 version(D_Exceptions) throw exception;
100                 else assert(0, errorMsg);
101             }
102             _data[0 .. _length] = T.init;
103             _length = cast(uint) array.length;
104             _data[0 .. _length] = array;
105             return this;
106         }
107 
108         /// ditto
109         ref typeof(this) opAssign(ref const SmallArray rhs) return nothrow
110         {
111             _length = rhs._length;
112             _data = rhs._data;
113             return this;
114         }
115         
116         /// ditto
117         ref typeof(this) opAssign(const SmallArray rhs) return nothrow
118         {
119             _length = rhs._length;
120             _data = rhs._data;
121             return this;
122         }
123 
124         /// ditto
125         ref typeof(this) opAssign(uint n)(ref const SmallArray!(T, n) rhs) return
126             if (n != maxLength)
127         {
128             static if (n < maxLength)
129             {
130                 _data[0 .. n] = rhs._data;
131                 if (_length > n)
132                     _data[n .. _length] = T.init;
133             }
134             else
135             {
136                 if (rhs._length > maxLength)
137                 {
138                     version(D_Exceptions) throw exception;
139                     else assert(0, errorMsg);
140                 }
141                 _data = rhs._data[0 .. maxLength];
142             }
143             _length = rhs._length;
144             return this;
145         }
146 
147         /// ditto
148         ref typeof(this) opAssign(uint n)(const SmallArray!(T, n) rhs) return
149             if (n != maxLength)
150         {
151             static if (n < maxLength)
152             {
153                 _data[0 .. n] = rhs._data;
154                 if (_length > n)
155                     _data[n .. _length] = T.init;
156             }
157             else
158             {
159                 if (rhs._length > maxLength)
160                 {
161                     version(D_Exceptions) throw exception;
162                     else assert(0, errorMsg);
163                 }
164                 _data = rhs._data[0 .. maxLength];
165             }
166             _length = rhs._length;
167             return this;
168         }
169 
170         ///
171         void trustedAssign(V[] array) @trusted
172         {
173             _data[0 .. _length] = T.init;
174             _length = cast(uint) array.length;
175             _data[0 .. _length] = array;
176         }
177 
178         ///
179         ref typeof(this) append(T elem)
180         {
181             import core.lifetime: move;
182             if (_length == maxLength)
183             {
184                 version(D_Exceptions) throw exception;
185                 else assert(0, errorMsg);
186             }
187             _data[_length++] = move(elem);
188             return this;
189         }
190 
191         ///
192         void trustedAppend(T elem)
193         {
194             import core.lifetime: move;
195             _data[_length++] = move(elem);
196         }
197 
198         ///
199         ref typeof(this) append(V[] array)
200         {
201             if (_length + array.length > maxLength)
202             {
203                 version(D_Exceptions) throw exception;
204                 else assert(0, errorMsg);
205             }
206             _data[_length .. _length + array.length] = array;
207             _length += array.length;
208             return this;
209         }
210 
211         /// ditto
212         alias put = append;
213 
214         /// ditto
215         alias opOpAssign(string op : "~") = append;
216 
217         ///
218         SmallArray concat(V[] array) scope const
219         {
220             SmallArray c = this;
221             c.append(array);
222             return c; 
223         }
224 
225         /// ditto
226         alias opBinary(string op : "~") = concat;
227 
228         /++
229         Returns an scope common array.
230 
231         The property is used as common array representation self alias.
232 
233         The alias helps with `[]`, `[i]`, `[i .. j]`, `==`, and `!=` operations and implicit conversion to strings.
234         +/
235         inout(T)[] opIndex() inout @trusted return scope
236         {
237             return _data[0 .. _length];
238         }
239 
240         ///
241         ref inout(T) opIndex(size_t index) inout scope return
242         {
243             return opIndex[index];
244         }
245 
246     const:
247 
248         ///
249         bool empty() @property
250         {
251             return _length == 0;
252         }
253 
254         ///
255         size_t length() @property
256         {
257             return _length;
258         }
259 
260         /// Hash implementation
261         size_t toHash()
262         {
263             return hashOf(opIndex);
264         }
265 
266         /// Comparisons operator overloads
267         bool opEquals(ref const SmallArray rhs)
268         {
269             return opIndex == rhs.opIndex;
270         }
271 
272         /// ditto
273         bool opEquals(SmallArray rhs)
274         {
275             return opIndex == rhs.opIndex;
276         }
277 
278         /// ditto
279         bool opEquals()(V[] array)
280         {
281             return opIndex == array;
282         }
283 
284         /// ditto
285         bool opEquals(uint rhsMaxLength)(auto ref SmallArray!(T, rhsMaxLength) array)
286         {
287             return opIndex == array.opIndex;
288         }
289 
290         /// ditto
291         auto opCmp()(V[] array)
292         {
293             return __cmp(opIndex, array);
294         }
295 
296         /// ditto
297         auto opCmp(uint rhsMaxLength)(auto ref SmallArray!(T, rhsMaxLength) array)
298         {
299             return __cmp(opIndex, array.opIndex);
300         }
301     }
302 }
303 
304 ///
305 @safe pure @nogc version(mir_test) unittest
306 {
307     SmallArray!(char, 16) s16;
308     assert(s16.empty);
309 
310     auto s8 = SmallArray!(char, 8)("Hellow!!");
311     assert(!s8.empty);
312     assert(s8 == "Hellow!!", s8[]);
313 
314     s16 = s8;
315     assert(s16 == "Hellow!!", s16[]);
316     s16[7] = '@';
317     s8 = null;
318     assert(s8.empty);
319     assert(s8 == null);
320     s8 = s16;
321     assert(s8 == "Hellow!@");
322 
323     auto s8_2 = s8;
324     assert(s8_2 == "Hellow!@");
325     assert(s8_2 == s8);
326 
327     assert(s8 < "Hey");
328     assert(s8 > "Hellow!");
329 
330     assert(s8.opCmp("Hey") < 0);
331     assert(s8.opCmp(s8) == 0);
332 }
333 
334 /// Concatenation
335 @safe pure @nogc version(mir_test) unittest
336 {
337     auto a = SmallArray!(char, 16)("asdf");
338     a ~= " ";
339     auto b = a ~ "qwerty";
340     static assert(is(typeof(b) == SmallArray!(char, 16)));
341     assert(b == "asdf qwerty");
342     b.put('!');
343     b.put("!");
344     assert(b == "asdf qwerty!!");
345 }