blob: 179fa664795f2b321b68df712a6c793fd7717028 [file] [log] [blame]
// Written in the D programming language.
/**
Functions and types that manipulate built-in arrays and associative arrays.
This module provides all kinds of functions to create, manipulate or convert arrays:
$(SCRIPT inhibitQuickIndex = 1;)
$(BOOKTABLE ,
$(TR $(TH Function Name) $(TH Description)
)
$(TR $(TD $(LREF _array))
$(TD Returns a copy of the input in a newly allocated dynamic _array.
))
$(TR $(TD $(LREF appender))
$(TD Returns a new $(LREF Appender) or $(LREF RefAppender) initialized with a given _array.
))
$(TR $(TD $(LREF assocArray))
$(TD Returns a newly allocated associative _array from a range of key/value tuples.
))
$(TR $(TD $(LREF byPair))
$(TD Construct a range iterating over an associative _array by key/value tuples.
))
$(TR $(TD $(LREF insertInPlace))
$(TD Inserts into an existing _array at a given position.
))
$(TR $(TD $(LREF join))
$(TD Concatenates a range of ranges into one _array.
))
$(TR $(TD $(LREF minimallyInitializedArray))
$(TD Returns a new _array of type $(D T).
))
$(TR $(TD $(LREF replace))
$(TD Returns a new _array with all occurrences of a certain subrange replaced.
))
$(TR $(TD $(LREF replaceFirst))
$(TD Returns a new _array with the first occurrence of a certain subrange replaced.
))
$(TR $(TD $(LREF replaceInPlace))
$(TD Replaces all occurrences of a certain subrange and puts the result into a given _array.
))
$(TR $(TD $(LREF replaceInto))
$(TD Replaces all occurrences of a certain subrange and puts the result into an output range.
))
$(TR $(TD $(LREF replaceLast))
$(TD Returns a new _array with the last occurrence of a certain subrange replaced.
))
$(TR $(TD $(LREF replaceSlice))
$(TD Returns a new _array with a given slice replaced.
))
$(TR $(TD $(LREF replicate))
$(TD Creates a new _array out of several copies of an input _array or range.
))
$(TR $(TD $(LREF sameHead))
$(TD Checks if the initial segments of two arrays refer to the same
place in memory.
))
$(TR $(TD $(LREF sameTail))
$(TD Checks if the final segments of two arrays refer to the same place
in memory.
))
$(TR $(TD $(LREF split))
$(TD Eagerly split a range or string into an _array.
))
$(TR $(TD $(LREF uninitializedArray))
$(TD Returns a new _array of type $(D T) without initializing its elements.
))
)
Copyright: Copyright Andrei Alexandrescu 2008- and Jonathan M Davis 2011-.
License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
Authors: $(HTTP erdani.org, Andrei Alexandrescu) and Jonathan M Davis
Source: $(PHOBOSSRC std/_array.d)
*/
module std.array;
import std.functional;
import std.meta;
import std.traits;
import std.range.primitives;
public import std.range.primitives : save, empty, popFront, popBack, front, back;
/**
* Allocates an array and initializes it with copies of the elements
* of range $(D r).
*
* Narrow strings are handled as a special case in an overload.
*
* Params:
* r = range (or aggregate with $(D opApply) function) whose elements are copied into the allocated array
* Returns:
* allocated and initialized array
*/
ForeachType!Range[] array(Range)(Range r)
if (isIterable!Range && !isNarrowString!Range && !isInfinite!Range)
{
if (__ctfe)
{
// Compile-time version to avoid memcpy calls.
// Also used to infer attributes of array().
typeof(return) result;
foreach (e; r)
result ~= e;
return result;
}
alias E = ForeachType!Range;
static if (hasLength!Range)
{
auto length = r.length;
if (length == 0)
return null;
import std.conv : emplaceRef;
auto result = (() @trusted => uninitializedArray!(Unqual!E[])(length))();
// Every element of the uninitialized array must be initialized
size_t i;
foreach (e; r)
{
emplaceRef!E(result[i], e);
++i;
}
return (() @trusted => cast(E[]) result)();
}
else
{
auto a = appender!(E[])();
foreach (e; r)
{
a.put(e);
}
return a.data;
}
}
///
@safe pure nothrow unittest
{
auto a = array([1, 2, 3, 4, 5][]);
assert(a == [ 1, 2, 3, 4, 5 ]);
}
@safe pure nothrow unittest
{
import std.algorithm.comparison : equal;
struct Foo
{
int a;
}
auto a = array([Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)][]);
assert(equal(a, [Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)]));
}
@system unittest
{
import std.algorithm.comparison : equal;
struct Foo
{
int a;
void opAssign(Foo foo)
{
assert(0);
}
auto opEquals(Foo foo)
{
return a == foo.a;
}
}
auto a = array([Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)][]);
assert(equal(a, [Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)]));
}
@safe unittest
{
// Issue 12315
static struct Bug12315 { immutable int i; }
enum bug12315 = [Bug12315(123456789)].array();
static assert(bug12315[0].i == 123456789);
}
@safe unittest
{
import std.range;
static struct S{int* p;}
auto a = array(immutable(S).init.repeat(5));
}
/**
Convert a narrow string to an array type that fully supports random access.
This is handled as a special case and always returns an array of `dchar`
Params:
str = `isNarrowString` to be converted to an array of `dchar`
Returns:
a $(D dchar[]), $(D const(dchar)[]), or $(D immutable(dchar)[]) depending on the constness of
the input.
*/
@trusted ElementType!String[] array(String)(scope String str)
if (isNarrowString!String)
{
import std.utf : toUTF32;
/* This function is @trusted because the following cast
* is unsafe - converting a dstring[] to a dchar[], for example.
* Our special knowledge of toUTF32 is needed to know the cast is safe.
*/
return cast(typeof(return)) str.toUTF32;
}
///
@safe unittest
{
import std.range.primitives : isRandomAccessRange;
assert("Hello D".array == "Hello D"d);
static assert(isRandomAccessRange!string == false);
assert("Hello D"w.array == "Hello D"d);
static assert(isRandomAccessRange!dstring == true);
}
@system unittest
{
// @system due to array!string
import std.conv : to;
static struct TestArray { int x; string toString() @safe { return to!string(x); } }
static struct OpAssign
{
uint num;
this(uint num) { this.num = num; }
// Templating opAssign to make sure the bugs with opAssign being
// templated are fixed.
void opAssign(T)(T rhs) { this.num = rhs.num; }
}
static struct OpApply
{
int opApply(scope int delegate(ref int) dg)
{
int res;
foreach (i; 0 .. 10)
{
res = dg(i);
if (res) break;
}
return res;
}
}
auto a = array([1, 2, 3, 4, 5][]);
//writeln(a);
assert(a == [ 1, 2, 3, 4, 5 ]);
auto b = array([TestArray(1), TestArray(2)][]);
//writeln(b);
class C
{
int x;
this(int y) { x = y; }
override string toString() const @safe { return to!string(x); }
}
auto c = array([new C(1), new C(2)][]);
//writeln(c);
auto d = array([1.0, 2.2, 3][]);
assert(is(typeof(d) == double[]));
//writeln(d);
auto e = [OpAssign(1), OpAssign(2)];
auto f = array(e);
assert(e == f);
assert(array(OpApply.init) == [0,1,2,3,4,5,6,7,8,9]);
assert(array("ABC") == "ABC"d);
assert(array("ABC".dup) == "ABC"d.dup);
}
//Bug# 8233
@safe unittest
{
assert(array("hello world"d) == "hello world"d);
immutable a = [1, 2, 3, 4, 5];
assert(array(a) == a);
const b = a;
assert(array(b) == a);
//To verify that the opAssign branch doesn't get screwed up by using Unqual.
//EDIT: array no longer calls opAssign.
struct S
{
ref S opAssign(S)(const ref S rhs)
{
assert(0);
}
int i;
}
foreach (T; AliasSeq!(S, const S, immutable S))
{
auto arr = [T(1), T(2), T(3), T(4)];
assert(array(arr) == arr);
}
}
@safe unittest
{
//9824
static struct S
{
@disable void opAssign(S);
int i;
}
auto arr = [S(0), S(1), S(2)];
arr.array();
}
// Bugzilla 10220
@safe unittest
{
import std.algorithm.comparison : equal;
import std.exception;
import std.range : repeat;
static struct S
{
int val;
@disable this();
this(int v) { val = v; }
}
assertCTFEable!(
{
auto r = S(1).repeat(2).array();
assert(equal(r, [S(1), S(1)]));
});
}
@safe unittest
{
//Turn down infinity:
static assert(!is(typeof(
repeat(1).array()
)));
}
/**
Returns a newly allocated associative _array from a range of key/value tuples.
Params:
r = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
of tuples of keys and values.
Returns: A newly allocated associative array out of elements of the input
range, which must be a range of tuples (Key, Value). Returns a null associative
array reference when given an empty range.
Duplicates: Associative arrays have unique keys. If r contains duplicate keys,
then the result will contain the value of the last pair for that key in r.
See_Also: $(REF Tuple, std,typecons), $(REF zip, std,range)
*/
auto assocArray(Range)(Range r)
if (isInputRange!Range)
{
import std.typecons : isTuple;
alias E = ElementType!Range;
static assert(isTuple!E, "assocArray: argument must be a range of tuples");
static assert(E.length == 2, "assocArray: tuple dimension must be 2");
alias KeyType = E.Types[0];
alias ValueType = E.Types[1];
static assert(isMutable!ValueType, "assocArray: value type must be mutable");
ValueType[KeyType] aa;
foreach (t; r)
aa[t[0]] = t[1];
return aa;
}
///
@safe pure /*nothrow*/ unittest
{
import std.range;
import std.typecons;
auto a = assocArray(zip([0, 1, 2], ["a", "b", "c"])); // aka zipMap
assert(is(typeof(a) == string[int]));
assert(a == [0:"a", 1:"b", 2:"c"]);
auto b = assocArray([ tuple("foo", "bar"), tuple("baz", "quux") ]);
assert(is(typeof(b) == string[string]));
assert(b == ["foo":"bar", "baz":"quux"]);
}
// @@@11053@@@ - Cannot be version (unittest) - recursive instantiation error
@safe unittest
{
import std.typecons;
static assert(!__traits(compiles, [ tuple("foo", "bar", "baz") ].assocArray()));
static assert(!__traits(compiles, [ tuple("foo") ].assocArray()));
assert([ tuple("foo", "bar") ].assocArray() == ["foo": "bar"]);
}
// Issue 13909
@safe unittest
{
import std.typecons;
auto a = [tuple!(const string, string)("foo", "bar")];
auto b = [tuple!(string, const string)("foo", "bar")];
assert(assocArray(a) == [cast(const(string)) "foo": "bar"]);
static assert(!__traits(compiles, assocArray(b)));
}
/**
Construct a range iterating over an associative array by key/value tuples.
Params: aa = The associative array to iterate over.
Returns: A $(REF_ALTTEXT forward range, isForwardRange, std,_range,primitives)
of Tuple's of key and value pairs from the given associative array.
*/
auto byPair(AA : Value[Key], Value, Key)(AA aa)
{
import std.algorithm.iteration : map;
import std.typecons : tuple;
return aa.byKeyValue.map!(pair => tuple(pair.key, pair.value));
}
///
@system unittest
{
import std.algorithm.sorting : sort;
import std.typecons : tuple, Tuple;
auto aa = ["a": 1, "b": 2, "c": 3];
Tuple!(string, int)[] pairs;
// Iteration over key/value pairs.
foreach (pair; aa.byPair)
{
pairs ~= pair;
}
// Iteration order is implementation-dependent, so we should sort it to get
// a fixed order.
sort(pairs);
assert(pairs == [
tuple("a", 1),
tuple("b", 2),
tuple("c", 3)
]);
}
@system unittest
{
import std.typecons : tuple, Tuple;
auto aa = ["a":2];
auto pairs = aa.byPair();
static assert(is(typeof(pairs.front) == Tuple!(string,int)));
static assert(isForwardRange!(typeof(pairs)));
assert(!pairs.empty);
assert(pairs.front == tuple("a", 2));
auto savedPairs = pairs.save;
pairs.popFront();
assert(pairs.empty);
assert(!savedPairs.empty);
assert(savedPairs.front == tuple("a", 2));
}
// Issue 17711
@system unittest
{
const(int[string]) aa = [ "abc": 123 ];
// Ensure that byKeyValue is usable with a const AA.
auto kv = aa.byKeyValue;
assert(!kv.empty);
assert(kv.front.key == "abc" && kv.front.value == 123);
kv.popFront();
assert(kv.empty);
// Ensure byPair is instantiable with const AA.
auto r = aa.byPair;
static assert(isInputRange!(typeof(r)));
assert(!r.empty && r.front[0] == "abc" && r.front[1] == 123);
r.popFront();
assert(r.empty);
}
private template blockAttribute(T)
{
import core.memory;
static if (hasIndirections!(T) || is(T == void))
{
enum blockAttribute = 0;
}
else
{
enum blockAttribute = GC.BlkAttr.NO_SCAN;
}
}
version (unittest)
{
import core.memory : UGC = GC;
static assert(!(blockAttribute!void & UGC.BlkAttr.NO_SCAN));
}
// Returns the number of dimensions in an array T.
private template nDimensions(T)
{
static if (isArray!T)
{
enum nDimensions = 1 + nDimensions!(typeof(T.init[0]));
}
else
{
enum nDimensions = 0;
}
}
version (unittest)
{
static assert(nDimensions!(uint[]) == 1);
static assert(nDimensions!(float[][]) == 2);
}
/++
Returns a new array of type $(D T) allocated on the garbage collected heap
without initializing its elements. This can be a useful optimization if every
element will be immediately initialized. $(D T) may be a multidimensional
array. In this case sizes may be specified for any number of dimensions from 0
to the number in $(D T).
uninitializedArray is nothrow and weakly pure.
uninitializedArray is @system if the uninitialized element type has pointers.
+/
auto uninitializedArray(T, I...)(I sizes) nothrow @system
if (isDynamicArray!T && allSatisfy!(isIntegral, I) && hasIndirections!(ElementEncodingType!T))
{
enum isSize_t(E) = is (E : size_t);
alias toSize_t(E) = size_t;
static assert(allSatisfy!(isSize_t, I),
"Argument types in "~I.stringof~" are not all convertible to size_t: "
~Filter!(templateNot!(isSize_t), I).stringof);
//Eagerlly transform non-size_t into size_t to avoid template bloat
alias ST = staticMap!(toSize_t, I);
return arrayAllocImpl!(false, T, ST)(sizes);
}
/// ditto
auto uninitializedArray(T, I...)(I sizes) nothrow @trusted
if (isDynamicArray!T && allSatisfy!(isIntegral, I) && !hasIndirections!(ElementEncodingType!T))
{
enum isSize_t(E) = is (E : size_t);
alias toSize_t(E) = size_t;
static assert(allSatisfy!(isSize_t, I),
"Argument types in "~I.stringof~" are not all convertible to size_t: "
~Filter!(templateNot!(isSize_t), I).stringof);
//Eagerlly transform non-size_t into size_t to avoid template bloat
alias ST = staticMap!(toSize_t, I);
return arrayAllocImpl!(false, T, ST)(sizes);
}
///
@system nothrow pure unittest
{
double[] arr = uninitializedArray!(double[])(100);
assert(arr.length == 100);
double[][] matrix = uninitializedArray!(double[][])(42, 31);
assert(matrix.length == 42);
assert(matrix[0].length == 31);
char*[] ptrs = uninitializedArray!(char*[])(100);
assert(ptrs.length == 100);
}
/++
Returns a new array of type $(D T) allocated on the garbage collected heap.
Partial initialization is done for types with indirections, for preservation
of memory safety. Note that elements will only be initialized to 0, but not
necessarily the element type's $(D .init).
minimallyInitializedArray is nothrow and weakly pure.
+/
auto minimallyInitializedArray(T, I...)(I sizes) nothrow @trusted
if (isDynamicArray!T && allSatisfy!(isIntegral, I))
{
enum isSize_t(E) = is (E : size_t);
alias toSize_t(E) = size_t;
static assert(allSatisfy!(isSize_t, I),
"Argument types in "~I.stringof~" are not all convertible to size_t: "
~Filter!(templateNot!(isSize_t), I).stringof);
//Eagerlly transform non-size_t into size_t to avoid template bloat
alias ST = staticMap!(toSize_t, I);
return arrayAllocImpl!(true, T, ST)(sizes);
}
///
@safe pure nothrow unittest
{
import std.algorithm.comparison : equal;
import std.range : repeat;
auto arr = minimallyInitializedArray!(int[])(42);
assert(arr.length == 42);
// Elements aren't necessarily initialized to 0
assert(!arr.equal(0.repeat(42)));
}
@safe pure nothrow unittest
{
cast(void) minimallyInitializedArray!(int[][][][][])();
double[] arr = minimallyInitializedArray!(double[])(100);
assert(arr.length == 100);
double[][] matrix = minimallyInitializedArray!(double[][])(42);
assert(matrix.length == 42);
foreach (elem; matrix)
{
assert(elem.ptr is null);
}
}
private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow
{
static assert(I.length <= nDimensions!T,
I.length.stringof~"dimensions specified for a "~nDimensions!T.stringof~" dimensional array.");
alias E = ElementEncodingType!T;
E[] ret;
static if (I.length != 0)
{
static assert(is(I[0] == size_t));
alias size = sizes[0];
}
static if (I.length == 1)
{
if (__ctfe)
{
static if (__traits(compiles, new E[](size)))
ret = new E[](size);
else static if (__traits(compiles, ret ~= E.init))
{
try
{
//Issue: if E has an impure postblit, then all of arrayAllocImpl
//Will be impure, even during non CTFE.
foreach (i; 0 .. size)
ret ~= E.init;
}
catch (Exception e)
throw new Error(e.msg);
}
else
assert(0, "No postblit nor default init on " ~ E.stringof ~
": At least one is required for CTFE.");
}
else
{
import core.memory : GC;
import core.stdc.string : memset;
import core.checkedint : mulu;
bool overflow;
const nbytes = mulu(size, E.sizeof, overflow);
if (overflow) assert(0);
auto ptr = cast(E*) GC.malloc(nbytes, blockAttribute!E);
static if (minimallyInitialized && hasIndirections!E)
memset(ptr, 0, nbytes);
ret = ptr[0 .. size];
}
}
else static if (I.length > 1)
{
ret = arrayAllocImpl!(false, E[])(size);
foreach (ref elem; ret)
elem = arrayAllocImpl!(minimallyInitialized, E)(sizes[1..$]);
}
return ret;
}
@safe nothrow pure unittest
{
auto s1 = uninitializedArray!(int[])();
auto s2 = minimallyInitializedArray!(int[])();
assert(s1.length == 0);
assert(s2.length == 0);
}
@safe nothrow pure unittest //@@@9803@@@
{
auto a = minimallyInitializedArray!(int*[])(1);
assert(a[0] == null);
auto b = minimallyInitializedArray!(int[][])(1);
assert(b[0].empty);
auto c = minimallyInitializedArray!(int*[][])(1, 1);
assert(c[0][0] == null);
}
@safe unittest //@@@10637@@@
{
static struct S
{
static struct I{int i; alias i this;}
int* p;
this() @disable;
this(int i)
{
p = &(new I(i)).i;
}
this(this)
{
p = &(new I(*p)).i;
}
~this()
{
assert(p != null);
}
}
auto a = minimallyInitializedArray!(S[])(1);
assert(a[0].p == null);
enum b = minimallyInitializedArray!(S[])(1);
}
@safe nothrow unittest
{
static struct S1
{
this() @disable;
this(this) @disable;
}
auto a1 = minimallyInitializedArray!(S1[][])(2, 2);
//enum b1 = minimallyInitializedArray!(S1[][])(2, 2);
static struct S2
{
this() @disable;
//this(this) @disable;
}
auto a2 = minimallyInitializedArray!(S2[][])(2, 2);
enum b2 = minimallyInitializedArray!(S2[][])(2, 2);
static struct S3
{
//this() @disable;
this(this) @disable;
}
auto a3 = minimallyInitializedArray!(S3[][])(2, 2);
enum b3 = minimallyInitializedArray!(S3[][])(2, 2);
}
// overlap
/*
NOTE: Undocumented for now, overlap does not yet work with ctfe.
Returns the overlapping portion, if any, of two arrays. Unlike $(D
equal), $(D overlap) only compares the pointers in the ranges, not the
values referred by them. If $(D r1) and $(D r2) have an overlapping
slice, returns that slice. Otherwise, returns the null slice.
*/
auto overlap(T, U)(T[] r1, U[] r2) @trusted pure nothrow
if (is(typeof(r1.ptr < r2.ptr) == bool))
{
import std.algorithm.comparison : min, max;
auto b = max(r1.ptr, r2.ptr);
auto e = min(r1.ptr + r1.length, r2.ptr + r2.length);
return b < e ? b[0 .. e - b] : null;
}
///
@safe pure /*nothrow*/ unittest
{
int[] a = [ 10, 11, 12, 13, 14 ];
int[] b = a[1 .. 3];
assert(overlap(a, b) == [ 11, 12 ]);
b = b.dup;
// overlap disappears even though the content is the same
assert(overlap(a, b).empty);
}
@safe /*nothrow*/ unittest
{
static void test(L, R)(L l, R r)
{
import std.stdio;
scope(failure) writeln("Types: L %s R %s", L.stringof, R.stringof);
assert(overlap(l, r) == [ 100, 12 ]);
assert(overlap(l, l[0 .. 2]) is l[0 .. 2]);
assert(overlap(l, l[3 .. 5]) is l[3 .. 5]);
assert(overlap(l[0 .. 2], l) is l[0 .. 2]);
assert(overlap(l[3 .. 5], l) is l[3 .. 5]);
}
int[] a = [ 10, 11, 12, 13, 14 ];
int[] b = a[1 .. 3];
a[1] = 100;
immutable int[] c = a.idup;
immutable int[] d = c[1 .. 3];
test(a, b);
assert(overlap(a, b.dup).empty);
test(c, d);
assert(overlap(c, d.idup).empty);
}
@safe pure nothrow unittest // bugzilla 9836
{
// range primitives for array should work with alias this types
struct Wrapper
{
int[] data;
alias data this;
@property Wrapper save() { return this; }
}
auto w = Wrapper([1,2,3,4]);
std.array.popFront(w); // should work
static assert(isInputRange!Wrapper);
static assert(isForwardRange!Wrapper);
static assert(isBidirectionalRange!Wrapper);
static assert(isRandomAccessRange!Wrapper);
}
private void copyBackwards(T)(T[] src, T[] dest)
{
import core.stdc.string : memmove;
assert(src.length == dest.length);
if (!__ctfe || hasElaborateCopyConstructor!T)
{
/* insertInPlace relies on dest being uninitialized, so no postblits allowed,
* as this is a MOVE that overwrites the destination, not a COPY.
* BUG: insertInPlace will not work with ctfe and postblits
*/
memmove(dest.ptr, src.ptr, src.length * T.sizeof);
}
else
{
immutable len = src.length;
for (size_t i = len; i-- > 0;)
{
dest[i] = src[i];
}
}
}
/++
Inserts $(D stuff) (which must be an input range or any number of
implicitly convertible items) in $(D array) at position $(D pos).
Params:
array = The array that $(D stuff) will be inserted into.
pos = The position in $(D array) to insert the $(D stuff).
stuff = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives),
or any number of implicitly convertible items to insert into $(D array).
+/
void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff)
if (!isSomeString!(T[])
&& allSatisfy!(isInputRangeOrConvertible!T, U) && U.length > 0)
{
static if (allSatisfy!(isInputRangeWithLengthOrConvertible!T, U))
{
import std.conv : emplaceRef;
immutable oldLen = array.length;
size_t to_insert = 0;
foreach (i, E; U)
{
static if (is(E : T)) //a single convertible value, not a range
to_insert += 1;
else
to_insert += stuff[i].length;
}
if (to_insert)
{
array.length += to_insert;
// Takes arguments array, pos, stuff
// Spread apart array[] at pos by moving elements
(() @trusted { copyBackwards(array[pos .. oldLen], array[pos+to_insert..$]); })();
// Initialize array[pos .. pos+to_insert] with stuff[]
auto j = 0;
foreach (i, E; U)
{
static if (is(E : T))
{
emplaceRef!T(array[pos + j++], stuff[i]);
}
else
{
foreach (v; stuff[i])
{
emplaceRef!T(array[pos + j++], v);
}
}
}
}
}
else
{
// stuff has some InputRanges in it that don't have length
// assume that stuff to be inserted is typically shorter
// then the array that can be arbitrary big
// TODO: needs a better implementation as there is no need to build an _array_
// a singly-linked list of memory blocks (rope, etc.) will do
auto app = appender!(T[])();
foreach (i, E; U)
app.put(stuff[i]);
insertInPlace(array, pos, app.data);
}
}
/// Ditto
void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff)
if (isSomeString!(T[]) && allSatisfy!(isCharOrStringOrDcharRange, U))
{
static if (is(Unqual!T == T)
&& allSatisfy!(isInputRangeWithLengthOrConvertible!dchar, U))
{
import std.utf : codeLength;
// mutable, can do in place
//helper function: re-encode dchar to Ts and store at *ptr
static T* putDChar(T* ptr, dchar ch)
{
static if (is(T == dchar))
{
*ptr++ = ch;
return ptr;
}
else
{
import std.utf : encode;
T[dchar.sizeof/T.sizeof] buf;
immutable len = encode(buf, ch);
final switch (len)
{
static if (T.sizeof == char.sizeof)
{
case 4:
ptr[3] = buf[3];
goto case;
case 3:
ptr[2] = buf[2];
goto case;
}
case 2:
ptr[1] = buf[1];
goto case;
case 1:
ptr[0] = buf[0];
}
ptr += len;
return ptr;
}
}
size_t to_insert = 0;
//count up the number of *codeunits* to insert
foreach (i, E; U)
to_insert += codeLength!T(stuff[i]);
array.length += to_insert;
@trusted static void moveToRight(T[] arr, size_t gap)
{
static assert(!hasElaborateCopyConstructor!T);
import core.stdc.string : memmove;
if (__ctfe)
{
for (size_t i = arr.length - gap; i; --i)
arr[gap + i - 1] = arr[i - 1];
}
else
memmove(arr.ptr + gap, arr.ptr, (arr.length - gap) * T.sizeof);
}
moveToRight(array[pos .. $], to_insert);
auto ptr = array.ptr + pos;
foreach (i, E; U)
{
static if (is(E : dchar))
{
ptr = putDChar(ptr, stuff[i]);
}
else
{
foreach (dchar ch; stuff[i])
ptr = putDChar(ptr, ch);
}
}
assert(ptr == array.ptr + pos + to_insert, "(ptr == array.ptr + pos + to_insert) is false");
}
else
{
// immutable/const, just construct a new array
auto app = appender!(T[])();
app.put(array[0 .. pos]);
foreach (i, E; U)
app.put(stuff[i]);
app.put(array[pos..$]);
array = app.data;
}
}
///
@safe pure unittest
{
int[] a = [ 1, 2, 3, 4 ];
a.insertInPlace(2, [ 1, 2 ]);
assert(a == [ 1, 2, 1, 2, 3, 4 ]);
a.insertInPlace(3, 10u, 11);
assert(a == [ 1, 2, 1, 10, 11, 2, 3, 4]);
}
//constraint helpers
private template isInputRangeWithLengthOrConvertible(E)
{
template isInputRangeWithLengthOrConvertible(R)
{
//hasLength not defined for char[], wchar[] and dchar[]
enum isInputRangeWithLengthOrConvertible =
(isInputRange!R && is(typeof(R.init.length))
&& is(ElementType!R : E)) || is(R : E);
}
}
//ditto
private template isCharOrStringOrDcharRange(T)
{
enum isCharOrStringOrDcharRange = isSomeString!T || isSomeChar!T ||
(isInputRange!T && is(ElementType!T : dchar));
}
//ditto
private template isInputRangeOrConvertible(E)
{
template isInputRangeOrConvertible(R)
{
enum isInputRangeOrConvertible =
(isInputRange!R && is(ElementType!R : E)) || is(R : E);
}
}
@system unittest
{
// @system due to insertInPlace
import core.exception;
import std.algorithm.comparison : equal;
import std.algorithm.iteration : filter;
import std.conv : to;
import std.exception;
bool test(T, U, V)(T orig, size_t pos, U toInsert, V result,
string file = __FILE__, size_t line = __LINE__)
{
{
static if (is(T == typeof(T.init.dup)))
auto a = orig.dup;
else
auto a = orig.idup;
a.insertInPlace(pos, toInsert);
if (!equal(a, result))
return false;
}
static if (isInputRange!U)
{
orig.insertInPlace(pos, filter!"true"(toInsert));
return equal(orig, result);
}
else
return true;
}
assert(test([1, 2, 3, 4], 0, [6, 7], [6, 7, 1, 2, 3, 4]));
assert(test([1, 2, 3, 4], 2, [8, 9], [1, 2, 8, 9, 3, 4]));
assert(test([1, 2, 3, 4], 4, [10, 11], [1, 2, 3, 4, 10, 11]));
assert(test([1, 2, 3, 4], 0, 22, [22, 1, 2, 3, 4]));
assert(test([1, 2, 3, 4], 2, 23, [1, 2, 23, 3, 4]));
assert(test([1, 2, 3, 4], 4, 24, [1, 2, 3, 4, 24]));
void testStr(T, U)(string file = __FILE__, size_t line = __LINE__)
{
auto l = to!T("hello");
auto r = to!U(" વિશ્વ");
enforce(test(l, 0, r, " વિશ્વhello"),
new AssertError("testStr failure 1", file, line));
enforce(test(l, 3, r, "hel વિશ્વlo"),
new AssertError("testStr failure 2", file, line));
enforce(test(l, l.length, r, "hello વિશ્વ"),
new AssertError("testStr failure 3", file, line));
}
foreach (T; AliasSeq!(char, wchar, dchar,
immutable(char), immutable(wchar), immutable(dchar)))
{
foreach (U; AliasSeq!(char, wchar, dchar,
immutable(char), immutable(wchar), immutable(dchar)))
{
testStr!(T[], U[])();
}
}
// variadic version
bool testVar(T, U...)(T orig, size_t pos, U args)
{
static if (is(T == typeof(T.init.dup)))
auto a = orig.dup;
else
auto a = orig.idup;
auto result = args[$-1];
a.insertInPlace(pos, args[0..$-1]);
if (!equal(a, result))
return false;
return true;
}
assert(testVar([1, 2, 3, 4], 0, 6, 7u, [6, 7, 1, 2, 3, 4]));
assert(testVar([1L, 2, 3, 4], 2, 8, 9L, [1, 2, 8, 9, 3, 4]));
assert(testVar([1L, 2, 3, 4], 4, 10L, 11, [1, 2, 3, 4, 10, 11]));
assert(testVar([1L, 2, 3, 4], 4, [10, 11], 40L, 42L,
[1, 2, 3, 4, 10, 11, 40, 42]));
assert(testVar([1L, 2, 3, 4], 4, 10, 11, [40L, 42],
[1, 2, 3, 4, 10, 11, 40, 42]));
assert(testVar("t".idup, 1, 'e', 's', 't', "test"));
assert(testVar("!!"w.idup, 1, "\u00e9ll\u00f4", 'x', "TTT"w, 'y',
"!\u00e9ll\u00f4xTTTy!"));
assert(testVar("flipflop"d.idup, 4, '_',
"xyz"w, '\U00010143', '_', "abc"d, "__",
"flip_xyz\U00010143_abc__flop"));
}
@system unittest
{
import std.algorithm.comparison : equal;
// insertInPlace interop with postblit
static struct Int
{
int* payload;
this(int k)
{
payload = new int;
*payload = k;
}
this(this)
{
int* np = new int;
*np = *payload;
payload = np;
}
~this()
{
if (payload)
*payload = 0; //'destroy' it
}
@property int getPayload(){ return *payload; }
alias getPayload this;
}
Int[] arr = [Int(1), Int(4), Int(5)];
assert(arr[0] == 1);
insertInPlace(arr, 1, Int(2), Int(3));
assert(equal(arr, [1, 2, 3, 4, 5])); //check it works with postblit
version (none) // illustrates that insertInPlace() will not work with CTFE and postblit
{
static bool testctfe()
{
Int[] arr = [Int(1), Int(4), Int(5)];
assert(arr[0] == 1);
insertInPlace(arr, 1, Int(2), Int(3));
return equal(arr, [1, 2, 3, 4, 5]); //check it works with postblit
}
enum E = testctfe();
}
}
@safe unittest
{
import std.exception;
assertCTFEable!(
{
int[] a = [1, 2];
a.insertInPlace(2, 3);
a.insertInPlace(0, -1, 0);
return a == [-1, 0, 1, 2, 3];
});
}
@system unittest // bugzilla 6874
{
import core.memory;
// allocate some space
byte[] a;
a.length = 1;
// fill it
a.length = a.capacity;
// write beyond
byte[] b = a[$ .. $];
b.insertInPlace(0, a);
// make sure that reallocation has happened
assert(GC.addrOf(&b[0]) == GC.addrOf(&b[$-1]));
}
/++
Returns whether the $(D front)s of $(D lhs) and $(D rhs) both refer to the
same place in memory, making one of the arrays a slice of the other which
starts at index $(D 0).
+/
@safe
pure nothrow bool sameHead(T)(in T[] lhs, in T[] rhs)
{
return lhs.ptr == rhs.ptr;
}
///
@safe pure nothrow unittest
{
auto a = [1, 2, 3, 4, 5];
auto b = a[0 .. 2];
assert(a.sameHead(b));
}
/++
Returns whether the $(D back)s of $(D lhs) and $(D rhs) both refer to the
same place in memory, making one of the arrays a slice of the other which
end at index $(D $).
+/
@trusted
pure nothrow bool sameTail(T)(in T[] lhs, in T[] rhs)
{
return lhs.ptr + lhs.length == rhs.ptr + rhs.length;
}
///
@safe pure nothrow unittest
{
auto a = [1, 2, 3, 4, 5];
auto b = a[3..$];
assert(a.sameTail(b));
}
@safe pure nothrow unittest
{
foreach (T; AliasSeq!(int[], const(int)[], immutable(int)[], const int[], immutable int[]))
{
T a = [1, 2, 3, 4, 5];
T b = a;
T c = a[1 .. $];
T d = a[0 .. 1];
T e = null;
assert(sameHead(a, a));
assert(sameHead(a, b));
assert(!sameHead(a, c));
assert(sameHead(a, d));
assert(!sameHead(a, e));
assert(sameTail(a, a));
assert(sameTail(a, b));
assert(sameTail(a, c));
assert(!sameTail(a, d));
assert(!sameTail(a, e));
//verifies R-value compatibilty
assert(a.sameHead(a[0 .. 0]));
assert(a.sameTail(a[$ .. $]));
}
}
/**
Params:
s = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
or a dynamic array
n = number of times to repeat `s`
Returns:
An array that consists of `s` repeated `n` times. This function allocates, fills, and
returns a new array.
See_Also:
For a lazy version, refer to $(REF repeat, std,range).
*/
ElementEncodingType!S[] replicate(S)(S s, size_t n)
if (isDynamicArray!S)
{
alias RetType = ElementEncodingType!S[];
// Optimization for return join(std.range.repeat(s, n));
if (n == 0)
return RetType.init;
if (n == 1)
return cast(RetType) s;
auto r = new Unqual!(typeof(s[0]))[n * s.length];
if (s.length == 1)
r[] = s[0];
else
{
immutable len = s.length, nlen = n * len;
for (size_t i = 0; i < nlen; i += len)
{
r[i .. i + len] = s[];
}
}
return r;
}
/// ditto
ElementType!S[] replicate(S)(S s, size_t n)
if (isInputRange!S && !isDynamicArray!S)
{
import std.range : repeat;
return join(std.range.repeat(s, n));
}
///
@safe unittest
{
auto a = "abc";
auto s = replicate(a, 3);
assert(s == "abcabcabc");
auto b = [1, 2, 3];
auto c = replicate(b, 3);
assert(c == [1, 2, 3, 1, 2, 3, 1, 2, 3]);
auto d = replicate(b, 0);
assert(d == []);
}
@safe unittest
{
import std.conv : to;
foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
{
S s;
immutable S t = "abc";
assert(replicate(to!S("1234"), 0) is null);
assert(replicate(to!S("1234"), 0) is null);
assert(replicate(to!S("1234"), 1) == "1234");
assert(replicate(to!S("1234"), 2) == "12341234");
assert(replicate(to!S("1"), 4) == "1111");
assert(replicate(t, 3) == "abcabcabc");
assert(replicate(cast(S) null, 4) is null);
}
}
/++
Eagerly split the string $(D s) into an array of words, using whitespace as
delimiter. Runs of whitespace are merged together (no empty words are produced).
$(D @safe), $(D pure) and $(D CTFE)-able.
Params:
s = the string to split
Returns:
An array of each word in `s`
See_Also:
$(REF splitter, std,algorithm,iteration) for a version that splits using any
separator.
$(REF splitter, std,regex) for a version that splits using a regular
expression defined separator.
+/
S[] split(S)(S s) @safe pure
if (isSomeString!S)
{
size_t istart;
bool inword = false;
S[] result;
foreach (i, dchar c ; s)
{
import std.uni : isWhite;
if (isWhite(c))
{
if (inword)
{
result ~= s[istart .. i];
inword = false;
}
}
else
{
if (!inword)
{
istart = i;
inword = true;
}
}
}
if (inword)
result ~= s[istart .. $];
return result;
}
///
@safe unittest
{
string str = "Hello World!";
assert(str.split == ["Hello", "World!"]);
string str2 = "Hello\t\tWorld\t!";
assert(str2.split == ["Hello", "World", "!"]);
}
/**
* `split` allocates memory, so the same effect can be achieved lazily
* using $(REF splitter, std,algorithm,iteration).
*/
@safe unittest
{
import std.ascii : isWhite;
import std.algorithm.comparison : equal;
import std.algorithm.iteration : splitter;
string str = "Hello World!";
assert(str.splitter!(isWhite).equal(["Hello", "World!"]));
}
@safe unittest
{
import std.conv : to;
import std.format;
import std.typecons;
static auto makeEntry(S)(string l, string[] r)
{return tuple(l.to!S(), r.to!(S[])());}
foreach (S; AliasSeq!(string, wstring, dstring,))
{
auto entries =
[
makeEntry!S("", []),
makeEntry!S(" ", []),
makeEntry!S("hello", ["hello"]),
makeEntry!S(" hello ", ["hello"]),
makeEntry!S(" h e l l o ", ["h", "e", "l", "l", "o"]),
makeEntry!S("peter\t\npaul\rjerry", ["peter", "paul", "jerry"]),
makeEntry!S(" \t\npeter paul\tjerry \n", ["peter", "paul", "jerry"]),
makeEntry!S("\u2000日\u202F本\u205F語\u3000", ["日", "本", "語"]),
makeEntry!S("  哈・郎博尔德}    ___一个", ["哈・郎博尔德}", "___一个"])
];
foreach (entry; entries)
assert(entry[0].split() == entry[1], format("got: %s, expected: %s.", entry[0].split(), entry[1]));
}
//Just to test that an immutable is split-able
immutable string s = " \t\npeter paul\tjerry \n";
assert(split(s) == ["peter", "paul", "jerry"]);
}
@safe unittest //purity, ctfe ...
{
import std.exception;
void dg() @safe pure {
assert(split("hello world"c) == ["hello"c, "world"c]);
assert(split("hello world"w) == ["hello"w, "world"w]);
assert(split("hello world"d) == ["hello"d, "world"d]);
}
dg();
assertCTFEable!dg;
}
///
@safe unittest
{
assert(split("hello world") == ["hello","world"]);
assert(split("192.168.0.1", ".") == ["192", "168", "0", "1"]);
auto a = split([1, 2, 3, 4, 5, 1, 2, 3, 4, 5], [2, 3]);
assert(a == [[1], [4, 5, 1], [4, 5]]);
}
/++
Eagerly splits $(D range) into an array, using $(D sep) as the delimiter.
The _range must be a
$(REF_ALTTEXT forward _range, isForwardRange, std,_range,primitives).
The separator can be a value of the same type as the elements in $(D range)
or it can be another forward _range.
Example:
If $(D range) is a $(D string), $(D sep) can be a $(D char) or another
$(D string). The return type will be an array of strings. If $(D range) is
an $(D int) array, $(D sep) can be an $(D int) or another $(D int) array.
The return type will be an array of $(D int) arrays.
Params:
range = a forward _range.
sep = a value of the same type as the elements of $(D range) or another
forward range.
Returns:
An array containing the divided parts of $(D range).
See_Also:
$(REF splitter, std,algorithm,iteration) for the lazy version of this
function.
+/
auto split(Range, Separator)(Range range, Separator sep)
if (isForwardRange!Range && is(typeof(ElementType!Range.init == Separator.init)))
{
import std.algorithm.iteration : splitter;
return range.splitter(sep).array;
}
///ditto
auto split(Range, Separator)(Range range, Separator sep)
if (
isForwardRange!Range && isForwardRange!Separator
&& is(typeof(ElementType!Range.init == ElementType!Separator.init)))
{
import std.algorithm.iteration : splitter;
return range.splitter(sep).array;
}
///ditto
auto split(alias isTerminator, Range)(Range range)
if (isForwardRange!Range && is(typeof(unaryFun!isTerminator(range.front))))
{
import std.algorithm.iteration : splitter;
return range.splitter!isTerminator.array;
}
///
@safe unittest
{
import std.uni : isWhite;
assert("Learning,D,is,fun".split(",") == ["Learning", "D", "is", "fun"]);
assert("Learning D is fun".split!isWhite == ["Learning", "D", "is", "fun"]);
assert("Learning D is fun".split(" D ") == ["Learning", "is fun"]);
}
@safe unittest
{
import std.algorithm.comparison : cmp;
import std.conv;
foreach (S; AliasSeq!(string, wstring, dstring,
immutable(string), immutable(wstring), immutable(dstring),
char[], wchar[], dchar[],
const(char)[], const(wchar)[], const(dchar)[],
const(char[]), immutable(char[])))
{
S s = to!S(",peter,paul,jerry,");
auto words = split(s, ",");
assert(words.length == 5, text(words.length));
assert(cmp(words[0], "") == 0);
assert(cmp(words[1], "peter") == 0);
assert(cmp(words[2], "paul") == 0);
assert(cmp(words[3], "jerry") == 0);
assert(cmp(words[4], "") == 0);
auto s1 = s[0 .. s.length - 1]; // lop off trailing ','
words = split(s1, ",");
assert(words.length == 4);
assert(cmp(words[3], "jerry") == 0);
auto s2 = s1[1 .. s1.length]; // lop off leading ','
words = split(s2, ",");
assert(words.length == 3);
assert(cmp(words[0], "peter") == 0);
auto s3 = to!S(",,peter,,paul,,jerry,,");
words = split(s3, ",,");
assert(words.length == 5);
assert(cmp(words[0], "") == 0);
assert(cmp(words[1], "peter") == 0);
assert(cmp(words[2], "paul") == 0);
assert(cmp(words[3], "jerry") == 0);
assert(cmp(words[4], "") == 0);
auto s4 = s3[0 .. s3.length - 2]; // lop off trailing ',,'
words = split(s4, ",,");
assert(words.length == 4);
assert(cmp(words[3], "jerry") == 0);
auto s5 = s4[2 .. s4.length]; // lop off leading ',,'
words = split(s5, ",,");
assert(words.length == 3);
assert(cmp(words[0], "peter") == 0);
}
}
/++
Conservative heuristic to determine if a range can be iterated cheaply.
Used by $(D join) in decision to do an extra iteration of the range to
compute the resultant length. If iteration is not cheap then precomputing
length could be more expensive than using $(D Appender).
For now, we only assume arrays are cheap to iterate.
+/
private enum bool hasCheapIteration(R) = isArray!R;
/++
Eagerly concatenates all of the ranges in `ror` together (with the GC)
into one array using `sep` as the separator if present.
Params:
ror = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
of input ranges
sep = An input range, or a single element, to join the ranges on
Returns:
An array of elements
See_Also:
For a lazy version, see $(REF joiner, std,algorithm,iteration)
+/
ElementEncodingType!(ElementType!RoR)[] join(RoR, R)(RoR ror, scope R sep)
if (isInputRange!RoR &&
isInputRange!(Unqual!(ElementType!RoR)) &&
isInputRange!R &&
is(Unqual!(ElementType!(ElementType!RoR)) == Unqual!(ElementType!R)))
{
alias RetType = typeof(return);
alias RetTypeElement = Unqual!(ElementEncodingType!RetType);
alias RoRElem = ElementType!RoR;
if (ror.empty)
return RetType.init;
// Constraint only requires input range for sep.
// This converts sep to an array (forward range) if it isn't one,
// and makes sure it has the same string encoding for string types.
static if (isSomeString!RetType &&
!is(RetTypeElement == Unqual!(ElementEncodingType!R)))
{
import std.conv : to;
auto sepArr = to!RetType(sep);
}
else static if (!isArray!R)
auto sepArr = array(sep);
else
alias sepArr = sep;
static if (hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem))
{
import std.conv : emplaceRef;
size_t length; // length of result array
size_t rorLength; // length of range ror
foreach (r; ror.save)
{
length += r.length;
++rorLength;
}
if (!rorLength)
return null;
length += (rorLength - 1) * sepArr.length;
auto result = (() @trusted => uninitializedArray!(RetTypeElement[])(length))();
size_t len;
foreach (e; ror.front)
emplaceRef(result[len++], e);
ror.popFront();
foreach (r; ror)
{
foreach (e; sepArr)
emplaceRef(result[len++], e);
foreach (e; r)
emplaceRef(result[len++], e);
}
assert(len == result.length);
return (() @trusted => cast(RetType) result)();
}
else
{
auto result = appender!RetType();
put(result, ror.front);
ror.popFront();
for (; !ror.empty; ror.popFront())
{
put(result, sep);
put(result, ror.front);
}
return result.data;
}
}
@safe unittest // Issue 14230
{
string[] ary = ["","aa","bb","cc"]; // leaded by _empty_ element
assert(ary.join(" @") == " @aa @bb @cc"); // OK in 2.067b1 and olders
}
/// Ditto
ElementEncodingType!(ElementType!RoR)[] join(RoR, E)(RoR ror, scope E sep)
if (isInputRange!RoR &&
isInputRange!(Unqual!(ElementType!RoR)) &&
is(E : ElementType!(ElementType!RoR)))
{
alias RetType = typeof(return);
alias RetTypeElement = Unqual!(ElementEncodingType!RetType);
alias RoRElem = ElementType!RoR;
if (ror.empty)
return RetType.init;
static if (hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem))
{
static if (isSomeChar!E && isSomeChar!RetTypeElement && E.sizeof > RetTypeElement.sizeof)
{
import std.utf : encode;
RetTypeElement[4 / RetTypeElement.sizeof] encodeSpace;
immutable size_t sepArrLength = encode(encodeSpace, sep);
return join(ror, encodeSpace[0 .. sepArrLength]);
}
else
{
import std.conv : emplaceRef;
size_t length;
size_t rorLength;
foreach (r; ror.save)
{
length += r.length;
++rorLength;
}
if (!rorLength)
return null;
length += rorLength - 1;
auto result = uninitializedArray!(RetTypeElement[])(length);
size_t len;
foreach (e; ror.front)
emplaceRef(result[len++], e);
ror.popFront();
foreach (r; ror)
{
emplaceRef(result[len++], sep);
foreach (e; r)
emplaceRef(result[len++], e);
}
assert(len == result.length);
return (() @trusted => cast(RetType) result)();
}
}
else
{
auto result = appender!RetType();
put(result, ror.front);
ror.popFront();
for (; !ror.empty; ror.popFront())
{
put(result, sep);
put(result, ror.front);
}
return result.data;
}
}
@safe unittest // Issue 10895
{
class A
{
string name;
alias name this;
this(string name) { this.name = name; }
}
auto a = [new A(`foo`)];
assert(a[0].length == 3);
auto temp = join(a, " ");
assert(a[0].length == 3);
}
@safe unittest // Issue 14230
{
string[] ary = ["","aa","bb","cc"];
assert(ary.join('@') == "@aa@bb@cc");
}
/// Ditto
ElementEncodingType!(ElementType!RoR)[] join(RoR)(RoR ror)
if (isInputRange!RoR &&
isInputRange!(Unqual!(ElementType!RoR)))
{
alias RetType = typeof(return);
alias RetTypeElement = Unqual!(ElementEncodingType!RetType);
alias RoRElem = ElementType!RoR;
if (ror.empty)
return RetType.init;
static if (hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem))
{
import std.conv : emplaceRef;
size_t length;
foreach (r; ror.save)
length += r.length;
auto result = (() @trusted => uninitializedArray!(RetTypeElement[])(length))();
size_t len;
foreach (r; ror)
foreach (e; r)
emplaceRef(result[len++], e);
assert(len == result.length);
return (() @trusted => cast(RetType) result)();
}
else
{
auto result = appender!RetType();
for (; !ror.empty; ror.popFront())
put(result, ror.front);
return result.data;
}
}
///
@safe pure nothrow unittest
{
assert(join(["hello", "silly", "world"], " ") == "hello silly world");
assert(join(["hello", "silly", "world"]) == "hellosillyworld");
assert(join([[1, 2, 3], [4, 5]], [72, 73]) == [1, 2, 3, 72, 73, 4, 5]);
assert(join([[1, 2, 3], [4, 5]]) == [1, 2, 3, 4, 5]);
const string[] arr = ["apple", "banana"];
assert(arr.join(",") == "apple,banana");
assert(arr.join() == "applebanana");
}
@safe pure unittest
{
import std.conv : to;
foreach (T; AliasSeq!(string,wstring,dstring))
{
auto arr2 = "Здравствуй Мир Unicode".to!(T);
auto arr = ["Здравствуй", "Мир", "Unicode"].to!(T[]);
assert(join(arr) == "ЗдравствуйМирUnicode");
foreach (S; AliasSeq!(char,wchar,dchar))
{
auto jarr = arr.join(to!S(' '));
static assert(is(typeof(jarr) == T));
assert(jarr == arr2);
}
foreach (S; AliasSeq!(string,wstring,dstring))
{
auto jarr = arr.join(to!S(" "));
static assert(is(typeof(jarr) == T));
assert(jarr == arr2);
}
}
foreach (T; AliasSeq!(string,wstring,dstring))
{
auto arr2 = "Здравствуй\u047CМир\u047CUnicode".to!(T);
auto arr = ["Здравствуй", "Мир", "Unicode"].to!(T[]);
foreach (S; AliasSeq!(wchar,dchar))
{
auto jarr = arr.join(to!S('\u047C'));
static assert(is(typeof(jarr) == T));
assert(jarr == arr2);
}
}
const string[] arr = ["apple", "banana"];
assert(arr.join(',') == "apple,banana");
}
@system unittest
{
import std.algorithm;
import std.conv : to;
import std.range;
foreach (R; AliasSeq!(string, wstring, dstring))
{
R word1 = "日本語";
R word2 = "paul";
R word3 = "jerry";
R[] words = [word1, word2, word3];
auto filteredWord1 = filter!"true"(word1);
auto filteredLenWord1 = takeExactly(filteredWord1, word1.walkLength());
auto filteredWord2 = filter!"true"(word2);
auto filteredLenWord2 = takeExactly(filteredWord2, word2.walkLength());
auto filteredWord3 = filter!"true"(word3);
auto filteredLenWord3 = takeExactly(filteredWord3, word3.walkLength());
auto filteredWordsArr = [filteredWord1, filteredWord2, filteredWord3];
auto filteredLenWordsArr = [filteredLenWord1, filteredLenWord2, filteredLenWord3];
auto filteredWords = filter!"true"(filteredWordsArr);
foreach (S; AliasSeq!(string, wstring, dstring))
(){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
assert(join(filteredWords, to!S(", ")) == "日本語, paul, jerry");
assert(join(filteredWords, to!(ElementType!S)(',')) == "日本語,paul,jerry");
assert(join(filteredWordsArr, to!(ElementType!(S))(',')) == "日本語,paul,jerry");
assert(join(filteredWordsArr, to!S(", ")) == "日本語, paul, jerry");
assert(join(filteredWordsArr, to!(ElementType!(S))(',')) == "日本語,paul,jerry");
assert(join(filteredLenWordsArr, to!S(", ")) == "日本語, paul, jerry");
assert(join(filter!"true"(words), to!S(", ")) == "日本語, paul, jerry");
assert(join(words, to!S(", ")) == "日本語, paul, jerry");
assert(join(filteredWords, to!S("")) == "日本語pauljerry");
assert(join(filteredWordsArr, to!S("")) == "日本語pauljerry");
assert(join(filteredLenWordsArr, to!S("")) == "日本語pauljerry");
assert(join(filter!"true"(words), to!S("")) == "日本語pauljerry");
assert(join(words, to!S("")) == "日本語pauljerry");
assert(join(filter!"true"([word1]), to!S(", ")) == "日本語");
assert(join([filteredWord1], to!S(", ")) == "日本語");
assert(join([filteredLenWord1], to!S(", ")) == "日本語");
assert(join(filter!"true"([filteredWord1]), to!S(", ")) == "日本語");
assert(join([word1], to!S(", ")) == "日本語");
assert(join(filteredWords, to!S(word1)) == "日本語日本語paul日本語jerry");
assert(join(filteredWordsArr, to!S(word1)) == "日本語日本語paul日本語jerry");
assert(join(filteredLenWordsArr, to!S(word1)) == "日本語日本語paul日本語jerry");
assert(join(filter!"true"(words), to!S(word1)) == "日本語日本語paul日本語jerry");
assert(join(words, to!S(word1)) == "日本語日本語paul日本語jerry");
auto filterComma = filter!"true"(to!S(", "));
assert(join(filteredWords, filterComma) == "日本語, paul, jerry");
assert(join(filteredWordsArr, filterComma) == "日本語, paul, jerry");
assert(join(filteredLenWordsArr, filterComma) == "日本語, paul, jerry");
assert(join(filter!"true"(words), filterComma) == "日本語, paul, jerry");
assert(join(words, filterComma) == "日本語, paul, jerry");
}();
assert(join(filteredWords) == "日本語pauljerry");
assert(join(filteredWordsArr) == "日本語pauljerry");
assert(join(filteredLenWordsArr) == "日本語pauljerry");
assert(join(filter!"true"(words)) == "日本語pauljerry");
assert(join(words) == "日本語pauljerry");
assert(join(filteredWords, filter!"true"(", ")) == "日本語, paul, jerry");
assert(join(filteredWordsArr, filter!"true"(", ")) == "日本語, paul, jerry");
assert(join(filteredLenWordsArr, filter!"true"(", ")) == "日本語, paul, jerry");
assert(join(filter!"true"(words), filter!"true"(", ")) == "日本語, paul, jerry");
assert(join(words, filter!"true"(", ")) == "日本語, paul, jerry");
assert(join(filter!"true"(cast(typeof(filteredWordsArr))[]), ", ").empty);
assert(join(cast(typeof(filteredWordsArr))[], ", ").empty);
assert(join(cast(typeof(filteredLenWordsArr))[], ", ").empty);
assert(join(filter!"true"(cast(R[])[]), ", ").empty);
assert(join(cast(R[])[], ", ").empty);
assert(join(filter!"true"(cast(typeof(filteredWordsArr))[])).empty);
assert(join(cast(typeof(filteredWordsArr))[]).empty);
assert(join(cast(typeof(filteredLenWordsArr))[]).empty);
assert(join(filter!"true"(cast(R[])[])).empty);
assert(join(cast(R[])[]).empty);
}
assert(join([[1, 2], [41, 42]], [5, 6]) == [1, 2, 5, 6, 41, 42]);
assert(join([[1, 2], [41, 42]], cast(int[])[]) == [1, 2, 41, 42]);
assert(join([[1, 2]], [5, 6]) == [1, 2]);
assert(join(cast(int[][])[], [5, 6]).empty);
assert(join([[1, 2], [41, 42]]) == [1, 2, 41, 42]);
assert(join(cast(int[][])[]).empty);
alias f = filter!"true";
assert(join([[1, 2], [41, 42]], [5, 6]) == [1, 2, 5, 6, 41, 42]);
assert(join(f([[1, 2], [41, 42]]), [5, 6]) == [1, 2, 5, 6, 41, 42]);
assert(join([f([1, 2]), f([41, 42])], [5, 6]) == [1, 2, 5, 6, 41, 42]);
assert(join(f([f([1, 2]), f([41, 42])]), [5, 6]) == [1, 2, 5, 6, 41, 42]);
assert(join([[1, 2], [41, 42]], f([5, 6])) == [1, 2, 5, 6, 41, 42]);
assert(join(f([[1, 2], [41, 42]]), f([5, 6])) == [1, 2, 5, 6, 41, 42]);
assert(join([f([1, 2]), f([41, 42])], f([5, 6])) == [1, 2, 5, 6, 41, 42]);
assert(join(f([f([1, 2]), f([41, 42])]), f([5, 6])) == [1, 2, 5, 6, 41, 42]);
}
// Issue 10683
@safe unittest
{
import std.range : join;
import std.typecons : tuple;
assert([[tuple(1)]].join == [tuple(1)]);
assert([[tuple("x")]].join == [tuple("x")]);
}
// Issue 13877
@safe unittest
{
// Test that the range is iterated only once.
import std.algorithm.iteration : map;
int c = 0;
auto j1 = [1, 2, 3].map!(_ => [c++]).join;
assert(c == 3);
assert(j1 == [0, 1, 2]);
c = 0;
auto j2 = [1, 2, 3].map!(_ => [c++]).join(9);
assert(c == 3);
assert(j2 == [0, 9, 1, 9, 2]);
c = 0;
auto j3 = [1, 2, 3].map!(_ => [c++]).join([9]);
assert(c == 3);
assert(j3 == [0, 9, 1, 9, 2]);
}
/++
Replace occurrences of `from` with `to` in `subject` in a new
array. If `sink` is defined, then output the new array into
`sink`.
Params:
sink = an $(REF_ALTTEXT output range, isOutputRange, std,range,primitives)
subject = the array to scan
from = the item to replace
to = the item to replace all instances of `from` with
Returns:
If `sink` isn't defined, a new array without changing the
contents of `subject`, or the original array if no match
is found.
See_Also:
$(REF map, std,algorithm,iteration) which can act as a lazy replace
+/
E[] replace(E, R1, R2)(E[] subject, R1 from, R2 to)
if (isDynamicArray!(E[]) && isForwardRange!R1 && isForwardRange!R2
&& (hasLength!R2 || isSomeString!R2))
{
import std.algorithm.searching : find;
if (from.empty) return subject;
auto balance = find(subject, from.save);
if (balance.empty)
return subject;
auto app = appender!(E[])();
app.put(subject[0 .. subject.length - balance.length]);
app.put(to.save);
replaceInto(app, balance[from.length .. $], from, to);
return app.data;
}
///
@safe unittest
{
assert("Hello Wörld".replace("o Wö", "o Wo") == "Hello World");
assert("Hello Wörld".replace("l", "h") == "Hehho Wörhd");
}
/// ditto
void replaceInto(E, Sink, R1, R2)(Sink sink, E[] subject, R1 from, R2 to)
if (isOutputRange!(Sink, E) && isDynamicArray!(E[])
&& isForwardRange!R1 && isForwardRange!R2
&& (hasLength!R2 || isSomeString!R2))
{
import std.algorithm.searching : find;
if (from.empty)
{
sink.put(subject);
return;
}
for (;;)
{
auto balance = find(subject, from.save);
if (balance.empty)
{
sink.put(subject);
break;
}
sink.put(subject[0 .. subject.length - balance.length]);
sink.put(to.save);
subject = balance[from.length .. $];
}
}
///
@safe unittest
{
auto arr = [1, 2, 3, 4, 5];
auto from = [2, 3];
auto to = [4, 6];
auto sink = appender!(int[])();
replaceInto(sink, arr, from, to);
assert(sink.data == [1, 4, 6, 4, 5]);
}
@safe unittest
{
import std.algorithm.comparison : cmp;
import std.conv : to;
foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
{
foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
(){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
auto s = to!S("This is a foo foo list");
auto from = to!T("foo");
auto into = to!S("silly");
S r;
int i;
r = replace(s, from, into);
i = cmp(r, "This is a silly silly list");
assert(i == 0);
r = replace(s, to!S(""), into);
i = cmp(r, "This is a foo foo list");
assert(i == 0);
assert(replace(r, to!S("won't find this"), to!S("whatever")) is r);
}();
}
immutable s = "This is a foo foo list";
assert(replace(s, "foo", "silly") == "This is a silly silly list");
}
@safe unittest
{
import std.algorithm.searching : skipOver;
import std.conv : to;
struct CheckOutput(C)
{
C[] desired;
this(C[] arr){ desired = arr; }
void put(C[] part){ assert(skipOver(desired, part)); }
}
foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
{
alias Char = ElementEncodingType!S;
S s = to!S("yet another dummy text, yet another ...");
S from = to!S("yet another");
S into = to!S("some");
replaceInto(CheckOutput!(Char)(to!S("some dummy text, some ..."))
, s, from, into);
}
}
/++
Replaces elements from `array` with indices ranging from `from`
(inclusive) to `to` (exclusive) with the range `stuff`.
Params:
subject = the array to scan
from = the starting index
to = the ending index
stuff = the items to replace in-between `from` and `to`
Returns:
A new array without changing the contents of `subject`.
+/
T[] replace(T, Range)(T[] subject, size_t from, size_t to, Range stuff)
if (isInputRange!Range &&
(is(ElementType!Range : T) ||
isSomeString!(T[]) && is(ElementType!Range : dchar)))
{
static if (hasLength!Range && is(ElementEncodingType!Range : T))
{
import std.algorithm.mutation : copy;
assert(from <= to);
immutable sliceLen = to - from;
auto retval = new Unqual!(T)[](subject.length - sliceLen + stuff.length);
retval[0 .. from] = subject[0 .. from];
if (!stuff.empty)
copy(stuff, retval[from .. from + stuff.length]);
retval[from + stuff.length .. $] = subject[to .. $];
return cast(T[]) retval;
}
else
{
auto app = appender!(T[])();
app.put(subject[0 .. from]);
app.put(stuff);
app.put(subject[to .. $]);
return app.data;
}
}
///
@safe unittest
{
auto a = [ 1, 2, 3, 4 ];
auto b = a.replace(1, 3, [ 9, 9, 9 ]);
assert(a == [ 1, 2, 3, 4 ]);
assert(b == [ 1, 9, 9, 9, 4 ]);
}
@system unittest
{
import core.exception;
import std.algorithm.iteration : filter;
import std.conv : to;
import std.exception;
auto a = [ 1, 2, 3, 4 ];
assert(replace(a, 0, 0, [5, 6, 7]) == [5, 6, 7, 1, 2, 3, 4]);
assert(replace(a, 0, 2, cast(int[])[]) == [3, 4]);
assert(replace(a, 0, 4, [5, 6, 7]) == [5, 6, 7]);
assert(replace(a, 0, 2, [5, 6, 7]) == [5, 6, 7, 3, 4]);
assert(replace(a, 2, 4, [5, 6, 7]) == [1, 2, 5, 6, 7]);
assert(replace(a, 0, 0, filter!"true"([5, 6, 7])) == [5, 6, 7, 1, 2, 3, 4]);
assert(replace(a, 0, 2, filter!"true"(cast(int[])[])) == [3, 4]);
assert(replace(a, 0, 4, filter!"true"([5, 6, 7])) == [5, 6, 7]);
assert(replace(a, 0, 2, filter!"true"([5, 6, 7])) == [5, 6, 7, 3, 4]);
assert(replace(a, 2, 4, filter!"true"([5, 6, 7])) == [1, 2, 5, 6, 7]);
assert(a == [ 1, 2, 3, 4 ]);
void testStr(T, U)(string file = __FILE__, size_t line = __LINE__)
{
auto l = to!T("hello");
auto r = to!U(" world");
enforce(replace(l, 0, 0, r) == " worldhello",
new AssertError("testStr failure 1", file, line));
enforce(replace(l, 0, 3, r) == " worldlo",
new AssertError("testStr failure 2", file, line));
enforce(replace(l, 3, l.length, r) == "hel world",
new AssertError("testStr failure 3", file, line));
enforce(replace(l, 0, l.length, r) == " world",
new AssertError("testStr failure 4", file, line));
enforce(replace(l, l.length, l.length, r) == "hello world",
new AssertError("testStr failure 5", file, line));
}
testStr!(string, string)();
testStr!(string, wstring)();
testStr!(string, dstring)();
testStr!(wstring, string)();
testStr!(wstring, wstring)();
testStr!(wstring, dstring)();
testStr!(dstring, string)();
testStr!(dstring, wstring)();
testStr!(dstring, dstring)();
enum s = "0123456789";
enum w = "⁰¹²³⁴⁵⁶⁷⁸⁹"w;
enum d = "⁰¹²³⁴⁵⁶⁷⁸⁹"d;
assert(replace(s, 0, 0, "***") == "***0123456789");
assert(replace(s, 10, 10, "***") == "0123456789***");
assert(replace(s, 3, 8, "1012") == "012101289");
assert(replace(s, 0, 5, "43210") == "4321056789");
assert(replace(s, 5, 10, "43210") == "0123443210");
assert(replace(w, 0, 0, "***"w) == "***⁰¹²³⁴⁵⁶⁷⁸⁹"w);
assert(replace(w, 10, 10, "***"w) == "⁰¹²³⁴⁵⁶⁷⁸⁹***"w);
assert(replace(w, 3, 8, "¹⁰¹²"w) == "⁰¹²¹⁰¹²⁸⁹"w);
assert(replace(w, 0, 5, "⁴³²¹⁰"w) == "⁴³²¹⁰⁵⁶⁷⁸⁹"w);
assert(replace(w, 5, 10, "⁴³²¹⁰"w) == "⁰¹²³⁴⁴³²¹⁰"w);
assert(replace(d, 0, 0, "***"d) == "***⁰¹²³⁴⁵⁶⁷⁸⁹"d);
assert(replace(d, 10, 10, "***"d) == "⁰¹²³⁴⁵⁶⁷⁸⁹***"d);
assert(replace(d, 3, 8, "¹⁰¹²"d) == "⁰¹²¹⁰¹²⁸⁹"d);
assert(replace(d, 0, 5, "⁴³²¹⁰"d) == "⁴³²¹⁰⁵⁶⁷⁸⁹"d);
assert(replace(d, 5, 10, "⁴³²¹⁰"d) == "⁰¹²³⁴⁴³²¹⁰"d);
}
/++
Replaces elements from `array` with indices ranging from `from`
(inclusive) to `to` (exclusive) with the range `stuff`. Expands or
shrinks the array as needed.
Params:
array = the _array to scan
from = the starting index
to = the ending index
stuff = the items to replace in-between `from` and `to`
+/
void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff)
if (is(typeof(replace(array, from, to, stuff))))
{
static if (isDynamicArray!Range &&
is(Unqual!(ElementEncodingType!Range) == T) &&
!isNarrowString!(T[]))
{
// optimized for homogeneous arrays that can be overwritten.
import std.algorithm.mutation : remove;
import std.typecons : tuple;
if (overlap(array, stuff).length)
{
// use slower/conservative method
array = array[0 .. from] ~ stuff ~ array[to .. $];
}
else if (stuff.length <= to - from)
{
// replacement reduces length
immutable stuffEnd = from + stuff.length;
array[from .. stuffEnd] = stuff[];
if (stuffEnd < to)
array = remove(array, tuple(stuffEnd, to));
}
else
{
// replacement increases length
// @@@TODO@@@: optimize this
immutable replaceLen = to - from;
array[from .. to] = stuff[0 .. replaceLen];
insertInPlace(array, to, stuff[replaceLen .. $]);
}
}
else
{
// default implementation, just do what replace does.
array = replace(array, from, to, stuff);
}
}
///
@safe unittest
{
int[] a = [1, 4, 5];
replaceInPlace(a, 1u, 2u, [2, 3, 4]);
assert(a == [1, 2, 3, 4, 5]);
replaceInPlace(a, 1u, 2u, cast(int[])[]);
assert(a == [1, 3, 4, 5]);
replaceInPlace(a, 1u, 3u, a[2 .. 4]);
assert(a == [1, 4, 5, 5]);
}
@safe unittest
{
// Bug# 12889
int[1][] arr = [[0], [1], [2], [3], [4], [5], [6]];
int[1][] stuff = [[0], [1]];
replaceInPlace(arr, 4, 6, stuff);
assert(arr == [[0], [1], [2], [3], [0], [1], [6]]);
}
@system unittest
{
// Bug# 14925
char[] a = "mon texte 1".dup;
char[] b = "abc".dup;
replaceInPlace(a, 4, 9, b);
assert(a == "mon abc 1");
// ensure we can replace in place with different encodings
string unicoded = "\U00010437";
string unicodedLong = "\U00010437aaaaa";
string base = "abcXXXxyz";
string result = "abc\U00010437xyz";
string resultLong = "abc\U00010437aaaaaxyz";
size_t repstart = 3;
size_t repend = 3 + 3;
void testStringReplaceInPlace(T, U)()
{
import std.algorithm.comparison : equal;
import std.conv;
auto a = unicoded.to!(U[]);
auto b = unicodedLong.to!(U[]);
auto test = base.to!(T[]);
test.replaceInPlace(repstart, repend, a);
assert(equal(test, result), "Failed for types " ~ T.stringof ~ " and " ~ U.stringof);
test = base.to!(T[]);
test.replaceInPlace(repstart, repend, b);
assert(equal(test, resultLong), "Failed for types " ~ T.stringof ~ " and " ~ U.stringof);
}
import std.meta : AliasSeq;
alias allChars = AliasSeq!(char, immutable(char), const(char),
wchar, immutable(wchar), const(wchar),
dchar, immutable(dchar), const(dchar));
foreach (T; allChars)
foreach (U; allChars)
testStringReplaceInPlace!(T, U)();
void testInout(inout(int)[] a)
{
// will be transferred to the 'replace' function
replaceInPlace(a, 1, 2, [1,2,3]);
}
}
@safe unittest
{
// the constraint for the first overload used to match this, which wouldn't compile.
import std.algorithm.comparison : equal;
long[] a = [1L, 2, 3];
int[] b = [4, 5, 6];
a.replaceInPlace(1, 2, b);
assert(equal(a, [1L, 4, 5, 6, 3]));
}
@system unittest
{
import core.exception;
import std.algorithm.comparison : equal;
import std.algorithm.iteration : filter;
import std.conv : to;
import std.exception;
bool test(T, U, V)(T orig, size_t from, size_t to, U toReplace, V result,
string file = __FILE__, size_t line = __LINE__)
{
{
static if (is(T == typeof(T.init.dup)))
auto a = orig.dup;
else
auto a = orig.idup;
a.replaceInPlace(from, to, toReplace);
if (!equal(a, result))
return false;
}
static if (isInputRange!U)
{
orig.replaceInPlace(from, to, filter!"true"(toReplace));
return equal(orig, result);
}
else
return true;
}
assert(test([1, 2, 3, 4], 0, 0, [5, 6, 7], [5, 6, 7, 1, 2, 3, 4]));
assert(test([1, 2, 3, 4], 0, 2, cast(int[])[], [3, 4]));
assert(test([1, 2, 3, 4], 0, 4, [5, 6, 7], [5, 6, 7]));
assert(test([1, 2, 3, 4], 0, 2, [5, 6, 7], [5, 6, 7, 3, 4]));
assert(test([1, 2, 3, 4], 2, 4, [5, 6, 7], [1, 2, 5, 6, 7]));
assert(test([1, 2, 3, 4], 0, 0, filter!"true"([5, 6, 7]), [5, 6, 7, 1, 2, 3, 4]));
assert(test([1, 2, 3, 4], 0, 2, filter!"true"(cast(int[])[]), [3, 4]));
assert(test([1, 2, 3, 4], 0, 4, filter!"true"([5, 6, 7]), [5, 6, 7]));
assert(test([1, 2, 3, 4], 0, 2, filter!"true"([5, 6, 7]), [5, 6, 7, 3, 4]));
assert(test([1, 2, 3, 4], 2, 4, filter!"true"([5, 6, 7]), [1, 2, 5, 6, 7]));
void testStr(T, U)(string file = __FILE__, size_t line = __LINE__)
{
auto l = to!T("hello");
auto r = to!U(" world");
enforce(test(l, 0, 0, r, " worldhello"),
new AssertError("testStr failure 1", file, line));
enforce(test(l, 0, 3, r, " worldlo"),
new AssertError("testStr failure 2", file, line));
enforce(test(l, 3, l.length, r, "hel world"),
new AssertError("testStr failure 3", file, line));
enforce(test(l, 0, l.length, r, " world"),
new AssertError("testStr failure 4", file, line));
enforce(test(l, l.length, l.length, r, "hello world"),
new AssertError("testStr failure 5", file, line));
}
testStr!(string, string)();
testStr!(string, wstring)();
testStr!(string, dstring)();
testStr!(wstring, string)();
testStr!(wstring, wstring)();
testStr!(wstring, dstring)();
testStr!(dstring, string)();
testStr!(dstring, wstring)();
testStr!(dstring, dstring)();
}
/++
Replaces the first occurrence of `from` with `to` in `subject`.
Params:
subject = the array to scan
from = the item to replace
to = the item to replace `from` with
Returns:
A new array without changing the contents of $(D subject), or the original
array if no match is found.
+/
E[] replaceFirst(E, R1, R2)(E[] subject, R1 from, R2 to)
if (isDynamicArray!(E[]) &&
isForwardRange!R1 && is(typeof(appender!(E[])().put(from[0 .. 1]))) &&
isForwardRange!R2 && is(typeof(appender!(E[])().put(to[0 .. 1]))))
{
if (from.empty) return subject;
static if (isSomeString!(E[]))
{
import std.string : indexOf;
immutable idx = subject.indexOf(from);
}
else
{
import std.algorithm.searching : countUntil;
immutable idx = subject.countUntil(from);
}
if (idx == -1)
return subject;
auto app = appender!(E[])();
app.put(subject[0 .. idx]);
app.put(to);
static if (isSomeString!(E[]) && isSomeString!R1)
{
import std.utf : codeLength;
immutable fromLength = codeLength!(Unqual!E, R1)(from);
}
else
immutable fromLength = from.length;
app.put(subject[idx + fromLength .. $]);
return app.data;
}
///
@safe unittest
{
auto a = [1, 2, 2, 3, 4, 5];
auto b = a.replaceFirst([2], [1337]);
assert(b == [1, 1337, 2, 3, 4, 5]);
auto s = "This is a foo foo list";
auto r = s.replaceFirst("foo", "silly");
assert(r == "This is a silly foo list");
}
@safe unittest
{
import std.algorithm.comparison : cmp;
import std.conv : to;
foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[],
const(char[]), immutable(char[])))
{
foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[],
const(char[]), immutable(char[])))
(){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
auto s = to!S("This is a foo foo list");
auto s2 = to!S("Thüs is a ßöö foo list");
auto from = to!T("foo");
auto from2 = to!T("ßöö");
auto into = to!T("silly");
auto into2 = to!T("sälly");
S r1 = replaceFirst(s, from, into);
assert(cmp(r1, "This is a silly foo list") == 0);
S r11 = replaceFirst(s2, from2, into2);
assert(cmp(r11, "Thüs is a sälly foo list") == 0,
to!string(r11) ~ " : " ~ S.stringof ~ " " ~ T.stringof);
S r2 = replaceFirst(r1, from, into);
assert(cmp(r2, "This is a silly silly list") == 0);
S r3 = replaceFirst(s, to!T(""), into);
assert(cmp(r3, "This is a foo foo list") == 0);
assert(replaceFirst(r3, to!T("won't find"), to!T("whatever")) is r3);
}();
}
}
//Bug# 8187
@safe unittest
{
auto res = ["a", "a"];
assert(replace(res, "a", "b") == ["b", "b"]);
assert(replaceFirst(res, "a", "b") == ["b", "a"]);
}
/++
Replaces the last occurrence of `from` with `to` in `subject`.
Params:
subject = the array to scan
from = the item to replace
to = the item to replace `from` with
Returns:
A new array without changing the contents of $(D subject), or the original
array if no match is found.
+/
E[] replaceLast(E, R1, R2)(E[] subject, R1 from , R2 to)
if (isDynamicArray!(E[]) &&
isForwardRange!R1 && is(typeof(appender!(E[])().put(from[0 .. 1]))) &&
isForwardRange!R2 && is(typeof(appender!(E[])().put(to[0 .. 1]))))
{
import std.range : retro;
if (from.empty) return subject;
static if (isSomeString!(E[]))
{
import std.string : lastIndexOf;
auto idx = subject.lastIndexOf(from);
}
else
{
import std.algorithm.searching : countUntil;
auto idx = retro(subject).countUntil(retro(from));
}
if (idx == -1)
return subject;
static if (isSomeString!(E[]) && isSomeString!R1)
{
import std.utf : codeLength;
auto fromLength = codeLength!(Unqual!E, R1)(from);
}
else
auto fromLength = from.length;
auto app = appender!(E[])();
static if (isSomeString!(E[]))
app.put(subject[0 .. idx]);
else
app.put(subject[0 .. $ - idx - fromLength]);
app.put(to);
static if (isSomeString!(E[]))
app.put(subject[idx+fromLength .. $]);
else
app.put(subject[$ - idx .. $]);
return app.data;
}
///
@safe unittest
{
auto a = [1, 2, 2, 3, 4, 5];
auto b = a.replaceLast([2], [1337]);
assert(b == [1, 2, 1337, 3, 4, 5]);
auto s = "This is a foo foo list";
auto r = s.replaceLast("foo", "silly");
assert(r == "This is a foo silly list", r);
}
@safe unittest
{
import std.algorithm.comparison : cmp;
import std.conv : to;
foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[],
const(char[]), immutable(char[])))
{
foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[],
const(char[]), immutable(char[])))
(){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
auto s = to!S("This is a foo foo list");
auto s2 = to!S("Thüs is a ßöö ßöö list");
auto from = to!T("foo");
auto from2 = to!T("ßöö");
auto into = to!T("silly");
auto into2 = to!T("sälly");
S r1 = replaceLast(s, from, into);
assert(cmp(r1, "This is a foo silly list") == 0, to!string(r1));
S r11 = replaceLast(s2, from2, into2);
assert(cmp(r11, "Thüs is a ßöö sälly list") == 0,
to!string(r11) ~ " : " ~ S.stringof ~ " " ~ T.stringof);
S r2 = replaceLast(r1, from, into);
assert(cmp(r2, "This is a silly silly list") == 0);
S r3 = replaceLast(s, to!T(""), into);
assert(cmp(r3, "This is a foo foo list") == 0);
assert(replaceLast(r3, to!T("won't find"), to!T("whatever")) is r3);
}();
}
}
/++
Creates a new array such that the items in `slice` are replaced with the
items in `replacement`. `slice` and `replacement` do not need to be the
same length. The result will grow or shrink based on the items given.
Params:
s = the base of the new array
slice = the slice of `s` to be replaced
replacement = the items to replace `slice` with
Returns:
A new array that is `s` with `slice` replaced by
`replacement[]`.
+/
inout(T)[] replaceSlice(T)(inout(T)[] s, in T[] slice, in T[] replacement)
in
{
// Verify that slice[] really is a slice of s[]
assert(overlap(s, slice) is slice);
}
body
{
auto result = new T[s.length - slice.length + replacement.length];
immutable so = slice.ptr - s.ptr;
result[0 .. so] = s[0 .. so];
result[so .. so + replacement.length] = replacement[];
result[so + replacement.length .. result.length] =
s[so + slice.length .. s.length];
return cast(inout(T)[]) result;
}
///
@system unittest
{
auto a = [1, 2, 3, 4, 5];
auto b = replaceSlice(a, a[1 .. 4], [0, 0, 0]);
assert(b == [1, 0, 0, 0, 5]);
}
@system unittest
{
import std.algorithm.comparison : cmp;
string s = "hello";
string slice = s[2 .. 4];
auto r = replaceSlice(s, slice, "bar");
int i;
i = cmp(r, "hebaro");
assert(i == 0);
}
/**
Implements an output range that appends data to an array. This is
recommended over $(D array ~= data) when appending many elements because it is more
efficient. `Appender` maintains its own array metadata locally, so it can avoid
global locking for each append where $(LREF capacity) is non-zero.
See_Also: $(LREF appender)
*/
struct Appender(A)
if (isDynamicArray!A)
{
import core.memory : GC;
private alias T = ElementEncodingType!A;
private struct Data
{
size_t capacity;
Unqual!T[] arr;
bool canExtend = false;
}
private Data* _data;
/**
* Constructs an `Appender` with a given array. Note that this does not copy the
* data. If the array has a larger capacity as determined by `arr.capacity`,
* it will be used by the appender. After initializing an appender on an array,
* appending to the original array will reallocate.
*/
this(A arr) @trusted pure nothrow
{
// initialize to a given array.
_data = new Data;
_data.arr = cast(Unqual!T[]) arr; //trusted
if (__ctfe)
return;
// We want to use up as much of the block the array is in as possible.
// if we consume all the block that we can, then array appending is
// safe WRT built-in append, and we can use the entire block.
// We only do this for mutable types that can be extended.
static if (isMutable!T && is(typeof(arr.length = size_t.max)))
{
immutable cap = arr.capacity; //trusted
// Replace with "GC.setAttr( Not Appendable )" once pure (and fixed)
if (cap > arr.length)
arr.length = cap;
}
_data.capacity = arr.length;
}
/**
* Reserve at least newCapacity elements for appending. Note that more elements
* may be reserved than requested. If `newCapacity <= capacity`, then nothing is
* done.
*/
void reserve(size_t newCapacity) @safe pure nothrow
{
if (_data)
{
if (newCapacity > _data.capacity)
ensureAddable(newCapacity - _data.arr.length);
}
else
{
ensureAddable(newCapacity);
}
}
/**
* Returns: the capacity of the array (the maximum number of elements the
* managed array can accommodate before triggering a reallocation). If any
* appending will reallocate, `0` will be returned.
*/
@property size_t capacity() const @safe pure nothrow
{
return _data ? _data.capacity : 0;
}
/**
* Use opSlice() from now on.
* Returns: The managed array.
*/
@property inout(ElementEncodingType!A)[] data() inout @trusted pure nothrow
{
return this[];
}
/**
* Returns: The managed array.
*/
@property inout(ElementEncodingType!A)[] opSlice() inout @trusted pure nothrow
{
/* @trusted operation:
* casting Unqual!T[] to inout(T)[]
*/
return cast(typeof(return))(_data ? _data.arr : null);
}
// ensure we can add nelems elements, resizing as necessary
private void ensureAddable(size_t nelems) @trusted pure nothrow
{
if (!_data)
_data = new Data;
immutable len = _data.arr.length;
immutable reqlen = len + nelems;
if (_data.capacity >= reqlen)
return;
// need to increase capacity
if (__ctfe)
{
static if (__traits(compiles, new Unqual!T[1]))
{
_data.arr.length = reqlen;
}
else
{
// avoid restriction of @disable this()
_data.arr = _data.arr[0 .. _data.capacity];
foreach (i; _data.capacity .. reqlen)
_data.arr ~= Unqual!T.init;
}
_data.arr = _data.arr[0 .. len];
_data.capacity = reqlen;
}
else
{
// Time to reallocate.
// We need to almost duplicate what's in druntime, except we
// have better access to the capacity field.
auto newlen = appenderNewCapacity!(T.sizeof)(_data.capacity, reqlen);
// first, try extending the current block
if (_data.canExtend)
{
immutable u = GC.extend(_data.arr.ptr, nelems * T.sizeof, (newlen - len) * T.sizeof);
if (u)
{
// extend worked, update the capacity
_data.capacity = u / T.sizeof;
return;
}
}
// didn't work, must reallocate
import core.checkedint : mulu;
bool overflow;
const nbytes = mulu(newlen, T.sizeof, overflow);
if (overflow) assert(0);
auto bi = GC.qalloc(nbytes, blockAttribute!T);
_data.capacity = bi.size / T.sizeof;
import core.stdc.string : memcpy;
if (len)
memcpy(bi.base, _data.arr.ptr, len * T.sizeof);
_data.arr = (cast(Unqual!T*) bi.base)[0 .. len];
_data.canExtend = true;
// leave the old data, for safety reasons
}
}
private template canPutItem(U)
{
enum bool canPutItem =
isImplicitlyConvertible!(U, T) ||
isSomeChar!T && isSomeChar!U;
}
private template canPutConstRange(Range)
{
enum bool canPutConstRange =
isInputRange!(Unqual!Range) &&
!isInputRange!Range &&
is(typeof(Appender.init.put(Range.init.front)));
}
private template canPutRange(Range)
{
enum bool canPutRange =
isInputRange!Range &&
is(typeof(Appender.init.put(Range.init.front)));
}
/**
* Appends `item` to the managed array.
*/
void put(U)(U item) if (canPutItem!U)
{
static if (isSomeChar!T && isSomeChar!U && T.sizeof < U.sizeof)
{
/* may throwable operation:
* - std.utf.encode
*/
// must do some transcoding around here
import std.utf : encode;
Unqual!T[T.sizeof == 1 ? 4 : 2] encoded;
auto len = encode(encoded, item);
put(encoded[0 .. len]);
}
else
{
import std.conv : emplaceRef;
ensureAddable(1);
immutable len = _data.arr.length;
auto bigData = (() @trusted => _data.arr.ptr[0 .. len + 1])();
emplaceRef!(Unqual!T)(bigData[len], cast(Unqual!T) item);
//We do this at the end, in case of exceptions
_data.arr = bigData;
}
}
// Const fixing hack.
void put(Range)(Range items) if (canPutConstRange!Range)
{
alias p = put!(Unqual!Range);
p(items);
}
/**
* Appends an entire range to the managed array.
*/
void put(Range)(Range items) if (canPutRange!Range)
{
// note, we disable this branch for appending one type of char to
// another because we can't trust the length portion.
static if (!(isSomeChar!T && isSomeChar!(ElementType!Range) &&
!is(immutable Range == immutable T[])) &&
is(typeof(items.length) == size_t))
{
// optimization -- if this type is something other than a string,
// and we are adding exactly one element, call the version for one
// element.
static if (!isSomeChar!T)
{
if (items.length == 1)
{
put(items.front);
return;
}
}
// make sure we have enough space, then add the items
@trusted auto bigDataFun(size_t extra)
{
ensureAddable(extra);
return _data.arr.ptr[0 .. _data.arr.length + extra];
}
auto bigData = bigDataFun(items.length);
immutable len = _data.arr.length;
immutable newlen = bigData.length;
alias UT = Unqual!T;
static if (is(typeof(_data.arr[] = items[])) &&
!hasElaborateAssign!UT && isAssignable!(UT, ElementEncodingType!Range))
{
bigData[len .. newlen] = items[];
}
else
{
import std.conv : emplaceRef;
foreach (ref it ; bigData[len .. newlen])
{
emplaceRef!T(it, items.front);
items.popFront();
}
}
//We do this at the end, in case of exceptions
_data.arr = bigData;
}
else
{
//pragma(msg, Range.stringof);
// Generic input range
for (; !items.empty; items.popFront())
{
put(items.front);
}
}
}
/**
* Appends `rhs` to the managed array.
* Params:
* rhs = Element or range.
*/
void opOpAssign(string op : "~", U)(U rhs)
if (__traits(compiles, put(rhs)))
{
put(rhs);
}
// only allow overwriting data on non-immutable and non-const data
static if (isMutable!T)
{
/**
* Clears the managed array. This allows the elements of the array to be reused
* for appending.
*
* Note: clear is disabled for immutable or const element types, due to the
* possibility that $(D Appender) might overwrite immutable data.
*/
void clear() @trusted pure nothrow
{
if (_data)
{
_data.arr = _data.arr.ptr[0 .. 0];
}
}
/**
* Shrinks the managed array to the given length.
*
* Throws: $(D Exception) if newlength is greater than the current array length.
* Note: shrinkTo is disabled for immutable or const element types.
*/
void shrinkTo(size_t newlength) @trusted pure
{
import std.exception : enforce;
if (_data)
{
enforce(newlength <= _data.arr.length, "Attempting to shrink Appender with newlength > length");
_data.arr = _data.arr.ptr[0 .. newlength];
}
else
enforce(newlength == 0, "Attempting to shrink empty Appender with non-zero newlength");
}
}
void toString(Writer)(scope Writer w)
{
import std.format : formattedWrite;
w.formattedWrite(typeof(this).stringof ~ "(%s)", data);
}
}
///
@safe unittest
{
auto app = appender!string();
string b = "abcdefg";
foreach (char c; b)
app.put(c);
assert(app[] == "abcdefg");
int[] a = [ 1, 2 ];
auto app2 = appender(a);
app2.put(3);
app2.put([ 4, 5, 6 ]);
assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]);
}
@safe unittest
{
import std.format : format;
auto app = appender!(int[])();
app.put(1);
app.put(2);
app.put(3);
assert("%s".format(app) == "Appender!(int[])(%s)".format([1,2,3]));
}
@safe unittest // issue 17251
{
static struct R
{
int front() const { return 0; }
bool empty() const { return true; }
void popFront() {}
}
auto app = appender!(R[]);
const(R)[1] r;
app.put(r[0]);
app.put(r[]);
}
//Calculates an efficient growth scheme based on the old capacity
//of data, and the minimum requested capacity.
//arg curLen: The current length
//arg reqLen: The length as requested by the user
//ret sugLen: A suggested growth.
private size_t appenderNewCapacity(size_t TSizeOf)(size_t curLen, size_t reqLen) @safe pure nothrow
{
import core.bitop : bsr;
import std.algorithm.comparison : max;
if (curLen == 0)
return max(reqLen,8);
ulong mult = 100 + (1000UL) / (bsr(curLen * TSizeOf) + 1);
// limit to doubling the length, we don't want to grow too much
if (mult > 200)
mult = 200;
auto sugLen = cast(size_t)((curLen * mult + 99) / 100);
return max(reqLen, sugLen);
}
/**
* A version of $(LREF Appender) that can update an array in-place.
* It forwards all calls to an underlying appender implementation.
* Any calls made to the appender also update the pointer to the
* original array passed in.
*
* Tip: Use the `arrayPtr` overload of $(LREF appender) for construction with type-inference.
*/
struct RefAppender(A)
if (isDynamicArray!A)
{
private
{
Appender!A impl;
A* arr;
}
/**
* Constructs a `RefAppender` with a given array reference. This does not copy the
* data. If the array has a larger capacity as determined by `arr.capacity`, it
* will be used by the appender.
*
* Note: Do not use built-in appending (i.e. `~=`) on the original array
* until you are done with the appender, because subsequent calls to the appender
* will reallocate the array data without those appends.
*
* Params:
* arr = Pointer to an array. Must not be _null.
*/
this(A* arr)
{
impl = Appender!A(*arr);
this.arr = arr;
}
/** Wraps remaining `Appender` methods such as $(LREF put).
* Params:
* fn = Method name to call.
* args = Arguments to pass to the method.
*/
void opDispatch(string fn, Args...)(Args args)
if (__traits(compiles, (Appender!A a) => mixin("a." ~ fn ~ "(args)")))
{
// we do it this way because we can't cache a void return
scope(exit) *this.arr = impl[];
mixin("return impl." ~ fn ~ "(args);");
}
/**
* Appends `rhs` to the managed array.
* Params:
* rhs = Element or range.
*/
void opOpAssign(string op : "~", U)(U rhs)
if (__traits(compiles, (Appender!A a){ a.put(rhs); }))
{
scope(exit) *this.arr = impl[];
impl.put(rhs);
}
/**
* Returns the capacity of the array (the maximum number of elements the
* managed array can accommodate before triggering a reallocation). If any
* appending will reallocate, $(D capacity) returns $(D 0).
*/
@property size_t capacity() const
{
return impl.capacity;
}
/* Use opSlice() instead.
* Returns: the managed array.
*/
@property inout(ElementEncodingType!A)[] data() inout
{
return impl[];
}
/**
* Returns: the managed array.
*/
@property inout(ElementEncodingType!A)[] opSlice() inout
{
return impl[];
}
}
///
@system pure nothrow
unittest
{
int[] a = [1, 2];
auto app2 = appender(&a);
assert(app2[] == [1, 2]);
assert(a == [1, 2]);
app2 ~= 3;
app2 ~= [4, 5, 6];
assert(app2[] == [1, 2, 3, 4, 5, 6]);
assert(a == [1, 2, 3, 4, 5, 6]);
app2.reserve(5);
assert(app2.capacity >= 5);
}
/++
Convenience function that returns an $(LREF Appender) instance,
optionally initialized with $(D array).
+/
Appender!A appender(A)()
if (isDynamicArray!A)
{
return Appender!A(null);
}
/// ditto
Appender!(E[]) appender(A : E[], E)(auto ref A array)
{
static assert(!isStaticArray!A || __traits(isRef, array),
"Cannot create Appender from an rvalue static array");
return Appender!(E[])(array);
}
@safe pure nothrow unittest
{
import std.exception;
{
auto app = appender!(char[])();
string b = "abcdefg";
foreach (char c; b) app.put(c);
assert(app[] == "abcdefg");
}
{
auto app = appender!(char[])();
string b = "abcdefg";
foreach (char c; b) app ~= c;
assert(app[] == "abcdefg");
}
{
int[] a = [ 1, 2 ];
auto app2 = appender(a);
assert(app2[] == [ 1, 2 ]);
app2.put(3);
app2.put([ 4, 5, 6 ][]);
assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]);
app2.put([ 7 ]);
assert(app2[] == [ 1, 2, 3, 4, 5, 6, 7 ]);
}
int[] a = [ 1, 2 ];
auto app2 = appender(a);
assert(app2[] == [ 1, 2 ]);
app2 ~= 3;
app2 ~= [ 4, 5, 6 ][];
assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]);
app2 ~= [ 7 ];
assert(app2[] == [ 1, 2, 3, 4, 5, 6, 7 ]);
app2.reserve(5);
assert(app2.capacity >= 5);
try // shrinkTo may throw
{
app2.shrinkTo(3);
}
catch (Exception) assert(0);
assert(app2[] == [ 1, 2, 3 ]);
assertThrown(app2.shrinkTo(5));
const app3 = app2;
assert(app3.capacity >= 3);
assert(app3[] == [1, 2, 3]);
auto app4 = appender([]);
try // shrinkTo may throw
{
app4.shrinkTo(0);
}
catch (Exception) assert(0);
// Issue 5663 & 9725 tests
foreach (S; AliasSeq!(char[], const(char)[], string))
{
{
Appender!S app5663i;
assertNotThrown(app5663i.put("\xE3"));
assert(app5663i[] == "\xE3");
Appender!S app5663c;
assertNotThrown(app5663c.put(cast(const(char)[])"\xE3"));
assert(app5663c[] == "\xE3");
Appender!S app5663m;
assertNotThrown(app5663m.put("\xE3".dup));
assert(app5663m[] == "\xE3");
}
// ditto for ~=
{
Appender!S app5663i;
assertNotThrown(app5663i ~= "\xE3");
assert(app5663i[] == "\xE3");
Appender!S app5663c;
assertNotThrown(app5663c ~= cast(const(char)[])"\xE3");
assert(app5663c[] == "\xE3");
Appender!S app5663m;
assertNotThrown(app5663m ~= "\xE3".dup);
assert(app5663m[] == "\xE3");
}
}
static struct S10122
{
int val;
@disable this();
this(int v) @safe pure nothrow { val = v; }
}
assertCTFEable!(
{
auto w = appender!(S10122[])();
w.put(S10122(1));
assert(w[].length == 1 && w[][0].val == 1);
});
}
///
@safe pure nothrow
unittest
{
auto w = appender!string;
// pre-allocate space for at least 10 elements (this avoids costly reallocations)
w.reserve(10);
assert(w.capacity >= 10);
w.put('a'); // single elements
w.put("bc"); // multiple elements
// use the append syntax
w ~= 'd';
w ~= "ef";
assert(w[] == "abcdef");
}
@safe pure nothrow unittest
{
{
auto w = appender!string();
w.reserve(4);
cast(void) w.capacity;
cast(void) w[];
try
{
wchar wc = 'a';
dchar dc = 'a';
w.put(wc); // decoding may throw
w.put(dc); // decoding may throw
}
catch (Exception) assert(0);
}
{
auto w = appender!(int[])();
w.reserve(4);
cast(void) w.capacity;
cast(void) w[];
w.put(10);
w.put([10]);
w.clear();
try
{
w.shrinkTo(0);
}
catch (Exception) assert(0);
struct N
{
int payload;
alias payload this;
}
w.put(N(1));
w.put([N(2)]);
struct S(T)
{
@property bool empty() { return true; }
@property T front() { return T.init; }
void popFront() {}
}
S!int r;
w.put(r);
}
}
@safe unittest
{
import std.algorithm;
import std.typecons;
//10690
[tuple(1)].filter!(t => true).array; // No error
[tuple("A")].filter!(t => true).array; // error
}
@system unittest
{
import std.range;
//Coverage for put(Range)
struct S1
{
}
struct S2
{
void opAssign(S2){}
}
auto a1 = Appender!(S1[])();
auto a2 = Appender!(S2[])();
auto au1 = Appender!(const(S1)[])();
auto au2 = Appender!(const(S2)[])();
a1.put(S1().repeat().take(10));
a2.put(S2().repeat().take(10));
auto sc1 = const(S1)();
auto sc2 = const(S2)();
au1.put(sc1.repeat().take(10));
au2.put(sc2.repeat().take(10));
}
@system unittest
{
struct S
{
int* p;
}
auto a0 = Appender!(S[])();
auto a1 = Appender!(const(S)[])();
auto a2 = Appender!(immutable(S)[])();
auto s0 = S(null);
auto s1 = const(S)(null);
auto s2 = immutable(S)(null);
a1.put(s0);
a1.put(s1);
a1.put(s2);
a1.put([s0]);
a1.put([s1]);
a1.put([s2]);
a0.put(s0);
static assert(!is(typeof(a0.put(a1))));
static assert(!is(typeof(a0.put(a2))));
a0.put([s0]);
static assert(!is(typeof(a0.put([a1]))));
static assert(!is(typeof(a0.put([a2]))));
static assert(!is(typeof(a2.put(a0))));
static assert(!is(typeof(a2.put(a1))));
a2.put(s2);
static assert(!is(typeof(a2.put([a0]))));
static assert(!is(typeof(a2.put([a1]))));
a2.put([s2]);
}
@safe unittest
{
//9528
const(E)[] fastCopy(E)(E[] src) {
auto app = appender!(const(E)[])();
foreach (i, e; src)
app.put(e);
return app[];
}
class C {}
struct S { const(C) c; }
S[] s = [ S(new C) ];
auto t = fastCopy(s); // Does not compile
}
@safe unittest
{
import std.algorithm.iteration : map;
//10753
struct Foo {
immutable dchar d;
}
struct Bar {
immutable int x;
}
"12".map!Foo.array;
[1, 2].map!Bar.array;
}
@safe unittest
{
import std.algorithm.comparison : equal;
//New appender signature tests
alias mutARR = int[];
alias conARR = const(int)[];
alias immARR = immutable(int)[];
mutARR mut;
conARR con;
immARR imm;
auto app1 = Appender!mutARR(mut); //Always worked. Should work. Should not create a warning.
app1.put(7);
assert(equal(app1[], [7]));
static assert(!is(typeof(Appender!mutARR(con)))); //Never worked. Should not work.
static assert(!is(typeof(Appender!mutARR(imm)))); //Never worked. Should not work.
auto app2 = Appender!conARR(mut); //Always worked. Should work. Should not create a warning.
app2.put(7);
assert(equal(app2[], [7]));
auto app3 = Appender!conARR(con); //Didn't work. Now works. Should not create a warning.
app3.put(7);
assert(equal(app3[], [7]));
auto app4 = Appender!conARR(imm); //Didn't work. Now works. Should not create a warning.
app4.put(7);
assert(equal(app4[], [7]));
//{auto app = Appender!immARR(mut);} //Worked. Will cease to work. Creates warning.
//static assert(!is(typeof(Appender!immARR(mut)))); //Worked. Will cease to work. Uncomment me after full deprecation.
static assert(!is(typeof(Appender!immARR(con)))); //Never worked. Should not work.
auto app5 = Appender!immARR(imm); //Didn't work. Now works. Should not create a warning.
app5.put(7);
assert(equal(app5[], [7]));
//Deprecated. Please uncomment and make sure this doesn't work:
//char[] cc;
//static assert(!is(typeof(Appender!string(cc))));
//This should always work:
auto app6 = appender!string(null);
assert(app6[] == null);
auto app7 = appender!(const(char)[])(null);
assert(app7[] == null);
auto app8 = appender!(char[])(null);
assert(app8[] == null);
}
@safe unittest //Test large allocations (for GC.extend)
{
import std.algorithm.comparison : equal;
import std.range;
Appender!(char[]) app;
app.reserve(1); //cover reserve on non-initialized
foreach (_; 0 .. 100_000)
app.put('a');
assert(equal(app[], 'a'.repeat(100_000)));
}
@safe unittest
{
auto reference = new ubyte[](2048 + 1); //a number big enough to have a full page (EG: the GC extends)
auto arr = reference.dup;
auto app = appender(arr[0 .. 0]);
app.reserve(1); //This should not trigger a call to extend
app.put(ubyte(1)); //Don't clobber arr
assert(reference[] == arr[]);
}
@safe unittest // clear method is supported only for mutable element types
{
Appender!string app;
app.put("foo");
static assert(!__traits(compiles, app.clear()));
assert(app[] == "foo");
}
@safe unittest
{
static struct D//dynamic
{
int[] i;
alias i this;
}
static struct S//static
{
int[5] i;
alias i this;
}
static assert(!is(Appender!(char[5])));
static assert(!is(Appender!D));
static assert(!is(Appender!S));
enum int[5] a = [];
int[5] b;
D d;
S s;
int[5] foo(){return a;}
static assert(!is(typeof(appender(a))));
static assert( is(typeof(appender(b))));
static assert( is(typeof(appender(d))));
static assert( is(typeof(appender(s))));
static assert(!is(typeof(appender(foo()))));
}
@system unittest
{
// Issue 13077
static class A {}
// reduced case
auto w = appender!(shared(A)[])();
w.put(new shared A());
// original case
import std.range;
InputRange!(shared A) foo()
{
return [new shared A].inputRangeObject;
}
auto res = foo.array;
}
/++
Convenience function that returns a $(LREF RefAppender) instance initialized
with `arrayPtr`. Don't use null for the array pointer, use the other
version of $(D appender) instead.
+/
RefAppender!(E[]) appender(P : E[]*, E)(P arrayPtr)
{
return RefAppender!(E[])(arrayPtr);
}
///
@system pure nothrow
unittest
{
int[] a = [1, 2];
auto app2 = appender(&a);
assert(app2[] == [1, 2]);
assert(a == [1, 2]);
app2 ~= 3;
app2 ~= [4, 5, 6];
assert(app2[] == [1, 2, 3, 4, 5, 6]);
assert(a == [1, 2, 3, 4, 5, 6]);
app2.reserve(5);
assert(app2.capacity >= 5);
}
@system unittest
{
import std.exception;
{
auto arr = new char[0];
auto app = appender(&arr);
string b = "abcdefg";
foreach (char c; b) app.put(c);
assert(app[] == "abcdefg");
assert(arr == "abcdefg");
}
{
auto arr = new char[0];
auto app = appender(&arr);
string b = "abcdefg";
foreach (char c; b) app ~= c;
assert(app[] == "abcdefg");
assert(arr == "abcdefg");
}
{
int[] a = [ 1, 2 ];
auto app2 = appender(&a);
assert(app2[] == [ 1, 2 ]);
assert(a == [ 1, 2 ]);
app2.put(3);
app2.put([ 4, 5, 6 ][]);
assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]);
assert(a == [ 1, 2, 3, 4, 5, 6 ]);
}
int[] a = [ 1, 2 ];
auto app2 = appender(&a);
assert(app2[] == [ 1, 2 ]);
assert(a == [ 1, 2 ]);
app2 ~= 3;
app2 ~= [ 4, 5, 6 ][];
assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]);
assert(a == [ 1, 2, 3, 4, 5, 6 ]);
app2.reserve(5);
assert(app2.capacity >= 5);
try // shrinkTo may throw
{
app2.shrinkTo(3);
}
catch (Exception) assert(0);
assert(app2[] == [ 1, 2, 3 ]);
assertThrown(app2.shrinkTo(5));
const app3 = app2;
assert(app3.capacity >= 3);
assert(app3[] == [1, 2, 3]);
}
@safe unittest // issue 14605
{
static assert(isOutputRange!(Appender!(int[]), int));
static assert(isOutputRange!(RefAppender!(int[]), int));
}
@safe unittest
{
Appender!(int[]) app;
short[] range = [1, 2, 3];
app.put(range);
assert(app[] == [1, 2, 3]);
}
@safe unittest
{
string s = "hello".idup;
char[] a = "hello".dup;
auto appS = appender(s);
auto appA = appender(a);
put(appS, 'w');
put(appA, 'w');
s ~= 'a'; //Clobbers here?
a ~= 'a'; //Clobbers here?
assert(appS[] == "hellow");
assert(appA[] == "hellow");
}