1 module nxt.container.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 		import nxt.modulo : Mod;
20 		alias Ix = Mod!(radix, ubyte);
21 	}
22 	else
23 		alias Ix = ubyte;
24 
25 	enum L = elementLength;
26 
27 	/// ElementType type `T`.
28 	static if (L == 1)
29 		alias T = Ix;
30 	else
31 		alias T = Ix[L];
32 
33 	/** Construct with `rhsCapacity`. */
34 	this(uint rhsCapacity)(in StaticModArray!(rhsCapacity,
35 											  elementLength,
36 											  span, useModuloFlag) rhs) {
37 		static if (capacity < rhsCapacity)
38 			assert(rhs.length <= capacity);
39 		foreach (immutable i, const ix; rhs)
40 			_store[i] = ix;
41 		_length = rhs.length;
42 	}
43 
44 	/** Construct with elements `es`. */
45 	this(Es...)(Es es)
46 	if (Es.length >= 1 &&
47 		Es.length <= capacity) {
48 		foreach (immutable i, ix; es)
49 			_store[i] = ix;
50 		_length = es.length;
51 	}
52 
53 	static if (L == 1) {
54 		/** Construct with elements in `es`. */
55 		this(const Ix[] es)
56 		in(es.length <= capacity) {
57 			_store[0 .. es.length] = es;
58 			_length = cast(typeof(_length))es.length;
59 		}
60 	}
61 
62 	/** Default key separator in printing. */
63 	enum keySeparator = ',';
64 
65 	@property auto toString()(char separator = keySeparator) const /*tlm*/
66 	{
67 		string s;
68 		foreach (immutable i, const ix; chunks) {
69 			if (i != 0) { s ~= separator; }
70 			import std.string : format;
71 			static if (elementLength == 1)
72 				s ~= format("%.2X", ix); // in hexadecimal
73 			else
74 			{
75 				foreach (const j, const subIx; ix[]) {
76 					if (j != 0) { s ~= '_'; } // separator
77 					s ~= format("%.2X", subIx); // in hexadecimal
78 				}
79 			}
80 		}
81 		return s;
82 	}
83 
84 	pure nothrow @safe @nogc:
85 
86 	/** Returns: `true` if `this` is empty, `false` otherwise. */
87 	pragma(inline, true)
88 	bool empty() const @property => _length == 0;
89 
90 	/** Get first element. */
91 	pragma(inline, true)
92 	auto front() inout in(!empty) => _store[0];
93 
94 	/** Get last element. */
95 	pragma(inline, true)
96 	auto back() inout in(!empty) => _store[_length - 1];
97 
98 	/** Returns: `true` if `this` is full, `false` otherwise. */
99 	pragma(inline, true)
100 	bool full() const => _length == capacity;
101 
102 	/** Pop first (front) element. */
103 	auto ref popFront() in(!empty) {
104 		/+ TODO: is there a reusable Phobos function for this? +/
105 		foreach (immutable i; 0 .. _length - 1)
106 			_store[i] = _store[i + 1]; // like `_store[i] = _store[i + 1];` but more generic
107 		_length = cast(typeof(_length))(_length - 1);
108 		return this;
109 	}
110 
111 	/** Pop `n` front elements. */
112 	auto ref popFrontN(size_t n) in(length >= n) {
113 		/+ TODO: is there a reusable Phobos function for this? +/
114 		foreach (immutable i; 0 .. _length - n)
115 			_store[i] = _store[i + n];
116 		_length = cast(typeof(_length))(_length - n);
117 		return this;
118 	}
119 
120 	/** Pop last (back) element. */
121 	auto ref popBack() in(!empty) {
122 		version (LDC) pragma(inline, true);
123 		_length = cast(typeof(_length))(_length - 1); /+ TODO: better? +/
124 		return this;
125 	}
126 
127 	/** Push/Add elements `es` at back.
128 		NOTE Doesn't invalidate any borrow.
129 	*/
130 	auto ref pushBack(Es...)(Es es)
131 	if (Es.length <= capacity)
132 	in(length + Es.length <= capacity) {
133 		foreach (immutable i, const e; es)
134 			_store[_length + i] = e;
135 		_length = cast(typeof(_length))(_length + Es.length);
136 		return this;
137 	}
138 
139 	/** Returns: `true` if `key` is contained in `this`. */
140 	bool contains(in Ix[] key) const @nogc
141 	{
142 		import std.algorithm.searching : canFind;
143 		if (key.length != L) { return false; }
144 		return chunks.canFind(key);	/+ TODO: use binarySearch instead +/
145 	}
146 
147 	static if (L == 1) {
148 		import std.traits : isUnsigned;
149 
150 		/** Returns: `true` if `ix` is contained in `this`. */
151 		static if (useModuloFlag) {
152 			pragma(inline, true)
153 			bool contains(ModUInt)(in Mod!(radix, ModUInt) ix) const @nogc
154 			if (isUnsigned!ModUInt) {
155 				import std.algorithm.searching : canFind;
156 				return chunks.canFind(ix); /+ TODO: use binarySearch +/
157 			}
158 		}
159 		else
160 		{
161 			pragma(inline, true)
162 			bool contains(UInt)(in UInt ix) const @nogc
163 			if (isUnsigned!UInt) {
164 				import std.algorithm.searching : canFind;
165 				return chunks.canFind(cast(T)ix); /+ TODO: use binarySearch +/
166 			}
167 		}
168 	}
169 
170 	/** Returns: elements as a slice. */
171 	pragma(inline, true)
172 	auto chunks() inout => _store[0 .. _length];
173 	alias chunks this;
174 
175 	/** Variant of `opIndex` with compile-time range checking. */
176 	auto ref at(uint ix)() inout @trusted if (ix < capacity) in(ix < _length) {
177 		version (D_Coverage) {} else version (LDC) pragma(inline, true);
178 		return _store.ptr[ix]; // uses `.ptr` because `ix` known at compile-time to be within bounds; `ix < capacity`
179 	}
180 
181 
182 	/** Get length. */
183 	pragma(inline, true)
184 	auto length() const => _length;
185 
186 	enum typeBits = 4; // number of bits in enclosing type used for representing type
187 
188 private:
189 	static if (L == 1)
190 		T[capacity] _store = void; // byte indexes
191 	else
192 		T[capacity] _store = void; // byte indexes
193 	static if (_store.sizeof == 6)
194 		ubyte _padding;
195 	import nxt.dip_traits : hasPreviewBitfields;
196 	static if (hasPreviewBitfields)
197 	{
198 		mixin("ubyte _length : 8-typeBits;"); // maximum length of 15
199 		mixin("ubyte _mustBeIgnored : typeBits;");
200 	}
201 	else
202 	{
203 		import std.bitmanip : bitfields;
204 		mixin(bitfields!(size_t, "_length", 4, // maximum length of 15
205 						 ubyte, "_mustBeIgnored", typeBits)); // must be here and ignored because it contains `WordVariant` type of `Node`
206 	}
207 }
208 
209 version (unittest) {
210 	static assert(StaticModArray!(3, 1, 8, false).sizeof == 4);
211 	static assert(StaticModArray!(7, 1, 8, false).sizeof == 8);
212 	static assert(StaticModArray!(3, 2, 8, false).sizeof == 8);
213 	static assert(StaticModArray!(2, 3, 8, false).sizeof == 8);
214 }
215 
216 ///
217 pure nothrow @safe @nogc unittest {
218 	import std.algorithm : equal;
219 
220 	version (useModulo) {
221 		enum span = 8;
222 		enum radix = 2^^span;
223 		import nxt.modulo : Mod, mod;
224 		alias Ix = Mod!(radix, ubyte);
225 		static Mod!radix mk(ubyte value) => mod!radix(value);
226 	}
227 	else
228 	{
229 		alias Ix = ubyte;
230 		static ubyte mk(ubyte value) => value;
231 	}
232 
233 	const ixs = [mk(11), mk(22), mk(33), mk(44)].s;
234 	enum capacity = 7;
235 
236 	auto x = StaticModArray!(capacity, 1, 8, true)(ixs);
237 	auto y = StaticModArray!(capacity, 1, 8, true)(mk(11), mk(22), mk(33), mk(44));
238 
239 	assert(x == y);
240 
241 	assert(x.length == 4);
242 	assert(!x.empty);
243 
244 	assert(!x.contains([mk(10)].s));
245 	assert(x.contains([mk(11)].s));
246 	assert(x.contains([mk(22)].s));
247 	assert(x.contains([mk(33)].s));
248 	assert(x.contains([mk(44)].s));
249 	assert(!x.contains([mk(45)].s));
250 
251 	assert(!x.contains(mk(10)));
252 	assert(x.contains(mk(11)));
253 	assert(x.contains(mk(22)));
254 	assert(x.contains(mk(33)));
255 	assert(x.contains(mk(44)));
256 	assert(!x.contains(mk(45)));
257 
258 	assert(x.equal([11, 22, 33, 44].s[]));
259 	assert(x.front == 11);
260 	assert(x.back == 44);
261 	assert(!x.full);
262 	x.popFront();
263 	assert(x.equal([22, 33, 44].s[]));
264 	assert(x.front == 22);
265 	assert(x.back == 44);
266 	assert(!x.full);
267 	x.popBack();
268 	assert(x.equal([22, 33].s[]));
269 	assert(x.front == 22);
270 	assert(x.back == 33);
271 	assert(!x.full);
272 	x.popFront();
273 	assert(x.equal([33].s[]));
274 	assert(x.front == 33);
275 	assert(x.back == 33);
276 	assert(!x.full);
277 	x.popFront();
278 	assert(x.empty);
279 	assert(!x.full);
280 	assert(x.length == 0);
281 
282 	x.pushBack(mk(11), mk(22), mk(33), mk(44), mk(55), mk(66), mk(77));
283 	assert(x.equal([11, 22, 33, 44, 55, 66, 77].s[]));
284 	assert(!x.empty);
285 	assert(x.full);
286 
287 	x.popFrontN(3);
288 	assert(x.equal([44, 55, 66, 77].s[]));
289 
290 	x.popFrontN(2);
291 	assert(x.equal([66, 77].s[]));
292 
293 	x.popFrontN(1);
294 	assert(x.equal([77].s[]));
295 
296 	x.popFrontN(1);
297 	assert(x.empty);
298 
299 	x.pushBack(mk(1)).pushBack(mk(2)).equal([1, 2].s[]);
300 	assert(x.equal([1, 2].s[]));
301 	assert(x.length == 2);
302 }
303 
304 pure nothrow @safe unittest {
305 	import std.algorithm : equal;
306 
307 	version (useModulo) {
308 		enum span = 8;
309 		enum radix = 2^^span;
310 		import nxt.modulo : Mod, mod;
311 		alias Ix = Mod!(radix, ubyte);
312 		static Mod!radix mk(ubyte value) => mod!radix(value);
313 	}
314 	else
315 	{
316 		alias Ix = ubyte;
317 		static ubyte mk(ubyte value) => value;
318 	}
319 
320 	const ixs = [mk(11), mk(22), mk(33), mk(44)].s;
321 	enum capacity = 7;
322 	auto z = StaticModArray!(capacity, 1, 8, true)(ixs);
323 	assert(z.sizeof == 8);
324 	try
325 	{
326 		assert(z.toString == `0B,16,21,2C`);
327 	}
328 	catch (Exception e) {}
329 }
330 
331 version (unittest) {
332 	import nxt.array_help : s;
333 }