1 module nxt.static_modarray;
2 
3 version = useModulo;
4 
5 /** Statically allocated `Mod`-array of fixed pre-allocated length `capacity` of
6  * `Mod`-elements in chunks of `elementLength`. `ElementType` is
7  * `Mod[elementLength]`.
8  */
9 struct StaticModArray(uint capacity,
10                       uint elementLength,
11                       uint span,
12                       bool useModuloFlag)
13 if (capacity*elementLength >= 2) // no use storing less than 2 bytes
14 {
15     private enum radix = 2^^span;
16 
17     /// Index modulo `radix` type.
18     static if (useModuloFlag)
19     {
20         import nxt.modulo : Mod;
21         alias Ix = Mod!(radix, ubyte);
22     }
23     else
24     {
25         alias Ix = ubyte;
26     }
27 
28     enum L = elementLength;
29 
30     /// ElementType type `T`.
31     static if (L == 1)
32     {
33         alias T = Ix;
34     }
35     else
36     {
37         alias T = Ix[L];
38     }
39 
40     /** Construct with `rhsCapacity`. */
41     this(uint rhsCapacity)(in StaticModArray!(rhsCapacity,
42                                               elementLength,
43                                               span, useModuloFlag) rhs)
44     {
45         static if (capacity < rhsCapacity)
46         {
47             assert(rhs.length <= capacity);
48         }
49         foreach (immutable i, const ix; rhs)
50         {
51             _store[i] = ix;
52         }
53         _length = rhs.length;
54     }
55 
56     /** Construct with elements `es`. */
57     this(Es...)(Es es)
58     if (Es.length >= 1 &&
59         Es.length <= capacity)
60     {
61         foreach (immutable i, ix; es)
62         {
63             _store[i] = ix;
64         }
65         _length = es.length;
66     }
67 
68     static if (L == 1)
69     {
70         /** Construct with elements in `es`. */
71         this(const Ix[] es)
72         {
73             assert(es.length <= capacity);
74             _store[0 .. es.length] = es;
75             _length = es.length;
76         }
77     }
78 
79     /** Default key separator in printing. */
80     enum keySeparator = ',';
81 
82     @property auto toString(char separator = keySeparator) const
83     {
84         string s;
85         foreach (immutable i, const ix; chunks)
86         {
87             if (i != 0) { s ~= separator; }
88             import std.string : format;
89             static if (elementLength == 1)
90             {
91                 s ~= format("%.2X", ix); // in hexadecimal
92             }
93             else
94             {
95                 foreach (const j, const subIx; ix[])
96                 {
97                     if (j != 0) { s ~= '_'; } // separator
98                     s ~= format("%.2X", subIx); // in hexadecimal
99                 }
100             }
101         }
102         return s;
103     }
104 
105     @safe pure nothrow @nogc:
106 
107     /** Get first element. */
108     auto front() inout
109     {
110         pragma(inline, true);
111         assert(!empty);
112         return _store[0];
113     }
114 
115     /** Get last element. */
116     auto back() inout
117     {
118         pragma(inline, true);
119         assert(!empty);
120         return _store[_length - 1];
121     }
122 
123     /** Returns: `true` if `this` is empty, `false` otherwise. */
124     bool empty() const
125     {
126         pragma(inline, true);
127         return _length == 0;
128     }
129 
130     /** Returns: `true` if `this` is full, `false` otherwise. */
131     bool full() const
132     {
133         pragma(inline, true);
134         return _length == capacity;
135     }
136 
137     /** Pop first (front) element. */
138     auto ref popFront()
139     {
140         assert(!empty);
141         // TODO is there a reusable Phobos function for this?
142         foreach (immutable i; 0 .. _length - 1)
143         {
144             _store[i] = _store[i + 1]; // like `_store[i] = _store[i + 1];` but more generic
145         }
146         _length = _length - 1;
147         return this;
148     }
149 
150     /** Pop `n` front elements. */
151     auto ref popFrontN(size_t n)
152     {
153         assert(length >= n);
154         // TODO is there a reusable Phobos function for this?
155         foreach (immutable i; 0 .. _length - n)
156         {
157             _store[i] = _store[i + n];
158         }
159         _length = _length - n;
160         return this;
161     }
162 
163     /** Pop last (back) element. */
164     auto ref popBack()
165     {
166         version(LDC) pragma(inline, true);
167         assert(!empty);
168         _length = cast(typeof(_length))(_length - 1); // TODO better?
169         return this;
170     }
171 
172     /** Push/Add elements `es` at back.
173         NOTE Doesn't invalidate any borrow.
174     */
175     auto ref pushBack(Es...)(Es es)
176     if (Es.length <= capacity)
177     {
178         assert(length + Es.length <= capacity);
179         foreach (immutable i, const e; es)
180         {
181             _store[_length + i] = e;
182         }
183         _length = _length + Es.length;
184         return this;
185     }
186 
187     /** Returns: `true` if `key` is contained in `this`. */
188     bool contains(in Ix[] key) const @nogc
189     {
190         pragma(inline, true);
191         // TODO use binarySearch instead of canFind
192         import std.algorithm.searching : canFind;
193         if (key.length != L) { return false; }
194         return (chunks.canFind(key));
195     }
196 
197     static if (L == 1)
198     {
199         import std.traits : isUnsigned;
200 
201         /** Returns: `true` if `ix` is contained in `this`. */
202         static if (useModuloFlag)
203         {
204             bool contains(ModUInt)(in Mod!(radix, ModUInt) ix) const @nogc
205             if (isUnsigned!ModUInt)
206             {
207                 pragma(inline, true);
208                 // TODO use binarySearch instead of canFind
209                 import std.algorithm.searching : canFind;
210                 return (chunks.canFind(ix));
211             }
212         }
213         else
214         {
215             bool contains(UInt)(in UInt ix) const @nogc
216             if (isUnsigned!UInt)
217             {
218                 pragma(inline, true);
219                 // TODO use binarySearch instead of canFind
220                 import std.algorithm.searching : canFind;
221                 return (chunks.canFind(cast(T)ix));
222             }
223         }
224     }
225 
226     /** Returns: elements as a slice. */
227     auto chunks() inout
228     {
229         pragma(inline, true);
230         return _store[0 .. _length];
231     }
232     alias chunks this;
233 
234     /** Variant of `opIndex` with compile-time range checking. */
235     auto ref at(uint ix)() inout @trusted
236     if (ix < capacity)      // assert below memory allocation bound
237     {
238         pragma(inline, true);
239         assert(ix < _length);   // assert accessing initialized elements
240         return _store.ptr[ix];    // uses `.ptr` because `ix` known at compile-time to be within bounds; `ix < capacity`
241     }
242 
243     /** Get length. */
244     auto length() const
245     {
246         pragma(inline, true);
247         return _length;
248     }
249 
250     /** Get remaining space available.
251         Name taken from the same member of https://docs.rs/fixedvec/0.2.3/fixedvec/
252      */
253     auto available() const
254     {
255         pragma(inline, true);
256         return capacity - _length;
257     }
258 
259     enum typeBits = 4; // number of bits in enclosing type used for representing type
260 
261 private:
262     static if (L == 1)
263     {
264         T[capacity] _store = void; // byte indexes
265     }
266     else
267     {
268         T[capacity] _store = void; // byte indexes
269     }
270 
271     static if (_store.sizeof == 6)
272     {
273         ubyte _padding;
274     }
275 
276     import std.bitmanip : bitfields;
277     mixin(bitfields!(size_t, "_length", 4, // maximum length of 15
278                      ubyte, "_mustBeIgnored", typeBits)); // must be here and ignored because it contains `WordVariant` type of `Node`
279 }
280 
281 static assert(StaticModArray!(3, 1, 8, false).sizeof == 4);
282 static assert(StaticModArray!(7, 1, 8, false).sizeof == 8);
283 static assert(StaticModArray!(3, 2, 8, false).sizeof == 8);
284 static assert(StaticModArray!(2, 3, 8, false).sizeof == 8);
285 
286 ///
287 @safe pure nothrow @nogc unittest
288 {
289     import std.algorithm : equal;
290 
291     version(useModulo)
292     {
293         enum span = 8;
294         enum radix = 2^^span;
295         import nxt.modulo : Mod, mod;
296         alias Ix = Mod!(radix, ubyte);
297         static Mod!radix mk(ubyte value)
298         {
299             return mod!radix(value);
300         }
301     }
302     else
303     {
304         alias Ix = ubyte;
305         static ubyte mk(ubyte value)
306         {
307             return value;
308         }
309     }
310 
311     const ixs = [mk(11), mk(22), mk(33), mk(44)].s;
312     enum capacity = 7;
313 
314     auto x = StaticModArray!(capacity, 1, 8, true)(ixs);
315     auto y = StaticModArray!(capacity, 1, 8, true)(mk(11), mk(22), mk(33), mk(44));
316 
317     assert(x == y);
318 
319     assert(x.length == 4);
320     assert(x.available == 3);
321     assert(!x.empty);
322 
323     assert(!x.contains([mk(10)].s));
324     assert(x.contains([mk(11)].s));
325     assert(x.contains([mk(22)].s));
326     assert(x.contains([mk(33)].s));
327     assert(x.contains([mk(44)].s));
328     assert(!x.contains([mk(45)].s));
329 
330     assert(!x.contains(mk(10)));
331     assert(x.contains(mk(11)));
332     assert(x.contains(mk(22)));
333     assert(x.contains(mk(33)));
334     assert(x.contains(mk(44)));
335     assert(!x.contains(mk(45)));
336 
337     assert(x.equal([11, 22, 33, 44].s[]));
338     assert(x.front == 11);
339     assert(x.back == 44);
340     assert(!x.full);
341     x.popFront();
342     assert(x.equal([22, 33, 44].s[]));
343     assert(x.front == 22);
344     assert(x.back == 44);
345     assert(!x.full);
346     x.popBack();
347     assert(x.equal([22, 33].s[]));
348     assert(x.front == 22);
349     assert(x.back == 33);
350     assert(!x.full);
351     x.popFront();
352     assert(x.equal([33].s[]));
353     assert(x.front == 33);
354     assert(x.back == 33);
355     assert(!x.full);
356     x.popFront();
357     assert(x.empty);
358     assert(!x.full);
359     assert(x.length == 0);
360 
361     x.pushBack(mk(11), mk(22), mk(33), mk(44), mk(55), mk(66), mk(77));
362     assert(x.equal([11, 22, 33, 44, 55, 66, 77].s[]));
363     assert(!x.empty);
364     assert(x.full);
365 
366     x.popFrontN(3);
367     assert(x.equal([44, 55, 66, 77].s[]));
368 
369     x.popFrontN(2);
370     assert(x.equal([66, 77].s[]));
371 
372     x.popFrontN(1);
373     assert(x.equal([77].s[]));
374 
375     x.popFrontN(1);
376     assert(x.empty);
377 
378     x.pushBack(mk(1)).pushBack(mk(2)).equal([1, 2].s[]);
379     assert(x.equal([1, 2].s[]));
380     assert(x.length == 2);
381 }
382 
383 @safe pure nothrow unittest
384 {
385     import std.algorithm : equal;
386 
387     version(useModulo)
388     {
389         enum span = 8;
390         enum radix = 2^^span;
391         import nxt.modulo : Mod, mod;
392         alias Ix = Mod!(radix, ubyte);
393         static Mod!radix mk(ubyte value)
394         {
395             return mod!radix(value);
396         }
397     }
398     else
399     {
400         alias Ix = ubyte;
401         static ubyte mk(ubyte value)
402         {
403             return value;
404         }
405     }
406 
407     const ixs = [mk(11), mk(22), mk(33), mk(44)].s;
408     enum capacity = 7;
409     auto z = StaticModArray!(capacity, 1, 8, true)(ixs);
410     assert(z.sizeof == 8);
411     try
412     {
413         assert(z.toString == `0B,16,21,2C`);
414     }
415     catch (Exception e) {}
416 }
417 
418 version(unittest)
419 {
420     import nxt.array_help : s;
421 }