mirror of
https://github.com/dlang/phobos.git
synced 2025-04-26 13:10:35 +03:00
5072 lines
143 KiB
D
5072 lines
143 KiB
D
// 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;)
|
||
$(DIVC quickindex,
|
||
$(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 `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 staticArray))
|
||
$(TD Creates a new static array from given data.
|
||
))
|
||
$(TR $(TD $(LREF uninitializedArray))
|
||
$(TD Returns a new array of type `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
|
||
$(HTTP jmdavisprog.com, 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 `r`.
|
||
*
|
||
* Narrow strings are handled as follows:
|
||
* - If autodecoding is turned on (default), then they are handled as a separate overload.
|
||
* - If autodecoding is turned off, then this is equivalent to duplicating the array.
|
||
*
|
||
* Params:
|
||
* r = range (or aggregate with `opApply` function) whose elements are copied into the allocated array
|
||
* Returns:
|
||
* allocated and initialized array
|
||
*/
|
||
ForeachType!Range[] array(Range)(Range r)
|
||
if (isIterable!Range && !isAutodecodableString!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)
|
||
{
|
||
const length = r.length;
|
||
if (length == 0)
|
||
return null;
|
||
|
||
import core.internal.lifetime : emplaceRef;
|
||
|
||
auto result = (() @trusted => uninitializedArray!(Unqual!E[])(length))();
|
||
|
||
// Every element of the uninitialized array must be initialized
|
||
size_t cnt; //Number of elements that have been initialized
|
||
try
|
||
{
|
||
foreach (e; r)
|
||
{
|
||
emplaceRef!E(result[cnt], e);
|
||
++cnt;
|
||
}
|
||
} catch (Exception e)
|
||
{
|
||
//https://issues.dlang.org/show_bug.cgi?id=22185
|
||
//Make any uninitialized elements safely destructible.
|
||
foreach (ref elem; result[cnt..$])
|
||
{
|
||
import core.internal.lifetime : emplaceInitializer;
|
||
emplaceInitializer(elem);
|
||
}
|
||
throw e;
|
||
}
|
||
/*
|
||
https://issues.dlang.org/show_bug.cgi?id=22673
|
||
|
||
We preallocated an array, we should ensure that enough range elements
|
||
were gathered such that every slot in the array is filled. If not, the GC
|
||
will collect the allocated array, leading to the `length - cnt` left over elements
|
||
being collected too - despite their contents having no guarantee of destructibility.
|
||
*/
|
||
assert(length == cnt,
|
||
"Range .length property was not equal to the length yielded by the range before becoming empty");
|
||
return (() @trusted => cast(E[]) result)();
|
||
}
|
||
else
|
||
{
|
||
auto a = appender!(E[])();
|
||
foreach (e; r)
|
||
{
|
||
a.put(e);
|
||
}
|
||
return a.data;
|
||
}
|
||
}
|
||
|
||
/// ditto
|
||
ForeachType!(typeof((*Range).init))[] array(Range)(Range r)
|
||
if (is(Range == U*, U) && isIterable!U && !isAutodecodableString!Range && !isInfinite!Range)
|
||
{
|
||
return array(*r);
|
||
}
|
||
|
||
///
|
||
@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)]));
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
struct MyRange
|
||
{
|
||
enum front = 123;
|
||
enum empty = true;
|
||
void popFront() {}
|
||
}
|
||
|
||
auto arr = (new MyRange).array;
|
||
assert(arr.empty);
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
immutable int[] a = [1, 2, 3, 4];
|
||
auto b = (&a).array;
|
||
assert(b == a);
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
import std.algorithm.comparison : equal;
|
||
struct Foo
|
||
{
|
||
int a;
|
||
noreturn opAssign(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)]));
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=12315
|
||
@safe pure nothrow unittest
|
||
{
|
||
static struct Bug12315 { immutable int i; }
|
||
enum bug12315 = [Bug12315(123456789)].array();
|
||
static assert(bug12315[0].i == 123456789);
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
import std.range;
|
||
static struct S{int* p;}
|
||
auto a = array(immutable(S).init.repeat(5));
|
||
assert(a.length == 5);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=18995
|
||
@system unittest
|
||
{
|
||
import core.memory : __delete;
|
||
int nAlive = 0;
|
||
struct S
|
||
{
|
||
bool alive;
|
||
this(int) { alive = true; ++nAlive; }
|
||
this(this) { nAlive += alive; }
|
||
~this() { nAlive -= alive; alive = false; }
|
||
}
|
||
|
||
import std.algorithm.iteration : map;
|
||
import std.range : iota;
|
||
|
||
auto arr = iota(3).map!(a => S(a)).array;
|
||
assert(nAlive == 3);
|
||
|
||
// No good way to ensure the GC frees this, just call the lifetime function
|
||
// directly.
|
||
__delete(arr);
|
||
|
||
assert(nAlive == 0);
|
||
}
|
||
|
||
@safe pure nothrow @nogc unittest
|
||
{
|
||
//Turn down infinity:
|
||
static assert(!is(typeof(
|
||
repeat(1).array()
|
||
)));
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=20937
|
||
@safe pure nothrow unittest
|
||
{
|
||
struct S {int* x;}
|
||
struct R
|
||
{
|
||
immutable(S) front;
|
||
bool empty;
|
||
@safe pure nothrow void popFront(){empty = true;}
|
||
}
|
||
R().array;
|
||
}
|
||
|
||
// Test that `array(scope InputRange r)` returns a non-scope array
|
||
// https://issues.dlang.org/show_bug.cgi?id=23300
|
||
@safe pure nothrow unittest
|
||
{
|
||
@safe int[] fun()
|
||
{
|
||
import std.algorithm.iteration : map;
|
||
int[3] arr = [1, 2, 3];
|
||
scope r = arr[].map!(x => x + 3);
|
||
return r.array;
|
||
}
|
||
}
|
||
|
||
/**
|
||
Convert a narrow autodecoding string to an array type that fully supports
|
||
random access. This is handled as a special case and always returns an array
|
||
of `dchar`
|
||
|
||
NOTE: This function is never used when autodecoding is turned off.
|
||
|
||
Params:
|
||
str = `isNarrowString` to be converted to an array of `dchar`
|
||
Returns:
|
||
a `dchar[]`, `const(dchar)[]`, or `immutable(dchar)[]` depending on the constness of
|
||
the input.
|
||
*/
|
||
CopyTypeQualifiers!(ElementType!String,dchar)[] array(String)(scope String str)
|
||
if (isAutodecodableString!String)
|
||
{
|
||
import std.utf : toUTF32;
|
||
auto temp = str.toUTF32;
|
||
/* Unsafe cast. Allowed because toUTF32 makes a new array
|
||
and copies all the elements.
|
||
*/
|
||
return () @trusted { return cast(CopyTypeQualifiers!(ElementType!String, dchar)[]) temp; } ();
|
||
}
|
||
|
||
///
|
||
@safe pure nothrow unittest
|
||
{
|
||
import std.range.primitives : isRandomAccessRange;
|
||
import std.traits : isAutodecodableString;
|
||
|
||
// note that if autodecoding is turned off, `array` will not transcode these.
|
||
static if (isAutodecodableString!string)
|
||
assert("Hello D".array == "Hello D"d);
|
||
else
|
||
assert("Hello D".array == "Hello D");
|
||
|
||
static if (isAutodecodableString!wstring)
|
||
assert("Hello D"w.array == "Hello D"d);
|
||
else
|
||
assert("Hello D"w.array == "Hello D"w);
|
||
|
||
static assert(isRandomAccessRange!dstring == true);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
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) @safe dg)
|
||
{
|
||
int res;
|
||
foreach (i; 0 .. 10)
|
||
{
|
||
res = dg(i);
|
||
if (res) break;
|
||
}
|
||
|
||
return res;
|
||
}
|
||
}
|
||
|
||
auto a = array([1, 2, 3, 4, 5][]);
|
||
assert(a == [ 1, 2, 3, 4, 5 ]);
|
||
|
||
auto b = array([TestArray(1), TestArray(2)][]);
|
||
assert(b == [TestArray(1), TestArray(2)]);
|
||
|
||
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)][]);
|
||
assert(c[0].x == 1);
|
||
assert(c[1].x == 2);
|
||
|
||
auto d = array([1.0, 2.2, 3][]);
|
||
assert(is(typeof(d) == double[]));
|
||
assert(d == [1.0, 2.2, 3]);
|
||
|
||
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]);
|
||
static if (isAutodecodableString!string)
|
||
{
|
||
assert(array("ABC") == "ABC"d);
|
||
assert(array("ABC".dup) == "ABC"d.dup);
|
||
}
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=8233
|
||
@safe pure nothrow 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;
|
||
}
|
||
|
||
static foreach (T; AliasSeq!(S, const S, immutable S))
|
||
{{
|
||
auto arr = [T(1), T(2), T(3), T(4)];
|
||
assert(array(arr) == arr);
|
||
}}
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=9824
|
||
@safe pure nothrow unittest
|
||
{
|
||
static struct S
|
||
{
|
||
@disable void opAssign(S);
|
||
int i;
|
||
}
|
||
auto arr = [S(0), S(1), S(2)];
|
||
arr.array();
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=10220
|
||
@safe pure nothrow 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)]));
|
||
});
|
||
}
|
||
//https://issues.dlang.org/show_bug.cgi?id=22673
|
||
@system unittest
|
||
{
|
||
struct LyingRange
|
||
{
|
||
enum size_t length = 100;
|
||
enum theRealLength = 50;
|
||
size_t idx = 0;
|
||
bool empty()
|
||
{
|
||
return idx <= theRealLength;
|
||
}
|
||
void popFront()
|
||
{
|
||
++idx;
|
||
}
|
||
size_t front()
|
||
{
|
||
return idx;
|
||
}
|
||
}
|
||
static assert(hasLength!LyingRange);
|
||
LyingRange rng;
|
||
import std.exception : assertThrown;
|
||
assertThrown!Error(array(rng));
|
||
}
|
||
//https://issues.dlang.org/show_bug.cgi?id=22185
|
||
@system unittest
|
||
{
|
||
import std.stdio;
|
||
static struct ThrowingCopy
|
||
{
|
||
int x = 420;
|
||
this(ref return scope ThrowingCopy rhs)
|
||
{
|
||
rhs.x = 420;
|
||
//
|
||
throw new Exception("This throws");
|
||
}
|
||
~this()
|
||
{
|
||
/*
|
||
Any time this destructor runs, it should be running on "valid"
|
||
data. This is is mimicked by having a .init other than 0 (the value the memory
|
||
practically will be from the GC).
|
||
*/
|
||
if (x != 420)
|
||
{
|
||
//This will only trigger during GC finalization so avoid writefln for now.
|
||
printf("Destructor failure in ThrowingCopy(%d) @ %p", x, &this);
|
||
assert(x == 420, "unittest destructor failed");
|
||
}
|
||
}
|
||
}
|
||
static struct LyingThrowingRange
|
||
{
|
||
enum size_t length = 100;
|
||
enum size_t evilRealLength = 50;
|
||
size_t idx;
|
||
ThrowingCopy front()
|
||
{
|
||
return ThrowingCopy(12);
|
||
}
|
||
bool empty()
|
||
{
|
||
return idx == evilRealLength;
|
||
}
|
||
void popFront()
|
||
{
|
||
++idx;
|
||
}
|
||
}
|
||
static assert(hasLength!LyingThrowingRange);
|
||
import std.exception : assertThrown;
|
||
{
|
||
assertThrown(array(LyingThrowingRange()));
|
||
}
|
||
import core.memory : GC;
|
||
/*
|
||
Force a collection early. Doesn't always actually finalize the bad objects
|
||
but trying to collect soon after the allocation is thrown away means any potential failures
|
||
will happen earlier.
|
||
*/
|
||
GC.collect();
|
||
}
|
||
|
||
/**
|
||
Returns a newly allocated associative array from a range of key/value tuples
|
||
or from a range of keys and a range of values.
|
||
|
||
Params:
|
||
r = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
|
||
of tuples of keys and values.
|
||
keys = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of keys
|
||
values = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of values
|
||
|
||
Returns:
|
||
|
||
A newly allocated associative array out of elements of the input
|
||
range, which must be a range of tuples (Key, Value) or
|
||
a range of keys and a range of values. If given two ranges of unequal
|
||
lengths after the elements of the shorter are exhausted the remaining
|
||
elements of the longer will not be considered.
|
||
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,"
|
||
~" but was a range of "~E.stringof);
|
||
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 (ref t; r)
|
||
aa[t[0]] = t[1];
|
||
return aa;
|
||
}
|
||
|
||
/// ditto
|
||
auto assocArray(Keys, Values)(Keys keys, Values values)
|
||
if (isInputRange!Values && isInputRange!Keys)
|
||
{
|
||
static if (isDynamicArray!Keys && isDynamicArray!Values
|
||
&& !isNarrowString!Keys && !isNarrowString!Values)
|
||
{
|
||
void* aa;
|
||
{
|
||
// aaLiteral is nothrow when the destructors don't throw
|
||
static if (is(typeof(() nothrow
|
||
{
|
||
import std.range : ElementType;
|
||
import std.traits : hasElaborateDestructor;
|
||
alias KeyElement = ElementType!Keys;
|
||
static if (hasElaborateDestructor!KeyElement)
|
||
KeyElement.init.__xdtor();
|
||
|
||
alias ValueElement = ElementType!Values;
|
||
static if (hasElaborateDestructor!ValueElement)
|
||
ValueElement.init.__xdtor();
|
||
})))
|
||
{
|
||
scope(failure) assert(false, "aaLiteral must not throw");
|
||
}
|
||
if (values.length > keys.length)
|
||
values = values[0 .. keys.length];
|
||
else if (keys.length > values.length)
|
||
keys = keys[0 .. values.length];
|
||
aa = aaLiteral(keys, values);
|
||
}
|
||
alias Key = typeof(keys[0]);
|
||
alias Value = typeof(values[0]);
|
||
return (() @trusted => cast(Value[Key]) aa)();
|
||
}
|
||
else
|
||
{
|
||
// zip is not always able to infer nothrow
|
||
alias Key = ElementType!Keys;
|
||
alias Value = ElementType!Values;
|
||
static assert(isMutable!Value, "assocArray: value type must be mutable");
|
||
Value[Key] aa;
|
||
foreach (key; keys)
|
||
{
|
||
if (values.empty) break;
|
||
|
||
// aa[key] is incorrectly not @safe if the destructor throws
|
||
// https://issues.dlang.org/show_bug.cgi?id=18592
|
||
static if (is(typeof(() @safe
|
||
{
|
||
import std.range : ElementType;
|
||
import std.traits : hasElaborateDestructor;
|
||
alias KeyElement = ElementType!Keys;
|
||
static if (hasElaborateDestructor!KeyElement)
|
||
KeyElement.init.__xdtor();
|
||
|
||
alias ValueElement = ElementType!Values;
|
||
static if (hasElaborateDestructor!ValueElement)
|
||
ValueElement.init.__xdtor();
|
||
|
||
aa[key] = values.front;
|
||
})))
|
||
{
|
||
() @trusted {
|
||
aa[key] = values.front;
|
||
}();
|
||
}
|
||
else
|
||
{
|
||
aa[key] = values.front;
|
||
}
|
||
values.popFront();
|
||
}
|
||
return aa;
|
||
}
|
||
}
|
||
|
||
///
|
||
@safe pure /*nothrow*/ unittest
|
||
{
|
||
import std.range : repeat, zip;
|
||
import std.typecons : tuple;
|
||
import std.range.primitives : autodecodeStrings;
|
||
auto a = assocArray(zip([0, 1, 2], ["a", "b", "c"])); // aka zipMap
|
||
static assert(is(typeof(a) == string[int]));
|
||
assert(a == [0:"a", 1:"b", 2:"c"]);
|
||
|
||
auto b = assocArray([ tuple("foo", "bar"), tuple("baz", "quux") ]);
|
||
static assert(is(typeof(b) == string[string]));
|
||
assert(b == ["foo":"bar", "baz":"quux"]);
|
||
|
||
static if (autodecodeStrings)
|
||
alias achar = dchar;
|
||
else
|
||
alias achar = immutable(char);
|
||
auto c = assocArray("ABCD", true.repeat);
|
||
static assert(is(typeof(c) == bool[achar]));
|
||
bool[achar] expected = ['D':true, 'A':true, 'B':true, 'C':true];
|
||
assert(c == expected);
|
||
}
|
||
|
||
// Cannot be version (StdUnittest) - recursive instantiation error
|
||
// https://issues.dlang.org/show_bug.cgi?id=11053
|
||
@safe pure nothrow unittest
|
||
{
|
||
import std.typecons;
|
||
static assert(!__traits(compiles, [ 1, 2, 3 ].assocArray()));
|
||
static assert(!__traits(compiles, [ tuple("foo", "bar", "baz") ].assocArray()));
|
||
static assert(!__traits(compiles, [ tuple("foo") ].assocArray()));
|
||
assert([ tuple("foo", "bar") ].assocArray() == ["foo": "bar"]);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=13909
|
||
@safe pure nothrow unittest
|
||
{
|
||
import std.typecons;
|
||
auto a = [tuple!(const string, string)("foo", "bar")];
|
||
auto b = [tuple!(string, const string)("foo", "bar")];
|
||
assert(a == b);
|
||
assert(assocArray(a) == [cast(const(string)) "foo": "bar"]);
|
||
static assert(!__traits(compiles, assocArray(b)));
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=5502
|
||
@safe pure nothrow unittest
|
||
{
|
||
auto a = assocArray([0, 1, 2], ["a", "b", "c"]);
|
||
static assert(is(typeof(a) == string[int]));
|
||
assert(a == [0:"a", 1:"b", 2:"c"]);
|
||
|
||
auto b = assocArray([0, 1, 2], [3L, 4, 5]);
|
||
static assert(is(typeof(b) == long[int]));
|
||
assert(b == [0: 3L, 1: 4, 2: 5]);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=5502
|
||
@safe pure unittest
|
||
{
|
||
import std.algorithm.iteration : filter, map;
|
||
import std.range : enumerate;
|
||
import std.range.primitives : autodecodeStrings;
|
||
|
||
auto r = "abcde".enumerate.filter!(a => a.index == 2);
|
||
auto a = assocArray(r.map!(a => a.value), r.map!(a => a.index));
|
||
static if (autodecodeStrings)
|
||
alias achar = dchar;
|
||
else
|
||
alias achar = immutable(char);
|
||
static assert(is(typeof(a) == size_t[achar]));
|
||
assert(a == [achar('c'): size_t(2)]);
|
||
}
|
||
|
||
@safe nothrow pure unittest
|
||
{
|
||
import std.range : iota;
|
||
auto b = assocArray(3.iota, 3.iota(6));
|
||
static assert(is(typeof(b) == int[int]));
|
||
assert(b == [0: 3, 1: 4, 2: 5]);
|
||
|
||
b = assocArray([0, 1, 2], [3, 4, 5]);
|
||
assert(b == [0: 3, 1: 4, 2: 5]);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
struct ThrowingElement
|
||
{
|
||
int i;
|
||
static bool b;
|
||
~this(){
|
||
if (b)
|
||
throw new Exception("");
|
||
}
|
||
}
|
||
static assert(!__traits(compiles, () nothrow { assocArray([ThrowingElement()], [0]);}));
|
||
assert(assocArray([ThrowingElement()], [0]) == [ThrowingElement(): 0]);
|
||
|
||
static assert(!__traits(compiles, () nothrow { assocArray([0], [ThrowingElement()]);}));
|
||
assert(assocArray([0], [ThrowingElement()]) == [0: ThrowingElement()]);
|
||
|
||
import std.range : iota;
|
||
static assert(!__traits(compiles, () nothrow { assocArray(1.iota, [ThrowingElement()]);}));
|
||
assert(assocArray(1.iota, [ThrowingElement()]) == [0: ThrowingElement()]);
|
||
}
|
||
|
||
@system unittest
|
||
{
|
||
import std.range : iota;
|
||
struct UnsafeElement
|
||
{
|
||
int i;
|
||
static bool b;
|
||
~this(){
|
||
int[] arr;
|
||
void* p = arr.ptr + 1; // unsafe
|
||
}
|
||
}
|
||
static assert(!__traits(compiles, () @safe { assocArray(1.iota, [UnsafeElement()]);}));
|
||
assert(assocArray(1.iota, [UnsafeElement()]) == [0: UnsafeElement()]);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
struct ValueRange
|
||
{
|
||
string front() const @system;
|
||
@safe:
|
||
void popFront() {}
|
||
bool empty() const { return false; }
|
||
}
|
||
int[] keys;
|
||
ValueRange values;
|
||
static assert(!__traits(compiles, assocArray(keys, values)));
|
||
}
|
||
|
||
/**
|
||
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. The members
|
||
of each pair can be accessed by name (`.key` and `.value`). or by integer
|
||
index (0 and 1 respectively).
|
||
*/
|
||
auto byPair(AA)(AA aa)
|
||
if (isAssociativeArray!AA)
|
||
{
|
||
import std.algorithm.iteration : map;
|
||
import std.typecons : tuple;
|
||
|
||
return aa.byKeyValue
|
||
.map!(pair => tuple!("key", "value")(pair.key, pair.value));
|
||
}
|
||
|
||
///
|
||
@safe pure nothrow 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)
|
||
{
|
||
if (pair.key == "b")
|
||
pairs ~= tuple("B", pair.value);
|
||
else
|
||
pairs ~= pair;
|
||
}
|
||
|
||
// Iteration order is implementation-dependent, so we should sort it to get
|
||
// a fixed order.
|
||
pairs.sort();
|
||
assert(pairs == [
|
||
tuple("B", 2),
|
||
tuple("a", 1),
|
||
tuple("c", 3)
|
||
]);
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
import std.typecons : tuple, Tuple;
|
||
import std.meta : AliasSeq;
|
||
|
||
auto aa = ["a":2];
|
||
auto pairs = aa.byPair();
|
||
|
||
alias PT = typeof(pairs.front);
|
||
static assert(is(PT : Tuple!(string,int)));
|
||
static assert(PT.fieldNames == AliasSeq!("key", "value"));
|
||
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));
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=17711
|
||
@safe pure nothrow 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;
|
||
}
|
||
}
|
||
|
||
@safe 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;
|
||
}
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
static assert(nDimensions!(uint[]) == 1);
|
||
static assert(nDimensions!(float[][]) == 2);
|
||
}
|
||
|
||
/++
|
||
Returns a new array of type `T` allocated on the garbage collected heap
|
||
without initializing its elements. This can be a useful optimization if every
|
||
element will be immediately initialized. `T` may be a multidimensional
|
||
array. In this case sizes may be specified for any number of dimensions from 0
|
||
to the number in `T`.
|
||
|
||
uninitializedArray is `nothrow` and weakly `pure`.
|
||
|
||
uninitializedArray is `@system` if the uninitialized element type has pointers.
|
||
|
||
Params:
|
||
T = The type of the resulting array elements
|
||
sizes = The length dimension(s) of the resulting array
|
||
Returns:
|
||
An array of `T` with `I.length` dimensions.
|
||
+/
|
||
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 `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 `.init`.
|
||
|
||
minimallyInitializedArray is `nothrow` and weakly `pure`.
|
||
|
||
Params:
|
||
T = The type of the array elements
|
||
sizes = The length dimension(s) of the resulting array
|
||
Returns:
|
||
An array of `T` with `I.length` dimensions.
|
||
+/
|
||
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, so don't do this:
|
||
// assert(arr.equal(0.repeat(42)));
|
||
// If that is needed, initialize the array normally instead:
|
||
auto arr2 = new int[42];
|
||
assert(arr2.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);
|
||
}
|
||
}
|
||
|
||
// from rt/lifetime.d
|
||
private extern(C) void[] _d_newarrayU(const TypeInfo ti, size_t length) pure nothrow;
|
||
|
||
// from rt/tracegc.d
|
||
version (D_ProfileGC)
|
||
private extern (C) void[] _d_newarrayUTrace(string file, size_t line,
|
||
string funcname, const scope TypeInfo ti, size_t length) pure nothrow;
|
||
|
||
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), "I[0] must be of type size_t not "
|
||
~ I[0].stringof);
|
||
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)
|
||
assert(0, e.msg);
|
||
}
|
||
else
|
||
assert(0, "No postblit nor default init on " ~ E.stringof ~
|
||
": At least one is required for CTFE.");
|
||
}
|
||
else
|
||
{
|
||
import core.stdc.string : memset;
|
||
|
||
/+
|
||
NOTES:
|
||
_d_newarrayU is part of druntime, and creates an uninitialized
|
||
block, just like GC.malloc. However, it also sets the appropriate
|
||
bits, and sets up the block as an appendable array of type E[],
|
||
which will inform the GC how to destroy the items in the block
|
||
when it gets collected.
|
||
|
||
_d_newarrayU returns a void[], but with the length set according
|
||
to E.sizeof.
|
||
+/
|
||
version (D_ProfileGC)
|
||
{
|
||
// FIXME: file, line, function should be propagated from the
|
||
// caller, not here.
|
||
*(cast(void[]*)&ret) = _d_newarrayUTrace(__FILE__, __LINE__,
|
||
__FUNCTION__, typeid(E[]), size);
|
||
}
|
||
else
|
||
*(cast(void[]*)&ret) = _d_newarrayU(typeid(E[]), size);
|
||
static if (minimallyInitialized && hasIndirections!E)
|
||
// _d_newarrayU would have asserted if the multiplication below
|
||
// had overflowed, so we don't have to check it again.
|
||
memset(ret.ptr, 0, E.sizeof * ret.length);
|
||
}
|
||
}
|
||
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);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=9803
|
||
@safe nothrow pure unittest
|
||
{
|
||
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);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=10637
|
||
@safe pure nothrow unittest
|
||
{
|
||
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()
|
||
{
|
||
// note, this assert is invalid -- a struct should always be able
|
||
// to run its dtor on the .init value, I'm leaving it here
|
||
// commented out because the original test case had it. I'm not
|
||
// sure what it's trying to prove.
|
||
//
|
||
// What happens now that minimallyInitializedArray adds the
|
||
// destructor run to the GC, is that this assert would fire in the
|
||
// GC, which triggers an invalid memory operation.
|
||
//assert(p != null);
|
||
}
|
||
}
|
||
auto a = minimallyInitializedArray!(S[])(1);
|
||
assert(a[0].p == null);
|
||
enum b = minimallyInitializedArray!(S[])(1);
|
||
assert(b[0].p == null);
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
static struct S1
|
||
{
|
||
this() @disable;
|
||
this(this) @disable;
|
||
}
|
||
auto a1 = minimallyInitializedArray!(S1[][])(2, 2);
|
||
assert(a1);
|
||
static struct S2
|
||
{
|
||
this() @disable;
|
||
//this(this) @disable;
|
||
}
|
||
auto a2 = minimallyInitializedArray!(S2[][])(2, 2);
|
||
assert(a2);
|
||
enum b2 = minimallyInitializedArray!(S2[][])(2, 2);
|
||
assert(b2 !is null);
|
||
static struct S3
|
||
{
|
||
//this() @disable;
|
||
this(this) @disable;
|
||
}
|
||
auto a3 = minimallyInitializedArray!(S3[][])(2, 2);
|
||
assert(a3);
|
||
enum b3 = minimallyInitializedArray!(S3[][])(2, 2);
|
||
assert(b3 !is null);
|
||
}
|
||
|
||
/++
|
||
Returns the overlapping portion, if any, of two arrays. Unlike `equal`,
|
||
`overlap` only compares the pointers and lengths in the
|
||
ranges, not the values referred by them. If `r1` and `r2` have an
|
||
overlapping slice, returns that slice. Otherwise, returns the null
|
||
slice.
|
||
|
||
Params:
|
||
a = The first array to compare
|
||
b = The second array to compare
|
||
Returns:
|
||
The overlapping portion of the two arrays.
|
||
+/
|
||
CommonType!(T[], U[]) overlap(T, U)(T[] a, U[] b) @trusted
|
||
if (is(typeof(a.ptr < b.ptr) == bool))
|
||
{
|
||
import std.algorithm.comparison : min;
|
||
|
||
auto end = min(a.ptr + a.length, b.ptr + b.length);
|
||
// CTFE requires pairing pointer comparisons, which forces a
|
||
// slightly inefficient implementation.
|
||
if (a.ptr <= b.ptr && b.ptr < a.ptr + a.length)
|
||
{
|
||
return b.ptr[0 .. end - b.ptr];
|
||
}
|
||
|
||
if (b.ptr <= a.ptr && a.ptr < b.ptr + b.length)
|
||
{
|
||
return a.ptr[0 .. end - a.ptr];
|
||
}
|
||
|
||
return 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);
|
||
|
||
static test()() @nogc
|
||
{
|
||
auto a = "It's three o'clock"d;
|
||
auto b = a[5 .. 10];
|
||
return b.overlap(a);
|
||
}
|
||
|
||
//works at compile-time
|
||
static assert(test == "three"d);
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
static void test(L, R)(L l, R r)
|
||
{
|
||
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.dup.idup).empty);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=9836
|
||
@safe pure nothrow unittest
|
||
{
|
||
// 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;
|
||
import std.format : format;
|
||
|
||
assert(src.length == dest.length, format!
|
||
"src.length %s must equal dest.length %s"(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 `stuff` (which must be an input range or any number of
|
||
implicitly convertible items) in `array` at position `pos`.
|
||
|
||
Params:
|
||
array = The array that `stuff` will be inserted into.
|
||
pos = The position in `array` to insert the `stuff`.
|
||
stuff = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives),
|
||
or any number of implicitly convertible items to insert into `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 core.internal.lifetime : 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, byDchar;
|
||
// 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,
|
||
"T must not have an elaborate copy constructor");
|
||
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 (ch; stuff[i].byDchar)
|
||
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]);
|
||
|
||
union U
|
||
{
|
||
float a = 3.0;
|
||
int b;
|
||
}
|
||
|
||
U u1 = { b : 3 };
|
||
U u2 = { b : 4 };
|
||
U u3 = { b : 5 };
|
||
U[] unionArr = [u2, u3];
|
||
unionArr.insertInPlace(2, [u1]);
|
||
assert(unionArr == [u2, u3, u1]);
|
||
unionArr.insertInPlace(0, [u3, u2]);
|
||
assert(unionArr == [u3, u2, u2, u3, u1]);
|
||
|
||
static class C
|
||
{
|
||
int a;
|
||
float b;
|
||
|
||
this(int a, float b) { this.a = a; this.b = b; }
|
||
}
|
||
|
||
C c1 = new C(42, 1.0);
|
||
C c2 = new C(0, 0.0);
|
||
C c3 = new C(int.max, float.init);
|
||
|
||
C[] classArr = [c1, c2, c3];
|
||
insertInPlace(classArr, 3, [c2, c3]);
|
||
C[5] classArr1 = classArr;
|
||
assert(classArr1 == [c1, c2, c3, c2, c3]);
|
||
insertInPlace(classArr, 0, c3, c1);
|
||
C[7] classArr2 = classArr;
|
||
assert(classArr2 == [c3, c1, c1, c2, c3, c2, c3]);
|
||
}
|
||
|
||
//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)
|
||
{
|
||
{
|
||
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));
|
||
}
|
||
|
||
static foreach (T; AliasSeq!(char, wchar, dchar,
|
||
immutable(char), immutable(wchar), immutable(dchar)))
|
||
{
|
||
static 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
|
||
}
|
||
|
||
@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];
|
||
});
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=6874
|
||
@system unittest
|
||
{
|
||
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 `front`s of `lhs` and `rhs` both refer to the
|
||
same place in memory, making one of the arrays a slice of the other which
|
||
starts at index `0`.
|
||
|
||
Params:
|
||
lhs = the first array to compare
|
||
rhs = the second array to compare
|
||
Returns:
|
||
`true` if $(D lhs.ptr == rhs.ptr), `false` otherwise.
|
||
+/
|
||
@safe
|
||
pure nothrow @nogc 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 `back`s of `lhs` and `rhs` both refer to the
|
||
same place in memory, making one of the arrays a slice of the other which
|
||
end at index `$`.
|
||
|
||
Params:
|
||
lhs = the first array to compare
|
||
rhs = the second array to compare
|
||
Returns:
|
||
`true` if both arrays are the same length and $(D lhs.ptr == rhs.ptr),
|
||
`false` otherwise.
|
||
+/
|
||
@trusted
|
||
pure nothrow @nogc 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
|
||
{
|
||
static 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;
|
||
|
||
static foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
|
||
{{
|
||
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 splits `range` into an array, using `sep` as the delimiter.
|
||
|
||
When no delimiter is provided, strings are split into an array of words,
|
||
using whitespace as delimiter.
|
||
Runs of whitespace are merged together (no empty words are produced).
|
||
|
||
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 `range`
|
||
or it can be another forward `range`.
|
||
|
||
Params:
|
||
s = the string to split by word if no separator is given
|
||
range = the range to split
|
||
sep = a value of the same type as the elements of `range` or another
|
||
isTerminator = a predicate that splits the range when it returns `true`.
|
||
|
||
Returns:
|
||
An array containing the divided parts of `range` (or the words of `s`).
|
||
|
||
See_Also:
|
||
$(REF splitter, std,algorithm,iteration) for a lazy version without allocating memory.
|
||
|
||
$(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;
|
||
auto result = appender!(S[]);
|
||
|
||
foreach (i, dchar c ; s)
|
||
{
|
||
import std.uni : isWhite;
|
||
if (isWhite(c))
|
||
{
|
||
if (inword)
|
||
{
|
||
put(result, s[istart .. i]);
|
||
inword = false;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!inword)
|
||
{
|
||
istart = i;
|
||
inword = true;
|
||
}
|
||
}
|
||
}
|
||
if (inword)
|
||
put(result, s[istart .. $]);
|
||
return result.data;
|
||
}
|
||
|
||
///
|
||
@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
|
||
{
|
||
string str = "Hello World!";
|
||
assert(str.split == ["Hello", "World!"]);
|
||
|
||
string str2 = "Hello\t\tWorld\t!";
|
||
assert(str2.split == ["Hello", "World", "!"]);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
import std.conv : to;
|
||
import std.format : format;
|
||
import std.typecons;
|
||
|
||
static auto makeEntry(S)(string l, string[] r)
|
||
{return tuple(l.to!S(), r.to!(S[])());}
|
||
|
||
static 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]]);
|
||
}
|
||
|
||
///ditto
|
||
auto split(Range, Separator)(Range range, Separator sep)
|
||
if (isForwardRange!Range && (
|
||
is(typeof(ElementType!Range.init == Separator.init)) ||
|
||
is(typeof(ElementType!Range.init == ElementType!Separator.init)) && isForwardRange!Separator
|
||
))
|
||
{
|
||
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.algorithm.comparison : cmp;
|
||
import std.conv;
|
||
|
||
static 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 `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 `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, R sep)
|
||
if (isInputRange!RoR &&
|
||
isInputRange!(Unqual!(ElementType!RoR)) &&
|
||
isInputRange!R &&
|
||
(is(immutable ElementType!(ElementType!RoR) == immutable ElementType!R) ||
|
||
(isSomeChar!(ElementType!(ElementType!RoR)) && isSomeChar!(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(immutable ElementEncodingType!RetType == immutable 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 core.internal.lifetime : 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, sepArr);
|
||
put(result, ror.front);
|
||
}
|
||
return result.data;
|
||
}
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=14230
|
||
@safe unittest
|
||
{
|
||
string[] ary = ["","aa","bb","cc"]; // leaded by _empty_ element
|
||
assert(ary.join(" @") == " @aa @bb @cc"); // OK in 2.067b1 and olders
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=21337
|
||
@system unittest
|
||
{
|
||
import std.algorithm.iteration : map;
|
||
|
||
static class Once
|
||
{
|
||
bool empty;
|
||
|
||
void popFront()
|
||
{
|
||
empty = true;
|
||
}
|
||
|
||
int front()
|
||
{
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
assert([1, 2].map!"[a]".join(new Once) == [1, 0, 2]);
|
||
}
|
||
|
||
/// Ditto
|
||
ElementEncodingType!(ElementType!RoR)[] join(RoR, E)(RoR ror, scope E sep)
|
||
if (isInputRange!RoR &&
|
||
isInputRange!(Unqual!(ElementType!RoR)) &&
|
||
((is(E : ElementType!(ElementType!RoR))) ||
|
||
(!autodecodeStrings && isSomeChar!(ElementType!(ElementType!RoR)) &&
|
||
isSomeChar!E)))
|
||
{
|
||
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 core.internal.lifetime : emplaceRef;
|
||
import std.format : format;
|
||
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, format!
|
||
"len %s must equal result.lenght %s"(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;
|
||
}
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=14230
|
||
@safe unittest
|
||
{
|
||
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 ConstRetTypeElement = ElementEncodingType!RetType;
|
||
static if (isAssignable!(Unqual!ConstRetTypeElement, ConstRetTypeElement))
|
||
{
|
||
alias RetTypeElement = Unqual!ConstRetTypeElement;
|
||
}
|
||
else
|
||
{
|
||
alias RetTypeElement = ConstRetTypeElement;
|
||
}
|
||
alias RoRElem = ElementType!RoR;
|
||
|
||
if (ror.empty)
|
||
return RetType.init;
|
||
|
||
static if (hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem))
|
||
{
|
||
import core.internal.lifetime : 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!RetTypeElement(result[len++], e);
|
||
assert(len == result.length,
|
||
"emplaced an unexpected number of elements");
|
||
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;
|
||
import std.range.primitives : autodecodeStrings;
|
||
|
||
static foreach (T; AliasSeq!(string,wstring,dstring))
|
||
{{
|
||
auto arr2 = "Здравствуй Мир Unicode".to!(T);
|
||
auto arr = ["Здравствуй", "Мир", "Unicode"].to!(T[]);
|
||
assert(join(arr) == "ЗдравствуйМирUnicode");
|
||
static foreach (S; AliasSeq!(char,wchar,dchar))
|
||
{{
|
||
auto jarr = arr.join(to!S(' '));
|
||
static assert(is(typeof(jarr) == T));
|
||
assert(jarr == arr2);
|
||
}}
|
||
static foreach (S; AliasSeq!(string,wstring,dstring))
|
||
{{
|
||
auto jarr = arr.join(to!S(" "));
|
||
static assert(is(typeof(jarr) == T));
|
||
assert(jarr == arr2);
|
||
}}
|
||
}}
|
||
|
||
static foreach (T; AliasSeq!(string,wstring,dstring))
|
||
{{
|
||
auto arr2 = "Здравствуй\u047CМир\u047CUnicode".to!(T);
|
||
auto arr = ["Здравствуй", "Мир", "Unicode"].to!(T[]);
|
||
static 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");
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
class A { }
|
||
|
||
const A[][] array;
|
||
auto result = array.join; // can't remove constness, so don't try
|
||
|
||
static assert(is(typeof(result) == const(A)[]));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
import std.algorithm;
|
||
import std.conv : to;
|
||
import std.range;
|
||
|
||
static 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);
|
||
|
||
static foreach (S; AliasSeq!(string, wstring, dstring))
|
||
{{
|
||
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]);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=10683
|
||
@safe unittest
|
||
{
|
||
import std.range : join;
|
||
import std.typecons : tuple;
|
||
assert([[tuple(1)]].join == [tuple(1)]);
|
||
assert([[tuple("x")]].join == [tuple("x")]);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=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.
|
||
|
||
Params:
|
||
subject = the array to scan
|
||
from = the item to replace
|
||
to = the item to replace all instances of `from` with
|
||
|
||
Returns:
|
||
A new array without changing the contents of `subject`, or the original
|
||
array if no match is found.
|
||
|
||
See_Also:
|
||
$(REF substitute, std,algorithm,iteration) for a lazy replace.
|
||
+/
|
||
E[] replace(E, R1, R2)(E[] subject, R1 from, R2 to)
|
||
if ((isForwardRange!R1 && isForwardRange!R2 && (hasLength!R2 || isSomeString!R2)) ||
|
||
is(Unqual!E : Unqual!R1))
|
||
{
|
||
size_t changed = 0;
|
||
return replace(subject, from, to, changed);
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
assert("Hello Wörld".replace("o Wö", "o Wo") == "Hello World");
|
||
assert("Hello Wörld".replace("l", "h") == "Hehho Wörhd");
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
assert([1, 2, 3, 4, 2].replace([2], [5]) == [1, 5, 3, 4, 5]);
|
||
assert([3, 3, 3].replace([3], [0]) == [0, 0, 0]);
|
||
assert([3, 3, 4, 3].replace([3, 3], [1, 1, 1]) == [1, 1, 1, 4, 3]);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=18215
|
||
@safe unittest
|
||
{
|
||
auto arr = ["aaa.dd", "b"];
|
||
arr = arr.replace("aaa.dd", ".");
|
||
assert(arr == [".", "b"]);
|
||
|
||
arr = ["_", "_", "aaa.dd", "b", "c", "aaa.dd", "e"];
|
||
arr = arr.replace("aaa.dd", ".");
|
||
assert(arr == ["_", "_", ".", "b", "c", ".", "e"]);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=18215
|
||
@safe unittest
|
||
{
|
||
assert([[0], [1, 2], [0], [3]].replace([0], [4]) == [[4], [1, 2], [4], [3]]);
|
||
assert([[0], [1, 2], [0], [3], [1, 2]]
|
||
.replace([1, 2], [0]) == [[0], [0], [0], [3], [0]]);
|
||
assert([[0], [1, 2], [0], [3], [1, 2], [0], [1, 2]]
|
||
.replace([[0], [1, 2]], [[4]]) == [[4], [0], [3], [1, 2], [4]]);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=10930
|
||
@safe unittest
|
||
{
|
||
assert([0, 1, 2].replace(1, 4) == [0, 4, 2]);
|
||
assert("äbö".replace('ä', 'a') == "abö");
|
||
}
|
||
|
||
// empty array
|
||
@safe unittest
|
||
{
|
||
int[] arr;
|
||
assert(replace(arr, 1, 2) == []);
|
||
}
|
||
|
||
/++
|
||
Replace occurrences of `from` with `to` in `subject` in a new array.
|
||
`changed` counts how many replacements took place.
|
||
|
||
Params:
|
||
subject = the array to scan
|
||
from = the item to replace
|
||
to = the item to replace all instances of `from` with
|
||
changed = the number of replacements
|
||
|
||
Returns:
|
||
A new array without changing the contents of `subject`, or the original
|
||
array if no match is found.
|
||
+/
|
||
E[] replace(E, R1, R2)(E[] subject, R1 from, R2 to, ref size_t changed)
|
||
if ((isForwardRange!R1 && isForwardRange!R2 && (hasLength!R2 || isSomeString!R2)) ||
|
||
is(Unqual!E : Unqual!R1))
|
||
{
|
||
import std.algorithm.searching : find;
|
||
import std.range : dropOne;
|
||
|
||
static if (isInputRange!R1)
|
||
{
|
||
if (from.empty) return subject;
|
||
alias rSave = a => a.save;
|
||
}
|
||
else
|
||
{
|
||
alias rSave = a => a;
|
||
}
|
||
|
||
auto balance = find(subject, rSave(from));
|
||
if (balance.empty)
|
||
return subject;
|
||
|
||
auto app = appender!(E[])();
|
||
app.put(subject[0 .. subject.length - balance.length]);
|
||
app.put(rSave(to));
|
||
++changed;
|
||
// replacing an element in an array is different to a range replacement
|
||
static if (is(Unqual!E : Unqual!R1))
|
||
replaceInto(app, balance.dropOne, from, to, changed);
|
||
else
|
||
replaceInto(app, balance[from.length .. $], from, to, changed);
|
||
|
||
return app.data;
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
size_t changed = 0;
|
||
assert("Hello Wörld".replace("o Wö", "o Wo", changed) == "Hello World");
|
||
assert(changed == 1);
|
||
|
||
changed = 0;
|
||
assert("Hello Wörld".replace("l", "h", changed) == "Hehho Wörhd");
|
||
import std.stdio : writeln;
|
||
writeln(changed);
|
||
assert(changed == 3);
|
||
}
|
||
|
||
/++
|
||
Replace occurrences of `from` with `to` in `subject` and output the result 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
|
||
|
||
See_Also:
|
||
$(REF substitute, std,algorithm,iteration) for a lazy replace.
|
||
+/
|
||
void replaceInto(E, Sink, R1, R2)(Sink sink, E[] subject, R1 from, R2 to)
|
||
if (isOutputRange!(Sink, E) &&
|
||
((isForwardRange!R1 && isForwardRange!R2 && (hasLength!R2 || isSomeString!R2)) ||
|
||
is(Unqual!E : Unqual!R1)))
|
||
{
|
||
size_t changed = 0;
|
||
replaceInto(sink, subject, from, to, changed);
|
||
}
|
||
|
||
///
|
||
@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]);
|
||
}
|
||
|
||
// empty array
|
||
@safe unittest
|
||
{
|
||
auto sink = appender!(int[])();
|
||
int[] arr;
|
||
replaceInto(sink, arr, 1, 2);
|
||
assert(sink.data == []);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
import std.algorithm.comparison : cmp;
|
||
import std.conv : to;
|
||
|
||
static foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
|
||
{
|
||
static foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
|
||
{{
|
||
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)); }
|
||
}
|
||
static 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);
|
||
}}
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=10930
|
||
@safe unittest
|
||
{
|
||
auto sink = appender!(int[])();
|
||
replaceInto(sink, [0, 1, 2], 1, 5);
|
||
assert(sink.data == [0, 5, 2]);
|
||
|
||
auto sink2 = appender!(dchar[])();
|
||
replaceInto(sink2, "äbö", 'ä', 'a');
|
||
assert(sink2.data == "abö");
|
||
}
|
||
|
||
/++
|
||
Replace occurrences of `from` with `to` in `subject` and output the result into
|
||
`sink`. `changed` counts how many replacements took place.
|
||
|
||
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
|
||
changed = the number of replacements
|
||
+/
|
||
void replaceInto(E, Sink, R1, R2)(Sink sink, E[] subject, R1 from, R2 to, ref size_t changed)
|
||
if (isOutputRange!(Sink, E) &&
|
||
((isForwardRange!R1 && isForwardRange!R2 && (hasLength!R2 || isSomeString!R2)) ||
|
||
is(Unqual!E : Unqual!R1)))
|
||
{
|
||
import std.algorithm.searching : find;
|
||
import std.range : dropOne;
|
||
|
||
static if (isInputRange!R1)
|
||
{
|
||
if (from.empty)
|
||
{
|
||
sink.put(subject);
|
||
return;
|
||
}
|
||
alias rSave = a => a.save;
|
||
}
|
||
else
|
||
{
|
||
alias rSave = a => a;
|
||
}
|
||
for (;;)
|
||
{
|
||
auto balance = find(subject, rSave(from));
|
||
if (balance.empty)
|
||
{
|
||
sink.put(subject);
|
||
break;
|
||
}
|
||
sink.put(subject[0 .. subject.length - balance.length]);
|
||
sink.put(rSave(to));
|
||
++changed;
|
||
// replacing an element in an array is different to a range replacement
|
||
static if (is(Unqual!E : Unqual!R1))
|
||
subject = balance.dropOne;
|
||
else
|
||
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[])();
|
||
|
||
size_t changed = 0;
|
||
replaceInto(sink, arr, from, to, changed);
|
||
|
||
assert(sink.data == [1, 4, 6, 4, 5]);
|
||
assert(changed == 1);
|
||
}
|
||
|
||
/++
|
||
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`.
|
||
|
||
See_Also:
|
||
$(REF substitute, std,algorithm,iteration) for a lazy replace.
|
||
+/
|
||
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, "from must be before or equal to 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 .. $];
|
||
static if (is(T == const) || is(T == immutable))
|
||
{
|
||
return () @trusted { return cast(T[]) retval; } ();
|
||
}
|
||
else
|
||
{
|
||
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);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=18166
|
||
@safe pure unittest
|
||
{
|
||
auto str = replace("aaaaa"d, 1, 4, "***"d);
|
||
assert(str == "a***a");
|
||
}
|
||
|
||
/++
|
||
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]);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=12889
|
||
@safe unittest
|
||
{
|
||
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
|
||
{
|
||
// https://issues.dlang.org/show_bug.cgi?id=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)
|
||
{
|
||
{
|
||
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 `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;
|
||
|
||
static foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[],
|
||
const(char[]), immutable(char[])))
|
||
{
|
||
static foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[],
|
||
const(char[]), immutable(char[])))
|
||
{{
|
||
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);
|
||
}}
|
||
}
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=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 `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;
|
||
|
||
static foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[],
|
||
const(char[]), immutable(char[])))
|
||
{
|
||
static foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[],
|
||
const(char[]), immutable(char[])))
|
||
{{
|
||
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[]`.
|
||
|
||
See_Also:
|
||
$(REF substitute, std,algorithm,iteration) for a lazy replace.
|
||
+/
|
||
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, "slice[] is not a subslice of s[]");
|
||
}
|
||
do
|
||
{
|
||
auto result = new T[s.length - slice.length + replacement.length];
|
||
immutable so = &slice[0] - &s[0];
|
||
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 () @trusted inout {
|
||
return cast(inout(T)[]) result;
|
||
}();
|
||
}
|
||
|
||
///
|
||
@safe 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]);
|
||
}
|
||
|
||
@safe 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
|
||
the $(DDSUBLINK spec/arrays, capacity-reserve, performance hit of looking up slice `capacity`)
|
||
for each append.
|
||
|
||
Params:
|
||
A = the array type to simulate.
|
||
|
||
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 tryExtendBlock = 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
|
||
{
|
||
// 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.
|
||
*
|
||
* Params:
|
||
* newCapacity = the capacity the `Appender` should have
|
||
*/
|
||
void reserve(size_t newCapacity)
|
||
{
|
||
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
|
||
{
|
||
return _data ? _data.capacity : 0;
|
||
}
|
||
|
||
/// Returns: The number of elements appended.
|
||
@property size_t length() const => _data ? _data.arr.length : 0;
|
||
|
||
/**
|
||
* Use opSlice() from now on.
|
||
* Returns: The managed array.
|
||
*/
|
||
@property inout(T)[] data() inout
|
||
{
|
||
return this[];
|
||
}
|
||
|
||
/**
|
||
* Returns: The managed array.
|
||
*/
|
||
@property inout(T)[] opSlice() inout @trusted
|
||
{
|
||
/* @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)
|
||
{
|
||
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
|
||
{
|
||
import core.stdc.string : memcpy, memset;
|
||
// 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.tryExtendBlock)
|
||
{
|
||
immutable u = (() @trusted => GC.extend(_data.arr.ptr, nelems * T.sizeof, (newlen - len) * T.sizeof))();
|
||
if (u)
|
||
{
|
||
// extend worked, update the capacity
|
||
// if the type has indirections, we need to zero any new
|
||
// data that we requested, as the existing data may point
|
||
// at large unused blocks.
|
||
static if (hasIndirections!T)
|
||
{
|
||
immutable addedSize = u - (_data.capacity * T.sizeof);
|
||
() @trusted { memset(_data.arr.ptr + _data.capacity, 0, addedSize); }();
|
||
}
|
||
|
||
_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(false, "the reallocation would exceed the "
|
||
~ "available pointer range");
|
||
|
||
auto bi = (() @trusted => GC.qalloc(nbytes, blockAttribute!T))();
|
||
_data.capacity = bi.size / T.sizeof;
|
||
if (len)
|
||
() @trusted { memcpy(bi.base, _data.arr.ptr, len * T.sizeof); }();
|
||
|
||
_data.arr = (() @trusted => (cast(Unqual!T*) bi.base)[0 .. len])();
|
||
|
||
// we requested new bytes that are not in the existing
|
||
// data. If T has pointers, then this new data could point at stale
|
||
// objects from the last time this block was allocated. Zero that
|
||
// new data out, it may point at large unused blocks!
|
||
static if (hasIndirections!T)
|
||
() @trusted {
|
||
memset(bi.base + (len * T.sizeof), 0, (newlen - len) * T.sizeof);
|
||
}();
|
||
|
||
_data.tryExtendBlock = true;
|
||
// leave the old data, for safety reasons
|
||
}
|
||
}
|
||
|
||
private template canPutItem(U)
|
||
{
|
||
enum bool canPutItem =
|
||
is(Unqual!U : Unqual!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. Performs encoding for
|
||
* `char` types if `A` is a differently typed `char` array.
|
||
*
|
||
* Params:
|
||
* item = the single item to append
|
||
*/
|
||
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 core.lifetime : emplace;
|
||
|
||
ensureAddable(1);
|
||
immutable len = _data.arr.length;
|
||
|
||
auto bigData = (() @trusted => _data.arr.ptr[0 .. len + 1])();
|
||
auto itemUnqual = (() @trusted => & cast() item)();
|
||
emplace(&bigData[len], *itemUnqual);
|
||
//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. Performs encoding for
|
||
* `char` elements if `A` is a differently typed `char` array.
|
||
*
|
||
* Params:
|
||
* items = the range of items to append
|
||
*/
|
||
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
|
||
auto bigDataFun(size_t extra)
|
||
{
|
||
ensureAddable(extra);
|
||
return (() @trusted => _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 core.internal.lifetime : 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 static if (isSomeChar!T && isSomeChar!(ElementType!Range) &&
|
||
!is(immutable T == immutable ElementType!Range))
|
||
{
|
||
// need to decode and encode
|
||
import std.utf : decodeFront;
|
||
while (!items.empty)
|
||
{
|
||
auto c = items.decodeFront;
|
||
put(c);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//pragma(msg, Range.stringof);
|
||
// Generic input range
|
||
for (; !items.empty; items.popFront())
|
||
{
|
||
put(items.front);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Appends to the managed array.
|
||
*
|
||
* See_Also: $(LREF Appender.put)
|
||
*/
|
||
alias opOpAssign(string op : "~") = put;
|
||
|
||
// 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 `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: `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");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Gives a string in the form of `Appender!(A)(data)`.
|
||
*
|
||
* Params:
|
||
* w = A `char` accepting
|
||
* $(REF_ALTTEXT output range, isOutputRange, std, range, primitives).
|
||
* fmt = A $(REF FormatSpec, std, format) which controls how the array
|
||
* is formatted.
|
||
* Returns:
|
||
* A `string` if `writer` is not set; `void` otherwise.
|
||
*/
|
||
string toString()() const
|
||
{
|
||
import std.format.spec : singleSpec;
|
||
|
||
auto app = appender!string();
|
||
auto spec = singleSpec("%s");
|
||
immutable len = _data ? _data.arr.length : 0;
|
||
// different reserve lengths because each element in a
|
||
// non-string-like array uses two extra characters for `, `.
|
||
static if (isSomeString!A)
|
||
{
|
||
app.reserve(len + 25);
|
||
}
|
||
else
|
||
{
|
||
// Multiplying by three is a very conservative estimate of
|
||
// length, as it assumes each element is only one char
|
||
app.reserve((len * 3) + 25);
|
||
}
|
||
toString(app, spec);
|
||
return app.data;
|
||
}
|
||
|
||
import std.format.spec : FormatSpec;
|
||
|
||
/// ditto
|
||
template toString(Writer)
|
||
if (isOutputRange!(Writer, char))
|
||
{
|
||
void toString(ref Writer w, scope const ref FormatSpec!char fmt) const
|
||
{
|
||
import std.format.write : formatValue;
|
||
import std.range.primitives : put;
|
||
put(w, Unqual!(typeof(this)).stringof);
|
||
put(w, '(');
|
||
formatValue(w, data, fmt);
|
||
put(w, ')');
|
||
}
|
||
}
|
||
}
|
||
|
||
///
|
||
@safe pure nothrow 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);
|
||
assert(app2.length == 3);
|
||
app2.put([ 4, 5, 6 ]);
|
||
assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]);
|
||
}
|
||
|
||
@safe pure unittest
|
||
{
|
||
import std.format : format;
|
||
import std.format.spec : singleSpec;
|
||
|
||
auto app = appender!(int[])();
|
||
app.put(1);
|
||
app.put(2);
|
||
app.put(3);
|
||
assert("%s".format(app) == "Appender!(int[])(%s)".format([1,2,3]));
|
||
|
||
auto app2 = appender!string();
|
||
auto spec = singleSpec("%s");
|
||
app.toString(app2, spec);
|
||
assert(app2[] == "Appender!(int[])([1, 2, 3])");
|
||
|
||
auto app3 = appender!string();
|
||
spec = singleSpec("%(%04d, %)");
|
||
app.toString(app3, spec);
|
||
assert(app3[] == "Appender!(int[])(0001, 0002, 0003)");
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=17251
|
||
@safe pure nothrow unittest
|
||
{
|
||
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[]);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=13300
|
||
@safe pure nothrow unittest
|
||
{
|
||
static test(bool isPurePostblit)()
|
||
{
|
||
static if (!isPurePostblit)
|
||
static int i;
|
||
|
||
struct Simple
|
||
{
|
||
@disable this(); // Without this, it works.
|
||
static if (!isPurePostblit)
|
||
this(this) { i++; }
|
||
else
|
||
pure this(this) { }
|
||
|
||
private:
|
||
this(int tmp) { }
|
||
}
|
||
|
||
struct Range
|
||
{
|
||
@property Simple front() { return Simple(0); }
|
||
void popFront() { count++; }
|
||
@property empty() { return count < 3; }
|
||
size_t count;
|
||
}
|
||
|
||
Range r;
|
||
auto a = r.array();
|
||
}
|
||
|
||
static assert(__traits(compiles, () pure { test!true(); }));
|
||
static assert(!__traits(compiles, () pure { test!false(); }));
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=19572
|
||
@safe pure nothrow unittest
|
||
{
|
||
static struct Struct
|
||
{
|
||
int value;
|
||
|
||
int fun() const { return 23; }
|
||
|
||
alias fun this;
|
||
}
|
||
|
||
Appender!(Struct[]) appender;
|
||
|
||
appender.put(const(Struct)(42));
|
||
|
||
auto result = appender[][0];
|
||
|
||
assert(result.value != 23);
|
||
}
|
||
|
||
@safe pure unittest
|
||
{
|
||
import std.conv : to;
|
||
import std.utf : byCodeUnit;
|
||
auto str = "ウェブサイト";
|
||
auto wstr = appender!wstring();
|
||
put(wstr, str.byCodeUnit);
|
||
assert(wstr.data == str.to!wstring);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=21256
|
||
@safe pure unittest
|
||
{
|
||
Appender!string app1;
|
||
app1.toString();
|
||
|
||
Appender!(int[]) app2;
|
||
app2.toString();
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=24856
|
||
@system unittest
|
||
{
|
||
import core.memory : GC;
|
||
import std.stdio : writeln;
|
||
import std.algorithm.searching : canFind;
|
||
GC.disable();
|
||
scope(exit) GC.enable();
|
||
void*[] freeme;
|
||
// generate some poison blocks to allocate from.
|
||
auto poison = cast(void*) 0xdeadbeef;
|
||
foreach (i; 0 .. 10)
|
||
{
|
||
auto blk = new void*[7];
|
||
blk[] = poison;
|
||
freeme ~= blk.ptr;
|
||
}
|
||
|
||
foreach (p; freeme)
|
||
GC.free(p);
|
||
|
||
int tests = 0;
|
||
foreach (i; 0 .. 10)
|
||
{
|
||
Appender!(void*[]) app;
|
||
app.put(null);
|
||
// if not a realloc of one of the deadbeef pointers, continue
|
||
if (!freeme.canFind(app.data.ptr))
|
||
continue;
|
||
++tests;
|
||
assert(!app.data.ptr[0 .. app.capacity].canFind(poison), "Appender not zeroing data!");
|
||
}
|
||
// just notify in the log whether this test actually could be done.
|
||
if (tests == 0)
|
||
writeln("WARNING: test of Appender zeroing did not occur");
|
||
}
|
||
|
||
//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)
|
||
{
|
||
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.
|
||
*
|
||
* Params:
|
||
* A = The array type to simulate
|
||
*/
|
||
struct RefAppender(A)
|
||
if (isDynamicArray!A)
|
||
{
|
||
private alias T = ElementEncodingType!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, `capacity` returns `0`.
|
||
*/
|
||
@property size_t capacity() const
|
||
{
|
||
return impl.capacity;
|
||
}
|
||
|
||
/// Returns: The number of elements appended.
|
||
@property size_t length() const => impl.length;
|
||
|
||
/* Use opSlice() instead.
|
||
* Returns: the managed array.
|
||
*/
|
||
@property inout(T)[] data() inout
|
||
{
|
||
return impl[];
|
||
}
|
||
|
||
/**
|
||
* Returns: the managed array.
|
||
*/
|
||
@property inout(ElementEncodingType!A)[] opSlice() inout
|
||
{
|
||
return impl[];
|
||
}
|
||
}
|
||
|
||
///
|
||
@safe pure nothrow
|
||
unittest
|
||
{
|
||
int[] a = [1, 2];
|
||
auto app2 = appender(&a);
|
||
assert(app2[] == [1, 2]);
|
||
assert(a == [1, 2]);
|
||
app2 ~= 3;
|
||
assert(app2.length == 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 `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
|
||
{
|
||
auto app = appender!(char[])();
|
||
string b = "abcdefg";
|
||
foreach (char c; b) app.put(c);
|
||
assert(app[] == "abcdefg");
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
auto app = appender!(char[])();
|
||
string b = "abcdefg";
|
||
foreach (char c; b) app ~= c;
|
||
assert(app[] == "abcdefg");
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
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 ]);
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
auto app4 = appender([]);
|
||
try // shrinkTo may throw
|
||
{
|
||
app4.shrinkTo(0);
|
||
}
|
||
catch (Exception) assert(0);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=5663
|
||
// https://issues.dlang.org/show_bug.cgi?id=9725
|
||
@safe pure nothrow unittest
|
||
{
|
||
import std.exception : assertNotThrown;
|
||
|
||
static 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");
|
||
}
|
||
}
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=10122
|
||
@safe pure nothrow unittest
|
||
{
|
||
import std.exception : assertCTFEable;
|
||
|
||
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
|
||
{
|
||
import std.exception : assertThrown;
|
||
|
||
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]);
|
||
}
|
||
|
||
///
|
||
@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);
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
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);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=10690
|
||
@safe pure nothrow unittest
|
||
{
|
||
import std.algorithm.iteration : filter;
|
||
import std.typecons : tuple;
|
||
[tuple(1)].filter!(t => true).array; // No error
|
||
[tuple("A")].filter!(t => true).array; // error
|
||
}
|
||
|
||
@safe pure nothrow 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)[])();
|
||
a1.put(S1().repeat().take(10));
|
||
a2.put(S2().repeat().take(10));
|
||
auto sc1 = const(S1)();
|
||
au1.put(sc1.repeat().take(10));
|
||
}
|
||
|
||
@system pure unittest
|
||
{
|
||
import std.range;
|
||
struct S2
|
||
{
|
||
void opAssign(S2){}
|
||
}
|
||
auto au2 = Appender!(const(S2)[])();
|
||
auto sc2 = const(S2)();
|
||
au2.put(sc2.repeat().take(10));
|
||
}
|
||
|
||
@system pure nothrow 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]);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=9528
|
||
@safe pure nothrow unittest
|
||
{
|
||
const(E)[] fastCopy(E)(E[] src) {
|
||
auto app = appender!(const(E)[])();
|
||
foreach (i, e; src)
|
||
app.put(e);
|
||
return app[];
|
||
}
|
||
|
||
static class C {}
|
||
static struct S { const(C) c; }
|
||
S[] s = [ S(new C) ];
|
||
|
||
auto t = fastCopy(s); // Does not compile
|
||
assert(t.length == 1);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=10753
|
||
@safe pure unittest
|
||
{
|
||
import std.algorithm.iteration : map;
|
||
struct Foo {
|
||
immutable dchar d;
|
||
}
|
||
struct Bar {
|
||
immutable int x;
|
||
}
|
||
"12".map!Foo.array;
|
||
[1, 2].map!Bar.array;
|
||
}
|
||
|
||
@safe pure nothrow 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 pure nothrow 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 pure nothrow 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 pure nothrow 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 pure nothrow 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
|
||
{
|
||
// https://issues.dlang.org/show_bug.cgi?id=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;
|
||
assert(res.length == 1);
|
||
}
|
||
|
||
/++
|
||
Convenience function that returns a $(LREF RefAppender) instance initialized
|
||
with `arrayPtr`. Don't use null for the array pointer, use the other
|
||
version of `appender` instead.
|
||
+/
|
||
RefAppender!(E[]) appender(P : E[]*, E)(P arrayPtr)
|
||
{
|
||
return RefAppender!(E[])(arrayPtr);
|
||
}
|
||
|
||
///
|
||
@safe 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);
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
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");
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
auto arr = new char[0];
|
||
auto app = appender(&arr);
|
||
string b = "abcdefg";
|
||
foreach (char c; b) app ~= c;
|
||
assert(app[] == "abcdefg");
|
||
assert(arr == "abcdefg");
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
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 ]);
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
import std.exception : assertThrown;
|
||
|
||
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]);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=14605
|
||
@safe pure nothrow unittest
|
||
{
|
||
static assert(isOutputRange!(Appender!(int[]), int));
|
||
static assert(isOutputRange!(RefAppender!(int[]), int));
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
Appender!(int[]) app;
|
||
short[] range = [1, 2, 3];
|
||
app.put(range);
|
||
assert(app[] == [1, 2, 3]);
|
||
}
|
||
|
||
@safe pure nothrow 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");
|
||
}
|
||
|
||
/++
|
||
Constructs a static array from a dynamic array whose length is known at compile-time.
|
||
The element type can be inferred or specified explicitly:
|
||
|
||
* $(D [1, 2].staticArray) returns `int[2]`
|
||
* $(D [1, 2].staticArray!float) returns `float[2]`
|
||
|
||
Note: `staticArray` returns by value, so expressions involving large arrays may be inefficient.
|
||
|
||
Params:
|
||
a = The input array.
|
||
|
||
Returns: A static array constructed from `a`.
|
||
+/
|
||
pragma(inline, true) T[n] staticArray(T, size_t n)(auto ref T[n] a)
|
||
{
|
||
return a;
|
||
}
|
||
|
||
/// static array from array literal
|
||
nothrow pure @safe @nogc unittest
|
||
{
|
||
auto a = [0, 1].staticArray;
|
||
static assert(is(typeof(a) == int[2]));
|
||
assert(a == [0, 1]);
|
||
}
|
||
|
||
/// ditto
|
||
pragma(inline, true) U[n] staticArray(U, T, size_t n)(auto ref T[n] a)
|
||
if (!is(T == U) && is(T : U))
|
||
{
|
||
return a[].staticArray!(U[n]);
|
||
}
|
||
|
||
/// static array from array with implicit casting of elements
|
||
nothrow pure @safe @nogc unittest
|
||
{
|
||
auto b = [0, 1].staticArray!long;
|
||
static assert(is(typeof(b) == long[2]));
|
||
assert(b == [0, 1]);
|
||
}
|
||
|
||
nothrow pure @safe @nogc unittest
|
||
{
|
||
int val = 3;
|
||
static immutable gold = [1, 2, 3];
|
||
[1, 2, val].staticArray.checkStaticArray!int([1, 2, 3]);
|
||
|
||
@nogc void checkNogc()
|
||
{
|
||
[1, 2, val].staticArray.checkStaticArray!int(gold);
|
||
}
|
||
|
||
checkNogc();
|
||
|
||
[1, 2, val].staticArray!double.checkStaticArray!double(gold);
|
||
[1, 2, 3].staticArray!int.checkStaticArray!int(gold);
|
||
|
||
[1, 2, 3].staticArray!(const(int)).checkStaticArray!(const(int))(gold);
|
||
[1, 2, 3].staticArray!(const(double)).checkStaticArray!(const(double))(gold);
|
||
{
|
||
const(int)[3] a2 = [1, 2, 3].staticArray;
|
||
}
|
||
|
||
[cast(byte) 1, cast(byte) 129].staticArray.checkStaticArray!byte([1, -127]);
|
||
}
|
||
|
||
/**
|
||
Constructs a static array from a range.
|
||
When `a.length` is not known at compile time, the number of elements must be
|
||
given as a template argument (e.g. `myrange.staticArray!2`).
|
||
Size and type can be combined, if the source range elements are implicitly
|
||
convertible to the requested element type (eg: `2.iota.staticArray!(long[2])`).
|
||
|
||
When the range `a` is known at compile time, it can be given as a
|
||
template argument to avoid having to specify the number of elements
|
||
(e.g.: `staticArray!(2.iota)` or `staticArray!(double, 2.iota)`).
|
||
|
||
Params:
|
||
a = The input range. If there are less elements than the specified length of the static array,
|
||
the rest of it is default-initialized. If there are more than specified, the first elements
|
||
up to the specified length are used.
|
||
rangeLength = Output for the number of elements used from `a`. Optional.
|
||
*/
|
||
auto staticArray(size_t n, T)(scope T a)
|
||
if (isInputRange!T)
|
||
{
|
||
alias U = ElementType!T;
|
||
return staticArray!(U[n], U, n)(a);
|
||
}
|
||
|
||
/// ditto
|
||
auto staticArray(size_t n, T)(scope T a, out size_t rangeLength)
|
||
if (isInputRange!T)
|
||
{
|
||
alias U = ElementType!T;
|
||
return staticArray!(U[n], U, n)(a, rangeLength);
|
||
}
|
||
|
||
/// ditto
|
||
auto staticArray(Un : U[n], U, size_t n, T)(scope T a)
|
||
if (isInputRange!T && is(ElementType!T : U))
|
||
{
|
||
size_t extraStackSpace;
|
||
return staticArray!(Un, U, n)(a, extraStackSpace);
|
||
}
|
||
|
||
/// ditto
|
||
auto staticArray(Un : U[n], U, size_t n, T)(scope T a, out size_t rangeLength)
|
||
if (isInputRange!T && is(ElementType!T : U))
|
||
{
|
||
import std.algorithm.mutation : uninitializedFill;
|
||
import std.range : take;
|
||
import core.internal.lifetime : emplaceRef;
|
||
|
||
if (__ctfe)
|
||
{
|
||
size_t i;
|
||
// Compile-time version to avoid unchecked memory access.
|
||
Unqual!U[n] ret;
|
||
for (auto iter = a.take(n); !iter.empty; iter.popFront())
|
||
{
|
||
ret[i] = iter.front;
|
||
i++;
|
||
}
|
||
|
||
rangeLength = i;
|
||
return (() @trusted => cast(U[n]) ret)();
|
||
}
|
||
|
||
auto ret = (() @trusted
|
||
{
|
||
Unqual!U[n] theArray = void;
|
||
return theArray;
|
||
}());
|
||
|
||
size_t i;
|
||
if (true)
|
||
{
|
||
// ret was void-initialized so let's initialize the unfilled part manually.
|
||
// also prevents destructors to be called on uninitialized memory if
|
||
// an exception is thrown
|
||
scope (exit) ret[i .. $].uninitializedFill(U.init);
|
||
|
||
for (auto iter = a.take(n); !iter.empty; iter.popFront())
|
||
{
|
||
emplaceRef!U(ret[i++], iter.front);
|
||
}
|
||
}
|
||
|
||
rangeLength = i;
|
||
return (() @trusted => cast(U[n]) ret)();
|
||
}
|
||
|
||
/// static array from range + size
|
||
nothrow pure @safe @nogc unittest
|
||
{
|
||
import std.range : iota;
|
||
|
||
auto input = 3.iota;
|
||
auto a = input.staticArray!2;
|
||
static assert(is(typeof(a) == int[2]));
|
||
assert(a == [0, 1]);
|
||
auto b = input.staticArray!(long[4]);
|
||
static assert(is(typeof(b) == long[4]));
|
||
assert(b == [0, 1, 2, 0]);
|
||
}
|
||
|
||
// Tests that code compiles when there is an elaborate destructor and exceptions
|
||
// are thrown. Unfortunately can't test that memory is initialized
|
||
// before having a destructor called on it.
|
||
@safe nothrow unittest
|
||
{
|
||
// exists only to allow doing something in the destructor. Not tested
|
||
// at the end because value appears to depend on implementation of the.
|
||
// function.
|
||
static int preventersDestroyed = 0;
|
||
|
||
static struct CopyPreventer
|
||
{
|
||
bool on = false;
|
||
this(this)
|
||
{
|
||
if (on) throw new Exception("Thou shalt not copy past me!");
|
||
}
|
||
|
||
~this()
|
||
{
|
||
preventersDestroyed++;
|
||
}
|
||
}
|
||
auto normalArray =
|
||
[
|
||
CopyPreventer(false),
|
||
CopyPreventer(false),
|
||
CopyPreventer(true),
|
||
CopyPreventer(false),
|
||
CopyPreventer(true),
|
||
];
|
||
|
||
try
|
||
{
|
||
auto staticArray = normalArray.staticArray!5;
|
||
assert(false);
|
||
}
|
||
catch (Exception e){}
|
||
}
|
||
|
||
|
||
nothrow pure @safe @nogc unittest
|
||
{
|
||
auto a = [1, 2].staticArray;
|
||
assert(is(typeof(a) == int[2]) && a == [1, 2]);
|
||
|
||
import std.range : iota;
|
||
|
||
2.iota.staticArray!2.checkStaticArray!int([0, 1]);
|
||
2.iota.staticArray!(double[2]).checkStaticArray!double([0, 1]);
|
||
2.iota.staticArray!(long[2]).checkStaticArray!long([0, 1]);
|
||
}
|
||
|
||
nothrow pure @safe @nogc unittest
|
||
{
|
||
import std.range : iota;
|
||
size_t copiedAmount;
|
||
2.iota.staticArray!1(copiedAmount);
|
||
assert(copiedAmount == 1);
|
||
2.iota.staticArray!3(copiedAmount);
|
||
assert(copiedAmount == 2);
|
||
}
|
||
|
||
/// ditto
|
||
auto staticArray(alias a)()
|
||
if (isInputRange!(typeof(a)))
|
||
{
|
||
return .staticArray!(size_t(a.length))(a);
|
||
}
|
||
|
||
/// ditto
|
||
auto staticArray(U, alias a)()
|
||
if (isInputRange!(typeof(a)))
|
||
{
|
||
return .staticArray!(U[size_t(a.length)])(a);
|
||
}
|
||
|
||
/// static array from CT range
|
||
nothrow pure @safe @nogc unittest
|
||
{
|
||
import std.range : iota;
|
||
|
||
enum a = staticArray!(2.iota);
|
||
static assert(is(typeof(a) == int[2]));
|
||
assert(a == [0, 1]);
|
||
|
||
enum b = staticArray!(long, 2.iota);
|
||
static assert(is(typeof(b) == long[2]));
|
||
assert(b == [0, 1]);
|
||
}
|
||
|
||
nothrow pure @safe @nogc unittest
|
||
{
|
||
import std.range : iota;
|
||
|
||
enum a = staticArray!(2.iota);
|
||
staticArray!(2.iota).checkStaticArray!int([0, 1]);
|
||
staticArray!(double, 2.iota).checkStaticArray!double([0, 1]);
|
||
staticArray!(long, 2.iota).checkStaticArray!long([0, 1]);
|
||
}
|
||
|
||
version (StdUnittest) private void checkStaticArray(T, T1, T2)(T1 a, T2 b) nothrow @safe pure @nogc
|
||
{
|
||
static assert(is(T1 == T[T1.length]));
|
||
assert(a == b, "a must be equal to b");
|
||
}
|