diff --git a/source/tanya/container/list.d b/source/tanya/container/list.d index adbd4fd..d0946d9 100644 --- a/source/tanya/container/list.d +++ b/source/tanya/container/list.d @@ -215,16 +215,19 @@ struct SList(T) * If $(D_PARAM init) is passed by reference, it will be copied. * * Params: + * R = Source list type. * init = Source list. * allocator = Allocator. */ - this(ref SList init, shared Allocator allocator = defaultAllocator) + this(R)(ref R init, shared Allocator allocator = defaultAllocator) + if (is(Unqual!R == SList)) { this(init[], allocator); } /// Ditto. - this(SList init, shared Allocator allocator = defaultAllocator) @trusted + this(R)(R init, shared Allocator allocator = defaultAllocator) @trusted + if (is(R == SList)) { this(allocator); if (allocator is init.allocator) @@ -734,18 +737,19 @@ struct SList(T) * * Returns: $(D_KEYWORD this). */ - ref typeof(this) opAssign(R)(const ref R that) + ref typeof(this) opAssign(R)(ref R that) if (is(Unqual!R == SList)) { return this = that[]; } /// Ditto. - ref typeof(this) opAssign(R)(const ref R that) - if (is(Unqual!R == SList)) + ref typeof(this) opAssign(R)(R that) + if (is(R == SList)) { swap(this.head, that.head); swap(this.allocator_, that.allocator_); + return this; } /** diff --git a/source/tanya/container/package.d b/source/tanya/container/package.d index 171f660..35400d5 100644 --- a/source/tanya/container/package.d +++ b/source/tanya/container/package.d @@ -14,5 +14,6 @@ module tanya.container; public import tanya.container.buffer; public import tanya.container.list; -public import tanya.container.slice; +public import tanya.container.string; +public import tanya.container.vector; public import tanya.container.queue; diff --git a/source/tanya/container/slice.d b/source/tanya/container/slice.d deleted file mode 100644 index 321acfb..0000000 --- a/source/tanya/container/slice.d +++ /dev/null @@ -1,2549 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Single-dimensioned array. - * - * Copyright: Eugene Wissner 2016-2017. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - */ -module tanya.container.slice; - -import core.checkedint; -import core.exception; -import std.algorithm.comparison; -import std.algorithm.mutation; -import std.conv; -import std.range.primitives; -import std.meta; -import std.traits; -import tanya.memory; - -/** - * Random-access range for the $(D_PSYMBOL Vector). - * - * Params: - * T = Element type. - */ -struct Slice(T) - if (!is(Unqual!T == char)) -{ - private T* begin, end; - private alias ContainerType = CopyConstness!(T, Vector!(Unqual!T)); - private ContainerType* vector; - - invariant - { - assert(this.begin <= this.end); - assert(this.vector !is null); - assert(this.begin >= this.vector.data); - assert(this.end <= this.vector.data + this.vector.length); - } - - private this(ref ContainerType vector, T* begin, T* end) @trusted - in - { - assert(begin <= end); - assert(begin >= vector.data); - assert(end <= vector.data + vector.length); - } - body - { - this.vector = &vector; - this.begin = begin; - this.end = end; - } - - @disable this(); - - @property Slice save() - { - return this; - } - - @property bool empty() const - { - return this.begin == this.end; - } - - @property size_t length() const - { - return this.end - this.begin; - } - - alias opDollar = length; - - @property ref inout(T) front() inout - in - { - assert(!empty); - } - body - { - return *this.begin; - } - - @property ref inout(T) back() inout @trusted - in - { - assert(!empty); - } - body - { - return *(this.end - 1); - } - - void popFront() @trusted - in - { - assert(!empty); - } - body - { - ++this.begin; - } - - void popBack() @trusted - in - { - assert(!empty); - } - body - { - --this.end; - } - - ref inout(T) opIndex(const size_t i) inout @trusted - in - { - assert(i < length); - } - body - { - return *(this.begin + i); - } - - Slice opIndex() - { - return typeof(return)(*this.vector, this.begin, this.end); - } - - Slice!(const T) opIndex() const - { - return typeof(return)(*this.vector, this.begin, this.end); - } - - Slice opSlice(const size_t i, const size_t j) @trusted - in - { - assert(i <= j); - assert(j <= length); - } - body - { - return typeof(return)(*this.vector, this.begin + i, this.begin + j); - } - - Slice!(const T) opSlice(const size_t i, const size_t j) const @trusted - in - { - assert(i <= j); - assert(j <= length); - } - body - { - return typeof(return)(*this.vector, this.begin + i, this.begin + j); - } - - inout(T[]) get() inout @trusted - { - return this.begin[0 .. length]; - } -} - -/** - * One dimensional array. - * - * Params: - * T = Content type. - */ -struct Vector(T) - if (!is(T == char)) -{ - private size_t length_; - private T* data; - private size_t capacity_; - - invariant - { - assert(this.length_ <= this.capacity_); - assert(this.capacity_ == 0 || this.data !is null); - } - - /** - * Creates a new $(D_PSYMBOL Vector) with the elements from a static array. - * - * Params: - * R = Static array size. - * init = Values to initialize the vector with. - * allocator = Allocator. - */ - this(size_t R)(T[R] init, shared Allocator allocator = defaultAllocator) - { - this(allocator); - insertBack!(T[])(init[]); - } - - /** - * Creates a new $(D_PSYMBOL Vector) with the elements from an input range. - * - * Params: - * R = Type of the initial range. - * init = Values to initialize the vector with. - * allocator = Allocator. - */ - this(R)(R init, shared Allocator allocator = defaultAllocator) - if (!isInfinite!R - && isInputRange!R - && isImplicitlyConvertible!(ElementType!R, T)) - { - this(allocator); - insertBack(init); - } - - /** - * Initializes this vector from another one. - * - * If $(D_PARAM init) is passed by value, it won't be copied, but moved. - * If the allocator of ($D_PARAM init) matches $(D_PARAM allocator), - * $(D_KEYWORD this) will just take the ownership over $(D_PARAM init)'s - * storage, otherwise, the storage will be allocated with - * $(D_PARAM allocator) and all elements will be moved; - * $(D_PARAM init) will be destroyed at the end. - * - * If $(D_PARAM init) is passed by reference, it will be copied. - * - * Params: - * R = Vector type. - * init = Source vector. - * allocator = Allocator. - */ - this(R)(const ref R init, shared Allocator allocator = defaultAllocator) - if (is(Unqual!R == Vector)) - { - this(allocator); - insertBack(init[]); - } - - /// Ditto. - this(R)(R init, shared Allocator allocator = defaultAllocator) @trusted - if (is(R == Vector)) - { - this(allocator); - if (allocator is init.allocator) - { - // Just steal all references and the allocator. - this.data = init.data; - this.length_ = init.length_; - this.capacity_ = init.capacity_; - - // Reset the source vector, so it can't destroy the moved storage. - init.length_ = init.capacity_ = 0; - init.data = null; - } - else - { - // Move each element. - reserve(init.length_); - moveEmplaceAll(init.data[0 .. init.length_], this.data[0 .. init.length_]); - this.length_ = init.length_; - // Destructor of init should destroy it here. - } - } - - /// - @trusted @nogc unittest - { - auto v1 = Vector!int([1, 2, 3]); - auto v2 = Vector!int(v1); - assert(v1 == v2); - - auto v3 = Vector!int(Vector!int([1, 2, 3])); - assert(v1 == v3); - assert(v3.length == 3); - assert(v3.capacity == 3); - } - - private @trusted @nogc unittest // const constructor tests - { - auto v1 = const Vector!int([1, 2, 3]); - auto v2 = Vector!int(v1); - assert(v1.data !is v2.data); - assert(v1 == v2); - - auto v3 = const Vector!int(Vector!int([1, 2, 3])); - assert(v1 == v3); - assert(v3.length == 3); - assert(v3.capacity == 3); - } - - /** - * Creates a new $(D_PSYMBOL Vector). - * - * Params: - * len = Initial length of the vector. - * init = Initial value to fill the vector with. - * allocator = Allocator. - */ - this(const size_t len, T init, shared Allocator allocator = defaultAllocator) @trusted - { - this(allocator); - reserve(len); - uninitializedFill(this.data[0 .. len], init); - length_ = len; - } - - /// Ditto. - this(const size_t len, shared Allocator allocator = defaultAllocator) - { - this(allocator); - length = len; - } - - /// Ditto. - this(shared Allocator allocator) - in - { - assert(allocator !is null); - } - body - { - allocator_ = allocator; - } - - /// - unittest - { - auto v = Vector!int([3, 8, 2]); - - assert(v.capacity == 3); - assert(v.length == 3); - assert(v[0] == 3 && v[1] == 8 && v[2] == 2); - } - - /// - unittest - { - auto v = Vector!int(3, 5); - - assert(v.capacity == 3); - assert(v.length == 3); - assert(v[0] == 5 && v[1] == 5 && v[2] == 5); - } - - @safe unittest - { - auto v1 = Vector!int(defaultAllocator); - } - - /** - * Destroys this $(D_PSYMBOL Vector). - */ - ~this() @trusted - { - clear(); - allocator.deallocate(this.data[0 .. capacity]); - } - - /** - * Copies the vector. - */ - this(this) - { - auto buf = this.data[0 .. this.length_]; - this.length_ = capacity_ = 0; - this.data = null; - insertBack(buf); - } - - /** - * Removes all elements. - */ - void clear() - { - length = 0; - } - - /// - unittest - { - auto v = Vector!int([18, 20, 15]); - v.clear(); - assert(v.length == 0); - assert(v.capacity == 3); - } - - /** - * Returns: How many elements the vector can contain without reallocating. - */ - @property size_t capacity() const - { - return capacity_; - } - - /// - @safe @nogc unittest - { - auto v = Vector!int(4); - assert(v.capacity == 4); - } - - /** - * Returns: Vector length. - */ - @property size_t length() const - { - return length_; - } - - /// Ditto. - size_t opDollar() const - { - return length; - } - - /** - * Expands/shrinks the vector. - * - * Params: - * len = New length. - */ - @property void length(const size_t len) @trusted - { - if (len == length) - { - return; - } - else if (len > length) - { - reserve(len); - initializeAll(this.data[length_ .. len]); - } - else - { - static if (hasElaborateDestructor!T) - { - const T* end = this.data + length_ - 1; - for (T* e = this.data + len; e != end; ++e) - { - destroy(*e); - } - } - } - length_ = len; - } - - /// - unittest - { - Vector!int v; - - v.length = 5; - assert(v.length == 5); - assert(v.capacity == 5); - - v.length = 7; - assert(v.length == 7); - assert(v.capacity == 7); - - assert(v[$ - 1] == 0); - v[$ - 1] = 3; - assert(v[$ - 1] == 3); - - v.length = 0; - assert(v.length == 0); - assert(v.capacity == 7); - } - - /** - * Reserves space for $(D_PARAM size) elements. - * - * If $(D_PARAM size) is less than or equal to the $(D_PSYMBOL capacity), the - * function call does not cause a reallocation and the vector capacity is not - * affected. - * - * Params: - * size = Desired size. - */ - void reserve(const size_t size) @trusted - { - if (capacity_ >= size) - { - return; - } - bool overflow; - immutable byteSize = mulu(size, T.sizeof, overflow); - assert(!overflow); - - void[] buf = this.data[0 .. this.capacity_]; - if (!allocator.reallocateInPlace(buf, byteSize)) - { - buf = allocator.allocate(byteSize); - if (buf is null) - { - onOutOfMemoryErrorNoGC(); - } - scope (failure) - { - allocator.deallocate(buf); - } - const T* end = this.data + this.length_; - for (T* src = this.data, dest = cast(T*) buf; src != end; ++src, ++dest) - { - moveEmplace(*src, *dest); - static if (hasElaborateDestructor!T) - { - destroy(*src); - } - } - allocator.deallocate(this.data[0 .. this.capacity_]); - this.data = cast(T*) buf; - } - this.capacity_ = size; - } - - /// - @nogc @safe unittest - { - Vector!int v; - assert(v.capacity == 0); - assert(v.length == 0); - - v.reserve(3); - assert(v.capacity == 3); - assert(v.length == 0); - } - - /** - * Requests the vector to reduce its capacity to fit the $(D_PARAM size). - * - * The request is non-binding. The vector won't become smaller than the - * $(D_PARAM length). - * - * Params: - * size = Desired size. - */ - void shrink(const size_t size) @trusted - { - if (capacity <= size) - { - return; - } - immutable n = max(length, size); - void[] buf = this.data[0 .. this.capacity_]; - if (allocator.reallocateInPlace(buf, n * T.sizeof)) - { - this.capacity_ = n; - } - } - - /// - @nogc @safe unittest - { - Vector!int v; - assert(v.capacity == 0); - assert(v.length == 0); - - v.reserve(5); - v.insertBack(1); - v.insertBack(3); - assert(v.capacity == 5); - assert(v.length == 2); - } - - /** - * Returns: $(D_KEYWORD true) if the vector is empty. - */ - @property bool empty() const - { - return length == 0; - } - - /** - * Removes the value at the back of the vector. - * - * Returns: The number of elements removed - * - * Precondition: $(D_INLINECODE !empty). - */ - void removeBack() - in - { - assert(!empty); - } - body - { - length = length - 1; - } - - /** - * Removes $(D_PARAM howMany) elements from the vector. - * - * This method doesn't fail if it could not remove $(D_PARAM howMany) - * elements. Instead, if $(D_PARAM howMany) is greater than the vector - * length, all elements are removed. - * - * Params: - * howMany = How many elements should be removed. - * - * Returns: The number of elements removed - */ - size_t removeBack(const size_t howMany) - out (removed) - { - assert(removed <= howMany); - } - body - { - immutable toRemove = min(howMany, length); - - length = length - toRemove; - - return toRemove; - } - - /// - unittest - { - auto v = Vector!int([5, 18, 17]); - - assert(v.removeBack(0) == 0); - assert(v.removeBack(2) == 2); - assert(v.removeBack(3) == 1); - assert(v.removeBack(3) == 0); - } - - /** - * Remove all elements beloning to $(D_PARAM r). - * - * Params: - * r = Range originally obtained from this vector. - * - * Returns: A range spanning the remaining elements in the array that - * initially were right after $(D_PARAM r). - * - * Precondition: $(D_PARAM r) refers to a region of $(D_KEYWORD this). - */ - Slice!T remove(Slice!T r) @trusted - in - { - assert(r.vector is &this); - assert(r.begin >= this.data); - assert(r.end <= this.data + length); - } - body - { - auto end = this.data + this.length; - moveAll(Slice!T(this, r.end, end), Slice!T(this, r.begin, end)); - length = length - r.length; - return Slice!T(this, r.begin, this.data + length); - } - - /// - @safe @nogc unittest - { - auto v = Vector!int([5, 18, 17, 2, 4, 6, 1]); - - assert(v.remove(v[1 .. 3]).length == 4); - assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6 && v[4] == 1); - assert(v.length == 5); - - assert(v.remove(v[4 .. 4]).length == 1); - assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6 && v[4] == 1); - assert(v.length == 5); - - assert(v.remove(v[4 .. 5]).length == 0); - assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6); - assert(v.length == 4); - - assert(v.remove(v[]).length == 0); - - } - - private void moveBack(R)(ref R el) @trusted - if (isImplicitlyConvertible!(R, T)) - { - reserve(this.length + 1); - moveEmplace(el, *(this.data + this.length_)); - ++this.length_; - } - - /** - * Inserts the $(D_PARAM el) into the vector. - * - * Params: - * R = Type of the inserted value(s) (single value, range or static array). - * el = Value(s) should be inserted. - * - * Returns: The number of elements inserted. - */ - size_t insertBack(R)(R el) - if (isImplicitlyConvertible!(R, T)) - { - moveBack(el); - return 1; - } - - /// Ditto. - size_t insertBack(R)(ref R el) @trusted - if (isImplicitlyConvertible!(R, T)) - { - reserve(this.length_ + 1); - emplace(this.data + this.length_, el); - ++this.length_; - return 1; - } - - /// Ditto. - size_t insertBack(R)(R el) - if (!isInfinite!R - && isInputRange!R - && isImplicitlyConvertible!(ElementType!R, T)) - { - static if (hasLength!R) - { - reserve(length + el.length); - } - size_t retLength; - foreach (e; el) - { - retLength += insertBack(e); - } - return retLength; - } - - /// Ditto. - size_t insertBack(size_t R)(T[R] el) - { - return insertBack!(T[])(el[]); - } - - /// Ditto. - alias insert = insertBack; - - /// - unittest - { - struct TestRange - { - int counter = 6; - - int front() - { - return counter; - } - - void popFront() - { - counter -= 2; - } - - bool empty() - { - return counter == 0; - } - } - - Vector!int v1; - - assert(v1.insertBack(5) == 1); - assert(v1.length == 1); - assert(v1.capacity == 1); - assert(v1.back == 5); - - assert(v1.insertBack(TestRange()) == 3); - assert(v1.length == 4); - assert(v1.capacity == 4); - assert(v1[0] == 5 && v1[1] == 6 && v1[2] == 4 && v1[3] == 2); - - assert(v1.insertBack([34, 234]) == 2); - assert(v1.length == 6); - assert(v1.capacity == 6); - assert(v1[4] == 34 && v1[5] == 234); - } - - /** - * Inserts $(D_PARAM el) before or after $(D_PARAM r). - * - * Params: - * R = Type of the inserted value(s) (single value, range or static array). - * r = Range originally obtained from this vector. - * el = Value(s) should be inserted. - * - * Returns: The number of elements inserted. - * - * Precondition: $(D_PARAM r) refers to a region of $(D_KEYWORD this). - */ - size_t insertAfter(R)(Slice!T r, R el) - if (!isInfinite!R - && isInputRange!R - && isImplicitlyConvertible!(ElementType!R, T)) - in - { - assert(r.vector is &this); - assert(r.begin >= this.data); - assert(r.end <= this.data + length); - } - body - { - immutable oldLen = length; - immutable offset = r.end - this.data; - immutable inserted = insertBack(el); - bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]); - return inserted; - } - - /// Ditto. - size_t insertAfter(size_t R)(Slice!T r, T[R] el) - in - { - assert(r.vector is &this); - assert(r.begin >= this.data); - assert(r.end <= this.data + length); - } - body - { - return insertAfter!(T[])(r, el[]); - } - - /// Ditto. - size_t insertAfter(R)(Slice!T r, auto ref R el) - if (isImplicitlyConvertible!(R, T)) - in - { - assert(r.vector is &this); - assert(r.begin >= this.data); - assert(r.end <= this.data + length); - } - body - { - immutable oldLen = length; - immutable offset = r.end - this.data; - - static if (__traits(isRef, el)) - { - insertBack(el); - } - else - { - moveBack(el); - } - bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]); - - return 1; - } - - /// Ditto. - size_t insertBefore(R)(Slice!T r, R el) - if (!isInfinite!R - && isInputRange!R - && isImplicitlyConvertible!(ElementType!R, T)) - in - { - assert(r.vector is &this); - assert(r.begin >= this.data); - assert(r.end <= this.data + length); - } - body - { - return insertAfter(Slice!T(this, this.data, r.begin), el); - } - - /// Ditto. - size_t insertBefore(size_t R)(Slice!T r, T[R] el) - in - { - assert(r.vector is &this); - assert(r.begin >= this.data); - assert(r.end <= this.data + length); - } - body - { - return insertBefore!(T[])(r, el[]); - } - - /// Ditto. - size_t insertBefore(R)(Slice!T r, auto ref R el) - if (isImplicitlyConvertible!(R, T)) - in - { - assert(r.vector is &this); - assert(r.begin >= this.data); - assert(r.end <= this.data + length); - } - body - { - immutable oldLen = length; - immutable offset = r.begin - this.data; - - static if (__traits(isRef, el)) - { - insertBack(el); - } - else - { - moveBack(el); - } - bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]); - - return 1; - } - - /// - unittest - { - Vector!int v1; - v1.insertAfter(v1[], [2, 8]); - assert(v1[0] == 2); - assert(v1[1] == 8); - assert(v1.length == 2); - - v1.insertAfter(v1[], [1, 2]); - assert(v1[0] == 2); - assert(v1[1] == 8); - assert(v1[2] == 1); - assert(v1[3] == 2); - assert(v1.length == 4); - - v1.insertAfter(v1[0 .. 0], [1, 2]); - assert(v1[0] == 1); - assert(v1[1] == 2); - assert(v1[2] == 2); - assert(v1[3] == 8); - assert(v1[4] == 1); - assert(v1[5] == 2); - assert(v1.length == 6); - - v1.insertAfter(v1[0 .. 4], 9); - assert(v1[0] == 1); - assert(v1[1] == 2); - assert(v1[2] == 2); - assert(v1[3] == 8); - assert(v1[4] == 9); - assert(v1[5] == 1); - assert(v1[6] == 2); - assert(v1.length == 7); - } - - /// - unittest - { - Vector!int v1; - v1.insertBefore(v1[], [2, 8]); - assert(v1[0] == 2); - assert(v1[1] == 8); - assert(v1.length == 2); - - v1.insertBefore(v1[], [1, 2]); - assert(v1[0] == 1); - assert(v1[1] == 2); - assert(v1[2] == 2); - assert(v1[3] == 8); - assert(v1.length == 4); - - v1.insertBefore(v1[0 .. 1], [1, 2]); - assert(v1[0] == 1); - assert(v1[1] == 2); - assert(v1[2] == 1); - assert(v1[3] == 2); - assert(v1[4] == 2); - assert(v1[5] == 8); - assert(v1.length == 6); - - v1.insertBefore(v1[2 .. $], 9); - assert(v1[0] == 1); - assert(v1[1] == 2); - assert(v1[2] == 9); - assert(v1[3] == 1); - assert(v1[4] == 2); - assert(v1[5] == 2); - assert(v1[6] == 8); - assert(v1.length == 7); - } - - /** - * Assigns a value to the element with the index $(D_PARAM pos). - * - * Params: - * value = Value. - * pos = Position. - * - * Returns: Assigned value. - * - * Precondition: $(D_INLINECODE length > pos). - */ - ref T opIndexAssign(ref T value, const size_t pos) - { - return opIndex(pos) = value; - } - - @safe unittest - { - Vector!int a = Vector!int(1); - a[0] = 5; - assert(a[0] == 5); - } - - /// Ditto. - T opIndexAssign(T value, const size_t pos) - { - return opIndexAssign(value, pos); - } - - /// Ditto. - Slice!T opIndexAssign(T value) - { - return opSliceAssign(value, 0, length); - } - - /// Ditto. - Slice!T opIndexAssign(ref T value) - { - return opSliceAssign(value, 0, length); - } - - /** - * Assigns a range or a static array. - * - * Params: - * R = Range type or static array length. - * value = Value. - * - * Returns: Assigned value. - * - * Precondition: $(D_INLINECODE length == value.length). - */ - Slice!T opIndexAssign(R)(R value) - if (!isInfinite!R && isInputRange!R - && isImplicitlyConvertible!(ElementType!R, T)) - { - return opSliceAssign!R(value, 0, length); - } - - /// Ditto. - Slice!T opIndexAssign(size_t R)(T[R] value) - { - return opSliceAssign!R(value, 0, length); - } - - /// - @nogc unittest - { - auto v1 = Vector!int([12, 1, 7]); - - v1[] = 3; - assert(v1[0] == 3); - assert(v1[1] == 3); - assert(v1[2] == 3); - - v1[] = [7, 1, 12]; - assert(v1[0] == 7); - assert(v1[1] == 1); - assert(v1[2] == 12); - } - - /** - * Params: - * pos = Index. - * - * Returns: The value at a specified index. - * - * Precondition: $(D_INLINECODE length > pos). - */ - ref inout(T) opIndex(const size_t pos) inout @trusted - in - { - assert(length > pos); - } - body - { - return *(this.data + pos); - } - - /** - * Returns: Random access range that iterates over elements of the vector, in - * forward order. - */ - Slice!T opIndex() @trusted - { - return typeof(return)(this, this.data, this.data + length); - } - - /// Ditto. - Slice!(const T) opIndex() const @trusted - { - return typeof(return)(this, this.data, this.data + length); - } - - /// - unittest - { - const v1 = Vector!int([6, 123, 34, 5]); - - assert(v1[0] == 6); - assert(v1[1] == 123); - assert(v1[2] == 34); - assert(v1[3] == 5); - static assert(is(typeof(v1[0]) == const(int))); - static assert(is(typeof(v1[]))); - } - - /** - * Comparison for equality. - * - * Params: - * that = The vector to compare with. - * - * Returns: $(D_KEYWORD true) if the vectors are equal, $(D_KEYWORD false) - * otherwise. - */ - bool opEquals()(auto ref typeof(this) that) @trusted - { - return equal(this.data[0 .. length], that.data[0 .. that.length]); - } - - /// Ditto. - bool opEquals()(const auto ref typeof(this) that) const @trusted - { - return equal(this.data[0 .. length], that.data[0 .. that.length]); - } - - /// Ditto. - bool opEquals(Slice!T that) - { - return equal(opIndex(), that); - } - - /** - * Comparison for equality. - * - * Params: - * R = Right hand side type. - * that = Right hand side vector range. - * - * Returns: $(D_KEYWORD true) if the vector and the range are equal, - * $(D_KEYWORD false) otherwise. - */ - bool opEquals(R)(Slice!R that) const - if (is(Unqual!R == T)) - { - return equal(opIndex(), that); - } - - /// - unittest - { - Vector!int v1, v2; - assert(v1 == v2); - - v1.length = 1; - v2.length = 2; - assert(v1 != v2); - - v1.length = 2; - v1[0] = v2[0] = 2; - v1[1] = 3; - v2[1] = 4; - assert(v1 != v2); - - v2[1] = 3; - assert(v1 == v2); - } - - /** - * Returns: The first element. - * - * Precondition: $(D_INLINECODE !empty). - */ - @property ref inout(T) front() inout - in - { - assert(!empty); - } - body - { - return *this.data; - } - - /// - @safe unittest - { - auto v = Vector!int([5]); - - assert(v.front == 5); - - v.length = 2; - v[1] = 15; - assert(v.front == 5); - } - - /** - * Returns: The last element. - * - * Precondition: $(D_INLINECODE !empty). - */ - @property ref inout(T) back() inout @trusted - in - { - assert(!empty); - } - body - { - return *(this.data + length - 1); - } - - /// - unittest - { - auto v = Vector!int([5]); - - assert(v.back == 5); - - v.length = 2; - v[1] = 15; - assert(v.back == 15); - } - - /** - * Params: - * i = Slice start. - * j = Slice end. - * - * Returns: A range that iterates over elements of the container from - * index $(D_PARAM i) up to (excluding) index $(D_PARAM j). - * - * Precondition: $(D_INLINECODE i <= j && j <= length). - */ - Slice!T opSlice(const size_t i, const size_t j) @trusted - in - { - assert(i <= j); - assert(j <= length); - } - body - { - return typeof(return)(this, this.data + i, this.data + j); - } - - /// Ditto. - Slice!(const T) opSlice(const size_t i, const size_t j) const @trusted - in - { - assert(i <= j); - assert(j <= length); - } - body - { - return typeof(return)(this, this.data + i, this.data + j); - } - - /// - unittest - { - Vector!int v; - auto r = v[]; - assert(r.length == 0); - assert(r.empty); - } - - /// - unittest - { - auto v = Vector!int([1, 2, 3]); - auto r = v[]; - - assert(r.front == 1); - assert(r.back == 3); - - r.popFront(); - assert(r.front == 2); - - r.popBack(); - assert(r.back == 2); - - assert(r.length == 1); - } - - /// - unittest - { - auto v = Vector!int([1, 2, 3, 4]); - auto r = v[1 .. 4]; - assert(r.length == 3); - assert(r[0] == 2); - assert(r[1] == 3); - assert(r[2] == 4); - - r = v[0 .. 0]; - assert(r.length == 0); - - r = v[4 .. 4]; - assert(r.length == 0); - } - - /** - * Slicing assignment. - * - * Params: - * R = Type of the assigned slice or length of the static array should be - * assigned. - * value = New value (single value, input range or static array). - * i = Slice start. - * j = Slice end. - * - * Returns: Slice with the assigned part of the vector. - * - * Precondition: $(D_INLINECODE i <= j && j <= length - * && value.length == j - i) - */ - Slice!T opSliceAssign(R)(R value, const size_t i, const size_t j) @trusted - if (!isInfinite!R - && isInputRange!R - && isImplicitlyConvertible!(ElementType!R, T)) - in - { - assert(i <= j); - assert(j <= length); - assert(j - i == walkLength(value)); - } - body - { - copy(value, this.data[i .. j]); - return opSlice(i, j); - } - - /// Ditto. - Slice!T opSliceAssign(size_t R)(T[R] value, const size_t i, const size_t j) - { - return opSliceAssign!(T[])(value[], i, j); - } - - /// Ditto. - Slice!T opSliceAssign(ref T value, const size_t i, const size_t j) @trusted - in - { - assert(i <= j); - assert(j <= length); - } - body - { - fill(this.data[i .. j], value); - return opSlice(i, j); - } - - /// Ditto. - Slice!T opSliceAssign(T value, const size_t i, const size_t j) - { - return opSliceAssign(value, i, j); - } - - /// - @nogc @safe unittest - { - auto v1 = Vector!int([3, 3, 3]); - auto v2 = Vector!int([1, 2]); - - v1[0 .. 2] = 286; - assert(v1[0] == 286); - assert(v1[1] == 286); - assert(v1[2] == 3); - - v2[0 .. $] = v1[1 .. 3]; - assert(v2[0] == 286); - assert(v2[1] == 3); - - v1[0 .. 2] = [5, 8]; - assert(v1[0] == 5); - assert(v1[1] == 8); - assert(v1[2] == 3); - } - - /** - * Returns an array used internally by the vector to store its owned elements. - * The length of the returned array may differ from the size of the allocated - * memory for the vector: the array contains only initialized elements, but - * not the reserved memory. - * - * Returns: The array with elements of this vector. - */ - inout(T[]) get() inout @trusted - { - return this.data[0 .. length]; - } - - /// - unittest - { - auto v = Vector!int([1, 2, 4]); - auto data = v.get(); - - assert(data[0] == 1); - assert(data[1] == 2); - assert(data[2] == 4); - assert(data.length == 3); - - data = v[1 .. 2].get(); - assert(data[0] == 2); - assert(data.length == 1); - } - - /** - * Assigns another vector. - * - * If $(D_PARAM that) is passed by value, it won't be copied, but moved. - * This vector will take the ownership over $(D_PARAM that)'s storage and - * the allocator. - * - * If $(D_PARAM that) is passed by reference, it will be copied. - * - * Params: - * R = Content type. - * that = The value should be assigned. - * - * Returns: $(D_KEYWORD this). - */ - ref typeof(this) opAssign(R)(const ref R that) - if (is(Unqual!R == Vector)) - { - return this = that[]; - } - - /// Ditto. - ref typeof(this) opAssign(R)(R that) @trusted - if (is(R == Vector)) - { - swap(this.data, that.data); - swap(this.length_, that.length_); - swap(this.capacity_, that.capacity_); - swap(this.allocator_, that.allocator_); - return this; - } - - /** - * Assigns a range to the vector. - * - * Params: - * R = Content type. - * that = The value should be assigned. - * - * Returns: $(D_KEYWORD this). - */ - ref typeof(this) opAssign(R)(R that) - if (!isInfinite!R - && isInputRange!R - && isImplicitlyConvertible!(ElementType!R, T)) - { - length = 0; - insertBack(that); - return this; - } - - /// - @safe @nogc unittest - { - auto v1 = const Vector!int([5, 15, 8]); - Vector!int v2; - v2 = v1; - assert(v1 == v2); - } - - /// - @safe @nogc unittest - { - auto v1 = const Vector!int([5, 15, 8]); - Vector!int v2; - v2 = v1[0 .. 2]; - assert(equal(v1[0 .. 2], v2[])); - } - - // Move assignment. - private @safe @nogc unittest - { - Vector!int v1; - v1 = Vector!int([5, 15, 8]); - } - - /** - * Assigns a static array. - * - * Params: - * R = Static array size. - * that = Values to initialize the vector with. - * - * Returns: $(D_KEYWORD this). - */ - ref typeof(this) opAssign(size_t R)(T[R] that) - { - return opAssign!(T[])(that[]); - } - - /// - @safe @nogc unittest - { - auto v1 = Vector!int([5, 15, 8]); - Vector!int v2; - - v2 = [5, 15, 8]; - assert(v1 == v2); - } - - mixin DefaultAllocator; -} - -/// -unittest -{ - auto v = Vector!int([5, 15, 8]); - - assert(v.front == 5); - assert(v[1] == 15); - assert(v.back == 8); - - auto r = v[]; - r[0] = 7; - assert(r.front == 7); - assert(r.front == v.front); -} - -@nogc unittest -{ - const v1 = Vector!int(); - const Vector!int v2; - const v3 = Vector!int([1, 5, 8]); - static assert(is(PointerTarget!(typeof(v3.data)) == const(int))); -} - -@nogc unittest -{ - // Test that const vectors return usable ranges. - auto v = const Vector!int([1, 2, 4]); - auto r1 = v[]; - - assert(r1.back == 4); - r1.popBack(); - assert(r1.back == 2); - r1.popBack(); - assert(r1.back == 1); - r1.popBack(); - assert(r1.length == 0); - - static assert(!is(typeof(r1[0] = 5))); - static assert(!is(typeof(v[0] = 5))); - - const r2 = r1[]; - static assert(is(typeof(r2[]))); -} - -@nogc unittest -{ - Vector!int v1; - const Vector!int v2; - - auto r1 = v1[]; - auto r2 = v1[]; - - assert(r1.length == 0); - assert(r2.empty); - assert(r1 == r2); - - v1.insertBack([1, 2, 4]); - assert(v1[] == v1); - assert(v2[] == v2); - assert(v2[] != v1); - assert(v1[] != v2); - assert(v1[].equal(v1[])); - assert(v2[].equal(v2[])); - assert(!v1[].equal(v2[])); -} - -@nogc unittest -{ - struct MutableEqualsStruct - { - int opEquals(typeof(this) that) @nogc - { - return true; - } - } - struct ConstEqualsStruct - { - int opEquals(const typeof(this) that) const @nogc - { - return true; - } - } - auto v1 = Vector!ConstEqualsStruct(); - auto v2 = Vector!ConstEqualsStruct(); - assert(v1 == v2); - assert(v1[] == v2); - assert(v1 == v2[]); - assert(v1[].equal(v2[])); - - auto v3 = const Vector!ConstEqualsStruct(); - auto v4 = const Vector!ConstEqualsStruct(); - assert(v3 == v4); - assert(v3[] == v4); - assert(v3 == v4[]); - assert(v3[].equal(v4[])); - - auto v7 = Vector!MutableEqualsStruct(1, MutableEqualsStruct()); - auto v8 = Vector!MutableEqualsStruct(1, MutableEqualsStruct()); - assert(v7 == v8); - assert(v7[] == v8); - assert(v7 == v8[]); - assert(v7[].equal(v8[])); -} - -@nogc unittest -{ - struct SWithDtor - { - ~this() @nogc - { - } - } - auto v = Vector!SWithDtor(); // Destructor can destroy empty vectors. -} - -private unittest -{ - class A - { - } - A a1, a2; - auto v1 = Vector!A([a1, a2]); -} - -private @safe @nogc unittest -{ - auto v = Vector!int([5, 15, 8]); - { - size_t i; - - foreach (e; v) - { - assert(i != 0 || e == 5); - assert(i != 1 || e == 15); - assert(i != 2 || e == 8); - ++i; - } - assert(i == 3); - } - { - size_t i = 3; - - foreach_reverse (e; v) - { - --i; - assert(i != 2 || e == 8); - assert(i != 1 || e == 15); - assert(i != 0 || e == 5); - } - assert(i == 0); - } -} - -private ref const(wchar) front(const wchar[] str) -pure nothrow @safe @nogc -in -{ - assert(str.length > 0); -} -body -{ - return str[0]; -} - -private void popFront(ref const(wchar)[] str, const size_t s = 1) -pure nothrow @safe @nogc -in -{ - assert(str.length >= s); -} -body -{ - str = str[s .. $]; -} - -/** - * Thrown on encoding errors. - */ -class UTFException : Exception -{ - /** - * Params: - * msg = The message for the exception. - * file = The file where the exception occurred. - * line = The line number where the exception occurred. - * next = The previous exception in the chain of exceptions, if any. - */ - this(string msg, - string file = __FILE__, - size_t line = __LINE__, - Throwable next = null) @nogc @safe pure nothrow - { - super(msg, file, line, next); - } -} - -/** - * Iterates $(D_PSYMBOL String) by UTF-8 code unit. - * - * Params: - * E = Element type ($(D_KEYWORD char) or $(D_INLINECODE const(char))). - */ -struct ByCodeUnit(E) - if (is(Unqual!E == char)) -{ - private E* begin, end; - private alias ContainerType = CopyConstness!(E, Slice!char); - private ContainerType* container; - - invariant - { - assert(this.begin <= this.end); - assert(this.container !is null); - assert(this.begin >= this.container.data); - assert(this.end <= this.container.data + this.container.length); - } - - private this(ref ContainerType container, E* begin, E* end) @trusted - in - { - assert(begin <= end); - assert(begin >= container.data); - assert(end <= container.data + container.length); - } - body - { - this.container = &container; - this.begin = begin; - this.end = end; - } - - @disable this(); - - @property ByCodeUnit save() - { - return this; - } - - @property bool empty() const - { - return this.begin == this.end; - } - - @property size_t length() const - { - return this.end - this.begin; - } - - alias opDollar = length; - - @property ref inout(E) front() inout - in - { - assert(!empty); - } - body - { - return *this.begin; - } - - @property ref inout(E) back() inout @trusted - in - { - assert(!empty); - } - body - { - return *(this.end - 1); - } - - void popFront() @trusted - in - { - assert(!empty); - } - body - { - ++this.begin; - } - - void popBack() @trusted - in - { - assert(!empty); - } - body - { - --this.end; - } - - ref inout(E) opIndex(const size_t i) inout @trusted - in - { - assert(i < length); - } - body - { - return *(this.begin + i); - } - - ByCodeUnit opIndex() - { - return typeof(return)(*this.container, this.begin, this.end); - } - - ByCodeUnit!(const E) opIndex() const - { - return typeof(return)(*this.container, this.begin, this.end); - } - - ByCodeUnit opSlice(const size_t i, const size_t j) @trusted - in - { - assert(i <= j); - assert(j <= length); - } - body - { - return typeof(return)(*this.container, this.begin + i, this.begin + j); - } - - ByCodeUnit!(const E) opSlice(const size_t i, const size_t j) const @trusted - in - { - assert(i <= j); - assert(j <= length); - } - body - { - return typeof(return)(*this.container, this.begin + i, this.begin + j); - } - - inout(E[]) get() inout @trusted - { - return this.begin[0 .. length]; - } -} - -/// UTF-8 string. -alias String = Slice!char; - -/** - * UTF-8 string. - * - * Params: - * T = $(D_KEYWORD char). - */ -struct Slice(T) - if (is(T == char)) -{ - private size_t length_; - private char* data; - private size_t capacity_; - - pure nothrow @safe @nogc invariant - { - assert(this.length_ <= this.capacity_); - } - - /** - * Constructs the string from a stringish range. - * - * Params: - * R = String type. - * str = Initial string. - * allocator = Allocator. - * - * Throws: $(D_PSYMBOL UTFException). - * - * Precondition: $(D_INLINECODE allocator is null). - */ - this(R)(const R str, shared Allocator allocator = defaultAllocator) - if (!isInfinite!R - && isInputRange!R - && isSomeChar!(ElementEncodingType!R)) - { - this(allocator); - insertBack(str); - } - - /// - @safe @nogc unittest - { - auto s = String("\u10437"w); - assert("\u10437" == s.get()); - } - - /// - @safe @nogc unittest - { - auto s = String("Отказаться от вина - в этом страшная вина."d); - assert("Отказаться от вина - в этом страшная вина." == s.get()); - } - - /** - * Initializes this string from another one. - * - * If $(D_PARAM init) is passed by value, it won't be copied, but moved. - * If the allocator of ($D_PARAM init) matches $(D_PARAM allocator), - * $(D_KEYWORD this) will just take the ownership over $(D_PARAM init)'s - * storage, otherwise, the storage will be allocated with - * $(D_PARAM allocator). $(D_PARAM init) will be destroyed at the end. - * - * If $(D_PARAM init) is passed by reference, it will be copied. - * - * Params: - * init = Source string. - * allocator = Allocator. - * - * Precondition: $(D_INLINECODE allocator is null). - */ - this(Slice!char init, shared Allocator allocator = defaultAllocator) - nothrow @trusted @nogc - { - this(allocator); - if (allocator !is init.allocator) - { - // Just steal all references and the allocator. - this.data = init.data; - this.length_ = init.length_; - this.capacity_ = init.capacity_; - - // Reset the source string, so it can't destroy the moved storage. - init.length_ = init.capacity_ = 0; - init.data = null; - } - else - { - reserve(init.length); - init.data[0 .. init.length].copy(this.data[0 .. init.length]); - this.length_ = init.length; - } - } - - /// Ditto. - this(ref const Slice!char init, shared Allocator allocator = defaultAllocator) - nothrow @trusted @nogc - { - this(allocator); - reserve(init.length); - init.data[0 .. init.length].copy(this.data[0 .. init.length]); - this.length_ = init.length; - } - - /// Ditto. - this(shared Allocator allocator) pure nothrow @safe @nogc - in - { - assert(allocator !is null); - } - body - { - this.allocator_ = allocator; - } - - /** - * Fills the string with $(D_PARAM n) consecutive copies of character $(D_PARAM chr). - * - * Params: - * C = Type of the character to fill the string with. - * n = Number of characters to copy. - * chr = Character to fill the string with. - */ - this(C)(const size_t n, const C chr, - shared Allocator allocator = defaultAllocator) @trusted - if (isSomeChar!C) - { - this(allocator); - if (n == 0) - { - return; - } - insertBack(chr); - - // insertBack should validate the character, so we can just copy it - // n - 1 times. - auto remaining = length * n; - - reserve(remaining); - - // Use a quick copy. - for (auto i = this.length_ * 2; i <= remaining; i *= 2) - { - this.data[0 .. this.length_].copy(this.data[this.length_ .. i]); - this.length_ = i; - } - remaining -= length; - copy(this.data[this.length_ - remaining .. this.length_], - this.data[this.length_ .. this.length_ + remaining]); - this.length_ += remaining; - } - - private unittest - { - { - auto s = String(1, 'О'); - assert(s.length == 2); - } - { - auto s = String(3, 'О'); - assert(s.length == 6); - } - { - auto s = String(8, 'О'); - assert(s.length == 16); - } - } - - /** - * Destroys the string. - */ - ~this() nothrow @trusted @nogc - { - allocator.deallocate(this.data[0 .. this.capacity_]); - } - - private void write4Bytes(ref const dchar src) - pure nothrow @trusted @nogc - in - { - assert(capacity - length >= 4); - assert(src - 0x10000 < 0x100000); - } - body - { - auto dst = this.data + length; - - *dst++ = 0xf0 | (src >> 18); - *dst++ = 0x80 | ((src >> 12) & 0x3f); - *dst++ = 0x80 | ((src >> 6) & 0x3f); - *dst = 0x80 | (src & 0x3f); - - this.length_ += 4; - } - - private size_t insertWideChar(C)(auto ref const C chr) @trusted - if (is(C == wchar) || is(C == dchar)) - in - { - assert(capacity - length >= C.sizeof); - } - body - { - auto dst = this.data + length; - if (chr < 0x80) - { - *dst = chr & 0x7f; - this.length_ += 1; - return 1; - } - else if (chr < 0x800) - { - *dst++ = 0xc0 | (chr >> 6) & 0xff; - *dst = 0x80 | (chr & 0x3f); - this.length_ += 2; - return 2; - } - else if (chr < 0xd800 || chr - 0xe000 < 0x2000) - { - *dst++ = 0xe0 | (chr >> 12) & 0xff; - *dst++ = 0x80 | ((chr >> 6) & 0x3f); - *dst = 0x80 | (chr & 0x3f); - this.length_ += 3; - return 3; - } - return 0; - } - - /** - * Inserts a single character at the end of the string. - * - * Params: - * chr = The character should be inserted. - * - * Returns: The number of bytes inserted. - * - * Throws: $(D_PSYMBOL UTFException). - */ - size_t insertBack(const char chr) @trusted @nogc - { - if ((chr & 0x80) != 0) - { - throw defaultAllocator.make!UTFException("Invalid UTF-8 character"); - } - reserve(length + 1); - - *(data + length) = chr; - ++this.length_; - - return 1; - } - - /// Ditto. - size_t insertBack(const wchar chr) @trusted @nogc - { - reserve(length + wchar.sizeof); - - auto ret = insertWideChar(chr); - if (ret == 0) - { - throw defaultAllocator.make!UTFException("Invalid UTF-16 sequeunce"); - } - return ret; - } - - /// Ditto. - size_t insertBack(const dchar chr) @trusted @nogc - { - reserve(length + dchar.sizeof); - - auto ret = insertWideChar(chr); - if (ret > 0) - { - return ret; - } - else if (chr - 0x10000 < 0x100000) - { - write4Bytes(chr); - return 4; - } - else - { - throw defaultAllocator.make!UTFException("Invalid UTF-32 sequeunce"); - } - } - - /** - * Inserts a stringish range at the end of the string. - * - * Params: - * R = Type of the inserted string. - * str = String should be inserted. - * - * Returns: The number of bytes inserted. - * - * Throws: $(D_PSYMBOL UTFException). - */ - size_t insertBack(R)(R str) @trusted - if (!isInfinite!R - && isInputRange!R - && is(Unqual!(ElementEncodingType!R) == char)) - { - size_t size; - static if (hasLength!R || isNarrowString!R) - { - size = str.length + length; - reserve(size); - } - - static if (isNarrowString!R) - { - str.copy(this.data[length .. size]); - this.length_ = size; - return str.length; - } - else - { - size_t insertedLength; - while (!str.empty) - { - ubyte expectedLength; - if ((str.front & 0x80) == 0x00) - { - expectedLength = 1; - } - else if ((str.front & 0xe0) == 0xc0) - { - expectedLength = 2; - } - else if ((str.front & 0xf0) == 0xe0) - { - expectedLength = 3; - } - else if ((str.front & 0xf8) == 0xf0) - { - expectedLength = 4; - } - else - { - throw defaultAllocator.make!UTFException("Invalid UTF-8 sequeunce"); - } - size = length + expectedLength; - reserve(size); - - for (; expectedLength > 0; --expectedLength) - { - if (str.empty) - { - throw defaultAllocator.make!UTFException("Invalid UTF-8 sequeunce"); - } - *(data + length) = str.front; - str.popFront(); - } - insertedLength += expectedLength; - this.length_ = size; - } - return insertedLength; - } - } - - /// Ditto. - size_t insertBack(R)(R str) @trusted - if (!isInfinite!R - && isInputRange!R - && is(Unqual!(ElementEncodingType!R) == wchar)) - { - static if (hasLength!R || isNarrowString!R) - { - reserve(length + str.length * wchar.sizeof); - } - - static if (isNarrowString!R) - { - const(wchar)[] range = str; - } - else - { - alias range = str; - } - - auto oldLength = length; - - while (!range.empty) - { - reserve(length + 4); - - auto ret = insertWideChar(range.front); - if (ret > 0) - { - range.popFront(); - } - else if (range.front - 0xd800 < 2048) - { // Surrogate pair. - static if (isNarrowString!R) - { - if (range.length < 2 || range[1] - 0xdc00 >= 0x400) - { - throw defaultAllocator.make!UTFException("Invalid UTF-16 sequeunce"); - } - dchar d = (range[0] - 0xd800) | ((range[1] - 0xdc00) >> 10); - - range.popFront(2); - } - else - { - dchar d = range.front - 0xd800; - range.popFront(); - - if (range.empty || range.front - 0xdc00 >= 0x400) - { - throw defaultAllocator.make!UTFException("Invalid UTF-16 sequeunce"); - } - d |= (range.front - 0xdc00) >> 10; - - range.popFront(); - } - write4Bytes(d); - } - else - { - throw defaultAllocator.make!UTFException("Invalid UTF-16 sequeunce"); - } - } - return this.length_ - oldLength; - } - - /// Ditto. - size_t insertBack(R)(R str) @trusted - if (!isInfinite!R - && isInputRange!R - && is(Unqual!(ElementEncodingType!R) == dchar)) - { - static if (hasLength!R || isSomeString!R) - { - reserve(length + str.length * 4); - } - - size_t insertedLength; - foreach (const dchar c; str) - { - insertedLength += insertBack(c); - } - return insertedLength; - } - - /// Ditto. - alias insert = insertBack; - - /** - * Reserves $(D_PARAM size) bytes for the string. - * - * If $(D_PARAM size) is less than or equal to the $(D_PSYMBOL capacity), the - * function call does not cause a reallocation and the string capacity is not - * affected. - * - * Params: - * size = Desired size in bytes. - */ - void reserve(const size_t size) nothrow @trusted @nogc - { - if (this.capacity_ >= size) - { - return; - } - - this.data = allocator.resize(this.data[0 .. this.capacity_], size).ptr; - this.capacity_ = size; - } - - /// - @nogc @safe unittest - { - String s; - assert(s.capacity == 0); - - s.reserve(3); - assert(s.capacity == 3); - - s.reserve(3); - assert(s.capacity == 3); - - s.reserve(1); - assert(s.capacity == 3); - } - - /** - * Requests the string to reduce its capacity to fit the $(D_PARAM size). - * - * The request is non-binding. The string won't become smaller than the - * string byte length. - * - * Params: - * size = Desired size. - */ - void shrink(const size_t size) nothrow @trusted @nogc - { - if (this.capacity_ <= size) - { - return; - } - - const n = max(this.length_, size); - void[] buf = this.data[0 .. this.capacity_]; - if (allocator.reallocate(buf, n)) - { - this.capacity_ = n; - this.data = cast(char*) buf; - } - } - - /// - @nogc @safe unittest - { - auto s = String("Die Alten lasen laut."); - assert(s.capacity == 21); - - s.reserve(30); - s.shrink(25); - assert(s.capacity == 25); - - s.shrink(18); - assert(s.capacity == 21); - } - - /** - * Returns: String capacity in bytes. - */ - @property size_t capacity() const pure nothrow @safe @nogc - { - return this.capacity_; - } - - /// - unittest - { - auto s = String("In allem Schreiben ist Schamlosigkeit."); - assert(s.capacity == 38); - } - - /** - * Returns an array used internally by the string. - * The length of the returned array may be smaller than the size of the - * reserved memory for the string. - * - * Returns: The array representing the string. - */ - inout(char[]) get() inout pure nothrow @trusted @nogc - { - return this.data[0 .. this.length_]; - } - - /** - * Returns: The number of code units that are required to encode the string. - */ - @property size_t length() const pure nothrow @safe @nogc - { - return this.length_; - } - - /// - alias opDollar = length; - - /// - unittest - { - auto s = String("Piscis primuin a capite foetat."); - assert(s.length == 31); - assert(s[$ - 1] == '.'); - } - - /** - * Params: - * pos = Position. - * - * Returns: Byte at $(D_PARAM pos). - * - * Precondition: $(D_INLINECODE length > pos). - */ - ref inout(char) opIndex(const size_t pos) inout pure nothrow @trusted @nogc - in - { - assert(length > pos); - } - body - { - return *(this.data + pos); - } - - /// - unittest - { - auto s = String("Alea iacta est."); - assert(s[0] == 'A'); - assert(s[4] == ' '); - } - - /** - * Returns: Random access range that iterates over the string by bytes, in - * forward order. - */ - ByCodeUnit!char opIndex() pure nothrow @trusted @nogc - { - return typeof(return)(this, this.data, this.data + length); - } - - /// Ditto. - ByCodeUnit!(const char) opIndex() const pure nothrow @trusted @nogc - { - return typeof(return)(this, this.data, this.data + length); - } - - /// - unittest - { - auto s = String("Plutarchus"); - auto r = s[]; - assert(r.front == 'P'); - assert(r.back == 's'); - - r.popFront(); - assert(r.front == 'l'); - assert(r.back == 's'); - - r.popBack(); - assert(r.front == 'l'); - assert(r.back == 'u'); - - assert(r.length == 8); - } - - /** - * Returns: $(D_KEYWORD true) if the vector is empty. - */ - @property bool empty() const pure nothrow @safe @nogc - { - return length == 0; - } - - /** - * Params: - * i = Slice start. - * j = Slice end. - * - * Returns: A range that iterates over the string by bytes from - * index $(D_PARAM i) up to (excluding) index $(D_PARAM j). - * - * Precondition: $(D_INLINECODE i <= j && j <= length). - */ - ByCodeUnit!char opSlice(const size_t i, const size_t j) - pure nothrow @trusted @nogc - in - { - assert(i <= j); - assert(j <= length); - } - body - { - return typeof(return)(this, this.data + i, this.data + j); - } - - /// Ditto. - ByCodeUnit!(const char) opSlice(const size_t i, const size_t j) - const pure nothrow @trusted @nogc - in - { - assert(i <= j); - assert(j <= length); - } - body - { - return typeof(return)(this, this.data + i, this.data + j); - } - - /// - unittest - { - auto s = String("Vladimir Soloviev"); - auto r = s[9 .. $]; - - assert(r.front == 'S'); - assert(r.back == 'v'); - - r.popFront(); - r.popBack(); - assert(r.front == 'o'); - assert(r.back == 'e'); - - r.popFront(); - r.popBack(); - assert(r.front == 'l'); - assert(r.back == 'i'); - - r.popFront(); - r.popBack(); - assert(r.front == 'o'); - assert(r.back == 'v'); - - r.popFront(); - r.popBack(); - assert(r.empty); - } - - mixin DefaultAllocator; -} diff --git a/source/tanya/container/string.d b/source/tanya/container/string.d new file mode 100644 index 0000000..4737fab --- /dev/null +++ b/source/tanya/container/string.d @@ -0,0 +1,905 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * UTF-8 string. + * + * Copyright: Eugene Wissner 2017. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + */ +module tanya.container.string; + +import core.exception; +import std.algorithm.comparison; +import std.algorithm.mutation; +import std.range; +import std.traits; +import tanya.memory; + +private ref const(wchar) front(const wchar[] str) +pure nothrow @safe @nogc +in +{ + assert(str.length > 0); +} +body +{ + return str[0]; +} + +private void popFront(ref const(wchar)[] str, const size_t s = 1) +pure nothrow @safe @nogc +in +{ + assert(str.length >= s); +} +body +{ + str = str[s .. $]; +} + +/** + * Thrown on encoding errors. + */ +class UTFException : Exception +{ + /** + * Params: + * msg = The message for the exception. + * file = The file where the exception occurred. + * line = The line number where the exception occurred. + * next = The previous exception in the chain of exceptions, if any. + */ + this(string msg, + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null) @nogc @safe pure nothrow + { + super(msg, file, line, next); + } +} + +/** + * Iterates $(D_PSYMBOL String) by UTF-8 code unit. + * + * Params: + * E = Element type ($(D_KEYWORD char) or $(D_INLINECODE const(char))). + */ +struct ByCodeUnit(E) + if (is(Unqual!E == char)) +{ + private E* begin, end; + private alias ContainerType = CopyConstness!(E, String); + private ContainerType* container; + + invariant + { + assert(this.begin <= this.end); + assert(this.container !is null); + assert(this.begin >= this.container.data); + assert(this.end <= this.container.data + this.container.length); + } + + private this(ref ContainerType container, E* begin, E* end) @trusted + in + { + assert(begin <= end); + assert(begin >= container.data); + assert(end <= container.data + container.length); + } + body + { + this.container = &container; + this.begin = begin; + this.end = end; + } + + @disable this(); + + @property ByCodeUnit save() + { + return this; + } + + @property bool empty() const + { + return this.begin == this.end; + } + + @property size_t length() const + { + return this.end - this.begin; + } + + alias opDollar = length; + + @property ref inout(E) front() inout + in + { + assert(!empty); + } + body + { + return *this.begin; + } + + @property ref inout(E) back() inout @trusted + in + { + assert(!empty); + } + body + { + return *(this.end - 1); + } + + void popFront() @trusted + in + { + assert(!empty); + } + body + { + ++this.begin; + } + + void popBack() @trusted + in + { + assert(!empty); + } + body + { + --this.end; + } + + ref inout(E) opIndex(const size_t i) inout @trusted + in + { + assert(i < length); + } + body + { + return *(this.begin + i); + } + + ByCodeUnit opIndex() + { + return typeof(return)(*this.container, this.begin, this.end); + } + + ByCodeUnit!(const E) opIndex() const + { + return typeof(return)(*this.container, this.begin, this.end); + } + + ByCodeUnit opSlice(const size_t i, const size_t j) @trusted + in + { + assert(i <= j); + assert(j <= length); + } + body + { + return typeof(return)(*this.container, this.begin + i, this.begin + j); + } + + ByCodeUnit!(const E) opSlice(const size_t i, const size_t j) const @trusted + in + { + assert(i <= j); + assert(j <= length); + } + body + { + return typeof(return)(*this.container, this.begin + i, this.begin + j); + } + + inout(E[]) get() inout @trusted + { + return this.begin[0 .. length]; + } +} + +/** + * UTF-8 string. + */ +struct String +{ + private size_t length_; + private char* data; + private size_t capacity_; + + pure nothrow @safe @nogc invariant + { + assert(this.length_ <= this.capacity_); + } + + /** + * Constructs the string from a stringish range. + * + * Params: + * R = String type. + * str = Initial string. + * allocator = Allocator. + * + * Throws: $(D_PSYMBOL UTFException). + * + * Precondition: $(D_INLINECODE allocator is null). + */ + this(R)(const R str, shared Allocator allocator = defaultAllocator) + if (!isInfinite!R + && isInputRange!R + && isSomeChar!(ElementEncodingType!R)) + { + this(allocator); + insertBack(str); + } + + /// + @safe @nogc unittest + { + auto s = String("\u10437"w); + assert("\u10437" == s.get()); + } + + /// + @safe @nogc unittest + { + auto s = String("Отказаться от вина - в этом страшная вина."d); + assert("Отказаться от вина - в этом страшная вина." == s.get()); + } + + /** + * Initializes this string from another one. + * + * If $(D_PARAM init) is passed by value, it won't be copied, but moved. + * If the allocator of ($D_PARAM init) matches $(D_PARAM allocator), + * $(D_KEYWORD this) will just take the ownership over $(D_PARAM init)'s + * storage, otherwise, the storage will be allocated with + * $(D_PARAM allocator). $(D_PARAM init) will be destroyed at the end. + * + * If $(D_PARAM init) is passed by reference, it will be copied. + * + * Params: + * init = Source string. + * allocator = Allocator. + * + * Precondition: $(D_INLINECODE allocator is null). + */ + this(String init, shared Allocator allocator = defaultAllocator) + nothrow @trusted @nogc + { + this(allocator); + if (allocator !is init.allocator) + { + // Just steal all references and the allocator. + this.data = init.data; + this.length_ = init.length_; + this.capacity_ = init.capacity_; + + // Reset the source string, so it can't destroy the moved storage. + init.length_ = init.capacity_ = 0; + init.data = null; + } + else + { + reserve(init.length); + init.data[0 .. init.length].copy(this.data[0 .. init.length]); + this.length_ = init.length; + } + } + + /// Ditto. + this(ref const String init, shared Allocator allocator = defaultAllocator) + nothrow @trusted @nogc + { + this(allocator); + reserve(init.length); + init.data[0 .. init.length].copy(this.data[0 .. init.length]); + this.length_ = init.length; + } + + /// Ditto. + this(shared Allocator allocator) pure nothrow @safe @nogc + in + { + assert(allocator !is null); + } + body + { + this.allocator_ = allocator; + } + + /** + * Fills the string with $(D_PARAM n) consecutive copies of character $(D_PARAM chr). + * + * Params: + * C = Type of the character to fill the string with. + * n = Number of characters to copy. + * chr = Character to fill the string with. + */ + this(C)(const size_t n, const C chr, + shared Allocator allocator = defaultAllocator) @trusted + if (isSomeChar!C) + { + this(allocator); + if (n == 0) + { + return; + } + insertBack(chr); + + // insertBack should validate the character, so we can just copy it + // n - 1 times. + auto remaining = length * n; + + reserve(remaining); + + // Use a quick copy. + for (auto i = this.length_ * 2; i <= remaining; i *= 2) + { + this.data[0 .. this.length_].copy(this.data[this.length_ .. i]); + this.length_ = i; + } + remaining -= length; + copy(this.data[this.length_ - remaining .. this.length_], + this.data[this.length_ .. this.length_ + remaining]); + this.length_ += remaining; + } + + private unittest + { + { + auto s = String(1, 'О'); + assert(s.length == 2); + } + { + auto s = String(3, 'О'); + assert(s.length == 6); + } + { + auto s = String(8, 'О'); + assert(s.length == 16); + } + } + + /** + * Destroys the string. + */ + ~this() nothrow @trusted @nogc + { + allocator.deallocate(this.data[0 .. this.capacity_]); + } + + private void write4Bytes(ref const dchar src) + pure nothrow @trusted @nogc + in + { + assert(capacity - length >= 4); + assert(src - 0x10000 < 0x100000); + } + body + { + auto dst = this.data + length; + + *dst++ = 0xf0 | (src >> 18); + *dst++ = 0x80 | ((src >> 12) & 0x3f); + *dst++ = 0x80 | ((src >> 6) & 0x3f); + *dst = 0x80 | (src & 0x3f); + + this.length_ += 4; + } + + private size_t insertWideChar(C)(auto ref const C chr) @trusted + if (is(C == wchar) || is(C == dchar)) + in + { + assert(capacity - length >= C.sizeof); + } + body + { + auto dst = this.data + length; + if (chr < 0x80) + { + *dst = chr & 0x7f; + this.length_ += 1; + return 1; + } + else if (chr < 0x800) + { + *dst++ = 0xc0 | (chr >> 6) & 0xff; + *dst = 0x80 | (chr & 0x3f); + this.length_ += 2; + return 2; + } + else if (chr < 0xd800 || chr - 0xe000 < 0x2000) + { + *dst++ = 0xe0 | (chr >> 12) & 0xff; + *dst++ = 0x80 | ((chr >> 6) & 0x3f); + *dst = 0x80 | (chr & 0x3f); + this.length_ += 3; + return 3; + } + return 0; + } + + /** + * Inserts a single character at the end of the string. + * + * Params: + * chr = The character should be inserted. + * + * Returns: The number of bytes inserted. + * + * Throws: $(D_PSYMBOL UTFException). + */ + size_t insertBack(const char chr) @trusted @nogc + { + if ((chr & 0x80) != 0) + { + throw defaultAllocator.make!UTFException("Invalid UTF-8 character"); + } + reserve(length + 1); + + *(data + length) = chr; + ++this.length_; + + return 1; + } + + /// Ditto. + size_t insertBack(const wchar chr) @trusted @nogc + { + reserve(length + wchar.sizeof); + + auto ret = insertWideChar(chr); + if (ret == 0) + { + throw defaultAllocator.make!UTFException("Invalid UTF-16 sequeunce"); + } + return ret; + } + + /// Ditto. + size_t insertBack(const dchar chr) @trusted @nogc + { + reserve(length + dchar.sizeof); + + auto ret = insertWideChar(chr); + if (ret > 0) + { + return ret; + } + else if (chr - 0x10000 < 0x100000) + { + write4Bytes(chr); + return 4; + } + else + { + throw defaultAllocator.make!UTFException("Invalid UTF-32 sequeunce"); + } + } + + /** + * Inserts a stringish range at the end of the string. + * + * Params: + * R = Type of the inserted string. + * str = String should be inserted. + * + * Returns: The number of bytes inserted. + * + * Throws: $(D_PSYMBOL UTFException). + */ + size_t insertBack(R)(R str) @trusted + if (!isInfinite!R + && isInputRange!R + && is(Unqual!(ElementEncodingType!R) == char)) + { + size_t size; + static if (hasLength!R || isNarrowString!R) + { + size = str.length + length; + reserve(size); + } + + static if (isNarrowString!R) + { + str.copy(this.data[length .. size]); + this.length_ = size; + return str.length; + } + else + { + size_t insertedLength; + while (!str.empty) + { + ubyte expectedLength; + if ((str.front & 0x80) == 0x00) + { + expectedLength = 1; + } + else if ((str.front & 0xe0) == 0xc0) + { + expectedLength = 2; + } + else if ((str.front & 0xf0) == 0xe0) + { + expectedLength = 3; + } + else if ((str.front & 0xf8) == 0xf0) + { + expectedLength = 4; + } + else + { + throw defaultAllocator.make!UTFException("Invalid UTF-8 sequeunce"); + } + size = length + expectedLength; + reserve(size); + + for (; expectedLength > 0; --expectedLength) + { + if (str.empty) + { + throw defaultAllocator.make!UTFException("Invalid UTF-8 sequeunce"); + } + *(data + length) = str.front; + str.popFront(); + } + insertedLength += expectedLength; + this.length_ = size; + } + return insertedLength; + } + } + + /// Ditto. + size_t insertBack(R)(R str) @trusted + if (!isInfinite!R + && isInputRange!R + && is(Unqual!(ElementEncodingType!R) == wchar)) + { + static if (hasLength!R || isNarrowString!R) + { + reserve(length + str.length * wchar.sizeof); + } + + static if (isNarrowString!R) + { + const(wchar)[] range = str; + } + else + { + alias range = str; + } + + auto oldLength = length; + + while (!range.empty) + { + reserve(length + 4); + + auto ret = insertWideChar(range.front); + if (ret > 0) + { + range.popFront(); + } + else if (range.front - 0xd800 < 2048) + { // Surrogate pair. + static if (isNarrowString!R) + { + if (range.length < 2 || range[1] - 0xdc00 >= 0x400) + { + throw defaultAllocator.make!UTFException("Invalid UTF-16 sequeunce"); + } + dchar d = (range[0] - 0xd800) | ((range[1] - 0xdc00) >> 10); + + range.popFront(2); + } + else + { + dchar d = range.front - 0xd800; + range.popFront(); + + if (range.empty || range.front - 0xdc00 >= 0x400) + { + throw defaultAllocator.make!UTFException("Invalid UTF-16 sequeunce"); + } + d |= (range.front - 0xdc00) >> 10; + + range.popFront(); + } + write4Bytes(d); + } + else + { + throw defaultAllocator.make!UTFException("Invalid UTF-16 sequeunce"); + } + } + return this.length_ - oldLength; + } + + /// Ditto. + size_t insertBack(R)(R str) @trusted + if (!isInfinite!R + && isInputRange!R + && is(Unqual!(ElementEncodingType!R) == dchar)) + { + static if (hasLength!R || isSomeString!R) + { + reserve(length + str.length * 4); + } + + size_t insertedLength; + foreach (const dchar c; str) + { + insertedLength += insertBack(c); + } + return insertedLength; + } + + /// Ditto. + alias insert = insertBack; + + /** + * Reserves $(D_PARAM size) bytes for the string. + * + * If $(D_PARAM size) is less than or equal to the $(D_PSYMBOL capacity), the + * function call does not cause a reallocation and the string capacity is not + * affected. + * + * Params: + * size = Desired size in bytes. + */ + void reserve(const size_t size) nothrow @trusted @nogc + { + if (this.capacity_ >= size) + { + return; + } + + this.data = allocator.resize(this.data[0 .. this.capacity_], size).ptr; + this.capacity_ = size; + } + + /// + @nogc @safe unittest + { + String s; + assert(s.capacity == 0); + + s.reserve(3); + assert(s.capacity == 3); + + s.reserve(3); + assert(s.capacity == 3); + + s.reserve(1); + assert(s.capacity == 3); + } + + /** + * Requests the string to reduce its capacity to fit the $(D_PARAM size). + * + * The request is non-binding. The string won't become smaller than the + * string byte length. + * + * Params: + * size = Desired size. + */ + void shrink(const size_t size) nothrow @trusted @nogc + { + if (this.capacity_ <= size) + { + return; + } + + const n = max(this.length_, size); + void[] buf = this.data[0 .. this.capacity_]; + if (allocator.reallocate(buf, n)) + { + this.capacity_ = n; + this.data = cast(char*) buf; + } + } + + /// + @nogc @safe unittest + { + auto s = String("Die Alten lasen laut."); + assert(s.capacity == 21); + + s.reserve(30); + s.shrink(25); + assert(s.capacity == 25); + + s.shrink(18); + assert(s.capacity == 21); + } + + /** + * Returns: String capacity in bytes. + */ + @property size_t capacity() const pure nothrow @safe @nogc + { + return this.capacity_; + } + + /// + unittest + { + auto s = String("In allem Schreiben ist Schamlosigkeit."); + assert(s.capacity == 38); + } + + /** + * Returns an array used internally by the string. + * The length of the returned array may be smaller than the size of the + * reserved memory for the string. + * + * Returns: The array representing the string. + */ + inout(char[]) get() inout pure nothrow @trusted @nogc + { + return this.data[0 .. this.length_]; + } + + /** + * Returns: The number of code units that are required to encode the string. + */ + @property size_t length() const pure nothrow @safe @nogc + { + return this.length_; + } + + /// + alias opDollar = length; + + /// + unittest + { + auto s = String("Piscis primuin a capite foetat."); + assert(s.length == 31); + assert(s[$ - 1] == '.'); + } + + /** + * Params: + * pos = Position. + * + * Returns: Byte at $(D_PARAM pos). + * + * Precondition: $(D_INLINECODE length > pos). + */ + ref inout(char) opIndex(const size_t pos) inout pure nothrow @trusted @nogc + in + { + assert(length > pos); + } + body + { + return *(this.data + pos); + } + + /// + unittest + { + auto s = String("Alea iacta est."); + assert(s[0] == 'A'); + assert(s[4] == ' '); + } + + /** + * Returns: Random access range that iterates over the string by bytes, in + * forward order. + */ + ByCodeUnit!char opIndex() pure nothrow @trusted @nogc + { + return typeof(return)(this, this.data, this.data + length); + } + + /// Ditto. + ByCodeUnit!(const char) opIndex() const pure nothrow @trusted @nogc + { + return typeof(return)(this, this.data, this.data + length); + } + + /// + unittest + { + auto s = String("Plutarchus"); + auto r = s[]; + assert(r.front == 'P'); + assert(r.back == 's'); + + r.popFront(); + assert(r.front == 'l'); + assert(r.back == 's'); + + r.popBack(); + assert(r.front == 'l'); + assert(r.back == 'u'); + + assert(r.length == 8); + } + + /** + * Returns: $(D_KEYWORD true) if the vector is empty. + */ + @property bool empty() const pure nothrow @safe @nogc + { + return length == 0; + } + + /** + * Params: + * i = Slice start. + * j = Slice end. + * + * Returns: A range that iterates over the string by bytes from + * index $(D_PARAM i) up to (excluding) index $(D_PARAM j). + * + * Precondition: $(D_INLINECODE i <= j && j <= length). + */ + ByCodeUnit!char opSlice(const size_t i, const size_t j) + pure nothrow @trusted @nogc + in + { + assert(i <= j); + assert(j <= length); + } + body + { + return typeof(return)(this, this.data + i, this.data + j); + } + + /// Ditto. + ByCodeUnit!(const char) opSlice(const size_t i, const size_t j) + const pure nothrow @trusted @nogc + in + { + assert(i <= j); + assert(j <= length); + } + body + { + return typeof(return)(this, this.data + i, this.data + j); + } + + /// + unittest + { + auto s = String("Vladimir Soloviev"); + auto r = s[9 .. $]; + + assert(r.front == 'S'); + assert(r.back == 'v'); + + r.popFront(); + r.popBack(); + assert(r.front == 'o'); + assert(r.back == 'e'); + + r.popFront(); + r.popBack(); + assert(r.front == 'l'); + assert(r.back == 'i'); + + r.popFront(); + r.popBack(); + assert(r.front == 'o'); + assert(r.back == 'v'); + + r.popFront(); + r.popBack(); + assert(r.empty); + } + + mixin DefaultAllocator; +} diff --git a/source/tanya/container/vector.d b/source/tanya/container/vector.d index 8e4fa01..1e9360a 100644 --- a/source/tanya/container/vector.d +++ b/source/tanya/container/vector.d @@ -12,4 +12,1644 @@ */ module tanya.container.vector; -public import tanya.container.slice; +import core.checkedint; +import core.exception; +import std.algorithm.comparison; +import std.algorithm.mutation; +import std.conv; +import std.range.primitives; +import std.meta; +import std.traits; +import tanya.memory; + +/** + * Random-access range for the $(D_PSYMBOL Vector). + * + * Params: + * E = Element type. + */ +struct Range(E) +{ + private E* begin, end; + private alias ContainerType = CopyConstness!(E, Vector!(Unqual!E)); + private ContainerType* vector; + + invariant + { + assert(this.begin <= this.end); + assert(this.vector !is null); + assert(this.begin >= this.vector.data); + assert(this.end <= this.vector.data + this.vector.length); + } + + private this(ref ContainerType vector, E* begin, E* end) @trusted + in + { + assert(begin <= end); + assert(begin >= vector.data); + assert(end <= vector.data + vector.length); + } + body + { + this.vector = &vector; + this.begin = begin; + this.end = end; + } + + @disable this(); + + @property Range save() + { + return this; + } + + @property bool empty() const + { + return this.begin == this.end; + } + + @property size_t length() const + { + return this.end - this.begin; + } + + alias opDollar = length; + + @property ref inout(E) front() inout + in + { + assert(!empty); + } + body + { + return *this.begin; + } + + @property ref inout(E) back() inout @trusted + in + { + assert(!empty); + } + body + { + return *(this.end - 1); + } + + void popFront() @trusted + in + { + assert(!empty); + } + body + { + ++this.begin; + } + + void popBack() @trusted + in + { + assert(!empty); + } + body + { + --this.end; + } + + ref inout(E) opIndex(const size_t i) inout @trusted + in + { + assert(i < length); + } + body + { + return *(this.begin + i); + } + + Range opIndex() + { + return typeof(return)(*this.vector, this.begin, this.end); + } + + Range!(const E) opIndex() const + { + return typeof(return)(*this.vector, this.begin, this.end); + } + + Range opSlice(const size_t i, const size_t j) @trusted + in + { + assert(i <= j); + assert(j <= length); + } + body + { + return typeof(return)(*this.vector, this.begin + i, this.begin + j); + } + + Range!(const E) opSlice(const size_t i, const size_t j) const @trusted + in + { + assert(i <= j); + assert(j <= length); + } + body + { + return typeof(return)(*this.vector, this.begin + i, this.begin + j); + } + + inout(E[]) get() inout @trusted + { + return this.begin[0 .. length]; + } +} + +/** + * One dimensional array. + * + * Params: + * T = Content type. + */ +struct Vector(T) +{ + private size_t length_; + private T* data; + private size_t capacity_; + + invariant + { + assert(this.length_ <= this.capacity_); + assert(this.capacity_ == 0 || this.data !is null); + } + + /** + * Creates a new $(D_PSYMBOL Vector) with the elements from a static array. + * + * Params: + * R = Static array size. + * init = Values to initialize the vector with. + * allocator = Allocator. + */ + this(size_t R)(T[R] init, shared Allocator allocator = defaultAllocator) + { + this(allocator); + insertBack!(T[])(init[]); + } + + /** + * Creates a new $(D_PSYMBOL Vector) with the elements from an input range. + * + * Params: + * R = Type of the initial range. + * init = Values to initialize the vector with. + * allocator = Allocator. + */ + this(R)(R init, shared Allocator allocator = defaultAllocator) + if (!isInfinite!R + && isInputRange!R + && isImplicitlyConvertible!(ElementType!R, T)) + { + this(allocator); + insertBack(init); + } + + /** + * Initializes this vector from another one. + * + * If $(D_PARAM init) is passed by value, it won't be copied, but moved. + * If the allocator of ($D_PARAM init) matches $(D_PARAM allocator), + * $(D_KEYWORD this) will just take the ownership over $(D_PARAM init)'s + * storage, otherwise, the storage will be allocated with + * $(D_PARAM allocator) and all elements will be moved; + * $(D_PARAM init) will be destroyed at the end. + * + * If $(D_PARAM init) is passed by reference, it will be copied. + * + * Params: + * R = Source vector type. + * init = Source vector. + * allocator = Allocator. + */ + this(R)(ref R init, shared Allocator allocator = defaultAllocator) + if (is(Unqual!R == Vector)) + { + this(allocator); + insertBack(init[]); + } + + /// Ditto. + this(R)(R init, shared Allocator allocator = defaultAllocator) @trusted + if (is(R == Vector)) + { + this(allocator); + if (allocator is init.allocator) + { + // Just steal all references and the allocator. + this.data = init.data; + this.length_ = init.length_; + this.capacity_ = init.capacity_; + + // Reset the source vector, so it can't destroy the moved storage. + init.length_ = init.capacity_ = 0; + init.data = null; + } + else + { + // Move each element. + reserve(init.length_); + moveEmplaceAll(init.data[0 .. init.length_], this.data[0 .. init.length_]); + this.length_ = init.length_; + // Destructor of init should destroy it here. + } + } + + /// + @trusted @nogc unittest + { + auto v1 = Vector!int([1, 2, 3]); + auto v2 = Vector!int(v1); + assert(v1 == v2); + + auto v3 = Vector!int(Vector!int([1, 2, 3])); + assert(v1 == v3); + assert(v3.length == 3); + assert(v3.capacity == 3); + } + + private @trusted @nogc unittest // const constructor tests + { + auto v1 = const Vector!int([1, 2, 3]); + auto v2 = Vector!int(v1); + assert(v1.data !is v2.data); + assert(v1 == v2); + + auto v3 = const Vector!int(Vector!int([1, 2, 3])); + assert(v1 == v3); + assert(v3.length == 3); + assert(v3.capacity == 3); + } + + /** + * Creates a new $(D_PSYMBOL Vector). + * + * Params: + * len = Initial length of the vector. + * init = Initial value to fill the vector with. + * allocator = Allocator. + */ + this(const size_t len, T init, shared Allocator allocator = defaultAllocator) @trusted + { + this(allocator); + reserve(len); + uninitializedFill(this.data[0 .. len], init); + length_ = len; + } + + /// Ditto. + this(const size_t len, shared Allocator allocator = defaultAllocator) + { + this(allocator); + length = len; + } + + /// Ditto. + this(shared Allocator allocator) + in + { + assert(allocator !is null); + } + body + { + allocator_ = allocator; + } + + /// + unittest + { + auto v = Vector!int([3, 8, 2]); + + assert(v.capacity == 3); + assert(v.length == 3); + assert(v[0] == 3 && v[1] == 8 && v[2] == 2); + } + + /// + unittest + { + auto v = Vector!int(3, 5); + + assert(v.capacity == 3); + assert(v.length == 3); + assert(v[0] == 5 && v[1] == 5 && v[2] == 5); + } + + @safe unittest + { + auto v1 = Vector!int(defaultAllocator); + } + + /** + * Destroys this $(D_PSYMBOL Vector). + */ + ~this() @trusted + { + clear(); + allocator.deallocate(this.data[0 .. capacity]); + } + + /** + * Copies the vector. + */ + this(this) + { + auto buf = this.data[0 .. this.length_]; + this.length_ = capacity_ = 0; + this.data = null; + insertBack(buf); + } + + /** + * Removes all elements. + */ + void clear() + { + length = 0; + } + + /// + unittest + { + auto v = Vector!int([18, 20, 15]); + v.clear(); + assert(v.length == 0); + assert(v.capacity == 3); + } + + /** + * Returns: How many elements the vector can contain without reallocating. + */ + @property size_t capacity() const + { + return capacity_; + } + + /// + @safe @nogc unittest + { + auto v = Vector!int(4); + assert(v.capacity == 4); + } + + /** + * Returns: Vector length. + */ + @property size_t length() const + { + return length_; + } + + /// Ditto. + size_t opDollar() const + { + return length; + } + + /** + * Expands/shrinks the vector. + * + * Params: + * len = New length. + */ + @property void length(const size_t len) @trusted + { + if (len == length) + { + return; + } + else if (len > length) + { + reserve(len); + initializeAll(this.data[length_ .. len]); + } + else + { + static if (hasElaborateDestructor!T) + { + const T* end = this.data + length_ - 1; + for (T* e = this.data + len; e != end; ++e) + { + destroy(*e); + } + } + } + length_ = len; + } + + /// + unittest + { + Vector!int v; + + v.length = 5; + assert(v.length == 5); + assert(v.capacity == 5); + + v.length = 7; + assert(v.length == 7); + assert(v.capacity == 7); + + assert(v[$ - 1] == 0); + v[$ - 1] = 3; + assert(v[$ - 1] == 3); + + v.length = 0; + assert(v.length == 0); + assert(v.capacity == 7); + } + + /** + * Reserves space for $(D_PARAM size) elements. + * + * If $(D_PARAM size) is less than or equal to the $(D_PSYMBOL capacity), the + * function call does not cause a reallocation and the vector capacity is not + * affected. + * + * Params: + * size = Desired size. + */ + void reserve(const size_t size) @trusted + { + if (capacity_ >= size) + { + return; + } + bool overflow; + immutable byteSize = mulu(size, T.sizeof, overflow); + assert(!overflow); + + void[] buf = this.data[0 .. this.capacity_]; + if (!allocator.reallocateInPlace(buf, byteSize)) + { + buf = allocator.allocate(byteSize); + if (buf is null) + { + onOutOfMemoryErrorNoGC(); + } + scope (failure) + { + allocator.deallocate(buf); + } + const T* end = this.data + this.length_; + for (T* src = this.data, dest = cast(T*) buf; src != end; ++src, ++dest) + { + moveEmplace(*src, *dest); + static if (hasElaborateDestructor!T) + { + destroy(*src); + } + } + allocator.deallocate(this.data[0 .. this.capacity_]); + this.data = cast(T*) buf; + } + this.capacity_ = size; + } + + /// + @nogc @safe unittest + { + Vector!int v; + assert(v.capacity == 0); + assert(v.length == 0); + + v.reserve(3); + assert(v.capacity == 3); + assert(v.length == 0); + } + + /** + * Requests the vector to reduce its capacity to fit the $(D_PARAM size). + * + * The request is non-binding. The vector won't become smaller than the + * $(D_PARAM length). + * + * Params: + * size = Desired size. + */ + void shrink(const size_t size) @trusted + { + if (capacity <= size) + { + return; + } + immutable n = max(length, size); + void[] buf = this.data[0 .. this.capacity_]; + if (allocator.reallocateInPlace(buf, n * T.sizeof)) + { + this.capacity_ = n; + } + } + + /// + @nogc @safe unittest + { + Vector!int v; + assert(v.capacity == 0); + assert(v.length == 0); + + v.reserve(5); + v.insertBack(1); + v.insertBack(3); + assert(v.capacity == 5); + assert(v.length == 2); + } + + /** + * Returns: $(D_KEYWORD true) if the vector is empty. + */ + @property bool empty() const + { + return length == 0; + } + + /** + * Removes the value at the back of the vector. + * + * Returns: The number of elements removed + * + * Precondition: $(D_INLINECODE !empty). + */ + void removeBack() + in + { + assert(!empty); + } + body + { + length = length - 1; + } + + /** + * Removes $(D_PARAM howMany) elements from the vector. + * + * This method doesn't fail if it could not remove $(D_PARAM howMany) + * elements. Instead, if $(D_PARAM howMany) is greater than the vector + * length, all elements are removed. + * + * Params: + * howMany = How many elements should be removed. + * + * Returns: The number of elements removed + */ + size_t removeBack(const size_t howMany) + out (removed) + { + assert(removed <= howMany); + } + body + { + immutable toRemove = min(howMany, length); + + length = length - toRemove; + + return toRemove; + } + + /// + unittest + { + auto v = Vector!int([5, 18, 17]); + + assert(v.removeBack(0) == 0); + assert(v.removeBack(2) == 2); + assert(v.removeBack(3) == 1); + assert(v.removeBack(3) == 0); + } + + /** + * Remove all elements beloning to $(D_PARAM r). + * + * Params: + * r = Range originally obtained from this vector. + * + * Returns: A range spanning the remaining elements in the array that + * initially were right after $(D_PARAM r). + * + * Precondition: $(D_PARAM r) refers to a region of $(D_KEYWORD this). + */ + Range!T remove(Range!T r) @trusted + in + { + assert(r.vector is &this); + assert(r.begin >= this.data); + assert(r.end <= this.data + length); + } + body + { + auto end = this.data + this.length; + moveAll(Range!T(this, r.end, end), Range!T(this, r.begin, end)); + length = length - r.length; + return Range!T(this, r.begin, this.data + length); + } + + /// + @safe @nogc unittest + { + auto v = Vector!int([5, 18, 17, 2, 4, 6, 1]); + + assert(v.remove(v[1 .. 3]).length == 4); + assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6 && v[4] == 1); + assert(v.length == 5); + + assert(v.remove(v[4 .. 4]).length == 1); + assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6 && v[4] == 1); + assert(v.length == 5); + + assert(v.remove(v[4 .. 5]).length == 0); + assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6); + assert(v.length == 4); + + assert(v.remove(v[]).length == 0); + + } + + private void moveBack(R)(ref R el) @trusted + if (isImplicitlyConvertible!(R, T)) + { + reserve(this.length + 1); + moveEmplace(el, *(this.data + this.length_)); + ++this.length_; + } + + /** + * Inserts the $(D_PARAM el) into the vector. + * + * Params: + * R = Type of the inserted value(s) (single value, range or static array). + * el = Value(s) should be inserted. + * + * Returns: The number of elements inserted. + */ + size_t insertBack(R)(R el) + if (isImplicitlyConvertible!(R, T)) + { + moveBack(el); + return 1; + } + + /// Ditto. + size_t insertBack(R)(ref R el) @trusted + if (isImplicitlyConvertible!(R, T)) + { + reserve(this.length_ + 1); + emplace(this.data + this.length_, el); + ++this.length_; + return 1; + } + + /// Ditto. + size_t insertBack(R)(R el) + if (!isInfinite!R + && isInputRange!R + && isImplicitlyConvertible!(ElementType!R, T)) + { + static if (hasLength!R) + { + reserve(length + el.length); + } + size_t retLength; + foreach (e; el) + { + retLength += insertBack(e); + } + return retLength; + } + + /// Ditto. + size_t insertBack(size_t R)(T[R] el) + { + return insertBack!(T[])(el[]); + } + + /// Ditto. + alias insert = insertBack; + + /// + unittest + { + struct TestRange + { + int counter = 6; + + int front() + { + return counter; + } + + void popFront() + { + counter -= 2; + } + + bool empty() + { + return counter == 0; + } + } + + Vector!int v1; + + assert(v1.insertBack(5) == 1); + assert(v1.length == 1); + assert(v1.capacity == 1); + assert(v1.back == 5); + + assert(v1.insertBack(TestRange()) == 3); + assert(v1.length == 4); + assert(v1.capacity == 4); + assert(v1[0] == 5 && v1[1] == 6 && v1[2] == 4 && v1[3] == 2); + + assert(v1.insertBack([34, 234]) == 2); + assert(v1.length == 6); + assert(v1.capacity == 6); + assert(v1[4] == 34 && v1[5] == 234); + } + + /** + * Inserts $(D_PARAM el) before or after $(D_PARAM r). + * + * Params: + * R = Type of the inserted value(s) (single value, range or static array). + * r = Range originally obtained from this vector. + * el = Value(s) should be inserted. + * + * Returns: The number of elements inserted. + * + * Precondition: $(D_PARAM r) refers to a region of $(D_KEYWORD this). + */ + size_t insertAfter(R)(Range!T r, R el) + if (!isInfinite!R + && isInputRange!R + && isImplicitlyConvertible!(ElementType!R, T)) + in + { + assert(r.vector is &this); + assert(r.begin >= this.data); + assert(r.end <= this.data + length); + } + body + { + immutable oldLen = length; + immutable offset = r.end - this.data; + immutable inserted = insertBack(el); + bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]); + return inserted; + } + + /// Ditto. + size_t insertAfter(size_t R)(Range!T r, T[R] el) + in + { + assert(r.vector is &this); + assert(r.begin >= this.data); + assert(r.end <= this.data + length); + } + body + { + return insertAfter!(T[])(r, el[]); + } + + /// Ditto. + size_t insertAfter(R)(Range!T r, auto ref R el) + if (isImplicitlyConvertible!(R, T)) + in + { + assert(r.vector is &this); + assert(r.begin >= this.data); + assert(r.end <= this.data + length); + } + body + { + immutable oldLen = length; + immutable offset = r.end - this.data; + + static if (__traits(isRef, el)) + { + insertBack(el); + } + else + { + moveBack(el); + } + bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]); + + return 1; + } + + /// Ditto. + size_t insertBefore(R)(Range!T r, R el) + if (!isInfinite!R + && isInputRange!R + && isImplicitlyConvertible!(ElementType!R, T)) + in + { + assert(r.vector is &this); + assert(r.begin >= this.data); + assert(r.end <= this.data + length); + } + body + { + return insertAfter(Range!T(this, this.data, r.begin), el); + } + + /// Ditto. + size_t insertBefore(size_t R)(Range!T r, T[R] el) + in + { + assert(r.vector is &this); + assert(r.begin >= this.data); + assert(r.end <= this.data + length); + } + body + { + return insertBefore!(T[])(r, el[]); + } + + /// Ditto. + size_t insertBefore(R)(Range!T r, auto ref R el) + if (isImplicitlyConvertible!(R, T)) + in + { + assert(r.vector is &this); + assert(r.begin >= this.data); + assert(r.end <= this.data + length); + } + body + { + immutable oldLen = length; + immutable offset = r.begin - this.data; + + static if (__traits(isRef, el)) + { + insertBack(el); + } + else + { + moveBack(el); + } + bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]); + + return 1; + } + + /// + unittest + { + Vector!int v1; + v1.insertAfter(v1[], [2, 8]); + assert(v1[0] == 2); + assert(v1[1] == 8); + assert(v1.length == 2); + + v1.insertAfter(v1[], [1, 2]); + assert(v1[0] == 2); + assert(v1[1] == 8); + assert(v1[2] == 1); + assert(v1[3] == 2); + assert(v1.length == 4); + + v1.insertAfter(v1[0 .. 0], [1, 2]); + assert(v1[0] == 1); + assert(v1[1] == 2); + assert(v1[2] == 2); + assert(v1[3] == 8); + assert(v1[4] == 1); + assert(v1[5] == 2); + assert(v1.length == 6); + + v1.insertAfter(v1[0 .. 4], 9); + assert(v1[0] == 1); + assert(v1[1] == 2); + assert(v1[2] == 2); + assert(v1[3] == 8); + assert(v1[4] == 9); + assert(v1[5] == 1); + assert(v1[6] == 2); + assert(v1.length == 7); + } + + /// + unittest + { + Vector!int v1; + v1.insertBefore(v1[], [2, 8]); + assert(v1[0] == 2); + assert(v1[1] == 8); + assert(v1.length == 2); + + v1.insertBefore(v1[], [1, 2]); + assert(v1[0] == 1); + assert(v1[1] == 2); + assert(v1[2] == 2); + assert(v1[3] == 8); + assert(v1.length == 4); + + v1.insertBefore(v1[0 .. 1], [1, 2]); + assert(v1[0] == 1); + assert(v1[1] == 2); + assert(v1[2] == 1); + assert(v1[3] == 2); + assert(v1[4] == 2); + assert(v1[5] == 8); + assert(v1.length == 6); + + v1.insertBefore(v1[2 .. $], 9); + assert(v1[0] == 1); + assert(v1[1] == 2); + assert(v1[2] == 9); + assert(v1[3] == 1); + assert(v1[4] == 2); + assert(v1[5] == 2); + assert(v1[6] == 8); + assert(v1.length == 7); + } + + /** + * Assigns a value to the element with the index $(D_PARAM pos). + * + * Params: + * value = Value. + * pos = Position. + * + * Returns: Assigned value. + * + * Precondition: $(D_INLINECODE length > pos). + */ + ref T opIndexAssign(ref T value, const size_t pos) + { + return opIndex(pos) = value; + } + + @safe unittest + { + Vector!int a = Vector!int(1); + a[0] = 5; + assert(a[0] == 5); + } + + /// Ditto. + T opIndexAssign(T value, const size_t pos) + { + return opIndexAssign(value, pos); + } + + /// Ditto. + Range!T opIndexAssign(T value) + { + return opSliceAssign(value, 0, length); + } + + /// Ditto. + Range!T opIndexAssign(ref T value) + { + return opSliceAssign(value, 0, length); + } + + /** + * Assigns a range or a static array. + * + * Params: + * R = Range type or static array length. + * value = Value. + * + * Returns: Assigned value. + * + * Precondition: $(D_INLINECODE length == value.length). + */ + Range!T opIndexAssign(R)(R value) + if (!isInfinite!R && isInputRange!R + && isImplicitlyConvertible!(ElementType!R, T)) + { + return opSliceAssign!R(value, 0, length); + } + + /// Ditto. + Range!T opIndexAssign(size_t R)(T[R] value) + { + return opSliceAssign!R(value, 0, length); + } + + /// + @nogc unittest + { + auto v1 = Vector!int([12, 1, 7]); + + v1[] = 3; + assert(v1[0] == 3); + assert(v1[1] == 3); + assert(v1[2] == 3); + + v1[] = [7, 1, 12]; + assert(v1[0] == 7); + assert(v1[1] == 1); + assert(v1[2] == 12); + } + + /** + * Params: + * pos = Index. + * + * Returns: The value at a specified index. + * + * Precondition: $(D_INLINECODE length > pos). + */ + ref inout(T) opIndex(const size_t pos) inout @trusted + in + { + assert(length > pos); + } + body + { + return *(this.data + pos); + } + + /** + * Returns: Random access range that iterates over elements of the vector, in + * forward order. + */ + Range!T opIndex() @trusted + { + return typeof(return)(this, this.data, this.data + length); + } + + /// Ditto. + Range!(const T) opIndex() const @trusted + { + return typeof(return)(this, this.data, this.data + length); + } + + /// + unittest + { + const v1 = Vector!int([6, 123, 34, 5]); + + assert(v1[0] == 6); + assert(v1[1] == 123); + assert(v1[2] == 34); + assert(v1[3] == 5); + static assert(is(typeof(v1[0]) == const(int))); + static assert(is(typeof(v1[]))); + } + + /** + * Comparison for equality. + * + * Params: + * that = The vector to compare with. + * + * Returns: $(D_KEYWORD true) if the vectors are equal, $(D_KEYWORD false) + * otherwise. + */ + bool opEquals()(auto ref typeof(this) that) @trusted + { + return equal(this.data[0 .. length], that.data[0 .. that.length]); + } + + /// Ditto. + bool opEquals()(const auto ref typeof(this) that) const @trusted + { + return equal(this.data[0 .. length], that.data[0 .. that.length]); + } + + /// Ditto. + bool opEquals(Range!T that) + { + return equal(opIndex(), that); + } + + /** + * Comparison for equality. + * + * Params: + * R = Right hand side type. + * that = Right hand side vector range. + * + * Returns: $(D_KEYWORD true) if the vector and the range are equal, + * $(D_KEYWORD false) otherwise. + */ + bool opEquals(R)(Range!R that) const + if (is(Unqual!R == T)) + { + return equal(opIndex(), that); + } + + /// + unittest + { + Vector!int v1, v2; + assert(v1 == v2); + + v1.length = 1; + v2.length = 2; + assert(v1 != v2); + + v1.length = 2; + v1[0] = v2[0] = 2; + v1[1] = 3; + v2[1] = 4; + assert(v1 != v2); + + v2[1] = 3; + assert(v1 == v2); + } + + /** + * Returns: The first element. + * + * Precondition: $(D_INLINECODE !empty). + */ + @property ref inout(T) front() inout + in + { + assert(!empty); + } + body + { + return *this.data; + } + + /// + @safe unittest + { + auto v = Vector!int([5]); + + assert(v.front == 5); + + v.length = 2; + v[1] = 15; + assert(v.front == 5); + } + + /** + * Returns: The last element. + * + * Precondition: $(D_INLINECODE !empty). + */ + @property ref inout(T) back() inout @trusted + in + { + assert(!empty); + } + body + { + return *(this.data + length - 1); + } + + /// + unittest + { + auto v = Vector!int([5]); + + assert(v.back == 5); + + v.length = 2; + v[1] = 15; + assert(v.back == 15); + } + + /** + * Params: + * i = Slice start. + * j = Slice end. + * + * Returns: A range that iterates over elements of the container from + * index $(D_PARAM i) up to (excluding) index $(D_PARAM j). + * + * Precondition: $(D_INLINECODE i <= j && j <= length). + */ + Range!T opSlice(const size_t i, const size_t j) @trusted + in + { + assert(i <= j); + assert(j <= length); + } + body + { + return typeof(return)(this, this.data + i, this.data + j); + } + + /// Ditto. + Range!(const T) opSlice(const size_t i, const size_t j) const @trusted + in + { + assert(i <= j); + assert(j <= length); + } + body + { + return typeof(return)(this, this.data + i, this.data + j); + } + + /// + unittest + { + Vector!int v; + auto r = v[]; + assert(r.length == 0); + assert(r.empty); + } + + /// + unittest + { + auto v = Vector!int([1, 2, 3]); + auto r = v[]; + + assert(r.front == 1); + assert(r.back == 3); + + r.popFront(); + assert(r.front == 2); + + r.popBack(); + assert(r.back == 2); + + assert(r.length == 1); + } + + /// + unittest + { + auto v = Vector!int([1, 2, 3, 4]); + auto r = v[1 .. 4]; + assert(r.length == 3); + assert(r[0] == 2); + assert(r[1] == 3); + assert(r[2] == 4); + + r = v[0 .. 0]; + assert(r.length == 0); + + r = v[4 .. 4]; + assert(r.length == 0); + } + + /** + * Slicing assignment. + * + * Params: + * R = Type of the assigned slice or length of the static array should be + * assigned. + * value = New value (single value, input range or static array). + * i = Slice start. + * j = Slice end. + * + * Returns: Slice with the assigned part of the vector. + * + * Precondition: $(D_INLINECODE i <= j && j <= length + * && value.length == j - i) + */ + Range!T opSliceAssign(R)(R value, const size_t i, const size_t j) @trusted + if (!isInfinite!R + && isInputRange!R + && isImplicitlyConvertible!(ElementType!R, T)) + in + { + assert(i <= j); + assert(j <= length); + assert(j - i == walkLength(value)); + } + body + { + copy(value, this.data[i .. j]); + return opSlice(i, j); + } + + /// Ditto. + Range!T opSliceAssign(size_t R)(T[R] value, const size_t i, const size_t j) + { + return opSliceAssign!(T[])(value[], i, j); + } + + /// Ditto. + Range!T opSliceAssign(ref T value, const size_t i, const size_t j) @trusted + in + { + assert(i <= j); + assert(j <= length); + } + body + { + fill(this.data[i .. j], value); + return opSlice(i, j); + } + + /// Ditto. + Range!T opSliceAssign(T value, const size_t i, const size_t j) + { + return opSliceAssign(value, i, j); + } + + /// + @nogc @safe unittest + { + auto v1 = Vector!int([3, 3, 3]); + auto v2 = Vector!int([1, 2]); + + v1[0 .. 2] = 286; + assert(v1[0] == 286); + assert(v1[1] == 286); + assert(v1[2] == 3); + + v2[0 .. $] = v1[1 .. 3]; + assert(v2[0] == 286); + assert(v2[1] == 3); + + v1[0 .. 2] = [5, 8]; + assert(v1[0] == 5); + assert(v1[1] == 8); + assert(v1[2] == 3); + } + + /** + * Returns an array used internally by the vector to store its owned elements. + * The length of the returned array may differ from the size of the allocated + * memory for the vector: the array contains only initialized elements, but + * not the reserved memory. + * + * Returns: The array with elements of this vector. + */ + inout(T[]) get() inout @trusted + { + return this.data[0 .. length]; + } + + /// + unittest + { + auto v = Vector!int([1, 2, 4]); + auto data = v.get(); + + assert(data[0] == 1); + assert(data[1] == 2); + assert(data[2] == 4); + assert(data.length == 3); + + data = v[1 .. 2].get(); + assert(data[0] == 2); + assert(data.length == 1); + } + + /** + * Assigns another vector. + * + * If $(D_PARAM that) is passed by value, it won't be copied, but moved. + * This vector will take the ownership over $(D_PARAM that)'s storage and + * the allocator. + * + * If $(D_PARAM that) is passed by reference, it will be copied. + * + * Params: + * R = Content type. + * that = The value should be assigned. + * + * Returns: $(D_KEYWORD this). + */ + ref typeof(this) opAssign(R)(ref R that) + if (is(Unqual!R == Vector)) + { + return this = that[]; + } + + /// Ditto. + ref typeof(this) opAssign(R)(R that) @trusted + if (is(R == Vector)) + { + swap(this.data, that.data); + swap(this.length_, that.length_); + swap(this.capacity_, that.capacity_); + swap(this.allocator_, that.allocator_); + return this; + } + + /** + * Assigns a range to the vector. + * + * Params: + * R = Content type. + * that = The value should be assigned. + * + * Returns: $(D_KEYWORD this). + */ + ref typeof(this) opAssign(R)(R that) + if (!isInfinite!R + && isInputRange!R + && isImplicitlyConvertible!(ElementType!R, T)) + { + length = 0; + insertBack(that); + return this; + } + + /// + @safe @nogc unittest + { + auto v1 = const Vector!int([5, 15, 8]); + Vector!int v2; + v2 = v1; + assert(v1 == v2); + } + + /// + @safe @nogc unittest + { + auto v1 = const Vector!int([5, 15, 8]); + Vector!int v2; + v2 = v1[0 .. 2]; + assert(equal(v1[0 .. 2], v2[])); + } + + // Move assignment. + private @safe @nogc unittest + { + Vector!int v1; + v1 = Vector!int([5, 15, 8]); + } + + /** + * Assigns a static array. + * + * Params: + * R = Static array size. + * that = Values to initialize the vector with. + * + * Returns: $(D_KEYWORD this). + */ + ref typeof(this) opAssign(size_t R)(T[R] that) + { + return opAssign!(T[])(that[]); + } + + /// + @safe @nogc unittest + { + auto v1 = Vector!int([5, 15, 8]); + Vector!int v2; + + v2 = [5, 15, 8]; + assert(v1 == v2); + } + + mixin DefaultAllocator; +} + +/// +unittest +{ + auto v = Vector!int([5, 15, 8]); + + assert(v.front == 5); + assert(v[1] == 15); + assert(v.back == 8); + + auto r = v[]; + r[0] = 7; + assert(r.front == 7); + assert(r.front == v.front); +} + +@nogc unittest +{ + const v1 = Vector!int(); + const Vector!int v2; + const v3 = Vector!int([1, 5, 8]); + static assert(is(PointerTarget!(typeof(v3.data)) == const(int))); +} + +@nogc unittest +{ + // Test that const vectors return usable ranges. + auto v = const Vector!int([1, 2, 4]); + auto r1 = v[]; + + assert(r1.back == 4); + r1.popBack(); + assert(r1.back == 2); + r1.popBack(); + assert(r1.back == 1); + r1.popBack(); + assert(r1.length == 0); + + static assert(!is(typeof(r1[0] = 5))); + static assert(!is(typeof(v[0] = 5))); + + const r2 = r1[]; + static assert(is(typeof(r2[]))); +} + +@nogc unittest +{ + Vector!int v1; + const Vector!int v2; + + auto r1 = v1[]; + auto r2 = v1[]; + + assert(r1.length == 0); + assert(r2.empty); + assert(r1 == r2); + + v1.insertBack([1, 2, 4]); + assert(v1[] == v1); + assert(v2[] == v2); + assert(v2[] != v1); + assert(v1[] != v2); + assert(v1[].equal(v1[])); + assert(v2[].equal(v2[])); + assert(!v1[].equal(v2[])); +} + +@nogc unittest +{ + struct MutableEqualsStruct + { + int opEquals(typeof(this) that) @nogc + { + return true; + } + } + struct ConstEqualsStruct + { + int opEquals(const typeof(this) that) const @nogc + { + return true; + } + } + auto v1 = Vector!ConstEqualsStruct(); + auto v2 = Vector!ConstEqualsStruct(); + assert(v1 == v2); + assert(v1[] == v2); + assert(v1 == v2[]); + assert(v1[].equal(v2[])); + + auto v3 = const Vector!ConstEqualsStruct(); + auto v4 = const Vector!ConstEqualsStruct(); + assert(v3 == v4); + assert(v3[] == v4); + assert(v3 == v4[]); + assert(v3[].equal(v4[])); + + auto v7 = Vector!MutableEqualsStruct(1, MutableEqualsStruct()); + auto v8 = Vector!MutableEqualsStruct(1, MutableEqualsStruct()); + assert(v7 == v8); + assert(v7[] == v8); + assert(v7 == v8[]); + assert(v7[].equal(v8[])); +} + +@nogc unittest +{ + struct SWithDtor + { + ~this() @nogc + { + } + } + auto v = Vector!SWithDtor(); // Destructor can destroy empty vectors. +} + +private unittest +{ + class A + { + } + A a1, a2; + auto v1 = Vector!A([a1, a2]); +} + +private @safe @nogc unittest +{ + auto v = Vector!int([5, 15, 8]); + { + size_t i; + + foreach (e; v) + { + assert(i != 0 || e == 5); + assert(i != 1 || e == 15); + assert(i != 2 || e == 8); + ++i; + } + assert(i == 3); + } + { + size_t i = 3; + + foreach_reverse (e; v) + { + --i; + assert(i != 2 || e == 8); + assert(i != 1 || e == 15); + assert(i != 0 || e == 5); + } + assert(i == 0); + } +} diff --git a/source/tanya/math/mp.d b/source/tanya/math/mp.d index 024a0e4..e55829c 100644 --- a/source/tanya/math/mp.d +++ b/source/tanya/math/mp.d @@ -12,11 +12,11 @@ */ module tanya.math.mp; -import core.exception; import std.algorithm; +import std.ascii; import std.range; import std.traits; -import tanya.math; +import tanya.container.vector; import tanya.memory; /** @@ -36,8 +36,8 @@ enum Sign : bool */ struct Integer { - private size_t size; - package ubyte* rep; + package digit[] rep; + package ptrdiff_t size; package Sign sign; pure nothrow @safe @nogc invariant @@ -45,6 +45,16 @@ struct Integer assert(this.size > 0 || !this.sign, "0 should be positive."); } + private alias digit = uint; + private alias word = ulong; + + // Count of bits per digit. + private enum : digit + { + digitBitCount = 28, + mask = 0xfffffff, + } + /** * Creates a multiple precision integer. * @@ -59,20 +69,21 @@ struct Integer if (isIntegral!T) { this(allocator); - assign(value); + this = value; } /// Ditto. - this(const ref Integer value, shared Allocator allocator = defaultAllocator) - nothrow @safe @nogc + this(T)(ref T value, shared Allocator allocator = defaultAllocator) + if (is(Unqual!T == Integer)) { this(allocator); - assign(value); + this = value; } /// Ditto. - this(Integer value, shared Allocator allocator = defaultAllocator) + this(T)(T value, shared Allocator allocator = defaultAllocator) nothrow @safe @nogc + if (is(T == Integer)) { this(allocator); if (allocator is value.allocator) @@ -86,7 +97,7 @@ struct Integer } else { - assign(value); + this = value; } } @@ -101,22 +112,6 @@ struct Integer this.allocator_ = allocator; } - private @nogc unittest - { - { - auto integer = Integer(79); - assert(integer.length == 1); - assert(integer.rep[0] == 79); - assert(!integer.sign); - } - { - auto integer = Integer(-2); - assert(integer.length == 1); - assert(integer.rep[0] == 2); - assert(integer.sign); - } - } - /** * Constructs the integer from a sign-magnitude $(D_KEYWORD ubyte) range. * @@ -128,26 +123,41 @@ struct Integer * * Precondition: $(D_INLINECODE allocator !is null) */ - this(R)(const Sign sign, R value, shared Allocator allocator = defaultAllocator) - @trusted - if (isInputRange!R && hasLength!R && is(Unqual!(ElementType!R) == ubyte)) + this(R)(const Sign sign, + R value, + shared Allocator allocator = defaultAllocator) + if (isBidirectionalRange!R && hasLength!R + && is(Unqual!(ElementType!R) == ubyte)) { this(allocator); - while (!value.empty && value.front == 0) + grow(value.length / (digitBitCount / 8) + 1); + + int bit, delta; + + for (; !value.empty; ++this.size) { - value.popFront(); + word w; + for (bit = delta; (bit < digitBitCount) && !value.empty; bit += 8) + { + w |= (cast(word) value.back) << bit; + value.popBack(); + } + + delta = bit - digitBitCount; + this.rep[this.size] |= w & mask; + + if (delta > 0) + { + this.rep[this.size + 1] = (w >> digitBitCount) & mask; + } } - this.rep = allocator.resize(this.rep[0 .. this.size], value.length).ptr; - this.size = value.length; - this.sign = sign; - value.copy(this.rep[0 .. this.size].retro); } - private @nogc unittest + nothrow @safe @nogc unittest { - ubyte[5] range = [ 0x02, 0x11, 0x00, 0x00, 0x01 ]; + ubyte[8] range = [ 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xdd, 0xee ]; auto integer = Integer(Sign.positive, range[]); - assert(equal(range[].retro, integer.rep[0 .. integer.size])); + assert(integer == 7383520307673030126); } /** @@ -155,9 +165,9 @@ struct Integer */ this(this) nothrow @trusted @nogc { - auto tmp = allocator.resize!ubyte(null, this.size); + auto tmp = allocator.resize!digit(null, this.size); this.rep[0 .. this.size].copy(tmp); - this.rep = tmp.ptr; + this.rep = tmp; } /** @@ -165,69 +175,15 @@ struct Integer */ ~this() nothrow @trusted @nogc { - allocator.deallocate(this.rep[0 .. this.size]); - } - - /* - * Figures out the minimum amount of space this value will take - * up in bytes and resizes the internal storage. Sets the sign. - */ - private void assign(T)(const ref T value) @trusted - if (isIntegral!T) - { - ubyte size = ulong.sizeof; - ulong mask; - - this.sign = Sign.positive; // Reset the sign. - static if (isSigned!T) - { - const absolute = value.abs; - } - else - { - alias absolute = value; - } - for (mask = 0xff00000000000000; mask >= 0xff; mask >>= 8) - { - if (absolute & mask) - { - break; - } - --size; - } - - this.rep = allocator.resize(this.rep[0 .. this.size], size).ptr; - this.size = size; - static if (isSigned!T) - { - this.sign = value < 0 ? Sign.negative : Sign.positive; - } - - /* Work backward through the int, masking off each byte (up to the - first 0 byte) and copy it into the internal representation in - big-endian format. */ - mask = 0xff; - ubyte shift; - for (size_t i; i < this.size; ++i, mask <<= 8, shift += 8) - { - this.rep[i] = cast(ubyte) ((absolute & mask) >> shift); - } - } - - private void assign(const ref Integer value) nothrow @trusted @nogc - { - this.rep = allocator.resize(this.rep[0 .. this.size], value.size).ptr; - this.size = value.size; - this.sign = value.sign; - value.rep[0 .. value.size].copy(this.rep[0 .. this.size]); + allocator.resize(this.rep, 0); } /** - * Returns: Integer size. + * Returns: Integer byte length. */ @property size_t length() const pure nothrow @safe @nogc { - return this.size; + return (countBits() + 7) / 8; // Round up. } /** @@ -242,154 +198,321 @@ struct Integer ref Integer opAssign(T)(const T value) if (isIntegral!T) { - assign(value); + rep[0 .. this.size].fill(digit.init); + grow(digitBitCount / 8 + 1); + + static if (isSigned!T) + { + ulong absolute; + if (value >= 0) + { + absolute = value; + this.sign = Sign.positive; + } + else + { + absolute = -value; + this.sign = Sign.negative; + } + } + else + { + ulong absolute = value; + this.sign = Sign.positive; + } + + for (this.size = 0; absolute; absolute >>= digitBitCount, ++this.size) + { + this.rep[this.size] = absolute & mask; + } + return this; } /// Ditto. - ref Integer opAssign(const ref Integer value) nothrow @safe @nogc + ref Integer opAssign(T)(ref T value) @trusted + if (is(Unqual!T == Integer)) { - assign(value); + this.rep = allocator.resize(this.rep, value.size); + value.rep[0 .. value.size].copy(this.rep[0 .. value.size]); + this.size = value.size; + this.sign = value.sign; + return this; } /// Ditto. - ref Integer opAssign(Integer value) nothrow @safe @nogc + ref Integer opAssign(T)(T value) nothrow @safe @nogc + if (is(T == Integer)) { swap(this.rep, value.rep); swap(this.sign, value.sign); swap(this.size, value.size); + swap(this.allocator_, value.allocator_); return this; } - private @nogc unittest + /** + * Casting. + * + * Params: + * T = Target type. + * + * Returns: Converted value. + */ + T opCast(T : bool)() const { - auto integer = Integer(1019); - assert(integer.length == 2); - assert(integer.rep[1] == 0b00000011 && integer.rep[0] == 0b11111011); + return this.size > 0; + } - integer = 3337; - assert(integer.length == 2); - assert(integer.rep[1] == 0b00001101 && integer.rep[0] == 0b00001001); + /// Ditto. + T opCast(T)() const + if (isIntegral!T && isUnsigned!T) + { + T ret; + ubyte shift; + for (size_t i; i < this.size && shift <= T.sizeof * 8; ++i) + { + ret |= (cast(T) this.rep[i]) << shift; + shift += digitBitCount; + } + return ret; + } - integer = 688; - assert(integer.length == 2); - assert(integer.rep[1] == 0b00000010 && integer.rep[0] == 0b10110000); + /// Ditto. + T opCast(T)() const + if (isIntegral!T && isSigned!T) + { + return this.sign ? -(cast(Unsigned!T) this) : cast(Unsigned!T) this; + } + + /// + @safe @nogc unittest + { + auto integer = Integer(79); + assert(cast(ushort) integer == 79); + + integer = -79; + assert(cast(short) integer == -79); + + integer = 4294967295; + assert(cast(long) integer == 4294967295); + + integer = -4294967295; + assert(cast(long) integer == -4294967295); + + integer = long.min; + assert(cast(long) integer == long.min); + + integer = long.min + 1; + assert(cast(long) integer == long.min + 1); integer = 0; - assert(integer.length == 0); + assert(cast(long) integer == 0); } - /* - * Extend the space for this by 1 byte and set the LSB to 1. + /* trim unused digits + * + * This is used to ensure that leading zero digits are + * trimed and the leading "size" digit will be non-zero + * Typically very fast. Also fixes the sign if there + * are no more leading digits */ - private void expand() nothrow @trusted @nogc + void contract() nothrow @safe @nogc { - rep = allocator.resize(this.rep[0 .. this.size], this.size + 1).ptr; - this.rep[this.size] = 0x01; - ++this.size; - } - - /* - * Go through this and see how many of the left-most bytes are unused. - * Remove them and resize this appropriately. - */ - private void contract() nothrow @trusted @nogc - { - const i = this.rep[0 .. this.size] - .retro - .countUntil!((const ref a) => a != 0); - - if (i > 0) + /* decrease size while the most significant digit is + * zero. + */ + while ((this.size > 0) && (this.rep[this.size - 1] == 0)) { - this.rep = allocator.resize(this.rep[0 .. this.size], this.size - i).ptr; - this.size -= i; + --this.size; } - else if (i == -1) + + /* reset the sign flag if size == 0 */ + if (this.size == 0) { this.sign = Sign.positive; - this.rep = allocator.resize(this.rep[0 .. this.size], 0).ptr; - this.size = 0; } } - private void add(const ref Integer summand) nothrow @trusted @nogc + private void grow(const size_t size) nothrow @trusted @nogc { - if (summand.length > this.length) + if (this.rep.length >= size) { - this.rep = allocator.resize!ubyte(this.rep[0 .. this.size], summand.size).ptr; - this.rep[this.size .. summand.size].initializeAll(); + return; + } + const oldLength = this.rep.length; + allocator.resize(this.rep, size); + this.rep[oldLength .. $].fill(digit.init); + } - this.size = summand.size; + private size_t countBits() const pure nothrow @safe @nogc + { + if (this.size == 0) + { + return 0; + } + auto r = (this.size - 1) * digitBitCount; + digit q = this.rep[this.size - 1]; + + while (q > 0) + { + ++r; + q >>= (cast(digit) 1); + } + return r; + } + + private void add(ref const Integer summand, ref Integer sum) + const nothrow @safe @nogc + { + const(digit)[] max, min; + + if (this.size > summand.size) + { + min = summand.rep[0 .. summand.size]; + max = this.rep[0 .. this.size]; + } + else + { + min = this.rep[0 .. this.size]; + max = summand.rep[0 .. summand.size]; + } + sum.grow(max.length + 1); + + const oldSize = sum.size; + sum.size = cast(int) (max.length + 1); + + auto result = sum.rep; + digit carry; + foreach (i, ref const d; min) + { + result.front = d + max.front + carry; + carry = result.front >> digitBitCount; + result.front &= mask; + + max.popFront(); + result.popFront(); } - bool carry; + // Copy higher digests if one of the summands is greater than another + // one. + for (; !max.empty; max.popFront(), result.popFront()) + { + result.front = max.front + carry; + carry = result.front >> digitBitCount; + result.front &= mask; + } + result.front = carry; + + // Clear digits above the old size. + for (auto i = sum.size; i < oldSize; ++i) + { + sum.rep[i] = 0; + } + + sum.contract(); + } + + private void add(const digit summand, ref Integer sum) + const nothrow @safe @nogc + { + sum.grow(this.size + 2); + + sum.rep[0] = this.rep[0] + summand; + auto carry = sum.rep[0] >> digitBitCount; + sum.rep[0] &= mask; + size_t i; - size_t j; - do + for (i = 1; i < this.size; ++i) { - uint sum; - if (j < summand.size) - { - sum = this.rep[i] + summand.rep[j] + carry; - ++j; - } - else - { - sum = this.rep[i] + carry; - } - - carry = sum > 0xff; - this.rep[i] = cast(ubyte) sum; + sum.rep[i] = this.rep[i] + carry; + carry = sum.rep[i] >> digitBitCount; + sum.rep[i] &= mask; } - while (++i < this.size); + sum.rep[i++] = carry; - if (carry) + for (; i < sum.size; ++i) { - // Still overflowed; allocate more space - expand(); + sum.rep[i] = 0; } + sum.size = this.size + 1; + sum.contract(); } - private void subtract(const ref Integer subtrahend) nothrow @trusted @nogc + private void subtract(ref const Integer subtrahend, ref Integer difference) + const nothrow @safe @nogc { + difference.grow(this.size); + + const oldSize = difference.size; + difference.size = this.size; + size_t i; - size_t j; - bool borrow; + digit carry; - while (i < this.size) + for (i = 0; i < subtrahend.size; ++i) { - int difference; - - if (j < subtrahend.size) - { - difference = this.rep[i] - subtrahend.rep[j] - borrow; - ++j; - } - else - { - difference = this.rep[i] - borrow; - } - borrow = difference < 0; - this.rep[i] = cast(ubyte) difference; - - ++i; + difference.rep[i] = (this.rep[i] - subtrahend.rep[i]) - carry; + carry = difference.rep[i] >> (cast(digit) (8 * digit.sizeof) - 1); + difference.rep[i] &= mask; } - if (borrow && i < this.size && this.rep[i]) + // Copy higher digests if the minuend has more digits than the + // subtrahend. + for (; i < this.size; ++i) { - --this.rep[i]; + difference.rep[i] = this.rep[i] - carry; + carry = difference.rep[i] >> (cast(digit) ((8 * digit.sizeof) - 1)); + difference.rep[i] &= mask; } - contract(); + + // Clear digits above the size. + for (i = difference.size; i < oldSize; ++i) + { + difference.rep[i] = 0; + } + + difference.contract(); } - private int compare(const ref Integer that) const nothrow @trusted @nogc + private void subtract(const digit subtrahend, ref Integer difference) + const nothrow @safe @nogc { - if (length > that.length) + difference.grow(this.size); + + const oldSize = difference.size; + + difference.sign = this.sign; + difference.size = this.size; + + difference.rep[0] = this.rep[0] - subtrahend; + auto carry = difference.rep[0] >> ((digit.sizeof * 8) - 1); + difference.rep[0] &= mask; + + size_t i; + for (i = 1; i < this.size; ++i) + { + difference.rep[i] = this.rep[i] - carry; + carry = difference.rep[i] >> ((digit.sizeof * 8) - 1); + difference.rep[i] &= mask; + } + + for (; i < oldSize; ++i) + { + difference.rep[i] = 0; + } + difference.contract(); + } + + // Compare the magnitude. + private int compare(ref const Integer that) const pure nothrow @safe @nogc + { + if (this.size > that.size) { return 1; } - else if (length < that.length) + else if (this.size < that.size) { return -1; } @@ -402,18 +525,33 @@ struct Integer * Comparison. * * Params: + * I = Comparand type. * that = The second integer. * * Returns: A positive number if $(D_INLINECODE this > that), a negative * number if $(D_INLINECODE this < that), `0` otherwise. */ - int opCmp(I : Integer)(const auto ref I that) const + int opCmp(I : Integer)(auto ref const I that) const { if (this.sign != that.sign) { - return this.sign ? -1 : 1; + if (this.sign == Sign.negative) + { + return -1; + } + else + { + return 1; + } + } + if (this.sign == Sign.negative) + { + return that.compare(this); + } + else + { + return compare(that); } - return compare(that); } /// @@ -434,7 +572,7 @@ struct Integer } /// Ditto. - int opCmp(I)(const auto ref I that) const + int opCmp(I)(const I that) const if (isIntegral!I) { if (that < 0 && !this.sign) @@ -445,7 +583,7 @@ struct Integer { return -1; } - else if (this.length > I.sizeof) + else if (this.size > I.sizeof) { return this.sign ? -1 : 1; } @@ -475,11 +613,12 @@ struct Integer /** * Params: + * I = Comparand type. * that = The second integer. * * Returns: Whether the two integers are equal. */ - bool opEquals(I)(const auto ref I that) const + bool opEquals(I)(auto ref const I that) const if (is(I : Integer) || isIntegral!I) { return opCmp!I(that) == 0; @@ -503,160 +642,112 @@ struct Integer * * Returns: $(D_KEYWORD this). */ - ref Integer opOpAssign(string op : "+")(const auto ref Integer operand) + ref Integer opOpAssign(string op : "+")(auto ref const Integer operand) { if (this.sign == operand.sign) { - add(operand); + add(operand, this); + } + else if (compare(operand) < 0) + { + this.sign = operand.sign; + operand.subtract(this, this); } else { - if (this >= operand) - { - subtract(operand); - } - else - { - // Swap the operands. - auto tmp = Integer(this, this.allocator); - this = Integer(operand, this.allocator); - subtract(tmp); - this.sign = operand.sign; - } - + subtract(operand, this); } return this; } - private @nogc unittest + /// + unittest { { auto h1 = Integer(1019); auto h2 = Integer(3337); h1 += h2; - assert(h1.length == 2); - assert(h1.rep[1] == 0x11 && h1.rep[0] == 0x04); + assert(h1 == 4356); } { auto h1 = Integer(4356); auto h2 = Integer(2_147_483_647); - ubyte[4] expected = [ 0x03, 0x11, 0x00, 0x80 ]; h1 += h2; - assert(h1.rep[0 .. h1.size] == expected); + assert(h1 == 2147488003); } { auto h1 = Integer(2147488003L); auto h2 = Integer(2_147_483_647); - ubyte[5] expected = [ 0x02, 0x11, 0x00, 0x00, 0x01 ]; h1 += h2; - assert(h1.rep[0 .. h1.size] == expected); + assert(h1 == 4294971650); } } /// Ditto. - ref Integer opOpAssign(string op : "-")(const auto ref Integer operand) + ref Integer opOpAssign(string op : "-")(auto ref const Integer operand) { - if (operand.sign == this.sign) + if (this.sign != operand.sign) { - if (this >= operand) - { - subtract(operand); - } - else - { - // Swap the operands. - auto tmp = Integer(this, this.allocator); - this = Integer(operand, this.allocator); - subtract(tmp); - - if (operand.sign || this.size == 0) - { - this.sign = Sign.positive; - } - else - { - this.sign = Sign.negative; - } - } + add(operand, this); + } + else if (compare(operand) >= 0) + { + subtract(operand, this); } else { - add(operand); + operand.subtract(this, this); + this.sign = this.sign ? Sign.positive : Sign.negative; } return this; } - private @nogc unittest + /// + unittest { { auto h1 = Integer(3); auto h2 = Integer(4); h1 -= h2; - assert(h1.length == 1); - assert(h1.rep[0] == 0x01); - assert(h1.sign == Sign.negative); + assert(h1 == -1); } { auto h1 = Integer(8589934590L); auto h2 = Integer(2147483647); - ubyte[5] expected = [ 0xff, 0xff, 0xff, 0x7f, 0x01 ]; - h1 -= h2; - assert(h1.rep[0 .. h1.size] == expected); + assert(h1 == 6442450943); } { auto h1 = Integer(6442450943); auto h2 = Integer(4294967294); - ubyte[4] expected = [ 0x01, 0x00, 0x00, 0x80 ]; h1 -= h2; - assert(h1.rep[0 .. h1.size] == expected); + assert(h1 == 2147483649); } { auto h1 = Integer(2147483649); auto h2 = Integer(h1); h1 -= h2; - assert(h1.length == 0); + assert(h1 == 0); } } /// Ditto. - ref Integer opOpAssign(string op : "*")(const auto ref Integer operand) @trusted + ref Integer opOpAssign(string op : "*")(auto ref const Integer operand) { - size_t i; - if (this.length == 0) - { - return this; - } - else if (operand.length == 0) - { - this = 0; - return this; - } - auto temp = Integer(this, this.allocator); - auto sign = this.sign != operand.sign; + const digits = this.size + operand.size + 1; - this = 0; - do - { - for (ubyte mask = 0x01; mask; mask <<= 1) - { - if (mask & operand.rep[i]) - { - add(temp); - } - temp <<= 1; - } - ++i; - } - while (i < operand.size); + multiply(operand, this, digits); - this.sign = sign ? Sign.negative : Sign.positive; + if (this.size > 0) + { + this.sign = this.sign == operand.sign ? Sign.positive : Sign.negative; + } return this; } /// - @safe @nogc unittest + nothrow @safe @nogc unittest { auto h1 = Integer(123); auto h2 = Integer(456); @@ -664,284 +755,155 @@ struct Integer assert(h1 == 56088); } - private @nogc unittest - { - assert((Integer(1) * Integer()).length == 0); - } - /// Ditto. - ref Integer opOpAssign(string op : "^^")(const auto ref Integer operand) - @trusted - { - size_t i; - - auto tmp1 = Integer(this, this.allocator); - this = 1; - - do - { - for (ubyte mask = 0x01; mask; mask <<= 1) - { - if (operand.rep[i] & mask) - { - this *= tmp1; - } - // Square tmp1 - auto tmp2 = tmp1; - tmp1 *= tmp2; - } - ++i; - } - while (i < operand.size); - - return this; - } - - private @nogc unittest - { - auto h1 = Integer(2); - auto h2 = Integer(4); - - h1 ^^= h2; - assert(h1.length == 1); - assert(h1.rep[0] == 0x10); - - h1 = Integer(2342); - h1 ^^= h2; - ubyte[6] expected = [ 0x10, 0x31, 0x9c, 0xab, 0x5c, 0x1b ]; - assert(h1.rep[0 .. h1.size] == expected); - } - - /// Ditto. - ref Integer opOpAssign(string op)(const auto ref Integer operand) @trusted - if ((op == "%") || (op == "/")) + ref Integer opOpAssign(string op : "/")(auto ref const Integer operand) in { assert(operand.length > 0, "Division by zero."); } body { - auto divisor = Integer(operand, this.allocator); - size_t bitSize; - - // First, left-shift divisor until it's >= than the dividend - while (compare(divisor) > 0) - { - divisor <<= 1; - ++bitSize; - } - static if (op == "/") - { - auto quotient = allocator.resize!ubyte(null, bitSize / 8 + 1); - quotient.initializeAll(); - } - - // bitPosition keeps track of which bit, of the quotient, - // is being set or cleared on the current operation. - auto bitPosition = 8 - (bitSize % 8) - 1; - do - { - if (compare(divisor) >= 0) - { - subtract(divisor); // dividend -= divisor - static if (op == "/") - { - quotient[$ - 1 - bitPosition / 8] |= 0x80 >> (bitPosition % 8); - } - } - if (bitSize != 0) - { - divisor >>= 1; - } - ++bitPosition; - } - while (bitSize--); - - static if (op == "/") - { - allocator.resize(this.rep[0 .. this.size], 0); - this.rep = quotient.ptr; - this.size = quotient.length; - this.sign = this.sign == divisor.sign ? Sign.positive : Sign.negative; - contract(); - } - + divide(operand, this); return this; } - private @nogc unittest + /// Ditto. + ref Integer opOpAssign(string op : "%")(auto ref const Integer operand) + in + { + assert(operand.length > 0, "Division by zero."); + } + body + { + divide(operand, null, this); + return this; + } + + nothrow @safe @nogc unittest { auto h1 = Integer(18); auto h2 = Integer(4); h1 %= h2; - assert(h1.length == 1); - assert(h1.rep[0] == 0x02); + assert(h1 == 2); h1 = 8; h1 %= h2; - assert(h1.length == 0); + assert(h1 == 0); h1 = 7; h1 %= h2; - assert(h1.length == 1); - assert(h1.rep[0] == 0x03); + assert(h1 == 3); h1 = 56088; h2 = 456; h1 /= h2; - assert(h1.length == 1); - assert(h1.rep[0] == 0x7b); + assert(h1 == 123); } /// Ditto. - ref Integer opOpAssign(string op : ">>")(const size_t operand) @trusted + ref Integer opOpAssign(string op : ">>")(const size_t operand) { - const step = operand / 8; - - if (step >= this.length) + if (operand == 0) { - this.rep = allocator.resize(this.rep[0 .. this.size], 0).ptr; - this.size = 0; return this; } - - size_t i, j; - ubyte carry; - const bit = operand % 8; - const delta = 8 - bit; - - for (j = step; j < length - 1; ++i, ++j) + if (operand >= digitBitCount) { - carry = cast(ubyte) (this.rep[i + 1] << delta); - this.rep[i] = (this.rep[i] >> bit) | carry; + shiftRight(operand / digitBitCount); } - this.rep[i] = this.rep[j] >> bit; - size_t newSize = length - step; - if (this.rep[i] == 0) + const bit = cast(digit) (operand % digitBitCount); + if (bit != 0) { - --newSize; - } - this.rep = allocator.resize(this.rep[0 .. this.size], newSize).ptr; - this.size = newSize; + const mask = ((cast(digit) 1) << bit) - 1; + const shift = digitBitCount - bit; + digit carry; + foreach (ref d; this.rep[0 .. this.size].retro) + { + const newCarry = d & mask; + d = (d >> bit) | (carry << shift); + carry = newCarry; + } + } + this.contract(); return this; } - private @nogc unittest + /// + nothrow @safe @nogc unittest { auto integer = Integer(4294967294); integer >>= 10; - assert(integer.length == 3); - assert(integer.rep[2] == 0x3f && integer.rep[1] == 0xff && integer.rep[0] == 0xff); + assert(integer == 4194303); integer = 27336704; integer >>= 1; - assert(integer.length == 3); - assert(integer.rep[2] == 0xd0 && integer.rep[1] == 0x90 && integer.rep[0] == 0x00); + assert(integer == 13668352); integer = 4294967294; integer >>= 20; - assert(integer.length == 2); - assert(integer.rep[1] == 0x0f && integer.rep[0] == 0xff); + assert(integer == 4095); integer >>= 0; - assert(integer.length == 2); - assert(integer.rep[1] == 0x0f && integer.rep[0] == 0xff); + assert(integer == 4095); integer >>= 20; - assert(integer.length == 0); + assert(integer == 0); integer >>= 2; - assert(integer.length == 0); + assert(integer == 0); integer = 1431655765; integer >>= 16; - assert(integer.length == 2); - assert(integer.rep[1] == 0x55 && integer.rep[0] == 0x55); + assert(integer == 21845); integer >>= 16; - assert(integer.length == 0); + assert(integer == 0); } /// Ditto. - ref Integer opOpAssign(string op : "<<")(const size_t operand) @trusted + ref Integer opOpAssign(string op : "<<")(const size_t operand) { - auto i = this.length; - size_t j; - size_t newSize; - const bit = operand % 8; - const delta = 8 - bit; - const step = operand / 8; - - auto carry = cast(ubyte) (this.rep[this.size - 1] >> delta); - if (carry != 0) + const step = operand / digitBitCount; + if (this.rep.length < this.size + step + 1) { - newSize = length + step + 1; - this.rep = allocator.resize(this.rep[0 .. this.size], newSize).ptr; - this.size = newSize; - j = newSize - 1; - this.rep[j] = carry; + grow(this.size + step + 1); } - else + if (operand >= digitBitCount) { - newSize = length + step; - this.rep = allocator.resize(this.rep[0 .. this.size], newSize).ptr; - this.size = j = newSize; + shiftLeft(step); } - --i, --j; - for (; i > 0; --i, --j) + const bit = cast(digit) (operand % digitBitCount); + if (bit != 0) { - this.rep[i] = cast(ubyte) (this.rep[j] << bit) | (this.rep[j - 1] >> delta); - } - this.rep[i] = cast(ubyte) (this.rep[j] << bit); - this.rep[0 .. step].fill(cast(ubyte) 0); + const mask = ((cast(digit) 1) << bit) - 1; + const shift = digitBitCount - bit; + digit carry; + foreach (ref d; this.rep[0 .. this.size]) + { + const newCarry = (d >> shift) & mask; + d = ((d << bit) | carry) & this.mask; + carry = newCarry; + } + + if (carry != 0) + { + this.rep[this.size++] = carry; + } + } + this.contract(); return this; } - private @nogc unittest + /// + nothrow @safe @nogc unittest { auto integer = Integer(4294967295); - ubyte[5] expected = [ 0xfe, 0xff, 0xff, 0xff, 0x01 ]; integer <<= 1; - assert(integer.rep[0 .. integer.size] == expected); - } - - private void decrement() nothrow @trusted @nogc - in - { - assert(this.length > 0); - } - body - { - for (ubyte* p = this.rep; p < this.rep + this.size; ++p) - { - --*p; - if (*p != ubyte.max) - { - break; - } - } - contract(); - } - - private void increment() nothrow @trusted @nogc - { - ubyte* p; - for (p = this.rep; p < this.rep + this.size; ++p) - { - ++*p; - if (*p > 0) - { - return; - } - } - if (p == this.rep + this.size) - { - expand(); - } + assert(integer == 8589934590); } /** @@ -954,15 +916,15 @@ struct Integer */ Integer opUnary(string op : "~")() const { - auto ret = Integer(this, this.allocator); - ret.rep[0 .. ret.size].each!((ref a) => a = ~a); + auto ret = Integer(this, allocator); + ret.rep[0 .. ret.size].each!((ref a) => a = ~a & mask); return ret; } /// Ditto. Integer opUnary(string op : "-")() const { - auto ret = Integer(this, this.allocator); + auto ret = Integer(this, allocator); ret.sign = ret.sign ? Sign.positive : Sign.negative; return ret; } @@ -980,35 +942,24 @@ struct Integer return this; } - private @nogc unittest + // + nothrow @safe @nogc unittest { auto h1 = Integer(79); Integer h2; h2 = +h1; - assert(h2.length == 1); - assert(h2.rep[0] == 79); - assert(!h2.sign); - assert(h2 !is h1); + assert(h2 == 79); h2 = -h1; - assert(h2.length == 1); - assert(h2.rep[0] == 79); - assert(h2.sign); + assert(h2 == -79); + assert(h1 == 79); h1 = -h2; - assert(h2.length == 1); - assert(h2.rep[0] == 79); - assert(h2.sign); - assert(h2 !is h1); - - h1 = -h2; - assert(h1.length == 1); - assert(h1.rep[0] == 79); - assert(!h1.sign); + assert(h1 == 79); h2 = ~h1; - assert(h2.rep[0] == ~cast(ubyte) 79); + assert(h2 == ~cast(ubyte) 79); } /// Ditto. @@ -1016,15 +967,11 @@ struct Integer { if (this.sign) { - decrement(); - if (this.length == 0) - { - this.sign = Sign.positive; - } + subtract(1, this); } else { - increment(); + add(1, this); } return this; } @@ -1032,59 +979,56 @@ struct Integer /// Ditto. ref Integer opUnary(string op : "--")() { - if (this.sign) + if (this.size == 0) { - increment(); + add(1, this); + this.sign = Sign.negative; + } + else if (this.sign) + { + add(1, this); } else { - decrement(); + subtract(1, this); } return this; } - private @nogc unittest + /// + nothrow @safe @nogc unittest { Integer integer; ++integer; - assert(integer.length == 1); - assert(integer.rep[0] == 0x01); + assert(integer == 1); --integer; - assert(integer.length == 0); + assert(integer == 0); integer = 511; ++integer; - assert(integer.length == 2); - assert(integer.rep[1] == 0x02 && integer.rep[0] == 0x00); + assert(integer == 512); --integer; - assert(integer.length == 2); - assert(integer.rep[1] == 0x01 && integer.rep[0] == 0xff); + assert(integer == 511); integer = 79; ++integer; - assert(integer.length == 1); - assert(integer.rep[0] == 0x50); + assert(integer == 80); --integer; - assert(integer.length == 1); - assert(integer.rep[0] == 0x4f); - - integer = 65535; - ++integer; - assert(integer.length == 3); - assert(integer.rep[2] == 0x01 && integer.rep[1] == 0x00 && integer.rep[0] == 0x00); - - --integer; - assert(integer.length == 2); - assert(integer.rep[1] == 0xff && integer.rep[0] == 0xff); + assert(integer == 79); integer = -2; ++integer; - assert(integer.length == 1); - assert(integer.rep[0] == 0x01); + assert(integer == -1); + + ++integer; + assert(integer == 0); + + --integer; + assert(integer == -1); } /** @@ -1093,13 +1037,13 @@ struct Integer * Params: * op = Operation. * operand = The second operand. + * + * Returns: Result. */ - Integer opBinary(string op)(const auto ref Integer operand) const - if (op == "+" || op == "-" || op == "*" || op == "^^") + Integer opBinary(string op)(auto ref const Integer operand) const + if ((op == "+" || op == "-") || (op == "*")) { - auto ret = Integer(this, this.allocator); - mixin("ret " ~ op ~ "= operand;"); - return ret; + mixin("return Integer(this, allocator) " ~ op ~ "= operand;"); } /// Ditto. @@ -1111,84 +1055,406 @@ struct Integer } body { - auto ret = Integer(this, this.allocator); - mixin("ret " ~ op ~ "= operand;"); - return ret; + mixin("return Integer(this, allocator) " ~ op ~ "= operand;"); } /// Ditto. - Integer opBinary(string op)(const auto ref size_t operand) const + Integer opBinary(string op)(const size_t operand) const if (op == "<<" || op == ">>") { - auto ret = Integer(this, this.allocator); - mixin("ret " ~ op ~ "= operand;"); - return ret; + mixin("return Integer(this, allocator) " ~ op ~ "= operand;"); + } + + // Shift right a certain amount of digits. + private void shiftRight(const size_t operand) nothrow @safe @nogc + { + if (operand == 0) + { + return; + } + if (this.size <= operand) + { + this = 0; + return; + } + const reducedSize = this.size - operand; + + this.rep[operand .. this.size].copy(this.rep[0 .. reducedSize]); + this.rep[reducedSize .. this.size].fill(digit.init); + this.size = reducedSize; + } + + // Shift left a certain amount of digits. + private void shiftLeft(const size_t operand) nothrow @safe @nogc + { + if (operand == 0) + { + return; + } + const increasedSize = this.size + operand; + grow(increasedSize); + + this.size = increasedSize; + + auto top = this.size - 1; + auto bottom = this.size - 1 - operand; + + for (; top >= operand; --bottom, --top) + { + this.rep[top] = this.rep[bottom]; + } + this.rep[0 .. operand].fill(digit.init); + } + + private void multiply(const digit factor, ref Integer product) + const nothrow @safe @nogc + { + product.grow(this.size + 1); + product.sign = this.sign; + + word carry; + size_t i; + + for (i = 0; i < this.size; ++i) + { + auto newCarry = carry + (cast(word) this.rep[i] * factor); + product.rep[i] = newCarry & mask; + carry = newCarry >> digitBitCount; + } + product.rep[i++] = carry & mask; + + for (; i < this.size; ++i) + { + product.rep[i] = 0; + } + product.size = this.size + 1; + product.contract(); + } + + private void multiply(ref const Integer factor, + ref Integer product, + const size_t digits) const nothrow @safe @nogc + { + Integer intermediate; + intermediate.grow(digits); + intermediate.size = digits; + + for (size_t i; i < this.size; ++i) + { + const limit = min(factor.size, digits - i); + word carry; + auto k = i; + + for (size_t j; j < limit; ++j, ++k) + { + const result = cast(word) intermediate.rep[k] + + (cast(word) this.rep[i] * factor.rep[j]) + + carry; + intermediate.rep[k] = result & mask; + carry = result >> digitBitCount; + } + if (k < digits) + { + intermediate.rep[k] = carry & mask; + } + } + intermediate.contract(); + swap(product, intermediate); + } + + private void divide(Q, ARGS...)(ref const Integer divisor, + auto ref Q quotient, + ref ARGS args) + const nothrow @safe @nogc + if ((is(Q : typeof(null)) + || (is(Q : Integer) && __traits(isRef, quotient))) + && (ARGS.length == 0 || (ARGS.length == 1 && is(ARGS[0] : Integer)))) + in + { + assert(divisor != 0, "Division by zero."); + } + body + { + if (compare(divisor) < 0) + { + static if (ARGS.length == 1) + { + args[0] = this; + } + static if (!is(Q == typeof(null))) + { + quotient = 0; + } + return; + } + + Integer q, t1, t2; + q.grow(this.size + 2); + q.size = this.size + 2; + + t1.grow(2); + t2.grow(3); + + auto x = Integer(this); + auto y = Integer(divisor); + + const sign = this.sign == divisor.sign ? Sign.positive : Sign.negative; + x.sign = y.sign = Sign.positive; + + auto norm = y.countBits() % digitBitCount; + if (norm < digitBitCount - 1) + { + norm = digitBitCount - 1 - norm; + x <<= norm; + y <<= norm; + } + else + { + norm = 0; + } + + auto n = x.size - 1; + auto t = y.size - 1; + + y.shiftLeft(n - t); + + while (x >= y) + { + ++q.rep[n - t]; + x -= y; + } + + y.shiftRight(n - t); + + for (auto i = n; i >= (t + 1); --i) + { + if (i > x.size) + { + continue; + } + if (x.rep[i] == y.rep[t]) + { + q.rep[(i - t) - 1] = (((cast(digit) 1) << digitBitCount) - 1); + } + else + { + word tmp = (cast(word) x.rep[i]) << digitBitCount; + tmp |= x.rep[i - 1]; + tmp /= y.rep[t]; + if (tmp > mask) + { + tmp = mask; + } + q.rep[i - t - 1] = tmp & mask; + } + + q.rep[i - t - 1] = (q.rep[i - t - 1] + 1) & mask; + do + { + q.rep[i - t - 1] = (q.rep[i - t - 1] - 1) & mask; + + // Left hand. + t1 = 0; + t1.rep[0] = ((t - 1) < 0) ? 0 : y.rep[t - 1]; + t1.rep[1] = y.rep[t]; + t1.size = 2; + t1.multiply(q.rep[i - t - 1], t1); + + // Right hand. + t2.rep[0] = ((i - 2) < 0) ? 0 : x.rep[i - 2]; + t2.rep[1] = ((i - 1) < 0) ? 0 : x.rep[i - 1]; + t2.rep[2] = x.rep[i]; + t2.size = 3; + } + while (t1.compare(t2) > 0); + + y.multiply(q.rep[i - t - 1], t1); + + t1.shiftLeft(i - t - 1); + + x -= t1; + + if (x.sign == Sign.negative) + { + t1 = y; + t1.shiftLeft(i - t - 1); + x += t1; + + q.rep[i - t - 1] = (q.rep[i - t - 1] - 1) & mask; + } + } + + x.sign = (x.size == 0) ? Sign.positive : this.sign; + static if (!is(Q == typeof(null))) + { + q.contract(); + swap(q, quotient); + quotient.sign = sign; + } + static if (ARGS.length == 1) + { + x >>= norm; + swap(x, args[0]); + } + } + + private Integer square() nothrow @safe @nogc + { + Integer result; + const resultSize = 2 * this.size + 1; + + result.grow(resultSize); + result.size = resultSize; + + for (size_t i; i < this.size; ++i) + { + const doubleI = 2 * i; + word product = cast(word) result.rep[doubleI] + + (cast(word) this.rep[i] * this.rep[i]); + + result.rep[doubleI] = product & mask; + + word carry = product >> digitBitCount; + size_t k = doubleI + 1; + + for (auto j = i + 1; j < this.size; ++j, ++k) + { + product = (cast(word) this.rep[i]) * (cast(word) this.rep[j]); + product = (cast(word) result.rep[k]) + (2 * product) + carry; + + result.rep[k] = product & mask; + carry = product >> digitBitCount; + } + for (; carry != 0; ++k) + { + product = (cast(word) result.rep[k]) + carry; + result.rep[k] = product & mask; + carry = product >> digitBitCount; + } + } + result.contract(); + + return result; + } + + Vector!ubyte toVector() const nothrow @safe @nogc + { + Vector!ubyte vector; + bool firstBit; + ubyte carry; + + if (this.size == 0) + { + return vector; + } + vector.reserve(this.size * digit.sizeof); + + // The first digit needs extra handling since it can have leading + // non significant zeros. + int digitCount = digitBitCount - 8; + const first = this.rep[this.size - 1]; + const prevBitCount = ((this.size - 1) * digitBitCount); + const fullBytesBitCount = ((prevBitCount - 1) / 8 + 1) * 8; + + // Find out the right alignment of the first byte. + if ((fullBytesBitCount - prevBitCount) == 0) + { + digitCount -= digit.sizeof * 8 - digitBitCount; + } + for (; digitCount >= 0; digitCount -= 8) + { + if (firstBit || ((first >> digitCount) != 0)) + { + firstBit = true; + vector.insertBack(cast(ubyte) (first >> digitCount)); + } + } + if (digitCount >= -8) + { + carry = (first << -digitCount) & 0xff; + digitCount += digitBitCount; + } + else + { + carry = 0; + digitCount = digitBitCount - 8; + } + + foreach_reverse (d; this.rep[0 .. this.size - 1]) + { + if (carry != 0) // Check the carry from the previous digit. + { + vector.insertBack(cast(ubyte) (carry | (d >> digitCount))); + digitCount -= 8; + } + // Write the digit by bytes. + for (; digitCount >= 0; digitCount -= 8) + { + vector.insertBack(cast(ubyte) (d >> digitCount)); + } + // Check for an incomplete byte. + if (digitCount >= -8) + { + carry = (d << -digitCount) & 0xff; + digitCount += digitBitCount; + } + else + { + carry = 0; + digitCount = digitBitCount - 8; + } + } + if (carry != 0) + { + vector.insertBack(cast(ubyte) (carry >> (digitBitCount - digitCount))); + } + + return vector; } /// - @safe @nogc unittest + nothrow @safe @nogc unittest { - auto integer1 = Integer(425); - auto integer2 = integer1 << 1; - assert(integer2 == 850); - - integer2 = integer1 >> 1; - assert(integer2 == 212); - } - - /** - * Casting. - * - * Params: - * T = Target type. - * - * Returns: $(D_KEYWORD false) if the $(D_PSYMBOL Integer) is 0, - * $(D_KEYWORD true) otherwise. - */ - T opCast(T : bool)() const - { - return this.length > 0; - } - - /// Ditto. - T opCast(T)() const - if (isIntegral!T && isSigned!T) - { - return this.sign ? -(cast(Unsigned!T) this) : cast(Unsigned!T) this; - } - - /// Ditto. - T opCast(T)() const @trusted - if (isIntegral!T && isUnsigned!T) - { - if (this.length == 0) { - return 0; + auto integer = Integer(0x66778899aabbddee); + ubyte[8] expected = [ 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xdd, 0xee ]; + + auto vector = integer.toVector(); + assert(equal(vector[], expected[])); } - T ret; - const(ubyte)* src = this.rep; - ubyte shift; - for (; src < this.rep + this.size && shift <= T.sizeof * 8; ++src, shift += 8) { - ret |= (cast(T) *src) << shift; + auto integer = Integer(0x03); + ubyte[1] expected = [ 0x03 ]; + + auto vector = integer.toVector(); + assert(equal(vector[], expected[])); } - return ret; - } + { + ubyte[63] expected = [ + 0x02, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x00, 0x61, 0x62, 0x63, + ]; + auto integer = Integer(Sign.positive, expected[]); - /// - @safe @nogc unittest - { - auto integer = Integer(79); - assert(cast(long) integer == 79); + auto vector = integer.toVector(); + assert(equal(vector[], expected[])); + } + { + ubyte[14] expected = [ + 0x22, 0x33, 0x44, 0x55, 0x05, 0x06, 0x07, + 0x08, 0x3a, 0x3b, 0x00, 0x61, 0x62, 0x63, + ]; + auto integer = Integer(Sign.positive, expected[]); - integer = -79; - assert(cast(long) integer == -79); - - integer = 4294967295; - assert(cast(long) integer == 4294967295); - - integer = -4294967295; - assert(cast(long) integer == -4294967295); + auto vector = integer.toVector(); + assert(equal(vector[], expected[])); + } } mixin DefaultAllocator; diff --git a/source/tanya/math/package.d b/source/tanya/math/package.d index fd5f2a2..3537bc3 100644 --- a/source/tanya/math/package.d +++ b/source/tanya/math/package.d @@ -90,18 +90,19 @@ body size_t i; auto tmp1 = Integer(x, x.allocator); auto result = Integer(x.allocator); + bool firstBit; - if (x.length == 0 && y.length != 0) + if (x.size == 0 && y.size != 0) { - i = y.length; + i = y.size; } else { result = 1; } - while (i < y.length) + while (i < y.size) { - for (ubyte mask = 0x01; mask; mask <<= 1) + for (uint mask = 0x01; mask != 0x10000000; mask <<= 1) { if (y.rep[i] & mask) {