1 /** Array container(s) with optional sortedness via template-parameter 2 * `Ordering` and optional use of GC via `useGCAllocation`. 3 * 4 * TODO: UniqueArray!(const(E)) where E has indirections 5 * 6 * TODO: Support for constructing from r-value range (container) of non-copyable 7 * elements. 8 * 9 * TODO: Add some way to implement lazy sorting either for the whole array (via 10 * bool flag) or completeSort at a certain offset (extra member). 11 * 12 * TODO: Replace ` = void` with construction or emplace 13 * 14 * TODO: Break out common logic into private `DynamicArray` and reuse with `alias 15 * this` to express StandardArray, SortedArray, SortedSetArray 16 * 17 * TODO: Use std.array.insertInPlace in insert()? 18 * TODO: Use std.array.replaceInPlace? 19 * 20 * TODO: Use `std.algorithm.mutation.move` and `std.range.primitives.moveAt` 21 * when moving internal sub-slices 22 * 23 * TODO: Add `c.insertAfter(r, x)` where `c` is a collection, `r` is a range 24 * previously extracted from `c`, and `x` is a value convertible to 25 * collection's element type. See_Also: 26 * https://forum.dlang.org/post/n3qq6e$2bis$1@digitalmars.com 27 * 28 * TODO: replace qcmeman with std.experimental.allocator parameter defaulting to 29 * `Mallocator` 30 * 31 * TODO: use `integer_sorting.radixSort` when element type `isSigned` or `isUnsigned` and 32 * above or below a certain threshold calculated by my benchmarks 33 * 34 * TODO: Remove explicit moves when DMD std.algorithm.mutation.move calls these 35 * members for us (if they exist) 36 */ 37 module nxt.array_ex; 38 39 /// Array element ordering. 40 enum Ordering 41 { 42 unsorted, // unsorted array 43 sortedValues, // sorted array with possibly duplicate values 44 sortedUniqueSet, // sorted array with unique values 45 } 46 47 /// Is `true` iff `ordering` is sorted. 48 enum isOrdered(Ordering ordering) = ordering != Ordering.unsorted; 49 50 version(unittest) 51 { 52 import std.algorithm.iteration : map, filter; 53 import std.algorithm.comparison : equal; 54 import std.conv : to; 55 import std.meta : AliasSeq; 56 import core.internal.traits : Unqual; 57 import nxt.dbgio : dbg; 58 import nxt.array_help : s; 59 } 60 61 import nxt.container_traits : ContainerElementType, needsMove; 62 63 /// Is `true` iff `C` is an instance of an `Array` container. 64 template isArrayContainer(C) 65 { 66 import std.traits : isInstanceOf; 67 enum isArrayContainer = isInstanceOf!(Array, C); 68 } 69 70 /// Semantics of copy construction and copy assignment. 71 enum Assignment 72 { 73 disabled, /// for reference counting use `std.typecons.RefCounted`. for safe slicing use `borrown` 74 move, /// only move construction allowed 75 copy /// always copy (often not the desirable) 76 } 77 78 /** Array of value types `E` with optional sortedness/ordering. 79 * 80 * Always `@safe pure nothrow @nogc` when possible. 81 * 82 * `Assignment` either 83 * - is disabled 84 * - does Rust-style move, or 85 * - does C++-style copying 86 * 87 * Params: 88 * GCAllocation = `true` iff `GC.malloc` is used for store allocation, 89 * otherwise C's `{m,ce,re}alloc()` is used. 90 */ 91 private struct Array(E, 92 // TODO: merge these flags into one to reduce template bloat 93 Assignment assignment = Assignment.disabled, 94 Ordering ordering = Ordering.unsorted, 95 bool useGCAllocation = false, 96 Capacity = size_t, // see also https://github.com/izabera/s 97 alias less = "a < b") // TODO: move out of this definition and support only for the case when `ordering` is not `Ordering.unsorted` 98 if (is(Capacity == ulong) || // 3 64-bit words 99 is(Capacity == uint)) // 2 64-bit words 100 { 101 import core.internal.traits : hasElaborateDestructor; 102 import core.lifetime : emplace, move, moveEmplace; 103 import std.algorithm.mutation : moveEmplaceAll; 104 import std.range.primitives : isInputRange, isInfinite, ElementType; 105 import std.traits : isIterable, isAssignable, Unqual, isArray, isScalarType, hasIndirections, TemplateOf; 106 import std.functional : binaryFun; 107 import std.meta : allSatisfy; 108 109 import nxt.qcmeman : malloc, calloc, realloc, free, gc_addRange, gc_removeRange; 110 111 private template shouldAddGCRange(T) 112 { 113 import std.traits : hasIndirections, isInstanceOf; 114 enum shouldAddGCRange = hasIndirections!T && !isInstanceOf!(Array, T); // TODO: unify to container_traits.shouldAddGCRange 115 } 116 117 /// Mutable element type. 118 private alias MutableE = Unqual!E; 119 120 /// Template for type of `this`. 121 private alias ThisTemplate = TemplateOf!(typeof(this)); 122 123 /// Same type as this but with mutable element type. 124 private alias MutableThis = ThisTemplate!(MutableE, assignment, ordering, useGCAllocation, Capacity, less); 125 126 static if (useGCAllocation || // either we asked for allocation 127 shouldAddGCRange!E) // or we need GC ranges 128 { 129 import core.memory : GC; 130 } 131 132 /// Is `true` iff `Array` can be interpreted as a narrow D `string` or `wstring`. 133 private enum isNarrowString = (is(MutableE == char) || 134 is(MutableE == wchar)); 135 136 static if (isOrdered!ordering) 137 { 138 static assert(!isNarrowString, "A narrow string cannot be an ordered array because it's not random access'"); 139 } 140 141 alias comp = binaryFun!less; //< comparison 142 143 /// Create a empty array. 144 // this(typeof(null)) nothrow 145 // { 146 // version(showCtors) dbg("ENTERING: smallCapacity:", smallCapacity, " @", __PRETTY_FUNCTION__); 147 // // nothing needed, rely on default initialization of data members 148 // } 149 150 /// Returns: an array of length `initialLength` with all elements default-initialized to `ElementType.init`. 151 static typeof(this) withLength(size_t initialLength) 152 @trusted 153 { 154 version(showCtors) dbg("ENTERING: smallCapacity:", smallCapacity, " @", __PRETTY_FUNCTION__); 155 156 debug { typeof(return) that; } 157 else { typeof(return) that = void; } 158 159 // TODO: functionize: 160 if (initialLength > smallCapacity) 161 { 162 emplace!Large(&that._large, initialLength, initialLength, true); // no elements so we need to zero 163 } 164 else 165 { 166 that._small.length = cast(ubyte)initialLength; 167 } 168 169 version(showCtors) dbg("EXITING: ", __PRETTY_FUNCTION__); 170 return that; 171 } 172 173 /// Returns: an array with initial capacity `initialCapacity`. 174 static typeof(this) withCapacity(size_t initialCapacity) 175 @trusted 176 { 177 version(showCtors) dbg("ENTERING: smallCapacity:", smallCapacity, " @", __PRETTY_FUNCTION__); 178 179 debug { typeof(return) that; } 180 else { typeof(return) that = void; } 181 182 if (initialCapacity > smallCapacity) 183 { 184 emplace!Large(&that._large, initialCapacity, 0, false); 185 } 186 else 187 { 188 that._small.length = 0; 189 } 190 191 version(showCtors) dbg("EXITING: ", __PRETTY_FUNCTION__); 192 return that; 193 } 194 195 /// Returns: an array of one element `element`. 196 static typeof(this) withElement(E element) 197 @trusted 198 { 199 version(showCtors) dbg("ENTERING: smallCapacity:", smallCapacity, " @", __PRETTY_FUNCTION__); 200 201 debug { typeof(return) that; } 202 else { typeof(return) that = void; } 203 204 // TODO: functionize: 205 enum initialLength = 1; 206 if (initialLength > smallCapacity) 207 { 208 emplace!Large(&that._large, initialLength, initialLength, false); 209 } 210 else 211 { 212 emplace!Small(&that._small, initialLength, false); 213 } 214 215 // move element 216 static if (__traits(isCopyable, E)) 217 { 218 that._mptr[0] = element; 219 } 220 else static if (!shouldAddGCRange!E) 221 { 222 moveEmplace(*cast(MutableE*)&element, // TODO: can we prevent this cast? 223 that._mptr[0]); // safe to cast away constness when no indirections 224 } 225 else 226 { 227 moveEmplace(element, that._mptr[0]); // TODO: remove `move` when compiler does it for us 228 } 229 230 return that; 231 } 232 233 /// Returns: an array of `Us.length` number of elements set to `elements`. 234 static typeof(this) withElements(Us...)(Us elements) 235 @trusted 236 { 237 version(showCtors) dbg("ENTERING: smallCapacity:", smallCapacity, " @", __PRETTY_FUNCTION__); 238 239 debug { typeof(return) that; } 240 else { typeof(return) that = void; } 241 242 // TODO: functionize: 243 enum initialLength = Us.length; 244 if (initialLength > smallCapacity) 245 { 246 emplace!Large(&that._large, initialLength, initialLength, false); 247 } 248 else 249 { 250 emplace!Small(&that._small, initialLength, false); 251 } 252 253 // move elements 254 foreach (immutable i, ref element; elements) 255 { 256 static if (!shouldAddGCRange!E) 257 { 258 moveEmplace(*cast(MutableE*)&element, 259 that._mptr[i]); // safe to cast away constness when no indirections 260 } 261 else 262 { 263 moveEmplace(element, that._mptr[i]); // TODO: remove `move` when compiler does it for us 264 } 265 } 266 267 static if (isOrdered!ordering) 268 { 269 that.sortElements!comp(); 270 } 271 272 return that; 273 } 274 275 static if (assignment == Assignment.copy) 276 { 277 /// Copy construction. 278 this(this) @trusted 279 { 280 version(showCtors) dbg("Copy ctor: ", typeof(this).stringof); 281 if (isLarge) // only large case needs special treatment 282 { 283 auto rhs_storePtr = _large.ptr; // save store pointer 284 _large.setCapacity(this.length); // pack by default 285 // _large.length already copied 286 _large.ptr = allocate(this.length, false); 287 foreach (immutable i; 0 .. this.length) 288 { 289 _large.ptr[i] = rhs_storePtr[i]; 290 } 291 } 292 } 293 294 /// Copy assignment. 295 void opAssign(typeof(this) rhs) @trusted 296 { 297 version(showCtors) dbg("Copy assign: ", typeof(this).stringof); 298 // self-assignment may happen when assigning derefenced pointer 299 if (isLarge) // large = ... 300 { 301 if (rhs.isLarge) // large = large 302 { 303 // TODO: functionize to Large.opAssign(Large rhs): 304 if (_large.ptr != rhs._large.ptr) // if not self assignment 305 { 306 _large.length = rhs._large.length; 307 reserve(rhs._large.length); 308 foreach (immutable i; 0 .. rhs._large.length) 309 { 310 _large.ptr[i] = rhs._large.ptr[i]; 311 } 312 } 313 } 314 else // large = small 315 { 316 { // make it small 317 clear(); // clear large storage 318 _large.isLarge = false; // TODO: needed? 319 } 320 _small = rhs._small; // small 321 } 322 } 323 else // small = ... 324 { 325 if (rhs.isLarge) // small = large 326 { 327 { // make it large 328 clear(); // clear small storage 329 _large.isLarge = true; // TODO: needed? 330 } 331 // TODO: functionize to Large.opAssign(Large rhs): 332 if (_large.ptr != rhs._large.ptr) // if not self assignment 333 { 334 _large.length = rhs._large.length; 335 reserve(rhs._large.length); 336 foreach (immutable i; 0 .. rhs._large.length) 337 { 338 _large.ptr[i] = rhs._large.ptr[i]; 339 } 340 } 341 } 342 else // small = small 343 { 344 _small = rhs._small; 345 } 346 } 347 } 348 } 349 else static if (assignment == Assignment.disabled) 350 { 351 @disable this(this); 352 } 353 else static if (assignment == Assignment.move) 354 { 355 /// Copy ctor moves. 356 this(typeof(this) rhs) @trusted 357 { 358 version(showCtors) dbg("Copying: ", typeof(this).stringof); 359 assert(!isBorrowed); 360 moveEmplace(rhs, this); // TODO: remove `move` when compiler does it for us 361 } 362 363 /// Assignment moves. 364 void opAssign(typeof(this) rhs) @trusted 365 { 366 assert(!isBorrowed); 367 import std.algorith.mutation : move; 368 move(rhs, this); // TODO: remove `move` when compiler does it for us 369 } 370 } 371 372 static if (__traits(isCopyable, E)) 373 { 374 /// Returns: shallow duplicate of `this`. 375 @property MutableThis dup() const @trusted // `MutableThis` mimics behaviour of `dup` for builtin D arrays 376 { 377 debug { typeof(return) that; } 378 else { typeof(return) that = void; } 379 380 if (isLarge) 381 { 382 emplace!(that.Large)(&that._large, _large.length, _large.length, false); 383 foreach (immutable i; 0 .. _large.length) 384 { 385 // TODO: is there a more standardized way of solving this other than this hacky cast? 386 that._large.ptr[i] = (cast(E*)_large.ptr)[i]; 387 } 388 } 389 else 390 { 391 emplace!(that.Small)(&that._small, _small.length, false); 392 // TODO: is there a more standardized way of solving this other than this hacky cast? 393 that._small.elms[0 .. _small.length] = (cast(E[_small.elms.length])_small.elms)[0 .. _small.length]; // copy elements 394 } 395 return that; 396 } 397 } 398 399 bool opEquals(in ref typeof(this) rhs) const 400 @trusted 401 { 402 static if (__traits(isCopyable, E)) 403 { 404 return this[] == rhs[]; // TODO: fix DMD to make this work for non-copyable aswell 405 } 406 else 407 { 408 if (this.length != rhs.length) { return false; } 409 foreach (immutable i; 0 .. this.length) 410 { 411 if (this.ptr[i] != rhs.ptr[i]) { return false; } 412 } 413 return true; 414 } 415 } 416 bool opEquals(in typeof(this) rhs) const 417 @trusted 418 { 419 static if (__traits(isCopyable, E)) 420 { 421 return this[] == rhs[]; // TODO: fix DMD to make this work for non-copyable aswell 422 } 423 else 424 { 425 if (this.length != rhs.length) { return false; } 426 foreach (immutable i; 0 .. this.length) 427 { 428 if (this.ptr[i] != rhs.ptr[i]) { return false; } 429 } 430 return true; 431 } 432 } 433 434 /// Compare with range `R` with comparable element type. 435 pragma(inline, true) 436 bool opEquals(R)(R rhs) const 437 if (isInputRange!R && !isInfinite!R) 438 { 439 return opSlice.equal(rhs); 440 } 441 442 /// Calculate D associative array (AA) key hash. 443 hash_t toHash() const // cannot currently be template-lazy 444 nothrow @trusted 445 { 446 pragma(msg, "WARNING: using toHash() when we should use toDigest instead"); 447 import core.internal.hash : hashOf; 448 static if (__traits(isCopyable, E)) 449 { 450 return this.length ^ hashOf(slice()); 451 } 452 else 453 { 454 typeof(return) hash = this.length; 455 foreach (immutable i; 0 .. this.length) 456 { 457 hash ^= this.ptr[i].hashOf; 458 } 459 return hash; 460 } 461 } 462 463 /** Construct from InputRange `values`. 464 If `values` are sorted `assumeSortedParameter` is `true`. 465 466 TODO: Have `assumeSortedParameter` only when `isOrdered!ordering` is true 467 */ 468 this(R)(R values, bool assumeSortedParameter = false) 469 @trusted 470 @("complexity", "O(n*log(n))") 471 if (isIterable!R) 472 { 473 version(showCtors) dbg("ENTERING: smallCapacity:", smallCapacity, " @", __PRETTY_FUNCTION__); 474 475 // append new data 476 import std.range.primitives : hasLength; 477 static if (hasLength!R) 478 { 479 // TODO: choose large or small depending on values.length 480 _small.isLarge = false; 481 _small.length = 0; 482 483 reserve(values.length); // fast reserve 484 setOnlyLength(values.length); 485 486 // static if (__traits(isRef)) 487 // { 488 // // TODO: dup elements 489 // } 490 // else 491 // { 492 // // TODO: move elements 493 // } 494 495 size_t i = 0; 496 foreach (ref value; move(values)) // TODO: remove `move` when compiler does it for us 497 { 498 // TODO: functionize: 499 static if (needsMove!(typeof(value))) 500 moveEmplace(value, _mptr[i++]); 501 else 502 _mptr[i++] = value; 503 } 504 } 505 else 506 { 507 // always start small 508 _small.isLarge = false; 509 _small.length = 0; 510 511 size_t i = 0; 512 foreach (ref value; move(values)) // TODO: remove `move` when compiler does it for us 513 { 514 reserve(i + 1); // slower reserve 515 // TODO: functionize: 516 static if (needsMove!(typeof(value))) 517 moveEmplace(value, _mptr[i++]); 518 else 519 _mptr[i++] = value; 520 setOnlyLength(i); // must be set here because correct length is needed in reserve call above in this same scope 521 } 522 } 523 524 if (!assumeSortedParameter) 525 { 526 static if (isOrdered!ordering) 527 sortElements!comp(); 528 static if (ordering == Ordering.sortedUniqueSet) 529 { 530 import std.algorithm.iteration : uniq; 531 size_t j = 0; 532 foreach (ref e; uniq(slice)) 533 { 534 auto ePtr = &e; 535 const separate = ePtr != &_mptr[j]; 536 if (separate) 537 move(*cast(MutableE*)ePtr, _mptr[j]); 538 ++j; 539 } 540 shrinkTo(j); 541 } 542 } 543 544 version(showCtors) dbg("EXITING: ", __PRETTY_FUNCTION__); 545 } 546 547 /// Sort all elements in-place regardless of `ordering`. 548 private void sortElements(alias comp_)() 549 @trusted 550 { 551 import std.algorithm.sorting : sort; 552 sort!comp_(_mptr[0 .. length]); 553 } 554 555 /// Reserve room for `newCapacity`. 556 void reserve(size_t newCapacity) 557 pure @trusted 558 { 559 assert(!isBorrowed); 560 if (newCapacity <= capacity) 561 return; 562 if (isLarge) 563 { 564 static if (shouldAddGCRange!E) 565 gc_removeRange(_mptr); 566 static if (__VERSION__ >= 2098) import std.math.algebraic : nextPow2; else import std.math : nextPow2; 567 reallocateLargeStoreAndSetCapacity(newCapacity.nextPow2); 568 static if (shouldAddGCRange!E) 569 gc_addRange(_mptr, _large.capacity * E.sizeof); 570 } 571 else 572 { 573 if (newCapacity > smallCapacity) // convert to large 574 { 575 auto tempLarge = Large(newCapacity, length, false); 576 577 // move small to temporary large 578 foreach (immutable i; 0 .. length) 579 moveEmplace(_small._mptr[i], 580 tempLarge._mptr[i]); 581 582 static if (hasElaborateDestructor!E) 583 destroyElements(); 584 585 // TODO: functionize: 586 { // make this large 587 moveEmplace(tempLarge, _large); 588 } 589 590 assert(isLarge); 591 } 592 else 593 { 594 // staying small 595 } 596 } 597 } 598 599 /// Pack/Compress storage. 600 void compress() 601 pure @trusted 602 { 603 assert(!isBorrowed); 604 if (isLarge) 605 { 606 if (this.length) 607 { 608 if (this.length <= smallCapacity) 609 { 610 Small tempSmall = Small(length, false); 611 612 // move elements to temporary small. TODO: make moveEmplaceAll work on char[],char[] and use 613 foreach (immutable i; 0 .. length) 614 moveEmplace(_large._mptr[i], 615 tempSmall._mptr[i]); 616 617 // free existing large data 618 static if (shouldAddGCRange!E) 619 gc_removeRange(_mptr); 620 621 static if (useGCAllocation) 622 GC.free(_mptr); 623 else 624 free(_mptr); 625 626 moveEmplace(tempSmall, _small); 627 } 628 else 629 { 630 if (_large.capacity != this.length) 631 { 632 static if (shouldAddGCRange!E) 633 gc_removeRange(_mptr); 634 reallocateLargeStoreAndSetCapacity(this.length); 635 static if (shouldAddGCRange!E) 636 gc_addRange(_mptr, _large.capacity * E.sizeof); 637 } 638 } 639 } 640 else // if empty 641 { 642 // free data 643 static if (shouldAddGCRange!E) 644 gc_removeRange(_mptr); 645 646 static if (useGCAllocation) 647 GC.free(_mptr); 648 else 649 free(_mptr); 650 651 _large.capacity = 0; 652 _large.ptr = null; 653 } 654 } 655 } 656 /// ditto 657 alias pack = compress; 658 659 /// Reallocate storage. TODO: move to Large.reallocateAndSetCapacity 660 private void reallocateLargeStoreAndSetCapacity(size_t newCapacity) pure @trusted 661 { 662 version(D_Coverage) {} else pragma(inline, true); 663 _large.setCapacity(newCapacity); 664 static if (useGCAllocation) 665 _large.ptr = cast(E*)GC.realloc(_mptr, E.sizeof * _large.capacity); 666 else // @nogc 667 { 668 _large.ptr = cast(E*)realloc(_mptr, E.sizeof * _large.capacity); 669 assert(_large.ptr, "Reallocation failed"); 670 } 671 } 672 673 /// Destruct. 674 ~this() @trusted @nogc 675 { 676 version(D_Coverage) {} else pragma(inline, true); 677 assert(!isBorrowed); 678 if (isLarge) 679 debug assert(_large.ptr != _ptrMagic, "Double free."); // trigger fault for double frees 680 release(); 681 if (isLarge) 682 debug _large.ptr = _ptrMagic; // tag as freed 683 } 684 685 /// Empty. 686 void clear() @nogc 687 { 688 version(D_Coverage) {} else pragma(inline, true); 689 assert(!isBorrowed); 690 release(); 691 resetInternalData(); 692 } 693 /// ditto 694 pragma(inline, true) 695 void opAssign(typeof(null)) 696 { 697 clear(); 698 } 699 700 /// Destroy elements. 701 static if (hasElaborateDestructor!E) 702 { 703 private void destroyElements() @trusted 704 { 705 foreach (immutable i; 0 .. this.length) 706 .destroy(_mptr[i]); 707 } 708 } 709 710 /// Release internal store. 711 private void release() @trusted @nogc 712 { 713 static if (hasElaborateDestructor!E) 714 destroyElements(); 715 if (isLarge) 716 { 717 static if (shouldAddGCRange!E) 718 gc_removeRange(_large.ptr); 719 720 static if (useGCAllocation) 721 GC.free(_large.ptr); 722 else // @nogc 723 { 724 static if (!shouldAddGCRange!E) 725 free(cast(MutableE*)_large.ptr); // safe to case away constness 726 else 727 free(_large.ptr); 728 } 729 } 730 } 731 732 /// Reset internal data. 733 pragma(inline, true) 734 private void resetInternalData() @trusted pure @nogc 735 { 736 if (isLarge) 737 { 738 _large.ptr = null; 739 _large.length = 0; 740 _large.capacity = 0; 741 } 742 else 743 _small.length = 0; // fast discardal 744 } 745 746 /// Is `true` if `U` can be assign to the element type `E` of `this`. 747 enum isElementAssignable(U) = isAssignable!(E, U); 748 749 /** Removal doesn't need to care about ordering. */ 750 ContainerElementType!(typeof(this), E) removeAt(size_t index) 751 @trusted 752 @("complexity", "O(length)") 753 { 754 assert(!isBorrowed); 755 assert(index < this.length); 756 auto value = move(_mptr[index]); 757 758 // TODO: use this instead: 759 // immutable si = index + 1; // source index 760 // immutable ti = index; // target index 761 // immutable restLength = this.length - (index + 1); 762 // moveEmplaceAll(_mptr[si .. si + restLength], 763 // _mptr[ti .. ti + restLength]); 764 765 foreach (immutable i; 0 .. this.length - (index + 1)) // each element index that needs to be moved 766 { 767 immutable si = index + i + 1; // source index 768 immutable ti = index + i; // target index 769 moveEmplace(_mptr[si], // TODO: remove `move` when compiler does it for us 770 _mptr[ti]); 771 } 772 773 decOnlyLength(); 774 return move(value); // TODO: remove `move` when compiler does it for us 775 } 776 777 /** Removal doesn't need to care about ordering. */ 778 pragma(inline, true) 779 ContainerElementType!(typeof(this), E) popFront() 780 @trusted 781 @("complexity", "O(length)") 782 { 783 return removeAt(0); 784 } 785 786 /** Removal doesn't need to care about ordering. */ 787 pragma(inline) 788 void popBack() @("complexity", "O(1)") 789 { 790 assert(!isBorrowed); 791 assert(!empty); 792 decOnlyLength(); 793 } 794 795 /** Pop back element and return it. */ 796 pragma(inline) 797 E backPop() 798 @trusted 799 { 800 assert(!isBorrowed); 801 assert(!empty); 802 decOnlyLength(); 803 // TODO: functionize: 804 static if (needsMove!E) 805 return move(_mptr[this.length]); // move is indeed need here 806 else 807 return _mptr[this.length]; // no move needed 808 } 809 810 /** Pop last `count` back elements. */ 811 pragma(inline, true) 812 void popBackN(size_t count) @("complexity", "O(1)") 813 { 814 assert(!isBorrowed); 815 shrinkTo(this.length - count); 816 } 817 818 static if (!isOrdered!ordering) // for unsorted arrays 819 { 820 /// Push back (append) `values`. 821 pragma(inline) void insertBack(Us...)(Us values) 822 @trusted 823 @("complexity", "O(1)") 824 if (values.length >= 1 && 825 allSatisfy!(isElementAssignable, Us)) 826 { 827 assert(!isBorrowed); 828 immutable newLength = this.length + values.length; 829 reserve(newLength); 830 foreach (immutable i, ref value; values) // `ref` so we can `move` 831 { 832 // TODO: functionize: 833 static if (needsMove!(typeof(value))) 834 moveEmplace(*cast(MutableE*)&value, _mptr[this.length + i]); 835 else 836 _mptr[this.length + i] = value; 837 } 838 setOnlyLength(this.length + values.length); 839 } 840 841 /// ditto 842 void insertBack(R)(R values) @("complexity", "O(values.length)") @trusted 843 if (isInputRange!R && !isInfinite!R && 844 !(isArray!R) && 845 !(isArrayContainer!R) && 846 isElementAssignable!(ElementType!R)) 847 { 848 assert(!isBorrowed); 849 import std.range.primitives : hasLength; 850 static if (hasLength!R) 851 { 852 const nextLength = this.length + values.length; 853 reserve(nextLength); 854 size_t i = 0; 855 foreach (ref value; values) // `ref` so we can `move` 856 { 857 // TODO: functionize: 858 static if (needsMove!(typeof(value))) 859 moveEmplace(*cast(Mutable!E*)&value, _mptr[this.length + i]); 860 else 861 _mptr[this.length + i] = value; 862 ++i; 863 } 864 setOnlyLength(nextLength); 865 } 866 else 867 { 868 foreach (ref value; values) // `ref` so we can `move` 869 insertBack(value); 870 } 871 } 872 873 /// ditto. 874 void insertBack(A)(A values) @trusted @("complexity", "O(values.length)") 875 if (isArray!A && 876 (is(MutableE == Unqual!(typeof(A.init[0]))) || // for narrow strings 877 isElementAssignable!(ElementType!A))) 878 { 879 assert(!isBorrowed); 880 static if (is(A == immutable(E)[])) 881 { 882 // immutable array cannot overlap with mutable array container 883 // data; no need to check for overlap with `overlaps()` 884 reserve(this.length + values.length); 885 _mptr[this.length .. this.length + values.length] = values[]; 886 setOnlyLength(this.length + values.length); 887 } 888 else 889 { 890 import nxt.overlapping : overlaps; 891 if (this.ptr == values[].ptr) // called for instances as: `this ~= this` 892 { 893 reserve(2*this.length); 894 foreach (immutable i; 0 .. this.length) 895 _mptr[this.length + i] = ptr[i]; // needs copying 896 setOnlyLength(2 * this.length); 897 } 898 else if (overlaps(this[], values[])) 899 assert(0, `TODO: Handle overlapping arrays`); 900 else 901 { 902 reserve(this.length + values.length); 903 static if (is(MutableE == Unqual!(ElementType!A))) // TODO: also when `E[]` is `A[]` 904 _mptr[this.length .. this.length + values.length] = values[]; 905 else 906 foreach (immutable i, ref value; values) 907 _mptr[this.length + i] = value; 908 setOnlyLength(this.length + values.length); 909 } 910 } 911 } 912 913 /// ditto. 914 void insertBack(A)(in ref A values) @trusted @("complexity", "O(values.length)") // TODO: `in` parameter qualifier doesn't work here. Compiler bug? 915 if (isArrayContainer!A && 916 (is(MutableE == Unqual!(typeof(A.init[0]))) || // for narrow strings 917 isElementAssignable!(ElementType!A))) 918 { 919 insertBack(values[]); 920 } 921 alias put = insertBack; // OutputRange support 922 923 924 // NOTE these separate overloads of opOpAssign are needed because one 925 // `const ref`-parameter-overload doesn't work because of compiler bug 926 // with: `this(this) @disable` 927 pragma(inline) 928 void opOpAssign(string op, Us...)(Us values) 929 if (op == "~" && 930 values.length >= 1 && 931 allSatisfy!(isElementAssignable, Us)) 932 { 933 assert(!isBorrowed); 934 insertBack(move(values)); // TODO: remove `move` when compiler does it for us 935 // static if (values.length == 1) 936 // { 937 // import std.traits : hasIndirections; 938 // static if (hasIndirections!(Us[0])) 939 // { 940 // insertBack(move(values)); // TODO: remove `move` when compiler does it for us 941 // } 942 // else 943 // { 944 // insertBack(move(cast(Unqual!(Us[0]))values[0])); // TODO: remove `move` when compiler does it for us 945 // } 946 // } 947 // else 948 // { 949 // insertBack(move(values)); // TODO: remove `move` when compiler does it for us 950 // } 951 } 952 953 pragma(inline) 954 void opOpAssign(string op, R)(R values) 955 if (op == "~" && 956 isInputRange!R && 957 !isInfinite!R && 958 allSatisfy!(isElementAssignable, ElementType!R)) 959 { 960 assert(!isBorrowed); 961 // TODO: use move(values) 962 insertBack(values); // TODO: remove `move` when compiler does it for us 963 } 964 965 pragma(inline, true) 966 void opOpAssign(string op, A)(ref A values) 967 if (op == "~" && 968 isArrayContainer!A && 969 isElementAssignable!(ElementType!A)) 970 { 971 assert(!isBorrowed); 972 insertBack(values); 973 } 974 } 975 976 import searching_ex : containsStoreIndex; // TODO: this is redundant but elides rdmd dependency error for array_ex.d 977 978 static if (isOrdered!ordering) 979 { 980 import std.range : SearchPolicy, assumeSorted; 981 982 /// Returns: `true` iff this contains `value`. 983 bool contains(U)(U value) const @nogc @("complexity", "O(log(length))") 984 { 985 return this[].contains(value); // reuse `SortedRange.contains` 986 } 987 988 /** Wrapper for `std.range.SortedRange.lowerBound` when this `ordering` is sorted. */ 989 auto lowerBound(SearchPolicy sp = SearchPolicy.binarySearch, U)(U e) inout @("complexity", "O(log(length))") 990 { 991 return this[].lowerBound!sp(e); // reuse `SortedRange.lowerBound` 992 } 993 994 /** Wrapper for `std.range.SortedRange.upperBound` when this `ordering` is sorted. */ 995 auto upperBound(SearchPolicy sp = SearchPolicy.binarySearch, U)(U e) inout @("complexity", "O(log(length))") 996 { 997 return this[].upperBound!sp(e); // reuse `SortedRange.upperBound` 998 } 999 1000 static if (ordering == Ordering.sortedUniqueSet) 1001 { 1002 /** Inserts several `values` into `this` ordered set. 1003 1004 Returns: `bool`-array with same length as `values`, where i:th 1005 `bool` value is set if `value[i]` wasn't previously in `this`. 1006 */ 1007 bool[Us.length] insertMany(SearchPolicy sp = SearchPolicy.binarySearch, Us...)(Us values) @("complexity", "O(length)") 1008 if (values.length >= 1 && 1009 allSatisfy!(isElementAssignable, Us)) 1010 in 1011 { 1012 assert(!isBorrowed); 1013 1014 // assert no duplicates in `values` 1015 import std.range.primitives : empty; 1016 import std.algorithm.searching : findAdjacent; 1017 import std.algorithm.sorting : sort; 1018 1019 // TODO: functionize or use other interface in pushing `values` 1020 import std.traits : CommonType; 1021 CommonType!Us[Us.length] valuesArray; 1022 foreach (immutable i, const ref value; values) 1023 valuesArray[i] = value; 1024 assert(sort(valuesArray[]).findAdjacent.empty, 1025 "Parameter `values` must not contain duplicate elements"); 1026 } 1027 do 1028 { 1029 static if (values.length == 1) // faster because `contains()` followed by `completeSort()` searches array twice 1030 { 1031 import nxt.searching_ex : containsStoreIndex; 1032 size_t index; 1033 if (slice.assumeSorted!comp.containsStoreIndex!sp(values, index)) // faster than `completeSort` for single value 1034 return [false]; 1035 else 1036 { 1037 insertAtIndexHelper(index, values); 1038 return [true]; 1039 } 1040 } 1041 else 1042 { 1043 import std.algorithm.sorting : completeSort; 1044 1045 debug { typeof(return) hits; } 1046 else { typeof(return) hits = void; } 1047 1048 size_t expandedLength = 0; 1049 immutable initialLength = this.length; 1050 foreach (immutable i, ref value; values) 1051 { 1052 // TODO: reuse completeSort with uniqueness handling? 1053 static if (values.length == 1) 1054 { 1055 // TODO: reuse single parameter overload linearUniqueInsert() and return 1056 } 1057 else 1058 { 1059 // TODO: reuse completeSort with uniqueness handling? 1060 } 1061 hits[i] = !this[0 .. initialLength].contains(value); 1062 if (hits[i]) 1063 { 1064 insertBackHelper(value); // NOTE: append but don't yet sort 1065 ++expandedLength; 1066 } 1067 } 1068 1069 if (expandedLength != 0) 1070 { 1071 immutable ix = this.length - expandedLength; 1072 // TODO: use `_mptr` here instead and functionize to @trusted helper function 1073 completeSort!comp(slice[0 .. ix].assumeSorted!comp, 1074 slice[ix .. this.length]); 1075 } 1076 return hits; 1077 } 1078 } 1079 } 1080 else static if (ordering == Ordering.sortedValues) 1081 { 1082 /** Inserts `values`. */ 1083 void insertMany(SearchPolicy sp = SearchPolicy.binarySearch, Us...)(Us values) @("complexity", "O(log(length))") 1084 if (values.length >= 1 && 1085 allSatisfy!(isElementAssignable, Us)) 1086 { 1087 assert(!isBorrowed); 1088 1089 // TODO: add optimization for values.length == 2 1090 static if (values.length == 1) 1091 { 1092 import nxt.searching_ex : containsStoreIndex; 1093 size_t index; 1094 if (!slice.assumeSorted!comp.containsStoreIndex!sp(values, index)) // faster than `completeSort` for single value 1095 insertAtIndexHelper(index, values); 1096 } 1097 else 1098 { 1099 insertBackHelper(values); // simpler because duplicates are allowed 1100 immutable ix = this.length - values.length; 1101 import std.algorithm.sorting : completeSort; 1102 completeSort!comp(_mptr[0 .. ix].assumeSorted!comp, 1103 _mptr[ix .. this.length]); 1104 } 1105 } 1106 } 1107 } 1108 else 1109 { 1110 /** Insert element(s) `values` at array offset `index`. */ 1111 pragma(inline, true) 1112 void insertAtIndex(Us...)(size_t index, Us values) @("complexity", "O(length)") 1113 if (values.length >= 1 && 1114 allSatisfy!(isElementAssignable, Us)) 1115 { 1116 assert(!isBorrowed); 1117 insertAtIndexHelper(index, values); 1118 } 1119 1120 /** Insert element(s) `values` at the beginning. */ 1121 pragma(inline, true) 1122 void pushFront(Us...)(Us values) @("complexity", "O(length)") 1123 if (values.length >= 1 && 1124 allSatisfy!(isElementAssignable, Us)) 1125 { 1126 insertAtIndex(0, values); 1127 } 1128 1129 alias prepend = pushFront; 1130 1131 import nxt.traits_ex : isComparable; 1132 static if (__traits(isCopyable, E) && 1133 !is(E == char) && 1134 !is(E == wchar) && 1135 isComparable!E) 1136 { 1137 /// Returns: a sorted array copy. 1138 Array!(E, assignment, ordering.sortedValues, useGCAllocation, Capacity, less_) toSortedArray(alias less_ = "a < b")() const 1139 { 1140 return typeof(return)(slice); 1141 } 1142 /// Returns: a sorted set array copy. 1143 Array!(E, assignment, ordering.sortedUniqueSet, useGCAllocation, Capacity, less_) toSortedSetArray(alias less_ = "a < b")() const 1144 { 1145 return typeof(return)(slice); 1146 } 1147 } 1148 } 1149 1150 /** Helper function used externally for unsorted and internally for sorted. */ 1151 private void insertAtIndexHelper(Us...)(size_t index, Us values) @trusted @("complexity", "O(length)") 1152 { 1153 reserve(this.length + values.length); 1154 1155 // TODO: factor this to robustCopy. It uses copy when no overlaps (my algorithm_em), iteration otherwise 1156 enum usePhobosCopy = false; 1157 static if (usePhobosCopy) 1158 { 1159 // TODO: why does this fail? 1160 import std.algorithm.mutation : copy; 1161 copy(ptr[index .. 1162 this.length], // source 1163 _mptr[index + values.length .. 1164 this.length + values.length]); // target 1165 } 1166 else 1167 { 1168 // move second part in reverse 1169 // TODO: functionize move 1170 foreach (immutable i; 0 .. this.length - index) // each element index that needs to be moved 1171 { 1172 immutable si = this.length - 1 - i; // source index 1173 immutable ti = si + values.length; // target index 1174 _mptr[ti] = ptr[si]; // TODO: move construct? 1175 } 1176 } 1177 1178 // set new values 1179 foreach (immutable i, ref value; values) 1180 { 1181 ptr[index + i] = value; // TODO: use range algorithm instead? 1182 } 1183 1184 setOnlyLength(this.length + values.length); 1185 } 1186 1187 private void insertBackHelper(Us...)(Us values) 1188 @trusted 1189 @("complexity", "O(1)") 1190 { 1191 const newLength = this.length + values.length; 1192 reserve(newLength); 1193 foreach (immutable i, ref value; values) 1194 { 1195 // TODO: functionize: 1196 static if (needsMove!(typeof(value))) 1197 moveEmplace(*cast(MutableE*)&value, 1198 _mptr[this.length + i]); 1199 else 1200 _mptr[this.length + i] = value; 1201 } 1202 setOnlyLength(newLength); 1203 } 1204 1205 @property @("complexity", "O(1)") 1206 pragma(inline): 1207 1208 /// ditto 1209 static if (isOrdered!ordering) 1210 { 1211 const pure: // indexing and slicing must be `const` when ordered 1212 1213 /// Slice operator must be const when ordered. 1214 auto opSlice() return scope @trusted // TODO: remove @trusted? 1215 { 1216 static if (is(E == class)) 1217 { 1218 // TODO: remove this workaround when else branch works for classes 1219 return (cast(E[])slice).assumeSorted!comp; 1220 } 1221 else 1222 { 1223 return (cast(const(E)[])slice).assumeSorted!comp; 1224 } 1225 } 1226 /// ditto 1227 auto opSlice(this This)(size_t i, size_t j) return scope @trusted // TODO: remove @trusted? 1228 { 1229 import std.range : assumeSorted; 1230 return (cast(const(E)[])slice[i .. j]).assumeSorted!comp; 1231 } 1232 private alias This = typeof(this); 1233 1234 /// Index operator must be const to preserve ordering. 1235 ref const(E) opIndex(size_t i) return scope @nogc @trusted 1236 { 1237 assert(i < this.length); 1238 return ptr[i]; 1239 } 1240 1241 /// Get front element (as constant reference to preserve ordering). 1242 ref const(E) front() return scope @nogc @trusted 1243 { 1244 assert(!empty); 1245 return ptr[0]; 1246 } 1247 1248 /// Get back element (as constant reference to preserve ordering). 1249 ref const(E) back() return scope @nogc @trusted 1250 { 1251 assert(!empty); 1252 return ptr[this.length - 1]; 1253 } 1254 } 1255 else 1256 { 1257 /// Set length to `newLength`. 1258 @property void length(size_t newLength) 1259 { 1260 if (newLength < length) 1261 { 1262 shrinkTo(newLength); 1263 } 1264 else 1265 { 1266 reserve(newLength); 1267 setOnlyLength(newLength); 1268 } 1269 } 1270 1271 @nogc: 1272 1273 /// Index assign operator. 1274 ref E opIndexAssign(V)(V value, size_t i) 1275 @trusted return scope 1276 { 1277 assert(!isBorrowed); 1278 assert(i < this.length); 1279 static if (isScalarType!E) 1280 ptr[i] = value; 1281 else 1282 move(*(cast(MutableE*)(&value)), _mptr[i]); // TODO: is this correct? 1283 return ptr[i]; 1284 } 1285 1286 /// Slice assign operator. 1287 static if (__traits(isCopyable, E)) 1288 { 1289 void opSliceAssign(V)(V value, size_t i, size_t j) 1290 @trusted return scope 1291 { 1292 assert(!isBorrowed); 1293 assert(i <= j); 1294 assert(j <= this.length); 1295 foreach (immutable i; 0 .. this.length) 1296 { 1297 ptr[i] = value; 1298 } 1299 } 1300 } 1301 1302 pure inout: // indexing and slicing has mutable access only when unordered 1303 1304 /// Slice operator. 1305 pragma(inline) 1306 inout(E)[] opSlice() return 1307 { 1308 return this.opSlice(0, this.length); 1309 } 1310 /// ditto 1311 inout(E)[] opSlice(size_t i, size_t j) 1312 @trusted return scope 1313 { 1314 assert(i <= j); 1315 assert(j <= this.length); 1316 return ptr[i .. j]; 1317 } 1318 1319 @trusted: 1320 1321 /// Index operator. 1322 ref inout(E) opIndex(size_t i) return scope 1323 { 1324 assert(i < this.length); 1325 return ptr[i]; 1326 } 1327 1328 /// Get front element reference. 1329 ref inout(E) front() return scope 1330 { 1331 assert(!empty); 1332 return ptr[0]; 1333 } 1334 1335 /// Get back element reference. 1336 ref inout(E) back() return scope 1337 { 1338 assert(!empty); 1339 return ptr[this.length - 1]; 1340 } 1341 } 1342 1343 alias data = opSlice; // `std.array.Appender` compatibility 1344 1345 // static if (__traits(isCopyable, E)) 1346 // { 1347 // string toString() const @property @trusted pure 1348 // { 1349 // import std.array : Appender; 1350 // import std.conv : to; 1351 // Appender!string s = "["; 1352 // foreach (immutable i; 0 .. this.length) 1353 // { 1354 // if (i) { s.put(','); } 1355 // s.put(ptr[i].to!string); 1356 // } 1357 // s.put("]"); 1358 // return s.data; 1359 // } 1360 // } 1361 1362 pure: 1363 1364 /** Allocate heap regionwith `newCapacity` number of elements of type `E`. 1365 If `zero` is `true` they will be zero-initialized. 1366 */ 1367 private static MutableE* allocate(size_t newCapacity, bool zero = false) 1368 { 1369 typeof(return) ptr = null; 1370 static if (useGCAllocation) 1371 { 1372 if (zero) { ptr = cast(typeof(return))GC.calloc(newCapacity, E.sizeof); } 1373 else { ptr = cast(typeof(return))GC.malloc(newCapacity * E.sizeof); } 1374 } 1375 else // @nogc 1376 { 1377 if (zero) { ptr = cast(typeof(return))calloc(newCapacity, E.sizeof); } 1378 else { ptr = cast(typeof(return))malloc(newCapacity * E.sizeof); } 1379 assert(ptr, "Allocation failed"); 1380 } 1381 static if (shouldAddGCRange!E) 1382 { 1383 gc_addRange(ptr, newCapacity * E.sizeof); 1384 } 1385 return ptr; 1386 } 1387 1388 @nogc: 1389 1390 /// Check if empty. 1391 bool empty() const { return this.length == 0; } 1392 1393 /// Get length. 1394 size_t length() 1395 const @trusted 1396 { 1397 if (isLarge) 1398 { 1399 return _large.length; 1400 } 1401 else 1402 { 1403 return _small.length; 1404 } 1405 } 1406 alias opDollar = length; /// ditto 1407 1408 /// Decrease only length. 1409 private void decOnlyLength() 1410 @trusted 1411 { 1412 if (isLarge) 1413 { 1414 assert(_large.length); 1415 _large.length = _large.length - 1; 1416 } 1417 else 1418 { 1419 assert(_small.length); 1420 _small.length = cast(SmallLengthType)(_small.length - 1); 1421 } 1422 } 1423 1424 /// Set only length. 1425 private void setOnlyLength(size_t newLength) 1426 @trusted 1427 { 1428 if (isLarge) 1429 { 1430 _large.length = newLength; // TODO: compress? 1431 } 1432 else 1433 { 1434 assert(newLength <= SmallLengthType.max); 1435 _small.length = cast(SmallLengthType)newLength; 1436 } 1437 } 1438 1439 /// Get reserved capacity of store. 1440 size_t capacity() 1441 const 1442 @trusted 1443 { 1444 if (isLarge) 1445 { 1446 return _large.capacity; 1447 } 1448 else 1449 { 1450 return smallCapacity; 1451 } 1452 } 1453 1454 /** Shrink length to `newLength`. 1455 * 1456 * If `newLength` >= `length` operation has no effect. 1457 */ 1458 pragma(inline) 1459 void shrinkTo(size_t newLength) 1460 { 1461 assert(!isBorrowed); 1462 if (newLength < length) 1463 { 1464 static if (hasElaborateDestructor!E) 1465 { 1466 dbg(length, " => ", newLength, " ", E.stringof); 1467 foreach (immutable i; newLength .. length) 1468 { 1469 .destroy(_mptr[i]); 1470 } 1471 } 1472 setOnlyLength(newLength); 1473 } 1474 } 1475 1476 /// Get internal pointer. 1477 inout(E*) ptr() 1478 inout 1479 @trusted return scope // array access is @trusted 1480 { 1481 // TODO: Use cast(ET[])?: alias ET = ContainerElementType!(typeof(this), E); 1482 if (isLarge) 1483 { 1484 return _large.ptr; 1485 } 1486 else 1487 { 1488 return _small.elms.ptr; 1489 } 1490 } 1491 1492 /// Get internal pointer to mutable content. Doesn't need to be qualified with `scope`. 1493 private MutableE* _mptr() 1494 const return scope 1495 { 1496 if (isLarge) 1497 { 1498 return _large._mptr; 1499 } 1500 else 1501 { 1502 return _small._mptr; 1503 } 1504 } 1505 1506 /// Get internal slice. 1507 private auto slice() inout 1508 @trusted return scope 1509 { 1510 return ptr[0 .. this.length]; 1511 } 1512 1513 /** Magic pointer value used to detect double calls to `free`. 1514 1515 Cannot conflict with return value from `malloc` because the least 1516 significant bit is set (when the value ends with a one). 1517 */ 1518 debug private enum _ptrMagic = cast(E*)0x0C6F3C6c0f3a8471; 1519 1520 /// Returns: `true` if `this` currently uses large array storage. 1521 bool isLarge() 1522 const @trusted // trusted access to anonymous union 1523 { 1524 assert(_large.isLarge == _small.isLarge); // must always be same 1525 return _large.isLarge; 1526 } 1527 1528 /// Returns: `true` if `this` currently uses small (packed) array storage. 1529 bool isSmall() const { return !isLarge; } 1530 1531 private 1532 { 1533 private alias SmallLengthType = ubyte; 1534 1535 private enum largeSmallLengthDifference = Large.sizeof - SmallLengthType.sizeof; 1536 private enum smallCapacity = largeSmallLengthDifference / E.sizeof; 1537 private enum smallPadSize = largeSmallLengthDifference - smallCapacity*E.sizeof; 1538 } 1539 1540 /** Tag `this` borrowed. 1541 Used by wrapper logic in owned.d and borrowed.d 1542 */ 1543 void tagAsBorrowed() 1544 @trusted 1545 { 1546 if (isLarge) { _large.isBorrowed = true; } 1547 else { _small.isBorrowed = true; } 1548 } 1549 1550 /** Tag `this` as not borrowed. 1551 Used by wrapper logic in owned.d and borrowed.d 1552 */ 1553 void untagAsNotBorrowed() 1554 @trusted 1555 { 1556 if (isLarge) { _large.isBorrowed = false; } 1557 else { _small.isBorrowed = false; } 1558 } 1559 1560 /// Returns: `true` if this is borrowed 1561 bool isBorrowed() 1562 @trusted 1563 { 1564 if (isLarge) { return _large.isBorrowed; } 1565 else { return _small.isBorrowed; } 1566 } 1567 1568 private: // data 1569 enum useAlignedTaggedPointer = false; // TODO: make this work when true 1570 static struct Large 1571 { 1572 static if (useAlignedTaggedPointer) 1573 { 1574 private enum lengthMax = Capacity.max; 1575 version(LittleEndian) // see: http://forum.dlang.org/posting/zifyahfohbwavwkwbgmw 1576 { 1577 import std.bitmanip : taggedPointer; 1578 mixin(taggedPointer!(uint*, "_uintptr", // GC-allocated store pointer. See_Also: http://forum.dlang.org/post/iubialncuhahhxsfvbbg@forum.dlang.org 1579 bool, "isLarge", 1, // bit 0 1580 bool, "isBorrowed", 1, // bit 1 1581 )); 1582 1583 pragma(inline, true): 1584 @property void ptr(E* c) 1585 { 1586 assert((cast(ulong)c & 0b11) == 0); 1587 _uintptr = cast(uint*)c; 1588 } 1589 1590 @property inout(E)* ptr() inout 1591 { 1592 return cast(E*)_uintptr; 1593 } 1594 1595 Capacity capacity; // store capacity 1596 Capacity length; // store length 1597 } 1598 else 1599 { 1600 static assert(0, "BigEndian support and test"); 1601 } 1602 } 1603 else 1604 { 1605 private enum lengthBits = 8*Capacity.sizeof - 2; 1606 private enum lengthMax = 2^^lengthBits - 1; 1607 1608 static if (useGCAllocation) 1609 { 1610 E* ptr; // GC-allocated store pointer. See_Also: http://forum.dlang.org/post/iubialncuhahhxsfvbbg@forum.dlang.org 1611 } 1612 else 1613 { 1614 @nogc E* ptr; // non-GC-allocated store pointer 1615 } 1616 1617 Capacity capacity; // store capacity 1618 1619 import std.bitmanip : bitfields; // TODO: replace with own logic cause this mixin costs compilation speed 1620 mixin(bitfields!(Capacity, "length", lengthBits, 1621 bool, "isBorrowed", 1, 1622 bool, "isLarge", 1, 1623 )); 1624 } 1625 1626 pragma(inline) 1627 this(size_t initialCapacity, size_t initialLength, bool zero) 1628 { 1629 assert(initialCapacity <= lengthMax); 1630 assert(initialLength <= lengthMax); 1631 1632 setCapacity(initialCapacity); 1633 this.capacity = cast(Capacity)initialCapacity; 1634 this.ptr = allocate(initialCapacity, zero); 1635 this.length = initialLength; 1636 this.isLarge = true; 1637 this.isBorrowed = false; 1638 } 1639 1640 pragma(inline, true): 1641 1642 void setCapacity(size_t newCapacity) 1643 { 1644 assert(newCapacity <= capacity.max); 1645 capacity = cast(Capacity)newCapacity; 1646 } 1647 1648 MutableE* _mptr() const 1649 @trusted 1650 { 1651 return cast(typeof(return))ptr; 1652 } 1653 } 1654 1655 /// Small string storage. 1656 static struct Small 1657 { 1658 enum capacity = smallCapacity; 1659 private enum lengthBits = 8*SmallLengthType.sizeof - 2; 1660 private enum lengthMax = 2^^lengthBits - 1; 1661 1662 import std.bitmanip : bitfields; // TODO: replace with own logic cause this mixin costs compilation speed 1663 static if (useAlignedTaggedPointer) 1664 { 1665 mixin(bitfields!(bool, "isLarge", 1, // defaults to false 1666 bool, "isBorrowed", 1, // default to false 1667 SmallLengthType, "length", lengthBits, 1668 )); 1669 static if (smallPadSize) { ubyte[smallPadSize] _ignoredPadding; } 1670 E[capacity] elms; 1671 } 1672 else 1673 { 1674 E[capacity] elms; 1675 static if (smallPadSize) { ubyte[smallPadSize] _ignoredPadding; } 1676 mixin(bitfields!(SmallLengthType, "length", lengthBits, 1677 bool, "isBorrowed", 1, // default to false 1678 bool, "isLarge", 1, // defaults to false 1679 )); 1680 } 1681 1682 pragma(inline) 1683 this(size_t initialLength, bool zero) 1684 { 1685 assert(initialLength <= lengthMax); 1686 1687 this.length = cast(SmallLengthType)initialLength; 1688 this.isLarge = false; 1689 this.isBorrowed = false; 1690 if (zero) 1691 { 1692 elms[] = E.init; 1693 } 1694 } 1695 1696 pragma(inline, true) 1697 MutableE* _mptr() const 1698 @trusted 1699 { 1700 return cast(typeof(return))(elms.ptr); 1701 } 1702 } 1703 1704 static assert(Large.sizeof == 1705 Small.sizeof); 1706 1707 static if (E.sizeof == 1 && 1708 size_t.sizeof == 8 && // 64-bit 1709 Small.capacity == 23) 1710 { 1711 static assert(Large.sizeof == 24); 1712 static assert(Small.sizeof == 24); 1713 } 1714 1715 union 1716 { 1717 Large _large; // indirected storage 1718 Small _small; // non-indirected storage 1719 } 1720 } 1721 1722 import std.traits : isDynamicArray; 1723 1724 /** Return an instance of `R` with capacity `capacity`. */ 1725 R withCapacityMake(R)(size_t capacity) 1726 if (__traits(hasMember, R, "withCapacity")) 1727 { 1728 return R.withCapacity(capacity); 1729 } 1730 /// ditto 1731 R withCapacityMake(R)(size_t capacity) 1732 if (isDynamicArray!R) 1733 { 1734 R r; 1735 // See http://forum.dlang.org/post/nupffaitocqjlamffuqi@forum.dlang.org 1736 r.reserve(capacity); 1737 return r; 1738 } 1739 1740 /// 1741 @safe pure nothrow unittest 1742 { 1743 immutable capacity = 10; 1744 auto x = capacity.withCapacityMake!(int[]); 1745 assert(x.capacity >= capacity); 1746 } 1747 1748 /** Return an instance of `R` of length `length`. */ 1749 R withLengthMake(R)(size_t length) 1750 if (__traits(hasMember, R, "withLength")) 1751 { 1752 return R.withLength(length); 1753 } 1754 /// ditto 1755 R withLengthMake(R)(size_t length) 1756 if (isDynamicArray!R) 1757 { 1758 R r; 1759 r.length = length; 1760 return r; 1761 } 1762 1763 /** Return an instance of `R` containing a single element `e`. */ 1764 R withElementMake(R)(typeof(R.init[0]) e) 1765 if (__traits(hasMember, R, "withElement")) 1766 { 1767 return R.withElement(e); 1768 } 1769 /// ditto 1770 R withElementMake(R)(typeof(R.init[0]) e) 1771 if (isDynamicArray!R) 1772 { 1773 return [e]; 1774 } 1775 1776 alias UniqueArray(E, bool useGCAllocation = false) = Array!(E, Assignment.disabled, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1777 alias CopyingArray(E, bool useGCAllocation = false) = Array!(E, Assignment.copy, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1778 1779 alias SortedCopyingArray(E, bool useGCAllocation = false, alias less = "a < b") = Array!(E, Assignment.copy, Ordering.sortedValues, useGCAllocation, size_t, less); 1780 alias SortedSetCopyingArray(E, bool useGCAllocation = false, alias less = "a < b") = Array!(E, Assignment.copy, Ordering.sortedUniqueSet, useGCAllocation, size_t, less); 1781 1782 alias SortedUniqueArray(E, bool useGCAllocation = false, alias less = "a < b") = Array!(E, Assignment.disabled, Ordering.sortedValues, useGCAllocation, size_t, less); 1783 alias SortedSetUniqueArray(E, bool useGCAllocation = false, alias less = "a < b") = Array!(E, Assignment.disabled, Ordering.sortedUniqueSet, useGCAllocation, size_t, less); 1784 1785 // string aliases 1786 alias UniqueString(bool useGCAllocation = false) = Array!(char, Assignment.disabled, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1787 alias CopyingString(bool useGCAllocation = false) = Array!(char, Assignment.copy, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1788 alias UniqueWString(bool useGCAllocation = false) = Array!(wchar, Assignment.disabled, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1789 alias CopyingWString(bool useGCAllocation = false) = Array!(wchar, Assignment.copy, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1790 alias UniqueDString(bool useGCAllocation = false) = Array!(dchar, Assignment.disabled, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1791 alias CopyingDString(bool useGCAllocation = false) = Array!(dchar, Assignment.copy, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1792 1793 /// 1794 @safe pure unittest 1795 { 1796 auto c = UniqueString!false(); 1797 auto w = UniqueWString!false(); 1798 auto d = UniqueDString!false(); 1799 } 1800 1801 /// 1802 @safe pure unittest 1803 { 1804 auto c = CopyingString!false(); 1805 auto w = CopyingWString!false(); 1806 auto d = CopyingDString!false(); 1807 } 1808 1809 /// 1810 @safe pure unittest 1811 { 1812 import std.conv : to; 1813 foreach (assignment; AliasSeq!(Assignment.disabled, Assignment.copy)) 1814 { 1815 foreach (Ch; AliasSeq!(char, wchar, dchar)) 1816 { 1817 alias Str = Array!(Ch, assignment); 1818 Str str_as = Str.withElement('a'); 1819 Str str_as2 = 'a'.withElementMake!Str; 1820 Str str_as3 = 'a'.withElementMake!(Ch[]); 1821 assert(str_as == str_as2); 1822 assert(str_as2 == str_as3); 1823 str_as ~= Ch('_'); 1824 assert(str_as[].equal("a_")); 1825 } 1826 } 1827 } 1828 1829 static void tester(Ordering ordering, bool supportGC, alias less)() 1830 { 1831 import std.functional : binaryFun; 1832 import std.range : iota, chain, repeat, only, ElementType; 1833 import std.algorithm : filter, map; 1834 import std.algorithm.sorting : isSorted, sort; 1835 import std.exception : assertThrown, assertNotThrown; 1836 import std.traits : isInstanceOf; 1837 import core.internal.traits : Unqual; 1838 1839 enum assignment = Assignment.copy; 1840 alias comp = binaryFun!less; //< comparison 1841 1842 alias E = int; 1843 1844 { 1845 alias A = SortedUniqueArray!E; 1846 auto x = A.withElements(0, 3, 2, 1); 1847 assert(x[].equal([0, 1, 2, 3].s[])); 1848 } 1849 1850 foreach (Ch; AliasSeq!(char, wchar, dchar)) 1851 { 1852 static if (!isOrdered!ordering || // either not ordered 1853 is(Ch == dchar)) // or not a not a narrow string 1854 { 1855 alias Str = Array!(Ch, assignment, ordering, supportGC, size_t, less); 1856 auto y = Str.withElements('a', 'b', 'c'); 1857 static assert(is(Unqual!(ElementType!Str) == Ch)); 1858 y = Str.init; 1859 1860 const(Ch)[] xs; 1861 { 1862 // immutable 1863 immutable x = Str.withElements('a', 'b', 'c'); 1864 static if (!isOrdered!ordering) 1865 { 1866 xs = x[]; // TODO: should fail with DIP-1000 scope 1867 } 1868 } 1869 } 1870 } 1871 1872 foreach (Ch; AliasSeq!(char)) 1873 { 1874 static if (!isOrdered!ordering) 1875 { 1876 alias Str = Array!(Ch, assignment, ordering, supportGC, size_t, less); 1877 auto str = Str.withElements('a', 'b', 'c'); 1878 1879 static if (isOrdered!ordering) 1880 { 1881 static assert(is(Unqual!(ElementType!Str) == Ch)); 1882 } 1883 else 1884 { 1885 static assert(is(ElementType!Str == Ch)); 1886 assert(str[] == `abc`); // TODO: this fails for wchar and dchar 1887 } 1888 } 1889 } 1890 1891 { 1892 alias A = Array!(int, assignment, ordering, supportGC, size_t, less); 1893 foreach (immutable n; [0, 1, 2, 3, 4, 5]) 1894 { 1895 assert(A.withLength(n).isSmall); 1896 } 1897 assert(!(A.withLength(6).isSmall)); 1898 assert((A.withLength(6).isLarge)); 1899 } 1900 1901 // test move construction 1902 { 1903 immutable maxLength = 1024; 1904 foreach (immutable n; 0 .. maxLength) 1905 { 1906 auto x = Array!(E, assignment, ordering, supportGC, size_t, less).withLength(n); 1907 1908 // test resize 1909 static if (!isOrdered!ordering) 1910 { 1911 assert(x.length == n); 1912 x.length = n + 1; 1913 assert(x.length == n + 1); 1914 x.length = n; 1915 } 1916 1917 const ptr = x.ptr; 1918 immutable capacity = x.capacity; 1919 assert(x.length == n); 1920 1921 import std.algorithm.mutation : move; 1922 auto y = Array!(E, assignment, ordering, supportGC, size_t, less)(); 1923 move(x, y); 1924 1925 assert(x.length == 0); 1926 assert(x.capacity == x.smallCapacity); 1927 1928 assert(y.length == n); 1929 assert(y.capacity == capacity); 1930 } 1931 } 1932 1933 foreach (immutable n; chain(0.only, iota(0, 10).map!(x => 2^^x))) 1934 { 1935 import std.array : array; 1936 import std.range : radial; 1937 1938 immutable zi = cast(int)0; // zero index 1939 immutable ni = cast(int)n; // number index 1940 1941 auto fw = iota(zi, ni); // 0, 1, 2, ..., n-1 1942 1943 auto bw = fw.array.radial; 1944 1945 Array!(E, assignment, ordering, supportGC, size_t, less) ss0 = bw; // reversed 1946 static assert(is(Unqual!(ElementType!(typeof(ss0))) == E)); 1947 static assert(isInstanceOf!(Array, typeof(ss0))); 1948 assert(ss0.length == n); 1949 1950 static if (isOrdered!ordering) 1951 { 1952 if (!ss0.empty) { assert(ss0[0] == ss0[0]); } // trigger use of opindex 1953 assert(ss0[].equal(fw.array 1954 .sort!comp)); 1955 assert(ss0[].isSorted!comp); 1956 } 1957 1958 Array!(E, assignment, ordering, supportGC, size_t, less) ss1 = fw; // ordinary 1959 assert(ss1.length == n); 1960 1961 static if (isOrdered!ordering) 1962 { 1963 assert(ss1[].equal(fw.array 1964 .sort!comp)); 1965 assert(ss1[].isSorted!comp); 1966 } 1967 1968 Array!(E, assignment, ordering, supportGC, size_t, less) ss2 = fw.filter!(x => x & 1); 1969 assert(ss2.length == n/2); 1970 1971 static if (isOrdered!ordering) 1972 { 1973 assert(ss2[].equal(fw.filter!(x => x & 1) 1974 .array 1975 .sort!comp)); 1976 assert(ss2[].isSorted!comp); 1977 } 1978 1979 auto ssA = Array!(E, assignment, ordering, supportGC, size_t, less).withLength(0); 1980 static if (isOrdered!ordering) 1981 { 1982 static if (less == "a < b") 1983 { 1984 alias A = Array!(E, assignment, ordering, supportGC, size_t, less); 1985 const A x = [1, 2, 3, 4, 5, 6]; 1986 assert(x.front == 1); 1987 assert(x.back == 6); 1988 assert(x.lowerBound(3).equal([1, 2].s[])); 1989 assert(x.upperBound(3).equal([4, 5, 6].s[])); 1990 assert(x[].equal(x[])); // already sorted 1991 } 1992 1993 foreach (i; bw) 1994 { 1995 static if (ordering == Ordering.sortedUniqueSet) 1996 { 1997 assert(ssA.insertMany(i)[].equal([true].s[])); 1998 assert(ssA.insertMany(i)[].equal([false].s[])); 1999 } 2000 else 2001 { 2002 ssA.insertMany(i); 2003 } 2004 } 2005 assert(ssA[].equal(sort!comp(fw.array))); 2006 2007 auto ssB = Array!(E, assignment, ordering, supportGC, size_t, less).withLength(0); 2008 static if (ordering == Ordering.sortedUniqueSet) 2009 { 2010 assert(ssB.insertMany(1, 7, 4, 9)[].equal(true.repeat(4))); 2011 assert(ssB.insertMany(3, 6, 8, 5, 1, 9)[].equal([true, true, true, true, false, false].s[])); 2012 assert(ssB.insertMany(3, 0, 2, 10, 11, 5)[].equal([false, true, true, true, true, false].s[])); 2013 assert(ssB.insertMany(0, 2, 10, 11)[].equal(false.repeat(4))); // false becuse already inserted 2014 assert(ssB.capacity == 16); 2015 } 2016 else 2017 { 2018 ssB.insertMany(1, 7, 4, 9); 2019 ssB.insertMany(3, 6, 8, 5); 2020 ssB.insertMany(0, 2, 10, 11); 2021 assert(ssB.capacity == 16); 2022 } 2023 2024 auto ssI = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].sort!comp; // values 2025 immutable ssO = [12, 13]; // values not range 2026 2027 assert(ssB[].equal(ssI)); 2028 2029 foreach (s; ssI) { assert(ssB.contains(s)); } 2030 foreach (s; ssO) { assert(!ssB.contains(s)); } 2031 2032 ssB.compress(); 2033 assert(ssB.capacity == 12); 2034 } 2035 else 2036 { 2037 { 2038 alias A = Array!(E, assignment, ordering, supportGC); 2039 A x = [1, 2, 3]; 2040 x ~= x; 2041 assert(x[].equal([1, 2, 3, 2042 1, 2, 3].s[])); 2043 x ~= x[]; 2044 assert(x[].equal([1, 2, 3, 1, 2, 3, 2045 1, 2, 3, 1, 2, 3].s[])); 2046 } 2047 2048 ssA ~= 3; 2049 ssA ~= 2; 2050 ssA ~= 1; 2051 assert(ssA[].equal([3, 2, 1].s[])); 2052 2053 ssA.compress(); 2054 2055 // popBack 2056 ssA[0] = 1; 2057 ssA[1] = 2; 2058 assert(ssA[].equal([1, 2, 1].s[])); 2059 assert(!ssA.empty); 2060 assert(ssA.front == 1); 2061 assert(ssA.back == 1); 2062 2063 assertNotThrown(ssA.popBack()); 2064 assert(ssA[].equal([1, 2].s[])); 2065 assert(!ssA.empty); 2066 assert(ssA.front == 1); 2067 assert(ssA.back == 2); 2068 2069 assertNotThrown(ssA.popBack()); 2070 assert(ssA[].equal([1].s[])); 2071 assert(!ssA.empty); 2072 assert(ssA.front == 1); 2073 assert(ssA.back == 1); 2074 2075 assertNotThrown(ssA.popBack()); 2076 assert(ssA.length == 0); 2077 assert(ssA.empty); 2078 assert(ssA.capacity != 0); 2079 2080 ssA.compress(); 2081 assert(ssA.length == 0); 2082 assert(ssA.empty); 2083 2084 // insertAt 2085 ssA ~= 1; 2086 ssA ~= 2; 2087 ssA ~= 3; 2088 ssA ~= 4; 2089 ssA ~= 5; 2090 ssA ~= 6; 2091 ssA ~= 7; 2092 ssA ~= 8; 2093 assert(ssA[].equal([1, 2, 3, 4, 5, 6, 7, 8].s[])); 2094 ssA.insertAtIndex(3, 100, 101); 2095 assert(ssA[].equal([1, 2, 3, 100, 101, 4, 5, 6, 7, 8].s[])); 2096 assertNotThrown(ssA.popFront()); 2097 assert(ssA[].equal([2, 3, 100, 101, 4, 5, 6, 7, 8].s[])); 2098 assertNotThrown(ssA.popFront()); 2099 assert(ssA[].equal([3, 100, 101, 4, 5, 6, 7, 8].s[])); 2100 assertNotThrown(ssA.popFront()); 2101 assert(ssA[].equal([100, 101, 4, 5, 6, 7, 8].s[])); 2102 assertNotThrown(ssA.popFront()); 2103 assertNotThrown(ssA.popFront()); 2104 assertNotThrown(ssA.popFront()); 2105 assertNotThrown(ssA.popFront()); 2106 assertNotThrown(ssA.popFront()); 2107 assertNotThrown(ssA.popFront()); 2108 assertNotThrown(ssA.popFront()); 2109 assert(ssA.empty); 2110 ssA.compress(); 2111 2112 // removeAt 2113 ssA ~= 1; 2114 ssA ~= 2; 2115 ssA ~= 3; 2116 ssA ~= 4; 2117 ssA ~= 5; 2118 assertNotThrown(ssA.removeAt(2)); 2119 assert(ssA[].equal([1, 2, 4, 5].s[])); 2120 2121 // insertBack and assignment from slice 2122 auto ssB = Array!(E, assignment, ordering, supportGC, size_t, less).withLength(0); 2123 ssB.insertBack([1, 2, 3, 4, 5].s[]); 2124 ssB.insertBack([6, 7]); 2125 assert(ssB[].equal([1, 2, 3, 4, 5, 6, 7].s[])); 2126 assert(ssB.backPop() == 7); 2127 assert(ssB.backPop() == 6); 2128 assert(ssB.backPop() == 5); 2129 assert(ssB.backPop() == 4); 2130 assert(ssB.backPop() == 3); 2131 assert(ssB.backPop() == 2); 2132 assert(ssB.backPop() == 1); 2133 assert(ssB.empty); 2134 2135 // insertBack(Array) 2136 { 2137 immutable s = [1, 2, 3]; 2138 Array!(E, assignment, ordering, supportGC, size_t, less) s1 = s; 2139 Array!(E, assignment, ordering, supportGC, size_t, less) s2 = s1[]; 2140 assert(s1[].equal(s)); 2141 s1 ~= s1; 2142 assert(s1[].equal(chain(s, s))); 2143 s1 ~= s2; 2144 assert(s1[].equal(chain(s, s, s))); 2145 } 2146 2147 // immutable ss_ = Array!(E, assignment, ordering, supportGC, size_t, less)(null); 2148 // assert(ss_.empty); 2149 2150 auto ssC = Array!(E, assignment, ordering, supportGC, size_t, less).withLength(0); 2151 immutable int[5] i5_ = [1, 2, 3, 4, 5]; 2152 immutable(int)[] i5 = i5_[]; 2153 ssC.insertBack(i5); 2154 assert(i5 == [1, 2, 3, 4, 5].s[]); 2155 assert(ssC[].equal(i5)); 2156 2157 auto ssCc = ssC; // copy it 2158 assert(ssCc[].equal(i5)); 2159 2160 ssC.shrinkTo(4); 2161 assert(ssC[].equal([1, 2, 3, 4].s[])); 2162 2163 ssC.shrinkTo(3); 2164 assert(ssC[].equal([1, 2, 3].s[])); 2165 2166 ssC.shrinkTo(2); 2167 assert(ssC[].equal([1, 2].s[])); 2168 2169 ssC.shrinkTo(1); 2170 assert(ssC[].equal([1].s[])); 2171 2172 ssC.shrinkTo(0); 2173 assert(ssC[].length == 0); 2174 assert(ssC.empty); 2175 2176 ssC.insertBack(i5); 2177 ssC.popBackN(3); 2178 assert(ssC[].equal([1, 2].s[])); 2179 2180 auto ssD = ssC; 2181 ssC.clear(); 2182 assert(ssC.empty); 2183 2184 assert(!ssD.empty); 2185 ssD = null; 2186 assert(ssD.empty); 2187 assert(ssD == typeof(ssD).init); 2188 2189 assert(ssCc[].equal(i5)); 2190 2191 ssCc = ssCc; // self assignment 2192 } 2193 } 2194 } 2195 2196 /// disabled copying 2197 @safe pure nothrow @nogc unittest 2198 { 2199 alias E = ubyte; 2200 alias A = Array!(E, Assignment.disabled, Ordering.unsorted, false, size_t, "a < b"); 2201 A a; 2202 immutable n = ubyte.max; 2203 size_t i = 0; 2204 foreach (ubyte e; 0 .. n) 2205 { 2206 a ~= e; 2207 // assert(a.back == e.to!E); 2208 assert(a.length == i + 1); 2209 ++i; 2210 } 2211 const b = a.dup; 2212 static assert(is(typeof(a) == Unqual!(typeof(b)))); 2213 assert(b.length == a.length); 2214 assert(a !is b); 2215 assert(a == b); 2216 } 2217 2218 /// disabled copying 2219 @safe pure nothrow unittest 2220 { 2221 alias E = string; 2222 2223 alias A = Array!(E, Assignment.disabled, Ordering.unsorted, false, size_t, "a < b"); 2224 A a; 2225 immutable n = 100_000; 2226 size_t i = 0; 2227 foreach (const ref e; 0 .. n) 2228 { 2229 a ~= e.to!E; 2230 // assert(a.back == e.to!E); 2231 assert(a.length == i + 1); 2232 ++i; 2233 } 2234 const b = a.dup; 2235 static assert(is(typeof(a) == Unqual!(typeof(b)))); 2236 assert(b.length == a.length); 2237 assert(a !is b); 2238 assert(a == b); 2239 } 2240 2241 /// disabled copying 2242 @safe pure nothrow unittest 2243 { 2244 alias E = string; 2245 alias A = Array!(E, Assignment.disabled, Ordering.unsorted, false, size_t, "a < b"); 2246 A a; 2247 immutable n = 100_000; 2248 size_t i = 0; 2249 foreach (const ref e; 0 .. n) 2250 { 2251 a ~= e.to!E; 2252 assert(a.length == i + 1); 2253 ++i; 2254 } 2255 const b = a.dup; 2256 static assert(is(typeof(a) == Unqual!(typeof(b)))); 2257 assert(a[] == b[]); 2258 } 2259 2260 /// disabled copying 2261 @safe pure nothrow @nogc unittest 2262 { 2263 import std.traits : isAssignable; 2264 2265 alias E = string; 2266 2267 alias A = Array!E; 2268 static assert(!__traits(isCopyable, A)); 2269 2270 alias CA = CopyingArray!E; 2271 static assert(__traits(isCopyable, CA)); 2272 2273 // import std.traits : isRvalueAssignable, isLvalueAssignable; 2274 // static assert(isRvalueAssignable!(A)); 2275 // static assert(isLvalueAssignable!(A)); 2276 2277 static assert(isAssignable!(A)); 2278 2279 // import std.range.primitives : hasSlicing; 2280 // TODO: make this evaluate to `true` 2281 // static assert(hasSlicing!A); 2282 2283 alias AA = Array!A; 2284 2285 AA aa; 2286 A a; 2287 a ~= "string"; 2288 aa ~= A.init; 2289 2290 assert(aa == aa); 2291 assert(AA.withLength(3) == AA.withLength(3)); 2292 assert(AA.withCapacity(3) == AA.withCapacity(3)); 2293 assert(AA.withLength(3).length == 3); 2294 assert(aa != AA.init); 2295 } 2296 2297 /// 2298 @safe pure nothrow @nogc unittest 2299 { 2300 alias E = int; 2301 alias A = Array!E; 2302 A a; 2303 import std.range : iota; 2304 import std.container.util : make; 2305 foreach (n; 0 .. 100) 2306 { 2307 const e = iota(0, n).make!Array; 2308 assert(e[].equal(iota(0, n))); 2309 } 2310 } 2311 2312 version(unittest) 2313 { 2314 import std.traits : EnumMembers; 2315 } 2316 2317 /// use GC 2318 pure nothrow unittest 2319 { 2320 foreach (ordering; EnumMembers!Ordering) 2321 { 2322 tester!(ordering, true, "a < b"); // use GC 2323 tester!(ordering, true, "a > b"); // use GC 2324 } 2325 } 2326 2327 /// don't use GC 2328 pure nothrow /+TODO: @nogc+/ unittest 2329 { 2330 foreach (ordering; EnumMembers!Ordering) 2331 { 2332 tester!(ordering, false, "a < b"); // don't use GC 2333 tester!(ordering, false, "a > b"); // don't use GC 2334 } 2335 } 2336 2337 /// 2338 @safe pure nothrow unittest 2339 { 2340 alias E = int; 2341 alias A = Array!E; 2342 A[string] map; 2343 map["a"] = A.init; 2344 map["B"] = A.withLength(42); 2345 2346 auto aPtr = "a" in map; 2347 assert(aPtr); 2348 assert(A.init == *aPtr); 2349 assert(*aPtr == A.init); 2350 2351 assert("z" !in map); 2352 auto zPtr = "z" in map; 2353 assert(!zPtr); 2354 } 2355 2356 /// test withElement and withElements 2357 @safe pure nothrow @nogc unittest 2358 { 2359 import std.algorithm.mutation : move; 2360 import std.range.primitives : ElementType; 2361 2362 alias A = Array!int; 2363 alias AA = Array!A; 2364 alias AAA = Array!AA; 2365 2366 foreach (A_; AliasSeq!(A, AA, AAA)) 2367 { 2368 alias E = ElementType!A_; 2369 A_ x = A_.withElement(E.init); 2370 A_ y = A_.withElements(E.init, E.init); 2371 assert(x.length == 1); 2372 assert(y.length == 2); 2373 immutable n = 100; 2374 foreach (_; 0 .. n) 2375 { 2376 auto e = E.init; 2377 x ~= move(e); 2378 y ~= E.init; 2379 } 2380 foreach (_; 0 .. n) 2381 { 2382 assert(x.backPop() == E.init); 2383 assert(y.backPop() == E.init); 2384 } 2385 assert(x.length == 1); 2386 assert(y.length == 2); 2387 2388 import std.algorithm : swap; 2389 swap(x, y); 2390 assert(x.length == 2); 2391 assert(y.length == 1); 2392 2393 swap(x[0], y[0]); 2394 } 2395 2396 } 2397 2398 /// assert same behaviour of `dup` as for builtin arrays 2399 @safe pure nothrow unittest 2400 { 2401 struct Vec { int x, y; } 2402 class Db { int* _ptr; } 2403 struct Node { int x; class Db; } 2404 // struct Node1 { const(int) x; class Db; } 2405 foreach (E; AliasSeq!(int, const(int), Vec, Node// , Node1 2406 )) 2407 { 2408 alias DA = E[]; // builtin D array/slice 2409 immutable DA da = [E.init]; // construct from array 2410 auto daCopy = da.dup; // duplicate 2411 daCopy[] = E.init; // opSliceAssign 2412 2413 alias CA = Array!E; // container array 2414 immutable ca = CA.withElement(E.init); 2415 2416 auto caCopy = ca.dup; 2417 2418 import std.traits : hasIndirections; 2419 static if (!hasIndirections!E) 2420 { 2421 const(E)[2] x = [E.init, E.init]; 2422 // TODO: caCopy ~= E.init; 2423 caCopy ~= x[]; 2424 assert(caCopy.length == 3); 2425 assert(caCopy[1 .. $] == x[]); 2426 } 2427 2428 // should have same element type 2429 static assert(is(typeof(caCopy[0]) == 2430 typeof(daCopy[0]))); 2431 2432 } 2433 } 2434 2435 /// array as AA key type 2436 @safe pure nothrow unittest 2437 { 2438 struct E { int x, y; } 2439 foreach (A; AliasSeq!(Array!E, 2440 CopyingArray!E)) 2441 { 2442 int[A] x; 2443 immutable n = 100; 2444 foreach (immutable i; 0 .. n) 2445 { 2446 assert(x.length == i); 2447 assert(A.withElement(E(i, 2*i)) !in x); 2448 x[A.withElement(E(i, 2*i))] = 42; 2449 assert(x.length == i + 1); 2450 auto a = A.withElement(E(i, 2*i)); 2451 import std.traits : isCopyable; 2452 static if (__traits(isCopyable, A)) 2453 { 2454 // TODO: why do these fail when `A` is uncopyable? 2455 assert(a in x); 2456 assert(A.withElement(E(i, 2*i)) in x); 2457 assert(x[A.withElement(E(i, 2*i))] == 42); 2458 } 2459 } 2460 } 2461 } 2462 2463 /// init and append to empty array as AA value type 2464 @safe pure nothrow unittest 2465 { 2466 alias Key = string; 2467 alias A = Array!int; 2468 2469 A[Key] x; 2470 2471 assert("a" !in x); 2472 2473 x["a"] = A.init; // if this init is removed.. 2474 x["a"] ~= 42; // ..then this fails 2475 2476 assert(x["a"] == A.withElement(42)); 2477 } 2478 2479 /// compress 2480 @safe pure nothrow @nogc unittest 2481 { 2482 alias A = Array!string; 2483 A a; 2484 2485 a.compress(); 2486 2487 a ~= "a"; 2488 a ~= "b"; 2489 a ~= "c"; 2490 2491 assert(a.length == 3); 2492 assert(a.capacity == 4); 2493 2494 a.compress(); 2495 2496 assert(a.capacity == a.length); 2497 } 2498 2499 /// 2500 @safe pure nothrow @nogc unittest 2501 { 2502 alias Key = UniqueArray!char; 2503 alias Value = UniqueArray!int; 2504 struct E 2505 { 2506 Key key; 2507 Value value; 2508 E dup() @safe pure nothrow @nogc 2509 { 2510 return E(key.dup, value.dup); 2511 } 2512 } 2513 E e; 2514 e.key = Key.withElement('a'); 2515 e.value = Value.withElement(42); 2516 2517 auto f = e.dup; 2518 assert(e == f); 2519 2520 e.key = Key.withElement('b'); 2521 assert(e != f); 2522 2523 e.key = Key.withElement('a'); 2524 assert(e == f); 2525 2526 e.value = Value.withElement(43); 2527 assert(e != f); 2528 2529 e.value = Value.withElement(42); 2530 assert(e == f); 2531 2532 } 2533 2534 /// append to empty to array as AA value type 2535 @safe pure nothrow @nogc unittest 2536 { 2537 import std.exception: assertThrown; 2538 import core.exception : RangeError; 2539 2540 alias Key = string; 2541 alias A = Array!int; 2542 2543 A[Key] x; 2544 // assertThrown!RangeError({ x["a"] ~= 42; }); // TODO: make this work 2545 } 2546 2547 /// map array of uncopyable 2548 @safe pure nothrow unittest 2549 { 2550 import std.range.primitives : isInputRange; 2551 import std.array : array; 2552 2553 alias A = UniqueArray!int; 2554 auto y = A.init[].map!(_ => _^^2).array; 2555 2556 A z = y.dup; // check that dup returns same type 2557 z = A.init; 2558 const w = [0, 1].s; 2559 z.insertBack(w[]); 2560 assert(z[].equal(w[])); 2561 } 2562 2563 /// 2564 version(none) 2565 @safe pure nothrow @nogc unittest 2566 { 2567 alias A = UniqueArray!int; 2568 A x; 2569 const y = [0, 1, 2, 3].s; 2570 2571 x.insertBack(y[]); 2572 assert(x[].equal(y[])); 2573 2574 x.clear(); 2575 x.insertBack(y[].map!(_ => _^^2)); // rhs has length (`hasLength`) 2576 assert(x[].equal(y[].map!(_ => _^^2))); 2577 2578 x.clear(); 2579 x.insertBack(y[].filter!(_ => _ & 1)); // rhs has no length (`!hasLength`) 2580 assert(x[].equal(y[].filter!(_ => _ & 1))); 2581 } 2582 2583 /// collection 2584 /*@safe*/ pure nothrow @nogc unittest // TODO: make @safe when collect has been made safe 2585 { 2586 import std.range : iota, isOutputRange; 2587 import nxt.algorithm_ex : collect; 2588 2589 alias E = int; 2590 alias A = Array!E; 2591 2592 immutable n = 100; 2593 static assert(isOutputRange!(A, E)); 2594 2595 assert((0.iota(n).collect!A)[].equal(0.iota(n))); 2596 } 2597 2598 /// map array of uncopyable 2599 @safe pure nothrow @nogc unittest 2600 { 2601 foreach (AT; AliasSeq!(SortedUniqueArray, 2602 SortedSetUniqueArray)) 2603 { 2604 alias A = AT!int; 2605 A a; 2606 A b = a.dup; 2607 } 2608 } 2609 2610 /// init and append to empty array as AA value type 2611 @safe pure nothrow @nogc unittest 2612 { 2613 alias A = Array!int; 2614 2615 const x = A.withElements(0, 1, 3, 0, 2, 1, 3); 2616 2617 assert(x.toSortedArray == [0, 0, 1, 1, 2, 3, 3].s[]); 2618 assert(x.toSortedSetArray == [0, 1, 2, 3].s[]); 2619 2620 assert(x.toSortedArray!"a > b" == [3, 3, 2, 1, 1, 0, 0].s[]); 2621 assert(x.toSortedSetArray!"a > b" == [3, 2, 1, 0].s[]); 2622 } 2623 2624 /** Return `data` appended with arguments `args`. 2625 2626 If `data` is an r-value it's modified and returned, otherwise a copy is made 2627 and returned. 2628 */ 2629 C append(C, Args...)(auto ref C data, 2630 auto ref Args args) 2631 if (args.length >= 1) // TODO: trait: when `C` is a container supporting `insertBack` 2632 { 2633 static if (__traits(isRef, data)) // `data` is an r-value 2634 { 2635 C mutableData = data.dup; 2636 } 2637 else // `data` is an l-value 2638 { 2639 alias mutableData = data; 2640 } 2641 // TODO: use `mutableData.insertBack(args);` instead 2642 foreach (ref arg; args) 2643 { 2644 mutableData.insertBack(arg); 2645 } 2646 import std.algorithm.mutation : move; 2647 return move(mutableData); 2648 } 2649 2650 /// append 2651 @safe pure nothrow @nogc unittest 2652 { 2653 alias Str = UniqueString!false; 2654 2655 assert(Str(`a`).append('b', 'c')[] == `abc`); 2656 assert(Str(`a`).append(`b`, `c`)[] == `abc`); 2657 2658 const Str x = Str(`a`).append('b', 'c'); // is moved 2659 assert(x[] == `abc`); 2660 2661 Str y = `x`; 2662 Str z = y.append('y', 'z', `w`); // needs dup 2663 assert(y.ptr != z.ptr); 2664 assert(z[] == `xyzw`); 2665 } 2666 2667 version(unittest) 2668 { 2669 private static struct SS 2670 { 2671 @disable this(this); 2672 int x; 2673 } 2674 } 2675 2676 /// uncopyable elements 2677 @safe pure nothrow @nogc unittest 2678 { 2679 alias A = UniqueArray!SS; 2680 A x; 2681 x ~= SS.init; 2682 // TODO: x.insertBack(A.init); 2683 } 2684 2685 // TODO: implement? 2686 // T opBinary(string op, R, Args...)(R lhs, 2687 // auto ref Args args) 2688 // { 2689 // return append(lhs, rhs); 2690 // } 2691 // @safe pure nothrow @nogc unittest 2692 // { 2693 // alias S = UniqueString!false; 2694 // // TODO 2695 // // const S x = S(`a`) ~ 'b'; 2696 // // assert(x[] == `abc`); 2697 // } 2698 2699 /// See_Also: http://forum.dlang.org/post/omfm56$28nu$1@digitalmars.com 2700 version(none) 2701 @safe pure nothrow unittest 2702 { 2703 import std.range.primitives : ElementType; 2704 import std.array : Appender; 2705 2706 struct S 2707 { 2708 string src; 2709 S[] subs; 2710 } 2711 2712 struct T 2713 { 2714 string src; 2715 Appender!(T[]) subs; 2716 } 2717 2718 static assert(is(ElementType!(S[]) == S)); 2719 static assert(is(ElementType!(T[]) == void)); // TODO: forward-reference bug: should be `T` 2720 2721 S s; 2722 s.subs ~= S.init; 2723 2724 T t; 2725 // t.subs ~= T.init; 2726 // t.subs.put(T.init); 2727 2728 // struct U 2729 // { 2730 // string src; 2731 // UniqueArray!U subs; 2732 // } 2733 // U u; 2734 } 2735 2736 /// class element 2737 @safe pure nothrow unittest 2738 { 2739 class Zing 2740 { 2741 void* raw; 2742 } 2743 class Edge : Zing 2744 { 2745 Zing[] actors; 2746 } 2747 2748 foreach (AT; AliasSeq!(UniqueArray, 2749 CopyingArray, 2750 SortedCopyingArray, 2751 SortedSetCopyingArray, 2752 SortedUniqueArray, 2753 SortedSetUniqueArray)) 2754 { 2755 alias A = AT!int; 2756 A a; 2757 } 2758 }