| /** | |
| * 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[]); | |
| } |