/** * Dynamic array implementation. * * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/root/array.d, root/_array.d) * Documentation: https://dlang.org/phobos/dmd_root_array.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/root/array.d */ module dmd.root.array; import core.stdc.stdlib : _compare_fp_t; import core.stdc.string; import dmd.root.rmem; import dmd.root.string; // `qsort` is only `nothrow` since 2.081.0 private extern(C) void qsort(scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc; debug { debug = stomp; // flush out dangling pointer problems by stomping on unused memory } extern (C++) struct Array(T) { size_t length; private: T[] data; enum SMALLARRAYCAP = 1; T[SMALLARRAYCAP] smallarray; // inline storage for small arrays public: /******************* * Params: * dim = initial length of array */ this(size_t dim) pure nothrow { reserve(dim); this.length = dim; } @disable this(this); ~this() pure nothrow { debug (stomp) memset(data.ptr, 0xFF, data.length); if (data.ptr != &smallarray[0]) mem.xfree(data.ptr); } ///returns elements comma separated in [] extern(D) const(char)[] toString() const { static const(char)[] toStringImpl(alias toStringFunc, Array)(Array* a, bool quoted = false) { const(char)[][] buf = (cast(const(char)[]*)mem.xcalloc((char[]).sizeof, a.length))[0 .. a.length]; size_t len = 2; // [ and ] const seplen = quoted ? 3 : 1; // ',' or null terminator and optionally '"' if (a.length == 0) len += 1; // null terminator else { foreach (u; 0 .. a.length) { buf[u] = toStringFunc(a.data[u]); len += buf[u].length + seplen; } } char[] str = (cast(char*)mem.xmalloc_noscan(len))[0..len]; str[0] = '['; char* p = str.ptr + 1; foreach (u; 0 .. a.length) { if (u) *p++ = ','; if (quoted) *p++ = '"'; memcpy(p, buf[u].ptr, buf[u].length); p += buf[u].length; if (quoted) *p++ = '"'; } *p++ = ']'; *p = 0; assert(p - str.ptr == str.length - 1); // null terminator mem.xfree(buf.ptr); return str[0 .. $-1]; } static if (is(typeof(T.init.toString()))) { return toStringImpl!(a => a.toString)(&this); } else static if (is(typeof(T.init.toDString()))) { return toStringImpl!(a => a.toDString)(&this, true); } else { assert(0); } } ///ditto const(char)* toChars() const { return toString.ptr; } ref Array push(T ptr) return pure nothrow { reserve(1); data[length++] = ptr; return this; } extern (D) ref Array pushSlice(T[] a) return pure nothrow { const oldLength = length; setDim(oldLength + a.length); memcpy(data.ptr + oldLength, a.ptr, a.length * T.sizeof); return this; } ref Array append(typeof(this)* a) return pure nothrow { insert(length, a); return this; } void reserve(size_t nentries) pure nothrow { //printf("Array::reserve: length = %d, data.length = %d, nentries = %d\n", cast(int)length, cast(int)data.length, cast(int)nentries); // Cold path void enlarge(size_t nentries) { pragma(inline, false); // never inline cold path if (data.length == 0) { // Not properly initialized, someone memset it to zero if (nentries <= SMALLARRAYCAP) { data = SMALLARRAYCAP ? smallarray[] : null; } else { auto p = cast(T*)mem.xmalloc(nentries * T.sizeof); data = p[0 .. nentries]; } } else if (data.length == SMALLARRAYCAP) { const allocdim = length + nentries; auto p = cast(T*)mem.xmalloc(allocdim * T.sizeof); memcpy(p, smallarray.ptr, length * T.sizeof); data = p[0 .. allocdim]; } else { /* Increase size by 1.5x to avoid excessive memory fragmentation */ auto increment = length / 2; if (nentries > increment) // if 1.5 is not enough increment = nentries; const allocdim = length + increment; debug (stomp) { // always move using allocate-copy-stomp-free auto p = cast(T*)mem.xmalloc(allocdim * T.sizeof); memcpy(p, data.ptr, length * T.sizeof); memset(data.ptr, 0xFF, data.length * T.sizeof); mem.xfree(data.ptr); } else auto p = cast(T*)mem.xrealloc(data.ptr, allocdim * T.sizeof); data = p[0 .. allocdim]; } debug (stomp) { if (length < data.length) memset(data.ptr + length, 0xFF, (data.length - length) * T.sizeof); } else { if (mem.isGCEnabled) if (length < data.length) memset(data.ptr + length, 0xFF, (data.length - length) * T.sizeof); } } if (data.length - length < nentries) // false means hot path enlarge(nentries); } void remove(size_t i) pure nothrow @nogc { if (length - i - 1) memmove(data.ptr + i, data.ptr + i + 1, (length - i - 1) * T.sizeof); length--; debug (stomp) memset(data.ptr + length, 0xFF, T.sizeof); } void insert(size_t index, typeof(this)* a) pure nothrow { if (a) { size_t d = a.length; reserve(d); if (length != index) memmove(data.ptr + index + d, data.ptr + index, (length - index) * T.sizeof); memcpy(data.ptr + index, a.data.ptr, d * T.sizeof); length += d; } } void insert(size_t index, T ptr) pure nothrow { reserve(1); memmove(data.ptr + index + 1, data.ptr + index, (length - index) * T.sizeof); data[index] = ptr; length++; } void setDim(size_t newdim) pure nothrow { if (length < newdim) { reserve(newdim - length); } length = newdim; } size_t find(T ptr) const nothrow pure { foreach (i; 0 .. length) if (data[i] is ptr) return i; return size_t.max; } bool contains(T ptr) const nothrow pure { return find(ptr) != size_t.max; } ref inout(T) opIndex(size_t i) inout nothrow pure { debug // This is called so often the array bounds become expensive return data[i]; else return data.ptr[i]; } inout(T)* tdata() inout pure nothrow @nogc @trusted { return data.ptr; } Array!T* copy() const pure nothrow { auto a = new Array!T(); a.setDim(length); memcpy(a.data.ptr, data.ptr, length * T.sizeof); return a; } void shift(T ptr) pure nothrow { reserve(1); memmove(data.ptr + 1, data.ptr, length * T.sizeof); data[0] = ptr; length++; } void zero() nothrow pure @nogc { data[0 .. length] = T.init; } T pop() nothrow pure @nogc { debug (stomp) { assert(length); auto result = data[length - 1]; remove(length - 1); return result; } else return data[--length]; } extern (D) inout(T)[] opSlice() inout nothrow pure @nogc { return data[0 .. length]; } extern (D) inout(T)[] opSlice(size_t a, size_t b) inout nothrow pure @nogc { assert(a <= b && b <= length); return data[a .. b]; } /** * Sort the elements of an array * * This function relies on `qsort`. * * Params: * pred = Predicate to use. Should be a function of type * `int function(scope const T* e1, scope const T* e2) nothrow`. * The return value of this function should follow the * usual C rule: `e1 >= e2 ? (e1 > e2) : -1`. * The function can have D linkage. * * Returns: * A reference to this, for easy chaining. */ extern(D) ref typeof(this) sort (alias pred) () nothrow { if (this.length < 2) return this; qsort(this.data.ptr, this.length, T.sizeof, &arraySortWrapper!(T, pred)); return this; } /// Ditto, but use `opCmp` by default extern(D) ref typeof(this) sort () () nothrow if (is(typeof(this.data[0].opCmp(this.data[1])) : int)) { return this.sort!(function (scope const(T)* pe1, scope const(T)* pe2) => pe1.opCmp(*pe2)); } alias opDollar = length; alias dim = length; } unittest { // Test for objects implementing toString() static struct S { int s = -1; string toString() const { return "S"; } } auto array = Array!S(4); assert(array.toString() == "[S,S,S,S]"); array.setDim(0); assert(array.toString() == "[]"); // Test for toDString() auto strarray = Array!(const(char)*)(2); strarray[0] = "hello"; strarray[1] = "world"; auto str = strarray.toString(); assert(str == `["hello","world"]`); // Test presence of null terminator. assert(str.ptr[str.length] == '\0'); } unittest { auto array = Array!double(4); array.shift(10); array.push(20); array[2] = 15; assert(array[0] == 10); assert(array.find(10) == 0); assert(array.find(20) == 5); assert(!array.contains(99)); array.remove(1); assert(array.length == 5); assert(array[1] == 15); assert(array.pop() == 20); assert(array.length == 4); array.insert(1, 30); assert(array[1] == 30); assert(array[2] == 15); } unittest { auto arrayA = Array!int(0); int[3] buf = [10, 15, 20]; arrayA.pushSlice(buf); assert(arrayA[] == buf[]); auto arrayPtr = arrayA.copy(); assert(arrayPtr); assert((*arrayPtr)[] == arrayA[]); assert(arrayPtr.tdata != arrayA.tdata); arrayPtr.setDim(0); int[2] buf2 = [100, 200]; arrayPtr.pushSlice(buf2); arrayA.append(arrayPtr); assert(arrayA[3..$] == buf2[]); arrayA.insert(0, arrayPtr); assert(arrayA[] == [100, 200, 10, 15, 20, 100, 200]); arrayA.zero(); foreach(e; arrayA) assert(e == 0); } /** * Exposes the given root Array as a standard D array. * Params: * array = the array to expose. * Returns: * The given array exposed to a standard D array. */ @property inout(T)[] peekSlice(T)(inout(Array!T)* array) pure nothrow @nogc { return array ? (*array)[] : null; } /** * Splits the array at $(D index) and expands it to make room for $(D length) * elements by shifting everything past $(D index) to the right. * Params: * array = the array to split. * index = the index to split the array from. * length = the number of elements to make room for starting at $(D index). */ void split(T)(ref Array!T array, size_t index, size_t length) pure nothrow { if (length > 0) { auto previousDim = array.length; array.setDim(array.length + length); for (size_t i = previousDim; i > index;) { i--; array[i + length] = array[i]; } } } unittest { auto array = Array!int(); array.split(0, 0); assert([] == array[]); array.push(1).push(3); array.split(1, 1); array[1] = 2; assert([1, 2, 3] == array[]); array.split(2, 3); array[2] = 8; array[3] = 20; array[4] = 4; assert([1, 2, 8, 20, 4, 3] == array[]); array.split(0, 0); assert([1, 2, 8, 20, 4, 3] == array[]); array.split(0, 1); array[0] = 123; assert([123, 1, 2, 8, 20, 4, 3] == array[]); array.split(0, 3); array[0] = 123; array[1] = 421; array[2] = 910; assert([123, 421, 910, 123, 1, 2, 8, 20, 4, 3] == (&array).peekSlice()); } /** * Reverse an array in-place. * Params: * a = array * Returns: * reversed a[] */ T[] reverse(T)(T[] a) pure nothrow @nogc @safe { if (a.length > 1) { const mid = (a.length + 1) >> 1; foreach (i; 0 .. mid) { T e = a[i]; a[i] = a[$ - 1 - i]; a[$ - 1 - i] = e; } } return a; } unittest { int[] a1 = []; assert(reverse(a1) == []); int[] a2 = [2]; assert(reverse(a2) == [2]); int[] a3 = [2,3]; assert(reverse(a3) == [3,2]); int[] a4 = [2,3,4]; assert(reverse(a4) == [4,3,2]); int[] a5 = [2,3,4,5]; assert(reverse(a5) == [5,4,3,2]); } unittest { //test toString/toChars. Identifier is a simple object that has a usable .toString import dmd.identifier : Identifier; import core.stdc.string : strcmp; auto array = Array!Identifier(); array.push(new Identifier("id1")); array.push(new Identifier("id2")); string expected = "[id1,id2]"; assert(array.toString == expected); assert(strcmp(array.toChars, expected.ptr) == 0); } /// Predicate to wrap a D function passed to `qsort` private template arraySortWrapper(T, alias fn) { pragma(mangle, "arraySortWrapper_" ~ T.mangleof ~ "_" ~ fn.mangleof) extern(C) int arraySortWrapper(scope const void* e1, scope const void* e2) nothrow { return fn(cast(const(T*))e1, cast(const(T*))e2); } } // Test sorting unittest { Array!(const(char)*) strings; strings.push("World"); strings.push("Foo"); strings.push("baguette"); strings.push("Avocado"); strings.push("Hello"); // Newer frontend versions will work with (e1, e2) and infer the type strings.sort!(function (scope const char** e1, scope const char** e2) => strcmp(*e1, *e2)); assert(strings[0] == "Avocado"); assert(strings[1] == "Foo"); assert(strings[2] == "Hello"); assert(strings[3] == "World"); assert(strings[4] == "baguette"); /// opCmp automatically supported static struct MyStruct { int a; int opCmp(const ref MyStruct other) const nothrow { // Reverse order return other.a - this.a; } } Array!MyStruct arr1; arr1.push(MyStruct(2)); arr1.push(MyStruct(4)); arr1.push(MyStruct(256)); arr1.push(MyStruct(42)); arr1.sort(); assert(arr1[0].a == 256); assert(arr1[1].a == 42); assert(arr1[2].a == 4); assert(arr1[3].a == 2); /// But only if user defined static struct OtherStruct { int a; static int pred (scope const OtherStruct* pe1, scope const OtherStruct* pe2) nothrow @nogc pure @safe { return pe1.a - pe2.a; } } static assert (!is(typeof(Array!(OtherStruct).init.sort()))); static assert (!is(typeof(Array!(OtherStruct).init.sort!pred))); } /** * Iterates the given array and calls the given callable for each element. * * Use this instead of `foreach` when the array may expand during iteration. * * Params: * callable = the callable to call for each element * array = the array to iterate * * See_Also: $(REF foreachDsymbol, dmd, dsymbol) */ template each(alias callable, T) if (is(ReturnType!(typeof((T t) => callable(t))) == void)) { void each(ref Array!T array) { // Do not use foreach, as the size of the array may expand during iteration for (size_t i = 0; i < array.length; ++i) callable(array[i]); } void each(Array!T* array) { if (array) each!callable(*array); } } /// @("iterate over an Array") unittest { static immutable expected = [2, 3, 4, 5]; Array!int array; foreach (e ; expected) array.push(e); int[] result; array.each!((e) { result ~= e; }); assert(result == expected); } @("iterate over a pointer to an Array") unittest { static immutable expected = [2, 3, 4, 5]; auto array = new Array!int; foreach (e ; expected) array.push(e); int[] result; array.each!((e) { result ~= e; }); assert(result == expected); } @("iterate while appending to the array being iterated") unittest { static immutable expected = [2, 3, 4, 5]; Array!int array; foreach (e ; expected[0 .. $ - 1]) array.push(e); int[] result; array.each!((e) { if (e == 2) array.push(5); result ~= e; }); assert(array[] == expected); assert(result == expected); } /** * Iterates the given array and calls the given callable for each element. * * If `callable` returns `!= 0`, it will stop the iteration and return that * value, otherwise it will return 0. * * Use this instead of `foreach` when the array may expand during iteration. * * Params: * callable = the callable to call for each element * array = the array to iterate * * Returns: the last value returned by `callable` * See_Also: $(REF foreachDsymbol, dmd, dsymbol) */ template each(alias callable, T) if (is(ReturnType!(typeof((T t) => callable(t))) == int)) { int each(ref Array!T array) { // Do not use foreach, as the size of the array may expand during iteration for (size_t i = 0; i < array.length; ++i) { if (const result = callable(array[i])) return result; } return 0; } int each(Array!T* array) { return array ? each!callable(*array) : 0; } } /// @("iterate over an Array and stop the iteration") unittest { Array!int array; foreach (e ; [2, 3, 4, 5]) array.push(e); int[] result; const returnValue = array.each!((e) { result ~= e; if (e == 3) return 8; return 0; }); assert(result == [2, 3]); assert(returnValue == 8); } @("iterate over an Array") unittest { static immutable expected = [2, 3, 4, 5]; Array!int array; foreach (e ; expected) array.push(e); int[] result; const returnValue = array.each!((e) { result ~= e; return 0; }); assert(result == expected); assert(returnValue == 0); } @("iterate over a pointer to an Array and stop the iteration") unittest { auto array = new Array!int; foreach (e ; [2, 3, 4, 5]) array.push(e); int[] result; const returnValue = array.each!((e) { result ~= e; if (e == 3) return 9; return 0; }); assert(result == [2, 3]); assert(returnValue == 9); } @("iterate while appending to the array being iterated and stop the iteration") unittest { Array!int array; foreach (e ; [2, 3]) array.push(e); int[] result; const returnValue = array.each!((e) { if (e == 2) array.push(1); result ~= e; if (e == 1) return 7; return 0; }); static immutable expected = [2, 3, 1]; assert(array[] == expected); assert(result == expected); assert(returnValue == 7); } /// Returns: A static array constructed from `array`. pragma(inline, true) T[n] staticArray(T, size_t n)(auto ref T[n] array) { return array; } /// pure nothrow @safe @nogc unittest { enum a = [0, 1].staticArray; static assert(is(typeof(a) == int[2])); static assert(a == [0, 1]); } /// Returns: `true` if the two given ranges are equal bool equal(Range1, Range2)(Range1 range1, Range2 range2) { template isArray(T) { static if (is(T U : U[])) enum isArray = true; else enum isArray = false; } static if (isArray!Range1 && isArray!Range2 && is(typeof(range1 == range2))) return range1 == range2; else { static if (hasLength!Range1 && hasLength!Range2 && is(typeof(r1.length == r2.length))) { if (range1.length != range2.length) return false; } for (; !range1.empty; range1.popFront(), range2.popFront()) { if (range2.empty) return false; if (range1.front != range2.front) return false; } return range2.empty; } } /// pure nothrow @nogc @safe unittest { enum a = [ 1, 2, 4, 3 ].staticArray; static assert(!equal(a[], a[1..$])); static assert(equal(a[], a[])); // different types enum b = [ 1.0, 2, 4, 3].staticArray; static assert(!equal(a[], b[1..$])); static assert(equal(a[], b[])); } pure nothrow @safe unittest { static assert(equal([1, 2, 3].map!(x => x * 2), [1, 2, 3].map!(x => x * 2))); static assert(!equal([1, 2].map!(x => x * 2), [1, 2, 3].map!(x => x * 2))); } /** * Lazily filters the given range based on the given predicate. * * Returns: a range containing only elements for which the predicate returns * `true` */ auto filter(alias predicate, Range)(Range range) if (isInputRange!(Unqual!Range) && isPredicateOf!(predicate, ElementType!Range)) { return Filter!(predicate, Range)(range); } /// pure nothrow @safe @nogc unittest { enum a = [1, 2, 3, 4].staticArray; enum result = a[].filter!(e => e > 2); enum expected = [3, 4].staticArray; static assert(result.equal(expected[])); } private struct Filter(alias predicate, Range) { private Range range; private bool primed; private void prime() { if (primed) return; while (!range.empty && !predicate(range.front)) range.popFront(); primed = true; } @property bool empty() { prime(); return range.empty; } @property auto front() { assert(!range.empty); prime(); return range.front; } void popFront() { assert(!range.empty); prime(); do { range.popFront(); } while (!range.empty && !predicate(range.front)); } auto opSlice() { return this; } } /** * Lazily iterates the given range and calls the given callable for each element. * * Returns: a range containing the result of each call to `callable` */ auto map(alias callable, Range)(Range range) if (isInputRange!(Unqual!Range) && isCallableWith!(callable, ElementType!Range)) { return Map!(callable, Range)(range); } /// pure nothrow @safe @nogc unittest { enum a = [1, 2, 3, 4].staticArray; enum expected = [2, 4, 6, 8].staticArray; enum result = a[].map!(e => e * 2); static assert(result.equal(expected[])); } private struct Map(alias callable, Range) { private Range range; @property bool empty() { return range.empty; } @property auto front() { assert(!range.empty); return callable(range.front); } void popFront() { assert(!range.empty); range.popFront(); } static if (hasLength!Range) { @property auto length() { return range.length; } alias opDollar = length; } } /// Returns: the length of the given range. auto walkLength(Range)(Range range) if (isInputRange!Range ) { static if (hasLength!Range) return range.length; else { size_t result; for (; !range.empty; range.popFront()) ++result; return result; } } /// pure nothrow @safe @nogc unittest { enum a = [1, 2, 3, 4].staticArray; static assert(a[].walkLength == 4); enum c = a[].filter!(e => e > 2); static assert(c.walkLength == 2); } /// Evaluates to the element type of `R`. template ElementType(R) { static if (is(typeof(R.init.front.init) T)) alias ElementType = T; else alias ElementType = void; } /// Evaluates to `true` if the given type satisfy the input range interface. enum isInputRange(R) = is(typeof(R.init) == R) && is(ReturnType!(typeof((R r) => r.empty)) == bool) && is(typeof((return ref R r) => r.front)) && !is(ReturnType!(typeof((R r) => r.front)) == void) && is(typeof((R r) => r.popFront)); /// Evaluates to `true` if `func` can be called with a value of `T` and returns /// a value that is convertible to `bool`. enum isPredicateOf(alias func, T) = is(typeof((T t) => !func(t))); /// Evaluates to `true` if `func` be called withl a value of `T`. enum isCallableWith(alias func, T) = __traits(compiles, { auto _ = (T t) => func(t); }); private: template ReturnType(T) { static if (is(T R == return)) alias ReturnType = R; else static assert(false, "argument is not a function"); } alias Unqual(T) = ReturnType!(typeof((T t) => cast() t)); template hasLength(Range) { static if (is(typeof(((Range* r) => r.length)(null)) Length)) enum hasLength = is(Length == size_t); else enum hasLength = false; } /// Implements the range interface primitive `front` for built-in arrays. @property ref inout(T) front(T)(return scope inout(T)[] a) pure nothrow @nogc @safe { assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof); return a[0]; } /// pure nothrow @nogc @safe unittest { enum a = [1, 2, 3].staticArray; static assert(a[].front == 1); } /// Implements the range interface primitive `empty` for types that obey $(LREF hasLength) property @property bool empty(T)(auto ref scope T a) if (is(typeof(a.length) : size_t)) { return !a.length; } /// pure nothrow @nogc @safe unittest { enum a = [1, 2, 3].staticArray; static assert(!a.empty); static assert(a[3 .. $].empty); } pure nothrow @safe unittest { int[string] b; assert(b.empty); b["zero"] = 0; assert(!b.empty); } /// Implements the range interface primitive `popFront` for built-in arrays. void popFront(T)(/*scope*/ ref inout(T)[] array) pure nothrow @nogc @safe { // does not compile with GDC 9 if this is `scope` assert(array.length, "Attempting to popFront() past the end of an array of " ~ T.stringof); array = array[1 .. $]; } /// pure nothrow @nogc @safe unittest { auto a = [1, 2, 3].staticArray; auto b = a[]; auto expected = [2, 3].staticArray; b.popFront(); assert(b == expected[]); }