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 }