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 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments 9 Authors: Ilia Ki 10 +/ 11 module mir.small_array; 12 13 private static immutable errorMsg = "Cannot create SmallArray: input range exceeds maximum allowed length."; 14 version(D_Exceptions) 15 private static immutable exception = new Exception(errorMsg); 16 17 /// 18 template SmallArray(T, uint maxLength) 19 if (maxLength) 20 { 21 import mir.serde: serdeProxy, serdeScoped; 22 import std.traits: Unqual, isIterable, isImplicitlyConvertible; 23 24 static if (isImplicitlyConvertible!(const T, T)) 25 alias V = const T; 26 else 27 alias V = T; 28 29 /// 30 @serdeScoped 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(scope 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) { import mir.exception : toMutable; throw exception.toMutable; } 80 else assert(0, errorMsg); 81 } 82 _data[_length++] = c; 83 } 84 } 85 86 /// `=` operator 87 ref typeof(this) opAssign(typeof(null)) scope return 88 { 89 _length = 0; 90 _data = T.init; 91 return this; 92 } 93 94 /// ditto 95 ref typeof(this) opAssign(V[] array) scope return @trusted 96 { 97 if (array.length > maxLength) 98 { 99 version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; } 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) scope 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) scope 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) scope 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) { import mir.exception : toMutable; throw exception.toMutable; } 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) scope 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) { import mir.exception : toMutable; throw exception.toMutable; } 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) { import mir.exception : toMutable; throw exception.toMutable; } 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) { import mir.exception : toMutable; throw exception.toMutable; } 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 scope return 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 scope 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(scope 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()(const scope V[] array) 280 { 281 return opIndex == array; 282 } 283 284 /// ditto 285 bool opEquals(uint rhsMaxLength)(auto ref scope SmallArray!(T, rhsMaxLength) array) 286 { 287 return opIndex == array.opIndex; 288 } 289 290 /// ditto 291 auto opCmp()(const scope V[] array) 292 { 293 return __cmp(opIndex, array); 294 } 295 296 /// ditto 297 auto opCmp(uint rhsMaxLength)(auto ref scope const 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!!"); 313 314 s16 = s8; 315 assert(s16 == "Hellow!!"); 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 }