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, isCopyable; 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 static if(isCopyable!Allocator) { 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 } else { 97 98 this(R)(R range) if(isInputRangeOf!(R, E)) { 99 this = range; 100 } 101 102 } 103 104 this(this) scope { 105 auto oldElements = _elements; 106 _elements = createVector(_elements.length); 107 () @trusted { 108 cast(MutE[])(_elements)[0 .. length.toSizeT] = oldElements[0 .. length.toSizeT]; 109 }(); 110 } 111 112 ~this() { 113 free; 114 } 115 116 /// Frees the memory and returns to .init 117 void free() scope { 118 import stdx.allocator: dispose; 119 () @trusted { _allocator.dispose(cast(void[]) _elements); }(); 120 clear; 121 } 122 123 /// Returns the first element 124 inout(E) front() inout { 125 return _elements[0]; 126 } 127 128 /// Returns the last element 129 inout(E) back() inout { 130 return _elements[(length - 1).toSizeT]; 131 } 132 133 static if(isElementMutable) { 134 /// Pops the front element off 135 void popFront() { 136 foreach(i; 0 .. length - 1) 137 _elements[i.toSizeT] = _elements[i.toSizeT + 1]; 138 139 popBack; 140 } 141 } 142 143 /// Pops the last element off 144 void popBack() { 145 --_length; 146 } 147 148 /// If the vector is empty 149 bool empty() const { 150 return length == 0; 151 } 152 153 /// The current length of the vector 154 @property long length() const { 155 return _length; 156 } 157 158 /// Set the length of the vector 159 @property void length(long newLength) { 160 if(capacity < newLength) reserve(newLength); 161 _length = newLength; 162 } 163 164 /// The current memory capacity of the vector 165 long capacity() const { 166 return _elements.length; 167 } 168 169 /// Clears the vector, resulting in an empty one 170 void clear() { 171 _length = 0; 172 } 173 174 /// Reserve memory to avoid allocations when appending 175 void reserve(long newLength) { 176 expandMemory(newLength); 177 } 178 179 static if(isElementMutable) { 180 181 /// Shrink to fit the current length. Returns if shrunk. 182 bool shrink() scope { 183 return shrink(length); 184 } 185 186 /// Shrink to fit the new length given. Returns if shrunk. 187 bool shrink(long newLength) scope @trusted { 188 import stdx.allocator: shrinkArray; 189 190 const delta = capacity - newLength; 191 const shrunk = _allocator.shrinkArray(_elements, delta.toSizeT); 192 _length = newLength; 193 194 return shrunk; 195 } 196 } 197 198 /// Access the ith element. Can throw RangeError. 199 ref inout(E) opIndex(long i) inout { 200 if(i < 0 || i >= length) 201 mixin(throwBoundsException); 202 return _elements[i.toSizeT]; 203 } 204 205 /// Returns a new vector after appending to the given vector. 206 Vector opBinary(string s, T)(auto ref T other) const if(s == "~" && is(Unqual!T == Vector)) { 207 import std.range: chain; 208 return Vector(chain(this[], other[])); 209 } 210 211 /// Assigns from a range. 212 void opAssign(R)(R range) scope if(isForwardRangeOf!(R, E)) { 213 import std.range.primitives: walkLength, save; 214 215 expand(range.save.walkLength); 216 217 long i = 0; 218 foreach(element; range) 219 _elements[toSizeT(i++)] = element; 220 } 221 222 // make it an output range 223 224 225 /// Append to the vector 226 void opOpAssign(string op) 227 (E other) 228 scope 229 if(op == "~") 230 { 231 put(other); 232 } 233 234 void put(E other) { 235 236 expand(length + 1); 237 238 const lastIndex = (length - 1).toSizeT; 239 static if(!isElementMutable) { 240 assert(_elements[lastIndex] == E.init, 241 "Assigning to non default initialised non mutable member"); 242 } 243 244 () @trusted { mutableElements[lastIndex] = other; }(); 245 } 246 247 /// Append to the vector from a range 248 void opOpAssign(string op, R) 249 (scope R range) 250 scope 251 if(op == "~" && isForwardRangeOf!(R, E)) 252 { 253 put(range); 254 } 255 256 void put(R)(scope R range) if(isForwardRangeOf!(R, E)) { 257 import std.range.primitives: walkLength, save; 258 259 long index = length; 260 expand(length + () @trusted { return range.save.walkLength; }()); 261 262 foreach(element; range) { 263 const safeIndex = toSizeT(index++); 264 static if(!isElementMutable) { 265 assert(_elements[safeIndex] == E.init, 266 "Assigning to non default initialised non mutable member"); 267 } 268 () @trusted { mutableElements[safeIndex] = element; }(); 269 } 270 } 271 272 /// Returns a slice 273 auto opSlice(this This)() scope return { 274 return _elements[0 .. length.toSizeT]; 275 } 276 277 /// Returns a slice 278 auto opSlice(this This)(long start, long end) scope return { 279 if(start < 0 || start >= length) 280 mixin(throwBoundsException); 281 282 if(end < 0 || end >= length) 283 mixin(throwBoundsException); 284 285 return _elements[start.toSizeT .. end.toSizeT]; 286 } 287 288 long opDollar() const { 289 return length; 290 } 291 292 static if(isElementMutable) { 293 /// Assign all elements to the given value 294 void opSliceAssign(E value) { 295 _elements[] = value; 296 } 297 } 298 299 300 static if(isElementMutable) { 301 /// Assign all elements in the given range to the given value 302 void opSliceAssign(E value, long start, long end) { 303 if(start < 0 || start >= length) 304 mixin(throwBoundsException); 305 306 if(end < 0 || end >= length) 307 mixin(throwBoundsException); 308 309 _elements[start.toSizeT .. end.toSizeT] = value; 310 } 311 } 312 313 static if(isElementMutable) { 314 /// Assign all elements using the given operation and the given value 315 void opSliceOpAssign(string op)(E value) scope { 316 foreach(ref elt; _elements) 317 mixin(`elt ` ~ op ~ `= value;`); 318 } 319 } 320 321 static if(isElementMutable) { 322 /// Assign all elements in the given range using the given operation and the given value 323 void opSliceOpAssign(string op)(E value, long start, long end) scope { 324 if(start < 0 || start >= length) 325 mixin(throwBoundsException); 326 327 if(end < 0 || end >= length) 328 mixin(throwBoundsException); 329 330 foreach(ref elt; _elements[start.toSizeT .. end.toSizeT]) 331 mixin(`elt ` ~ op ~ `= value;`); 332 } 333 } 334 335 bool opCast(U)() const scope if(is(U == bool)) { 336 return length > 0; 337 } 338 339 static if(is(Unqual!E == char)) { 340 // return a null-terminated C string 341 auto stringz(this This)() return scope { 342 if(capacity == length) reserve(length + 1); 343 344 static if(!isElementMutable) { 345 assert(_elements[length.toSizeT] == E.init || _elements[length.toSizeT] == 0, 346 "Assigning to non default initialised non mutable member"); 347 } 348 349 () @trusted { mutableElements[length.toSizeT] = 0; }(); 350 351 return &_elements[0]; 352 } 353 } 354 355 auto ptr(this This)() return scope { 356 return &_elements[0]; 357 } 358 359 private: 360 361 E[] _elements; 362 long _length; 363 364 static if(isSingleton!Allocator) 365 alias _allocator = Allocator.instance; 366 else static if(isTheAllocator!Allocator) 367 alias _allocator = theAllocator; 368 else 369 Allocator _allocator; 370 371 E[] createVector(long length) scope { 372 import stdx.allocator: makeArray; 373 return () @trusted { return _allocator.makeArray!E(length.toSizeT); }(); 374 } 375 376 void fromElements(E[] elements) { 377 378 _elements = createVector(elements.length); 379 () @trusted { (cast(MutE[]) _elements)[] = elements[]; }(); 380 _length = elements.length; 381 } 382 383 void expand(long newLength) scope { 384 expandMemory(newLength); 385 _length = newLength; 386 } 387 388 void expandMemory(long newLength) scope { 389 import stdx.allocator: expandArray; 390 391 if(newLength > capacity) { 392 if(length == 0) 393 _elements = createVector(newLength); 394 else { 395 const newCapacity = (newLength * 3) / 2; 396 const delta = newCapacity - capacity; 397 () @trusted { _allocator.expandArray(mutableElements, delta.toSizeT); }(); 398 } 399 } 400 } 401 402 ref MutE[] mutableElements() scope return @system { 403 auto ptr = &_elements; 404 return *(cast(MutE[]*) ptr); 405 } 406 } 407 408 static if (__VERSION__ >= 2082) // version identifier D_Exceptions was added in 2.082 409 { 410 version (D_Exceptions) 411 private enum haveExceptions = true; 412 else 413 private enum haveExceptions = false; 414 } 415 else 416 { 417 version (D_BetterC) 418 private enum haveExceptions = false; 419 else 420 private enum haveExceptions = true; 421 } 422 423 static if (haveExceptions) 424 { 425 private static immutable boundsException = new BoundsException("Out of bounds index"); 426 private enum throwBoundsException = q{throw boundsException;}; 427 class BoundsException: Exception { 428 import std.exception: basicExceptionCtors; 429 430 mixin basicExceptionCtors; 431 } 432 } 433 else 434 { 435 private enum throwBoundsException = q{assert(0, "Out of bounds index");}; 436 } 437 438 private template isInputRangeOf(R, E) { 439 import std.range.primitives: isInputRange; 440 enum isInputRangeOf = isInputRange!R && canAssignFrom!(R, E); 441 } 442 443 private template isForwardRangeOf(R, E) { 444 import std.range.primitives: isForwardRange; 445 enum isForwardRangeOf = isForwardRange!R && canAssignFrom!(R, E); 446 } 447 448 449 private template canAssignFrom(R, E) { 450 enum canAssignFrom = is(typeof({ 451 import automem.vector: frontNoAutoDecode; 452 E element = R.init.frontNoAutoDecode; 453 })); 454 } 455 private size_t toSizeT(long length) @safe @nogc pure nothrow { 456 static if(size_t.sizeof < long.sizeof) 457 assert(length < cast(long) size_t.max); 458 return cast(size_t) length; 459 } 460 461 // Because autodecoding is fun 462 private template ElementType(R) { 463 import std.traits: isSomeString; 464 465 static if(isSomeString!R) { 466 alias ElementType = typeof(R.init[0]); 467 } else { 468 import std.range.primitives: ElementType_ = ElementType; 469 alias ElementType = ElementType_!R; 470 } 471 } 472 473 @("ElementType") 474 @safe pure unittest { 475 import automem.vector: ElementType; 476 static assert(is(ElementType!(int[]) == int)); 477 static assert(is(ElementType!(char[]) == char)); 478 static assert(is(ElementType!(wchar[]) == wchar)); 479 static assert(is(ElementType!(dchar[]) == dchar)); 480 } 481 482 483 // More fun with autodecoding 484 private auto frontNoAutoDecode(R)(R range) { 485 import std.traits: isSomeString; 486 487 static if(isSomeString!R) 488 return range[0]; 489 else { 490 import std.range.primitives: front; 491 return range.front; 492 } 493 }