1 /** 2 Dynamic arrays with deterministic memory usage 3 akin to C++'s std::vector or Rust's std::vec::Vec 4 */ 5 module automem.vector; 6 7 8 import automem.traits: isAllocator, isGlobal; 9 import std.range.primitives: isInputRange; 10 import stdx.allocator: theAllocator; 11 import stdx.allocator.mallocator: Mallocator; 12 13 14 alias String = StringA!(typeof(theAllocator)); 15 alias StringM = StringA!Mallocator; 16 17 18 template StringA(A = typeof(theAllocator)) if(isAllocator!A) { 19 alias StringA = Vector!(immutable char, A); 20 } 21 22 /** 23 Create a vector from a variadic list of elements, inferring the type of 24 the elements and the allocator 25 */ 26 auto vector(A = typeof(theAllocator), E) 27 (E[] elements...) 28 if(isAllocator!A && isGlobal!A) 29 { 30 return Vector!(E, A)(elements); 31 } 32 33 /// ditto 34 auto vector(A = typeof(theAllocator), E) 35 (A allocator, E[] elements...) 36 if(isAllocator!A && !isGlobal!A) 37 { 38 return Vector!(E, A)(allocator, elements); 39 } 40 41 /** 42 Create a vector from an input range, inferring the type of the elements 43 and the allocator. 44 */ 45 auto vector(A = typeof(theAllocator), R) 46 (R range) 47 if(isAllocator!A && isGlobal!A && isInputRange!R) 48 { 49 import automem.vector: ElementType; 50 return Vector!(ElementType!R, A)(range); 51 } 52 53 54 /// ditto 55 auto vector(A = typeof(theAllocator), R) 56 (A allocator, R range) 57 if(isAllocator!A && !isGlobal!A && isInputRange!R) 58 { 59 import automem.vector: ElementType; 60 return Vector!(ElementType!R, A)(allocator, range); 61 } 62 63 /** 64 A dynamic array with deterministic memory usage 65 akin to C++'s std::vector or Rust's std::vec::Vec 66 */ 67 struct Vector(E, Allocator = typeof(theAllocator)) if(isAllocator!Allocator) { 68 69 import automem.traits: isGlobal, isSingleton, isTheAllocator; 70 import std.traits: Unqual; 71 72 alias MutE = Unqual!E; 73 enum isElementMutable = !is(E == immutable) && !is(E == const); 74 75 static if(isGlobal!Allocator) { 76 77 this(E[] elements...) { 78 fromElements(elements); 79 } 80 81 this(R)(R range) if(isInputRangeOf!(R, E)) { 82 this = range; 83 } 84 85 } else { 86 87 this(Allocator allocator, E[] elements...) { 88 _allocator = allocator; 89 fromElements(elements); 90 } 91 92 this(R)(Allocator allocator, R range) if(isInputRangeOf!(R, E)) { 93 _allocator = allocator; 94 this = range; 95 } 96 } 97 98 this(this) scope { 99 auto oldElements = _elements; 100 _elements = createVector(_elements.length); 101 () @trusted { 102 cast(MutE[])(_elements)[0 .. length.toSizeT] = oldElements[0 .. length.toSizeT]; 103 }(); 104 } 105 106 ~this() { 107 free; 108 } 109 110 /// Frees the memory and returns to .init 111 void free() scope { 112 import stdx.allocator: dispose; 113 () @trusted { _allocator.dispose(cast(void[]) _elements); }(); 114 clear; 115 } 116 117 /// Returns the first element 118 inout(E) front() inout { 119 return _elements[0]; 120 } 121 122 /// Returns the last element 123 inout(E) back() inout { 124 return _elements[(length - 1).toSizeT]; 125 } 126 127 static if(isElementMutable) { 128 /// Pops the front element off 129 void popFront() { 130 foreach(i; 0 .. length - 1) 131 _elements[i.toSizeT] = _elements[i.toSizeT + 1]; 132 133 popBack; 134 } 135 } 136 137 /// Pops the last element off 138 void popBack() { 139 --_length; 140 } 141 142 /// If the vector is empty 143 bool empty() const { 144 return length == 0; 145 } 146 147 /// The current length of the vector 148 @property long length() const { 149 return _length; 150 } 151 152 /// Set the length of the vector 153 @property void length(long newLength) { 154 if(capacity < newLength) reserve(newLength); 155 _length = newLength; 156 } 157 158 /// The current memory capacity of the vector 159 long capacity() const { 160 return _elements.length; 161 } 162 163 /// Clears the vector, resulting in an empty one 164 void clear() { 165 _length = 0; 166 } 167 168 /// Reserve memory to avoid allocations when appending 169 void reserve(long newLength) { 170 expandMemory(newLength); 171 } 172 173 static if(isElementMutable) { 174 175 /// Shrink to fit the current length. Returns if shrunk. 176 bool shrink() scope { 177 return shrink(length); 178 } 179 180 /// Shrink to fit the new length given. Returns if shrunk. 181 bool shrink(long newLength) scope @trusted { 182 import stdx.allocator: shrinkArray; 183 184 const delta = capacity - newLength; 185 const shrunk = _allocator.shrinkArray(_elements, delta.toSizeT); 186 _length = newLength; 187 188 return shrunk; 189 } 190 } 191 192 /// Access the ith element. Can throw RangeError. 193 ref inout(E) opIndex(long i) inout { 194 if(i < 0 || i >= length) 195 throw boundsException; 196 return _elements[i.toSizeT]; 197 } 198 199 /// Returns a new vector after appending to the given vector. 200 Vector opBinary(string s, T)(auto ref T other) const if(s == "~" && is(Unqual!T == Vector)) { 201 import std.range: chain; 202 return Vector(chain(this[], other[])); 203 } 204 205 /// Assigns from a range. 206 void opAssign(R)(R range) scope if(isForwardRangeOf!(R, E)) { 207 import std.range.primitives: walkLength, save; 208 209 expand(range.save.walkLength); 210 211 long i = 0; 212 foreach(element; range) 213 _elements[toSizeT(i++)] = element; 214 } 215 216 /// Append to the vector 217 void opOpAssign(string op) 218 (E other) 219 scope 220 if(op == "~") 221 { 222 expand(length + 1); 223 224 const lastIndex = (length - 1).toSizeT; 225 static if(!isElementMutable) { 226 assert(_elements[lastIndex] == E.init, 227 "Assigning to non default initialised non mutable member"); 228 } 229 230 () @trusted { mutableElements[lastIndex] = other; }(); 231 } 232 233 /// Append to the vector from a range 234 void opOpAssign(string op, R) 235 (scope R range) 236 scope 237 if(op == "~" && isForwardRangeOf!(R, E)) 238 { 239 import std.range.primitives: walkLength, save; 240 241 long index = length; 242 expand(length + () @trusted { return range.save.walkLength; }()); 243 244 foreach(element; range) { 245 const safeIndex = toSizeT(index++); 246 static if(!isElementMutable) { 247 assert(_elements[safeIndex] == E.init, 248 "Assigning to non default initialised non mutable member"); 249 } 250 () @trusted { mutableElements[safeIndex] = element; }(); 251 } 252 } 253 254 /// Returns a slice 255 auto opSlice(this This)() scope return { 256 return _elements[0 .. length.toSizeT]; 257 } 258 259 /// Returns a slice 260 auto opSlice(this This)(long start, long end) scope return { 261 if(start < 0 || start >= length) 262 throw boundsException; 263 264 if(end < 0 || end >= length) 265 throw boundsException; 266 267 return _elements[start.toSizeT .. end.toSizeT]; 268 } 269 270 long opDollar() const { 271 return length; 272 } 273 274 static if(isElementMutable) { 275 /// Assign all elements to the given value 276 void opSliceAssign(E value) { 277 _elements[] = value; 278 } 279 } 280 281 282 static if(isElementMutable) { 283 /// Assign all elements in the given range to the given value 284 void opSliceAssign(E value, long start, long end) { 285 if(start < 0 || start >= length) 286 throw boundsException; 287 288 if(end < 0 || end >= length) 289 throw boundsException; 290 291 _elements[start.toSizeT .. end.toSizeT] = value; 292 } 293 } 294 295 static if(isElementMutable) { 296 /// Assign all elements using the given operation and the given value 297 void opSliceOpAssign(string op)(E value) scope { 298 foreach(ref elt; _elements) 299 mixin(`elt ` ~ op ~ `= value;`); 300 } 301 } 302 303 static if(isElementMutable) { 304 /// Assign all elements in the given range using the given operation and the given value 305 void opSliceOpAssign(string op)(E value, long start, long end) scope { 306 if(start < 0 || start >= length) 307 throw boundsException; 308 309 if(end < 0 || end >= length) 310 throw boundsException; 311 312 foreach(ref elt; _elements[start.toSizeT .. end.toSizeT]) 313 mixin(`elt ` ~ op ~ `= value;`); 314 } 315 } 316 317 bool opCast(U)() const scope if(is(U == bool)) { 318 return length > 0; 319 } 320 321 static if(is(Unqual!E == char)) { 322 // return a null-terminated C string 323 auto stringz(this This)() return scope { 324 if(capacity == length) reserve(length + 1); 325 326 static if(!isElementMutable) { 327 assert(_elements[length] == E.init || _elements[length] == 0, 328 "Assigning to non default initialised non mutable member"); 329 } 330 331 () @trusted { mutableElements[length] = 0; }(); 332 333 return &_elements[0]; 334 } 335 } 336 337 private: 338 339 E[] _elements; 340 long _length; 341 342 static if(isSingleton!Allocator) 343 alias _allocator = Allocator.instance; 344 else static if(isTheAllocator!Allocator) 345 alias _allocator = theAllocator; 346 else 347 Allocator _allocator; 348 349 E[] createVector(long length) { 350 import stdx.allocator: makeArray; 351 return () @trusted { return _allocator.makeArray!E(length.toSizeT); }(); 352 } 353 354 void fromElements(E[] elements) { 355 356 _elements = createVector(elements.length); 357 () @trusted { (cast(MutE[]) _elements)[] = elements[]; }(); 358 _length = elements.length; 359 } 360 361 void expand(long newLength) scope { 362 expandMemory(newLength); 363 _length = newLength; 364 } 365 366 void expandMemory(long newLength) scope { 367 import stdx.allocator: expandArray; 368 369 if(newLength > capacity) { 370 if(length == 0) 371 _elements = createVector(newLength); 372 else { 373 const newCapacity = (newLength * 3) / 2; 374 const delta = newCapacity - capacity; 375 () @trusted { _allocator.expandArray(mutableElements, delta.toSizeT); }(); 376 } 377 } 378 } 379 380 ref MutE[] mutableElements() scope return @system { 381 auto ptr = &_elements; 382 return *(cast(MutE[]*) ptr); 383 } 384 } 385 386 private static immutable boundsException = new BoundsException("Out of bounds index"); 387 388 class BoundsException: Exception { 389 import std.exception: basicExceptionCtors; 390 391 mixin basicExceptionCtors; 392 } 393 394 private template isInputRangeOf(R, E) { 395 import std.range.primitives: isInputRange; 396 enum isInputRangeOf = isInputRange!R && canAssignFrom!(R, E); 397 } 398 399 private template isForwardRangeOf(R, E) { 400 import std.range.primitives: isForwardRange; 401 enum isForwardRangeOf = isForwardRange!R && canAssignFrom!(R, E); 402 } 403 404 405 private template canAssignFrom(R, E) { 406 enum canAssignFrom = is(typeof({ 407 import automem.vector: frontNoAutoDecode; 408 E element = R.init.frontNoAutoDecode; 409 })); 410 } 411 private size_t toSizeT(long length) @safe @nogc pure nothrow { 412 static if(size_t.sizeof < long.sizeof) 413 assert(length < cast(long) size_t.max); 414 return cast(size_t) length; 415 } 416 417 // Because autodecoding is fun 418 private template ElementType(R) { 419 import std.traits: isSomeString; 420 421 static if(isSomeString!R) { 422 alias ElementType = typeof(R.init[0]); 423 } else { 424 import std.range.primitives: ElementType_ = ElementType; 425 alias ElementType = ElementType_!R; 426 } 427 } 428 429 @("ElementType") 430 @safe pure unittest { 431 import automem.vector: ElementType; 432 static assert(is(ElementType!(int[]) == int)); 433 static assert(is(ElementType!(char[]) == char)); 434 static assert(is(ElementType!(wchar[]) == wchar)); 435 static assert(is(ElementType!(dchar[]) == dchar)); 436 } 437 438 439 // More fun with autodecoding 440 private auto frontNoAutoDecode(R)(R range) { 441 import std.traits: isSomeString; 442 443 static if(isSomeString!R) 444 return range[0]; 445 else { 446 import std.range.primitives: front; 447 return range.front; 448 } 449 }