mirror of
https://github.com/dlang/phobos.git
synced 2025-04-26 05:00:35 +03:00

* Fix UndocumentedDeclarationCheck linting issue * Fix IfConstraintsIndentCheck linting issue * Address feedback * Fix publictests CI * Fix old (libdparse) D-Scanner linting warn
2718 lines
77 KiB
D
2718 lines
77 KiB
D
/**
|
||
This module is a submodule of $(MREF std, range).
|
||
|
||
It defines the bidirectional and forward range primitives for arrays:
|
||
$(LREF empty), $(LREF front), $(LREF back), $(LREF popFront), $(LREF popBack) and $(LREF save).
|
||
|
||
It provides basic range functionality by defining several templates for testing
|
||
whether a given object is a range, and what kind of range it is:
|
||
|
||
$(SCRIPT inhibitQuickIndex = 1;)
|
||
$(DIVC quickindex,
|
||
$(BOOKTABLE ,
|
||
$(TR $(TD $(LREF isInputRange))
|
||
$(TD Tests if something is an $(I input range), defined to be
|
||
something from which one can sequentially read data using the
|
||
primitives `front`, `popFront`, and `empty`.
|
||
))
|
||
$(TR $(TD $(LREF isOutputRange))
|
||
$(TD Tests if something is an $(I output range), defined to be
|
||
something to which one can sequentially write data using the
|
||
$(LREF put) primitive.
|
||
))
|
||
$(TR $(TD $(LREF isForwardRange))
|
||
$(TD Tests if something is a $(I forward range), defined to be an
|
||
input range with the additional capability that one can save one's
|
||
current position with the `save` primitive, thus allowing one to
|
||
iterate over the same range multiple times.
|
||
))
|
||
$(TR $(TD $(LREF isBidirectionalRange))
|
||
$(TD Tests if something is a $(I bidirectional range), that is, a
|
||
forward range that allows reverse traversal using the primitives $(D
|
||
back) and `popBack`.
|
||
))
|
||
$(TR $(TD $(LREF isRandomAccessRange))
|
||
$(TD Tests if something is a $(I random access range), which is a
|
||
bidirectional range that also supports the array subscripting
|
||
operation via the primitive `opIndex`.
|
||
))
|
||
))
|
||
|
||
It also provides number of templates that test for various range capabilities:
|
||
|
||
$(BOOKTABLE ,
|
||
$(TR $(TD $(LREF hasMobileElements))
|
||
$(TD Tests if a given range's elements can be moved around using the
|
||
primitives `moveFront`, `moveBack`, or `moveAt`.
|
||
))
|
||
$(TR $(TD $(LREF ElementType))
|
||
$(TD Returns the element type of a given range.
|
||
))
|
||
$(TR $(TD $(LREF ElementEncodingType))
|
||
$(TD Returns the encoding element type of a given range.
|
||
))
|
||
$(TR $(TD $(LREF hasSwappableElements))
|
||
$(TD Tests if a range is a forward range with swappable elements.
|
||
))
|
||
$(TR $(TD $(LREF hasAssignableElements))
|
||
$(TD Tests if a range is a forward range with mutable elements.
|
||
))
|
||
$(TR $(TD $(LREF hasLvalueElements))
|
||
$(TD Tests if a range is a forward range with elements that can be
|
||
passed by reference and have their address taken.
|
||
))
|
||
$(TR $(TD $(LREF hasLength))
|
||
$(TD Tests if a given range has the `length` attribute.
|
||
))
|
||
$(TR $(TD $(LREF isInfinite))
|
||
$(TD Tests if a given range is an $(I infinite range).
|
||
))
|
||
$(TR $(TD $(LREF hasSlicing))
|
||
$(TD Tests if a given range supports the array slicing operation $(D
|
||
R[x .. y]).
|
||
))
|
||
)
|
||
|
||
Finally, it includes some convenience functions for manipulating ranges:
|
||
|
||
$(BOOKTABLE ,
|
||
$(TR $(TD $(LREF popFrontN))
|
||
$(TD Advances a given range by up to $(I n) elements.
|
||
))
|
||
$(TR $(TD $(LREF popBackN))
|
||
$(TD Advances a given bidirectional range from the right by up to
|
||
$(I n) elements.
|
||
))
|
||
$(TR $(TD $(LREF popFrontExactly))
|
||
$(TD Advances a given range by up exactly $(I n) elements.
|
||
))
|
||
$(TR $(TD $(LREF popBackExactly))
|
||
$(TD Advances a given bidirectional range from the right by exactly
|
||
$(I n) elements.
|
||
))
|
||
$(TR $(TD $(LREF moveFront))
|
||
$(TD Removes the front element of a range.
|
||
))
|
||
$(TR $(TD $(LREF moveBack))
|
||
$(TD Removes the back element of a bidirectional range.
|
||
))
|
||
$(TR $(TD $(LREF moveAt))
|
||
$(TD Removes the $(I i)'th element of a random-access range.
|
||
))
|
||
$(TR $(TD $(LREF walkLength))
|
||
$(TD Computes the length of any range in O(n) time.
|
||
))
|
||
$(TR $(TD $(LREF put))
|
||
$(TD Outputs element `e` to a range.
|
||
))
|
||
)
|
||
|
||
Source: $(PHOBOSSRC std/range/primitives.d)
|
||
|
||
License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
||
|
||
Authors: $(HTTP erdani.com, Andrei Alexandrescu), David Simcha, and
|
||
$(HTTP jmdavisprog.com, Jonathan M Davis). Credit for some of the ideas
|
||
in building this module goes to
|
||
$(HTTP fantascienza.net/leonardo/so/, Leonardo Maffi).
|
||
*/
|
||
module std.range.primitives;
|
||
|
||
import std.traits;
|
||
|
||
/**
|
||
Returns `true` if `R` is an input range. An input range must
|
||
define the primitives `empty`, `popFront`, and `front`. The
|
||
following code should compile for any input range.
|
||
|
||
----
|
||
R r; // can define a range object
|
||
if (r.empty) {} // can test for empty
|
||
r.popFront(); // can invoke popFront()
|
||
auto h = r.front; // can get the front of the range of non-void type
|
||
----
|
||
|
||
The following are rules of input ranges are assumed to hold true in all
|
||
Phobos code. These rules are not checkable at compile-time, so not conforming
|
||
to these rules when writing ranges or range based code will result in
|
||
undefined behavior.
|
||
|
||
$(UL
|
||
$(LI `r.empty` returns `false` if and only if there is more data
|
||
available in the range.)
|
||
$(LI `r.empty` evaluated multiple times, without calling
|
||
`r.popFront`, or otherwise mutating the range object or the
|
||
underlying data, yields the same result for every evaluation.)
|
||
$(LI `r.front` returns the current element in the range.
|
||
It may return by value or by reference.)
|
||
$(LI `r.front` can be legally evaluated if and only if evaluating
|
||
`r.empty` has, or would have, equaled `false`.)
|
||
$(LI `r.front` evaluated multiple times, without calling
|
||
`r.popFront`, or otherwise mutating the range object or the
|
||
underlying data, yields the same result for every evaluation.)
|
||
$(LI `r.popFront` advances to the next element in the range.)
|
||
$(LI `r.popFront` can be called if and only if evaluating `r.empty`
|
||
has, or would have, equaled `false`.)
|
||
)
|
||
|
||
Also, note that Phobos code assumes that the primitives `r.front` and
|
||
`r.empty` are $(BIGOH 1) time complexity wise or "cheap" in terms of
|
||
running time. $(BIGOH) statements in the documentation of range functions
|
||
are made with this assumption.
|
||
|
||
See_Also:
|
||
The header of $(MREF std,range) for tutorials on ranges.
|
||
|
||
Params:
|
||
R = type to be tested
|
||
E = if present, the elements of the range must be
|
||
$(DDSUBLINK spec/const3, implicit_qualifier_conversions, qualifier-convertible)
|
||
to this type
|
||
|
||
Returns:
|
||
`true` if R is an input range (possibly with element type `E`), `false` if not
|
||
*/
|
||
enum bool isInputRange(R) =
|
||
is(typeof(R.init) == R)
|
||
&& is(typeof((R r) { return r.empty; } (R.init)) == bool)
|
||
&& (is(typeof((return ref R r) => r.front)) || is(typeof(ref (return ref R r) => r.front)))
|
||
&& !is(typeof((R r) { return r.front; } (R.init)) == void)
|
||
&& is(typeof((R r) => r.popFront));
|
||
|
||
/// ditto
|
||
enum bool isInputRange(R, E) =
|
||
.isInputRange!R && isQualifierConvertible!(ElementType!R, E);
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
struct A {}
|
||
struct B
|
||
{
|
||
void popFront();
|
||
@property bool empty();
|
||
@property int front();
|
||
}
|
||
static assert(!isInputRange!A);
|
||
static assert( isInputRange!B);
|
||
static assert( isInputRange!(int[]));
|
||
static assert( isInputRange!(char[]));
|
||
static assert(!isInputRange!(char[4]));
|
||
static assert( isInputRange!(inout(int)[]));
|
||
static assert(!isInputRange!(int[], string));
|
||
static assert( isInputRange!(int[], int));
|
||
static assert( isInputRange!(int[], const int));
|
||
static assert(!isInputRange!(int[], immutable int));
|
||
|
||
static assert(!isInputRange!(const(int)[], int));
|
||
static assert( isInputRange!(const(int)[], const int));
|
||
static assert(!isInputRange!(const(int)[], immutable int));
|
||
|
||
static assert(!isInputRange!(immutable(int)[], int));
|
||
static assert( isInputRange!(immutable(int)[], const int));
|
||
static assert( isInputRange!(immutable(int)[], immutable int));
|
||
|
||
static struct NotDefaultConstructible
|
||
{
|
||
@disable this();
|
||
void popFront();
|
||
@property bool empty();
|
||
@property int front();
|
||
}
|
||
static assert( isInputRange!NotDefaultConstructible);
|
||
|
||
static struct NotDefaultConstructibleOrCopyable
|
||
{
|
||
@disable this();
|
||
@disable this(this);
|
||
void popFront();
|
||
@property bool empty();
|
||
@property int front();
|
||
}
|
||
static assert(isInputRange!NotDefaultConstructibleOrCopyable);
|
||
|
||
static struct Frontless
|
||
{
|
||
void popFront();
|
||
@property bool empty();
|
||
}
|
||
static assert(!isInputRange!Frontless);
|
||
|
||
static struct VoidFront
|
||
{
|
||
void popFront();
|
||
@property bool empty();
|
||
void front();
|
||
}
|
||
static assert(!isInputRange!VoidFront);
|
||
}
|
||
// https://issues.dlang.org/show_bug.cgi?id=16034
|
||
@safe unittest
|
||
{
|
||
struct One
|
||
{
|
||
int entry = 1;
|
||
@disable this(this);
|
||
}
|
||
|
||
assert(isInputRange!(One[]));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
import std.algorithm.comparison : equal;
|
||
|
||
static struct R
|
||
{
|
||
static struct Front
|
||
{
|
||
R* impl;
|
||
@property int value() { return impl._front; }
|
||
alias value this;
|
||
}
|
||
|
||
int _front;
|
||
|
||
@property bool empty() { return _front >= 3; }
|
||
@property auto front() { return Front(&this); }
|
||
void popFront() { _front++; }
|
||
}
|
||
R r;
|
||
|
||
static assert(isInputRange!R);
|
||
assert(r.equal([ 0, 1, 2 ]));
|
||
}
|
||
|
||
/+
|
||
puts the whole raw element `e` into `r`. doPut will not attempt to
|
||
iterate, slice or transcode `e` in any way shape or form. It will $(B only)
|
||
call the correct primitive (`r.put(e)`, $(D r.front = e) or
|
||
`r(e)` once.
|
||
|
||
This can be important when `e` needs to be placed in `r` unchanged.
|
||
Furthermore, it can be useful when working with `InputRange`s, as doPut
|
||
guarantees that no more than a single element will be placed.
|
||
+/
|
||
private void doPut(R, E)(ref R r, auto ref E e)
|
||
{
|
||
static if (is(PointerTarget!R == struct))
|
||
enum usingPut = hasMember!(PointerTarget!R, "put");
|
||
else
|
||
enum usingPut = hasMember!(R, "put");
|
||
|
||
static if (usingPut)
|
||
{
|
||
static assert(is(typeof(r.put(e))),
|
||
"Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ ".");
|
||
r.put(e);
|
||
}
|
||
else static if (isNarrowString!R && is(const(E) == const(typeof(r[0]))))
|
||
{
|
||
// one character, we can put it
|
||
r[0] = e;
|
||
r = r[1 .. $];
|
||
}
|
||
else static if (isNarrowString!R && isNarrowString!E && is(typeof(r[] = e)))
|
||
{
|
||
// slice assign. Note that this is a duplicate from put, but because
|
||
// putChar uses doPut exclusively, we have to copy it here.
|
||
immutable len = e.length;
|
||
r[0 .. len] = e;
|
||
r = r[len .. $];
|
||
}
|
||
else static if (isInputRange!R)
|
||
{
|
||
static assert(is(typeof(r.front = e)),
|
||
"Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ ".");
|
||
r.front = e;
|
||
r.popFront();
|
||
}
|
||
else static if (is(typeof(r(e))))
|
||
{
|
||
r(e);
|
||
}
|
||
else
|
||
{
|
||
static assert(false,
|
||
"Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ ".");
|
||
}
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
static assert(!isNativeOutputRange!(int, int));
|
||
static assert( isNativeOutputRange!(int[], int));
|
||
static assert(!isNativeOutputRange!(int[][], int));
|
||
|
||
static assert(!isNativeOutputRange!(int, int[]));
|
||
static assert(!isNativeOutputRange!(int[], int[]));
|
||
static assert( isNativeOutputRange!(int[][], int[]));
|
||
|
||
static assert(!isNativeOutputRange!(int, int[][]));
|
||
static assert(!isNativeOutputRange!(int[], int[][]));
|
||
static assert(!isNativeOutputRange!(int[][], int[][]));
|
||
|
||
static assert(!isNativeOutputRange!(int[4], int));
|
||
static assert( isNativeOutputRange!(int[4][], int)); //Scary!
|
||
static assert( isNativeOutputRange!(int[4][], int[4]));
|
||
|
||
static assert( isNativeOutputRange!( char[], char));
|
||
static assert(!isNativeOutputRange!( char[], dchar));
|
||
static assert( isNativeOutputRange!(dchar[], char));
|
||
static assert( isNativeOutputRange!(dchar[], dchar));
|
||
|
||
}
|
||
|
||
/++
|
||
Outputs `e` to `r`. The exact effect is dependent upon the two
|
||
types. Several cases are accepted, as described below. The code snippets
|
||
are attempted in order, and the first to compile "wins" and gets
|
||
evaluated.
|
||
|
||
In this table "doPut" is a method that places `e` into `r`, using the
|
||
correct primitive: `r.put(e)` if `R` defines `put`, $(D r.front = e)
|
||
if `r` is an input range (followed by `r.popFront()`), or `r(e)`
|
||
otherwise.
|
||
|
||
$(BOOKTABLE ,
|
||
$(TR
|
||
$(TH Code Snippet)
|
||
$(TH Scenario)
|
||
)
|
||
$(TR
|
||
$(TD `r.doPut(e);`)
|
||
$(TD `R` specifically accepts an `E`.)
|
||
)
|
||
$(TR
|
||
$(TD $(D r.doPut([ e ]);))
|
||
$(TD `R` specifically accepts an `E[]`.)
|
||
)
|
||
$(TR
|
||
$(TD `r.putChar(e);`)
|
||
$(TD `R` accepts some form of string or character. put will
|
||
transcode the character `e` accordingly.)
|
||
)
|
||
$(TR
|
||
$(TD $(D for (; !e.empty; e.popFront()) put(r, e.front);))
|
||
$(TD Copying range `E` into `R`.)
|
||
)
|
||
)
|
||
|
||
Tip: `put` should $(I not) be used "UFCS-style", e.g. `r.put(e)`.
|
||
Doing this may call `R.put` directly, by-passing any transformation
|
||
feature provided by `Range.put`. $(D put(r, e)) is prefered.
|
||
+/
|
||
void put(R, E)(ref R r, E e)
|
||
{
|
||
//First level: simply straight up put.
|
||
static if (is(typeof(doPut(r, e))))
|
||
{
|
||
doPut(r, e);
|
||
}
|
||
//Optional optimization block for straight up array to array copy.
|
||
else static if (isDynamicArray!R &&
|
||
!isAutodecodableString!R &&
|
||
isDynamicArray!E &&
|
||
is(typeof(r[] = e[])))
|
||
{
|
||
immutable len = e.length;
|
||
r[0 .. len] = e[];
|
||
r = r[len .. $];
|
||
}
|
||
//Accepts E[] ?
|
||
else static if (is(typeof(doPut(r, [e]))) && !isDynamicArray!R)
|
||
{
|
||
if (__ctfe)
|
||
{
|
||
E[1] arr = [e];
|
||
doPut(r, arr[]);
|
||
}
|
||
else
|
||
doPut(r, (ref e) @trusted { return (&e)[0 .. 1]; }(e));
|
||
}
|
||
//special case for char to string.
|
||
else static if (isSomeChar!E && is(typeof(putChar(r, e))))
|
||
{
|
||
putChar(r, e);
|
||
}
|
||
//Extract each element from the range
|
||
//We can use "put" here, so we can recursively test a RoR of E.
|
||
else static if (isInputRange!E && is(typeof(put(r, e.front))))
|
||
{
|
||
//Special optimization: If E is a narrow string, and r accepts characters no-wider than the string's
|
||
//Then simply feed the characters 1 by 1.
|
||
static if (isAutodecodableString!E && !isAggregateType!E && (
|
||
(is(E : const char[]) && is(typeof(doPut(r, char.max))) && !is(typeof(doPut(r, dchar.max))) &&
|
||
!is(typeof(doPut(r, wchar.max)))) ||
|
||
(is(E : const wchar[]) && is(typeof(doPut(r, wchar.max))) && !is(typeof(doPut(r, dchar.max)))) ) )
|
||
{
|
||
foreach (c; e)
|
||
doPut(r, c);
|
||
}
|
||
else
|
||
{
|
||
for (; !e.empty; e.popFront())
|
||
put(r, e.front);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
static assert(false, "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ ".");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* When an output range's `put` method only accepts elements of type
|
||
* `T`, use the global `put` to handle outputting a `T[]` to the range
|
||
* or vice-versa.
|
||
*/
|
||
@safe pure unittest
|
||
{
|
||
import std.traits : isSomeChar;
|
||
|
||
static struct A
|
||
{
|
||
string data;
|
||
|
||
void put(C)(C c)
|
||
if (isSomeChar!C)
|
||
{
|
||
data ~= c;
|
||
}
|
||
}
|
||
static assert(isOutputRange!(A, char));
|
||
|
||
auto a = A();
|
||
put(a, "Hello");
|
||
assert(a.data == "Hello");
|
||
}
|
||
|
||
/**
|
||
* `put` treats dynamic arrays as array slices, and will call `popFront`
|
||
* on the slice after an element has been copied.
|
||
*
|
||
* Be sure to save the position of the array before calling `put`.
|
||
*/
|
||
@safe pure nothrow unittest
|
||
{
|
||
int[] a = [1, 2, 3], b = [10, 20];
|
||
auto c = a;
|
||
put(a, b);
|
||
assert(c == [10, 20, 3]);
|
||
// at this point, a was advanced twice, so it only contains
|
||
// its last element while c represents the whole array
|
||
assert(a == [3]);
|
||
}
|
||
|
||
/**
|
||
* It's also possible to `put` any width strings or characters into narrow
|
||
* strings -- put does the conversion for you.
|
||
*
|
||
* Note that putting the same width character as the target buffer type is
|
||
* `nothrow`, but transcoding can throw a $(REF UTFException, std, utf).
|
||
*/
|
||
@safe pure unittest
|
||
{
|
||
// the elements must be mutable, so using string or const(char)[]
|
||
// won't compile
|
||
char[] s1 = new char[13];
|
||
auto r1 = s1;
|
||
put(r1, "Hello, World!"w);
|
||
assert(s1 == "Hello, World!");
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
// same thing, just using same character width.
|
||
char[] s1 = new char[13];
|
||
auto r1 = s1;
|
||
put(r1, "Hello, World!");
|
||
assert(s1 == "Hello, World!");
|
||
}
|
||
|
||
|
||
@safe pure nothrow @nogc unittest
|
||
{
|
||
static struct R() { void put(scope const(char)[]) {} }
|
||
R!() r;
|
||
put(r, 'a');
|
||
}
|
||
|
||
//Helper function to handle chars as quickly and as elegantly as possible
|
||
//Assumes r.put(e)/r(e) has already been tested
|
||
private void putChar(R, E)(ref R r, E e)
|
||
if (isSomeChar!E)
|
||
{
|
||
// https://issues.dlang.org/show_bug.cgi?id=9186: Can't use (E[]).init
|
||
enum csCond = is(typeof(doPut(r, (){ ref const( char)[] cstringInit(); return cstringInit(); }())));
|
||
enum wsCond = is(typeof(doPut(r, (){ ref const(wchar)[] wstringInit(); return wstringInit(); }())));
|
||
enum dsCond = is(typeof(doPut(r, (){ ref const(dchar)[] dstringInit(); return dstringInit(); }())));
|
||
|
||
//Use "max" to avoid static type demotion
|
||
enum ccCond = is(typeof(doPut(r, char.max)));
|
||
enum wcCond = is(typeof(doPut(r, wchar.max)));
|
||
//enum dcCond = is(typeof(doPut(r, dchar.max)));
|
||
|
||
//Fast transform a narrow char into a wider string
|
||
static if ((wsCond && E.sizeof < wchar.sizeof) || (dsCond && E.sizeof < dchar.sizeof))
|
||
{
|
||
enum w = wsCond && E.sizeof < wchar.sizeof;
|
||
Select!(w, wchar, dchar) c = e;
|
||
typeof(c)[1] arr = [c];
|
||
doPut(r, arr[]);
|
||
}
|
||
//Encode a wide char into a narrower string
|
||
else static if (wsCond || csCond)
|
||
{
|
||
import std.utf : encode;
|
||
/+static+/ Select!(wsCond, wchar[2], char[4]) buf; //static prevents purity.
|
||
doPut(r, buf[0 .. encode(buf, e)]);
|
||
}
|
||
//Slowly encode a wide char into a series of narrower chars
|
||
else static if (wcCond || ccCond)
|
||
{
|
||
import std.encoding : encode;
|
||
alias C = Select!(wcCond, wchar, char);
|
||
encode!(C, R)(e, r);
|
||
}
|
||
else
|
||
{
|
||
static assert(false, "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ ".");
|
||
}
|
||
}
|
||
|
||
pure @safe unittest
|
||
{
|
||
auto f = delegate (const(char)[]) {};
|
||
putChar(f, cast(dchar)'a');
|
||
}
|
||
|
||
|
||
@safe pure unittest
|
||
{
|
||
static struct R() { void put(scope const(char)[]) {} }
|
||
R!() r;
|
||
putChar(r, 'a');
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
struct A {}
|
||
static assert(!isInputRange!(A));
|
||
struct B
|
||
{
|
||
void put(int) {}
|
||
}
|
||
B b;
|
||
put(b, 5);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
int[] a = new int[10];
|
||
int b;
|
||
static assert(isInputRange!(typeof(a)));
|
||
put(a, b);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
void myprint(scope const(char)[] s) { }
|
||
auto r = &myprint;
|
||
put(r, 'a');
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
int[] a = new int[10];
|
||
static assert(!__traits(compiles, put(a, 1.0L)));
|
||
put(a, 1);
|
||
assert(a.length == 9);
|
||
/*
|
||
* a[0] = 65; // OK
|
||
* a[0] = 'A'; // OK
|
||
* a[0] = "ABC"[0]; // OK
|
||
* put(a, "ABC"); // OK
|
||
*/
|
||
put(a, "ABC");
|
||
assert(a.length == 6);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
char[] a = new char[10];
|
||
static assert(!__traits(compiles, put(a, 1.0L)));
|
||
static assert(!__traits(compiles, put(a, 1)));
|
||
//char[] is now an output range for char, wchar, dchar, and ranges of such.
|
||
static assert(__traits(compiles, putChar(a, 'a')));
|
||
static assert(__traits(compiles, put(a, wchar('a'))));
|
||
static assert(__traits(compiles, put(a, dchar('a'))));
|
||
static assert(__traits(compiles, put(a, "ABC")));
|
||
static assert(__traits(compiles, put(a, "ABC"w)));
|
||
static assert(__traits(compiles, put(a, "ABC"d)));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
// attempt putting into narrow strings by transcoding
|
||
char[] a = new char[10];
|
||
auto b = a;
|
||
put(a, "ABC"w);
|
||
assert(b[0 .. 3] == "ABC");
|
||
assert(a.length == 7);
|
||
|
||
a = b; // reset
|
||
put(a, 'λ');
|
||
assert(b[0 .. 2] == "λ");
|
||
assert(a.length == 8);
|
||
|
||
a = b; // reset
|
||
put(a, "ABC"d);
|
||
assert(b[0 .. 3] == "ABC");
|
||
assert(a.length == 7);
|
||
|
||
a = b; // reset
|
||
put(a, '𐐷');
|
||
assert(b[0 .. 4] == "𐐷");
|
||
assert(a.length == 6);
|
||
|
||
wchar[] aw = new wchar[10];
|
||
auto bw = aw;
|
||
put(aw, "ABC");
|
||
assert(bw[0 .. 3] == "ABC"w);
|
||
assert(aw.length == 7);
|
||
|
||
aw = bw; // reset
|
||
put(aw, 'λ');
|
||
assert(bw[0 .. 1] == "λ"w);
|
||
assert(aw.length == 9);
|
||
|
||
aw = bw; // reset
|
||
put(aw, "ABC"d);
|
||
assert(bw[0 .. 3] == "ABC"w);
|
||
assert(aw.length == 7);
|
||
|
||
aw = bw; // reset
|
||
put(aw, '𐐷');
|
||
assert(bw[0 .. 2] == "𐐷"w);
|
||
assert(aw.length == 8);
|
||
|
||
aw = bw; // reset
|
||
put(aw, "𐐷"); // try transcoding from char[]
|
||
assert(bw[0 .. 2] == "𐐷"w);
|
||
assert(aw.length == 8);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
int[][] a = new int[][10];
|
||
int[] b = new int[10];
|
||
int c;
|
||
put(b, c);
|
||
assert(b.length == 9);
|
||
put(a, b);
|
||
assert(a.length == 9);
|
||
static assert(!__traits(compiles, put(a, c)));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
int[][] a = new int[][](3);
|
||
int[] b = [1];
|
||
auto aa = a;
|
||
put(aa, b);
|
||
assert(aa == [[], []]);
|
||
assert(a == [[1], [], []]);
|
||
int[][3] c = [2];
|
||
aa = a;
|
||
put(aa, c[]);
|
||
assert(aa.empty);
|
||
assert(a == [[2], [2], [2]]);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
// Test fix for bug 7476.
|
||
struct LockingTextWriter
|
||
{
|
||
void put(dchar c){}
|
||
}
|
||
struct RetroResult
|
||
{
|
||
bool end = false;
|
||
@property bool empty() const { return end; }
|
||
@property dchar front(){ return 'a'; }
|
||
void popFront(){ end = true; }
|
||
}
|
||
LockingTextWriter w;
|
||
RetroResult re;
|
||
put(w, re);
|
||
}
|
||
|
||
@system unittest
|
||
{
|
||
import std.conv : to;
|
||
import std.meta : AliasSeq;
|
||
import std.typecons : tuple;
|
||
|
||
static struct PutC(C)
|
||
{
|
||
string result;
|
||
void put(const(C) c) { result ~= to!string((&c)[0 .. 1]); }
|
||
}
|
||
static struct PutS(C)
|
||
{
|
||
string result;
|
||
void put(const(C)[] s) { result ~= to!string(s); }
|
||
}
|
||
static struct PutSS(C)
|
||
{
|
||
string result;
|
||
void put(const(C)[][] ss)
|
||
{
|
||
foreach (s; ss)
|
||
result ~= to!string(s);
|
||
}
|
||
}
|
||
|
||
PutS!char p;
|
||
putChar(p, cast(dchar)'a');
|
||
|
||
//Source Char
|
||
static foreach (SC; AliasSeq!(char, wchar, dchar))
|
||
{{
|
||
SC ch = 'I';
|
||
dchar dh = '♥';
|
||
immutable(SC)[] s = "日本語!";
|
||
immutable(SC)[][] ss = ["日本語", "が", "好き", "ですか", "?"];
|
||
|
||
//Target Char
|
||
static foreach (TC; AliasSeq!(char, wchar, dchar))
|
||
{
|
||
//Testing PutC and PutS
|
||
static foreach (Type; AliasSeq!(PutC!TC, PutS!TC))
|
||
{{
|
||
Type type;
|
||
auto sink = new Type();
|
||
|
||
//Testing put and sink
|
||
foreach (value ; tuple(type, sink))
|
||
{
|
||
put(value, ch);
|
||
assert(value.result == "I");
|
||
put(value, dh);
|
||
assert(value.result == "I♥");
|
||
put(value, s);
|
||
assert(value.result == "I♥日本語!");
|
||
put(value, ss);
|
||
assert(value.result == "I♥日本語!日本語が好きですか?");
|
||
}
|
||
}}
|
||
}
|
||
}}
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
static struct CharRange
|
||
{
|
||
char c;
|
||
enum empty = false;
|
||
void popFront(){}
|
||
ref char front() return @property
|
||
{
|
||
return c;
|
||
}
|
||
}
|
||
CharRange c;
|
||
put(c, cast(dchar)'H');
|
||
put(c, "hello"d);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=9823
|
||
@system unittest
|
||
{
|
||
const(char)[] r;
|
||
void delegate(const(char)[]) dg = (s) { r = s; };
|
||
put(dg, ["ABC"]);
|
||
assert(r == "ABC");
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=10571
|
||
@safe unittest
|
||
{
|
||
import std.format.write : formattedWrite;
|
||
string buf;
|
||
formattedWrite((scope const(char)[] s) { buf ~= s; }, "%s", "hello");
|
||
assert(buf == "hello");
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
import std.format.write : formattedWrite;
|
||
import std.meta : AliasSeq;
|
||
struct PutC(C)
|
||
{
|
||
void put(C){}
|
||
}
|
||
struct PutS(C)
|
||
{
|
||
void put(const(C)[]){}
|
||
}
|
||
struct CallC(C)
|
||
{
|
||
void opCall(C){}
|
||
}
|
||
struct CallS(C)
|
||
{
|
||
void opCall(const(C)[]){}
|
||
}
|
||
struct FrontC(C)
|
||
{
|
||
enum empty = false;
|
||
auto front()@property{return C.init;}
|
||
void front(C)@property{}
|
||
void popFront(){}
|
||
}
|
||
struct FrontS(C)
|
||
{
|
||
enum empty = false;
|
||
auto front()@property{return C[].init;}
|
||
void front(const(C)[])@property{}
|
||
void popFront(){}
|
||
}
|
||
void foo()
|
||
{
|
||
static foreach (C; AliasSeq!(char, wchar, dchar))
|
||
{{
|
||
formattedWrite((C c){}, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
|
||
formattedWrite((const(C)[]){}, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
|
||
formattedWrite(PutC!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
|
||
formattedWrite(PutS!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
|
||
CallC!C callC;
|
||
CallS!C callS;
|
||
formattedWrite(callC, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
|
||
formattedWrite(callS, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
|
||
formattedWrite(FrontC!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
|
||
formattedWrite(FrontS!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
|
||
}}
|
||
formattedWrite((dchar[]).init, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
|
||
}
|
||
}
|
||
|
||
/+
|
||
Returns `true` if `R` is a native output range for elements of type
|
||
`E`. An output range is defined functionally as a range that
|
||
supports the operation $(D doPut(r, e)) as defined above. if $(D doPut(r, e))
|
||
is valid, then `put(r,e)` will have the same behavior.
|
||
|
||
The two guarantees isNativeOutputRange gives over the larger `isOutputRange`
|
||
are:
|
||
1: `e` is $(B exactly) what will be placed (not `[e]`, for example).
|
||
2: if `E` is a non $(empty) `InputRange`, then placing `e` is
|
||
guaranteed to not overflow the range.
|
||
+/
|
||
package(std) enum bool isNativeOutputRange(R, E) =
|
||
is(typeof(doPut(lvalueOf!R, lvalueOf!E)));
|
||
|
||
@safe unittest
|
||
{
|
||
int[] r = new int[](4);
|
||
static assert(isInputRange!(int[]));
|
||
static assert( isNativeOutputRange!(int[], int));
|
||
static assert(!isNativeOutputRange!(int[], int[]));
|
||
static assert( isOutputRange!(int[], int[]));
|
||
|
||
if (!r.empty)
|
||
put(r, 1); //guaranteed to succeed
|
||
if (!r.empty)
|
||
put(r, [1, 2]); //May actually error out.
|
||
}
|
||
|
||
/++
|
||
Returns `true` if `R` is an output range for elements of type
|
||
`E`. An output range is defined functionally as a range that
|
||
supports the operation $(D put(r, e)) as defined above.
|
||
|
||
See_Also:
|
||
The header of $(MREF std,range) for tutorials on ranges.
|
||
+/
|
||
enum bool isOutputRange(R, E) =
|
||
is(typeof(put(lvalueOf!R, lvalueOf!E)));
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
void myprint(scope const(char)[] s) { }
|
||
static assert(isOutputRange!(typeof(&myprint), char));
|
||
|
||
static assert( isOutputRange!(char[], char));
|
||
static assert( isOutputRange!(dchar[], wchar));
|
||
static assert( isOutputRange!(dchar[], dchar));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
import std.array;
|
||
import std.stdio : writeln;
|
||
|
||
auto app = appender!string();
|
||
string s;
|
||
static assert( isOutputRange!(Appender!string, string));
|
||
static assert( isOutputRange!(Appender!string*, string));
|
||
static assert(!isOutputRange!(Appender!string, int));
|
||
static assert( isOutputRange!(wchar[], wchar));
|
||
static assert( isOutputRange!(dchar[], char));
|
||
static assert( isOutputRange!(dchar[], string));
|
||
static assert( isOutputRange!(dchar[], wstring));
|
||
static assert( isOutputRange!(dchar[], dstring));
|
||
|
||
static assert(!isOutputRange!(const(int)[], int));
|
||
static assert(!isOutputRange!(inout(int)[], int));
|
||
}
|
||
|
||
|
||
/**
|
||
Returns `true` if `R` is a forward range. A forward range is an
|
||
input range `r` that can save "checkpoints" by saving `r.save`
|
||
to another value of type `R`. Notable examples of input ranges that
|
||
are $(I not) forward ranges are file/socket ranges; copying such a
|
||
range will not save the position in the stream, and they most likely
|
||
reuse an internal buffer as the entire stream does not sit in
|
||
memory. Subsequently, advancing either the original or the copy will
|
||
advance the stream, so the copies are not independent.
|
||
|
||
The following code should compile for any forward range.
|
||
|
||
----
|
||
static assert(isInputRange!R);
|
||
R r1;
|
||
auto s1 = r1.save;
|
||
static assert(is(typeof(s1) == R));
|
||
----
|
||
|
||
Saving a range is not duplicating it; in the example above, `r1`
|
||
and `r2` still refer to the same underlying data. They just
|
||
navigate that data independently.
|
||
|
||
The semantics of a forward range (not checkable during compilation)
|
||
are the same as for an input range, with the additional requirement
|
||
that backtracking must be possible by saving a copy of the range
|
||
object with `save` and using it later.
|
||
|
||
`save` behaves in many ways like a copy constructor, and its
|
||
implementation typically is done using copy construction.
|
||
|
||
The existence of a copy constructor, however, does not imply
|
||
the range is a forward range. For example, a range that reads
|
||
from a TTY consumes its input and cannot save its place and
|
||
read it again, and so cannot be a forward range and cannot
|
||
have a `save` function.
|
||
|
||
|
||
See_Also:
|
||
The header of $(MREF std,range) for tutorials on ranges.
|
||
|
||
Params:
|
||
R = type to be tested
|
||
E = if present, the elements of the range must be
|
||
$(DDSUBLINK spec/const3, implicit_qualifier_conversions, qualifier-convertible)
|
||
to this type
|
||
|
||
Returns:
|
||
`true` if R is a forward range (possibly with element type `E`), `false` if not
|
||
*/
|
||
enum bool isForwardRange(R) = isInputRange!R
|
||
&& is(typeof((R r) { return r.save; } (R.init)) == R);
|
||
|
||
/// ditto
|
||
enum bool isForwardRange(R, E) =
|
||
.isForwardRange!R && isQualifierConvertible!(ElementType!R, E);
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
static assert(!isForwardRange!(int));
|
||
static assert( isForwardRange!(int[]));
|
||
static assert( isForwardRange!(inout(int)[]));
|
||
|
||
static assert( isForwardRange!(int[], const int));
|
||
static assert(!isForwardRange!(int[], immutable int));
|
||
|
||
static assert(!isForwardRange!(const(int)[], int));
|
||
static assert( isForwardRange!(const(int)[], const int));
|
||
static assert(!isForwardRange!(const(int)[], immutable int));
|
||
|
||
static assert(!isForwardRange!(immutable(int)[], int));
|
||
static assert( isForwardRange!(immutable(int)[], const int));
|
||
static assert( isForwardRange!(immutable(int)[], immutable int));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
// BUG 14544
|
||
struct R14544
|
||
{
|
||
int front() { return 0;}
|
||
void popFront() {}
|
||
bool empty() { return false; }
|
||
R14544 save() {return this;}
|
||
}
|
||
|
||
static assert( isForwardRange!R14544 );
|
||
}
|
||
|
||
/**
|
||
Returns `true` if `R` is a bidirectional range. A bidirectional
|
||
range is a forward range that also offers the primitives `back` and
|
||
`popBack`. The following code should compile for any bidirectional
|
||
range.
|
||
|
||
The semantics of a bidirectional range (not checkable during
|
||
compilation) are assumed to be the following (`r` is an object of
|
||
type `R`):
|
||
|
||
$(UL $(LI `r.back` returns (possibly a reference to) the last
|
||
element in the range. Calling `r.back` is allowed only if calling
|
||
`r.empty` has, or would have, returned `false`.))
|
||
|
||
See_Also:
|
||
The header of $(MREF std,range) for tutorials on ranges.
|
||
|
||
Params:
|
||
R = type to be tested
|
||
E = if present, the elements of the range must be
|
||
$(DDSUBLINK spec/const3, implicit_qualifier_conversions, qualifier-convertible)
|
||
to this type
|
||
|
||
Returns:
|
||
`true` if R is a bidirectional range (possibly with element type `E`), `false` if not
|
||
*/
|
||
enum bool isBidirectionalRange(R) = isForwardRange!R
|
||
&& is(typeof((R r) => r.popBack))
|
||
&& (is(typeof((return ref R r) => r.back)) || is(typeof(ref (return ref R r) => r.back)))
|
||
&& is(typeof(R.init.back.init) == ElementType!R);
|
||
|
||
/// ditto
|
||
enum bool isBidirectionalRange(R, E) =
|
||
.isBidirectionalRange!R && isQualifierConvertible!(ElementType!R, E);
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
alias R = int[];
|
||
R r = [0,1];
|
||
static assert(isForwardRange!R); // is forward range
|
||
r.popBack(); // can invoke popBack
|
||
auto t = r.back; // can get the back of the range
|
||
auto w = r.front;
|
||
static assert(is(typeof(t) == typeof(w))); // same type for front and back
|
||
|
||
// Checking the element type
|
||
static assert( isBidirectionalRange!(int[], const int));
|
||
static assert(!isBidirectionalRange!(int[], immutable int));
|
||
|
||
static assert(!isBidirectionalRange!(const(int)[], int));
|
||
static assert( isBidirectionalRange!(const(int)[], const int));
|
||
static assert(!isBidirectionalRange!(const(int)[], immutable int));
|
||
|
||
static assert(!isBidirectionalRange!(immutable(int)[], int));
|
||
static assert( isBidirectionalRange!(immutable(int)[], const int));
|
||
static assert( isBidirectionalRange!(immutable(int)[], immutable int));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
struct A {}
|
||
struct B
|
||
{
|
||
void popFront();
|
||
@property bool empty();
|
||
@property int front();
|
||
}
|
||
struct C
|
||
{
|
||
@property bool empty();
|
||
@property C save();
|
||
void popFront();
|
||
@property int front();
|
||
void popBack();
|
||
@property int back();
|
||
}
|
||
static assert(!isBidirectionalRange!(A));
|
||
static assert(!isBidirectionalRange!(B));
|
||
static assert( isBidirectionalRange!(C));
|
||
static assert( isBidirectionalRange!(int[]));
|
||
static assert( isBidirectionalRange!(char[]));
|
||
static assert( isBidirectionalRange!(inout(int)[]));
|
||
}
|
||
|
||
/**
|
||
Returns `true` if `R` is a random-access range. A random-access
|
||
range is a bidirectional range that also offers the primitive $(D
|
||
opIndex), OR an infinite forward range that offers `opIndex`. In
|
||
either case, the range must either offer `length` or be
|
||
infinite. The following code should compile for any random-access
|
||
range.
|
||
|
||
The semantics of a random-access range (not checkable during
|
||
compilation) are assumed to be the following (`r` is an object of
|
||
type `R`): $(UL $(LI `r.opIndex(n)` returns a reference to the
|
||
`n`th element in the range.))
|
||
|
||
Although `char[]` and `wchar[]` (as well as their qualified
|
||
versions including `string` and `wstring`) are arrays, $(D
|
||
isRandomAccessRange) yields `false` for them because they use
|
||
variable-length encodings (UTF-8 and UTF-16 respectively). These types
|
||
are bidirectional ranges only.
|
||
|
||
See_Also:
|
||
The header of $(MREF std,range) for tutorials on ranges.
|
||
|
||
Params:
|
||
R = type to be tested
|
||
E = if present, the elements of the range must be
|
||
$(DDSUBLINK spec/const3, implicit_qualifier_conversions, qualifier-convertible)
|
||
to this type
|
||
|
||
Returns:
|
||
`true` if R is a random-access range (possibly with element type `E`), `false` if not
|
||
*/
|
||
enum bool isRandomAccessRange(R) =
|
||
is(typeof(lvalueOf!R[1]) == ElementType!R)
|
||
&& !(isAutodecodableString!R && !isAggregateType!R)
|
||
&& isForwardRange!R
|
||
&& (isBidirectionalRange!R || isInfinite!R)
|
||
&& (hasLength!R || isInfinite!R)
|
||
&& (isInfinite!R || !is(typeof(lvalueOf!R[$ - 1]))
|
||
|| is(typeof(lvalueOf!R[$ - 1]) == ElementType!R));
|
||
|
||
/// ditto
|
||
enum bool isRandomAccessRange(R, E) =
|
||
.isRandomAccessRange!R && isQualifierConvertible!(ElementType!R, E);
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
import std.traits : isAggregateType, isAutodecodableString;
|
||
|
||
alias R = int[];
|
||
|
||
// range is finite and bidirectional or infinite and forward.
|
||
static assert(isBidirectionalRange!R ||
|
||
isForwardRange!R && isInfinite!R);
|
||
|
||
R r = [0,1];
|
||
auto e = r[1]; // can index
|
||
auto f = r.front;
|
||
static assert(is(typeof(e) == typeof(f))); // same type for indexed and front
|
||
static assert(!(isAutodecodableString!R && !isAggregateType!R)); // narrow strings cannot be indexed as ranges
|
||
static assert(hasLength!R || isInfinite!R); // must have length or be infinite
|
||
|
||
// $ must work as it does with arrays if opIndex works with $
|
||
static if (is(typeof(r[$])))
|
||
{
|
||
static assert(is(typeof(f) == typeof(r[$])));
|
||
|
||
// $ - 1 doesn't make sense with infinite ranges but needs to work
|
||
// with finite ones.
|
||
static if (!isInfinite!R)
|
||
static assert(is(typeof(f) == typeof(r[$ - 1])));
|
||
}
|
||
|
||
// Checking the element type
|
||
static assert( isRandomAccessRange!(int[], const int));
|
||
static assert(!isRandomAccessRange!(int[], immutable int));
|
||
|
||
static assert(!isRandomAccessRange!(const(int)[], int));
|
||
static assert( isRandomAccessRange!(const(int)[], const int));
|
||
static assert(!isRandomAccessRange!(const(int)[], immutable int));
|
||
|
||
static assert(!isRandomAccessRange!(immutable(int)[], int));
|
||
static assert( isRandomAccessRange!(immutable(int)[], const int));
|
||
static assert( isRandomAccessRange!(immutable(int)[], immutable int));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
struct A {}
|
||
struct B
|
||
{
|
||
void popFront();
|
||
@property bool empty();
|
||
@property int front();
|
||
}
|
||
struct C
|
||
{
|
||
void popFront();
|
||
@property bool empty();
|
||
@property int front();
|
||
void popBack();
|
||
@property int back();
|
||
}
|
||
struct D
|
||
{
|
||
@property bool empty();
|
||
@property D save();
|
||
@property int front();
|
||
void popFront();
|
||
@property int back();
|
||
void popBack();
|
||
ref int opIndex(uint);
|
||
@property size_t length();
|
||
alias opDollar = length;
|
||
//int opSlice(uint, uint);
|
||
}
|
||
struct E
|
||
{
|
||
bool empty();
|
||
E save();
|
||
int front();
|
||
void popFront();
|
||
int back();
|
||
void popBack();
|
||
ref int opIndex(uint);
|
||
size_t length();
|
||
alias opDollar = length;
|
||
//int opSlice(uint, uint);
|
||
}
|
||
static assert(!isRandomAccessRange!(A));
|
||
static assert(!isRandomAccessRange!(B));
|
||
static assert(!isRandomAccessRange!(C));
|
||
static assert( isRandomAccessRange!(D));
|
||
static assert( isRandomAccessRange!(E));
|
||
static assert( isRandomAccessRange!(int[]));
|
||
static assert( isRandomAccessRange!(inout(int)[]));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
// Test fix for bug 6935.
|
||
struct R
|
||
{
|
||
@disable this();
|
||
|
||
@property bool empty() const { return false; }
|
||
@property int front() const { return 0; }
|
||
void popFront() {}
|
||
|
||
@property R save() { return this; }
|
||
|
||
@property int back() const { return 0; }
|
||
void popBack(){}
|
||
|
||
int opIndex(size_t n) const { return 0; }
|
||
@property size_t length() const { return 0; }
|
||
alias opDollar = length;
|
||
|
||
void put(int e){ }
|
||
}
|
||
static assert(isInputRange!R);
|
||
static assert(isForwardRange!R);
|
||
static assert(isBidirectionalRange!R);
|
||
static assert(isRandomAccessRange!R);
|
||
static assert(isOutputRange!(R, int));
|
||
}
|
||
|
||
/**
|
||
Returns `true` iff `R` is an input range that supports the
|
||
`moveFront` primitive, as well as `moveBack` and `moveAt` if it's a
|
||
bidirectional or random access range. These may be explicitly implemented, or
|
||
may work via the default behavior of the module level functions `moveFront`
|
||
and friends. The following code should compile for any range
|
||
with mobile elements.
|
||
|
||
----
|
||
alias E = ElementType!R;
|
||
R r;
|
||
static assert(isInputRange!R);
|
||
static assert(is(typeof(moveFront(r)) == E));
|
||
static if (isBidirectionalRange!R)
|
||
static assert(is(typeof(moveBack(r)) == E));
|
||
static if (isRandomAccessRange!R)
|
||
static assert(is(typeof(moveAt(r, 0)) == E));
|
||
----
|
||
*/
|
||
enum bool hasMobileElements(R) =
|
||
isInputRange!R
|
||
&& is(typeof(moveFront(lvalueOf!R)) == ElementType!R)
|
||
&& (!isBidirectionalRange!R
|
||
|| is(typeof(moveBack(lvalueOf!R)) == ElementType!R))
|
||
&& (!isRandomAccessRange!R
|
||
|| is(typeof(moveAt(lvalueOf!R, 0)) == ElementType!R));
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
import std.algorithm.iteration : map;
|
||
import std.range : iota, repeat;
|
||
|
||
static struct HasPostblit
|
||
{
|
||
this(this) {}
|
||
}
|
||
|
||
auto nonMobile = map!"a"(repeat(HasPostblit.init));
|
||
static assert(!hasMobileElements!(typeof(nonMobile)));
|
||
static assert( hasMobileElements!(int[]));
|
||
static assert( hasMobileElements!(inout(int)[]));
|
||
static assert( hasMobileElements!(typeof(iota(1000))));
|
||
|
||
static assert( hasMobileElements!( string));
|
||
static assert( hasMobileElements!(dstring));
|
||
static assert( hasMobileElements!( char[]));
|
||
static assert( hasMobileElements!(dchar[]));
|
||
}
|
||
|
||
/**
|
||
The element type of `R`. `R` does not have to be a range. The
|
||
element type is determined as the type yielded by `r.front` for an
|
||
object `r` of type `R`. For example, `ElementType!(T[])` is
|
||
`T` if `T[]` isn't a narrow string; if it is, the element type is
|
||
`dchar`. If `R` doesn't have `front`, `ElementType!R` is
|
||
`void`.
|
||
*/
|
||
template ElementType(R)
|
||
{
|
||
static if (is(typeof(R.init.front.init) T))
|
||
alias ElementType = T;
|
||
else
|
||
alias ElementType = void;
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
import std.range : iota;
|
||
|
||
// Standard arrays: returns the type of the elements of the array
|
||
static assert(is(ElementType!(int[]) == int));
|
||
|
||
// Accessing .front retrieves the decoded dchar
|
||
static assert(is(ElementType!(char[]) == dchar)); // rvalue
|
||
static assert(is(ElementType!(dchar[]) == dchar)); // lvalue
|
||
|
||
// Ditto
|
||
static assert(is(ElementType!(string) == dchar));
|
||
static assert(is(ElementType!(dstring) == immutable(dchar)));
|
||
|
||
// For ranges it gets the type of .front.
|
||
auto range = iota(0, 10);
|
||
static assert(is(ElementType!(typeof(range)) == int));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
static assert(is(ElementType!(byte[]) == byte));
|
||
static assert(is(ElementType!(wchar[]) == dchar)); // rvalue
|
||
static assert(is(ElementType!(wstring) == dchar));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
enum XYZ : string { a = "foo" }
|
||
auto x = XYZ.a.front;
|
||
immutable char[3] a = "abc";
|
||
int[] i;
|
||
void[] buf;
|
||
static assert(is(ElementType!(XYZ) == dchar));
|
||
static assert(is(ElementType!(typeof(a)) == dchar));
|
||
static assert(is(ElementType!(typeof(i)) == int));
|
||
static assert(is(ElementType!(typeof(buf)) == void));
|
||
static assert(is(ElementType!(inout(int)[]) == inout(int)));
|
||
static assert(is(ElementType!(inout(int[])) == inout(int)));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
static assert(is(ElementType!(int[5]) == int));
|
||
static assert(is(ElementType!(int[0]) == int));
|
||
static assert(is(ElementType!(char[5]) == dchar));
|
||
static assert(is(ElementType!(char[0]) == dchar));
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=11336
|
||
@safe unittest
|
||
{
|
||
static struct S
|
||
{
|
||
this(this) @disable;
|
||
}
|
||
static assert(is(ElementType!(S[]) == S));
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=11401
|
||
@safe unittest
|
||
{
|
||
// ElementType should also work for non-@propety 'front'
|
||
struct E { ushort id; }
|
||
struct R
|
||
{
|
||
E front() { return E.init; }
|
||
}
|
||
static assert(is(ElementType!R == E));
|
||
}
|
||
|
||
/**
|
||
The encoding element type of `R`. For narrow strings (`char[]`,
|
||
`wchar[]` and their qualified variants including `string` and
|
||
`wstring`), `ElementEncodingType` is the character type of the
|
||
string. For all other types, `ElementEncodingType` is the same as
|
||
`ElementType`.
|
||
*/
|
||
template ElementEncodingType(R)
|
||
{
|
||
static if (is(StringTypeOf!R) && is(R : E[], E))
|
||
alias ElementEncodingType = E;
|
||
else
|
||
alias ElementEncodingType = ElementType!R;
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
import std.range : iota;
|
||
// internally the range stores the encoded type
|
||
static assert(is(ElementEncodingType!(char[]) == char));
|
||
|
||
static assert(is(ElementEncodingType!(wstring) == immutable(wchar)));
|
||
|
||
static assert(is(ElementEncodingType!(byte[]) == byte));
|
||
|
||
auto range = iota(0, 10);
|
||
static assert(is(ElementEncodingType!(typeof(range)) == int));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
static assert(is(ElementEncodingType!(wchar[]) == wchar));
|
||
static assert(is(ElementEncodingType!(dchar[]) == dchar));
|
||
static assert(is(ElementEncodingType!(string) == immutable(char)));
|
||
static assert(is(ElementEncodingType!(dstring) == immutable(dchar)));
|
||
static assert(is(ElementEncodingType!(int[]) == int));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
enum XYZ : string { a = "foo" }
|
||
auto x = XYZ.a.front;
|
||
immutable char[3] a = "abc";
|
||
int[] i;
|
||
void[] buf;
|
||
static assert(is(ElementType!(XYZ) : dchar));
|
||
static assert(is(ElementEncodingType!(char[]) == char));
|
||
static assert(is(ElementEncodingType!(string) == immutable char));
|
||
static assert(is(ElementType!(typeof(a)) : dchar));
|
||
static assert(is(ElementType!(typeof(i)) == int));
|
||
static assert(is(ElementEncodingType!(typeof(i)) == int));
|
||
static assert(is(ElementType!(typeof(buf)) : void));
|
||
|
||
static assert(is(ElementEncodingType!(inout char[]) : inout(char)));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
static assert(is(ElementEncodingType!(int[5]) == int));
|
||
static assert(is(ElementEncodingType!(int[0]) == int));
|
||
static assert(is(ElementEncodingType!(char[5]) == char));
|
||
static assert(is(ElementEncodingType!(char[0]) == char));
|
||
}
|
||
|
||
/**
|
||
Returns `true` if `R` is an input range and has swappable
|
||
elements. The following code should compile for any range
|
||
with swappable elements.
|
||
|
||
----
|
||
R r;
|
||
static assert(isInputRange!R);
|
||
swap(r.front, r.front);
|
||
static if (isBidirectionalRange!R) swap(r.back, r.front);
|
||
static if (isRandomAccessRange!R) swap(r[0], r.front);
|
||
----
|
||
*/
|
||
template hasSwappableElements(R)
|
||
{
|
||
import std.algorithm.mutation : swap;
|
||
enum bool hasSwappableElements = isInputRange!R
|
||
&& is(typeof((ref R r) => swap(r.front, r.front)))
|
||
&& (!isBidirectionalRange!R
|
||
|| is(typeof((ref R r) => swap(r.back, r.front))))
|
||
&& (!isRandomAccessRange!R
|
||
|| is(typeof((ref R r) => swap(r[0], r.front))));
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
static assert(!hasSwappableElements!(const int[]));
|
||
static assert(!hasSwappableElements!(const(int)[]));
|
||
static assert(!hasSwappableElements!(inout(int)[]));
|
||
static assert( hasSwappableElements!(int[]));
|
||
|
||
static assert(!hasSwappableElements!( string));
|
||
static assert(!hasSwappableElements!(dstring));
|
||
static assert(!hasSwappableElements!( char[]));
|
||
static assert( hasSwappableElements!(dchar[]));
|
||
}
|
||
|
||
/**
|
||
Returns `true` if `R` is an input range and has mutable
|
||
elements. The following code should compile for any range
|
||
with assignable elements.
|
||
|
||
----
|
||
R r;
|
||
static assert(isInputRange!R);
|
||
r.front = r.front;
|
||
static if (isBidirectionalRange!R) r.back = r.front;
|
||
static if (isRandomAccessRange!R) r[0] = r.front;
|
||
----
|
||
*/
|
||
enum bool hasAssignableElements(R) = isInputRange!R
|
||
&& is(typeof(lvalueOf!R.front = lvalueOf!R.front))
|
||
&& (!isBidirectionalRange!R
|
||
|| is(typeof(lvalueOf!R.back = lvalueOf!R.back)))
|
||
&& (!isRandomAccessRange!R
|
||
|| is(typeof(lvalueOf!R[0] = lvalueOf!R.front)));
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
static assert(!hasAssignableElements!(const int[]));
|
||
static assert(!hasAssignableElements!(const(int)[]));
|
||
static assert( hasAssignableElements!(int[]));
|
||
static assert(!hasAssignableElements!(inout(int)[]));
|
||
|
||
static assert(!hasAssignableElements!( string));
|
||
static assert(!hasAssignableElements!(dstring));
|
||
static assert(!hasAssignableElements!( char[]));
|
||
static assert( hasAssignableElements!(dchar[]));
|
||
}
|
||
|
||
/**
|
||
Tests whether the range `R` has lvalue elements. These are defined as
|
||
elements that can be passed by reference and have their address taken.
|
||
The following code should compile for any range with lvalue elements.
|
||
----
|
||
void passByRef(ref ElementType!R stuff);
|
||
...
|
||
static assert(isInputRange!R);
|
||
passByRef(r.front);
|
||
static if (isBidirectionalRange!R) passByRef(r.back);
|
||
static if (isRandomAccessRange!R) passByRef(r[0]);
|
||
----
|
||
*/
|
||
enum bool hasLvalueElements(R) = isInputRange!R
|
||
&& is(typeof(isLvalue(lvalueOf!R.front)))
|
||
&& (!isBidirectionalRange!R
|
||
|| is(typeof(isLvalue(lvalueOf!R.back))))
|
||
&& (!isRandomAccessRange!R
|
||
|| is(typeof(isLvalue(lvalueOf!R[0]))));
|
||
|
||
/* Compile successfully if argument of type T is an lvalue
|
||
*/
|
||
private void isLvalue(T)(T)
|
||
if (0);
|
||
|
||
private void isLvalue(T)(ref T)
|
||
if (1);
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
import std.range : iota, chain;
|
||
|
||
static assert( hasLvalueElements!(int[]));
|
||
static assert( hasLvalueElements!(const(int)[]));
|
||
static assert( hasLvalueElements!(inout(int)[]));
|
||
static assert( hasLvalueElements!(immutable(int)[]));
|
||
static assert(!hasLvalueElements!(typeof(iota(3))));
|
||
|
||
static assert(!hasLvalueElements!( string));
|
||
static assert( hasLvalueElements!(dstring));
|
||
static assert(!hasLvalueElements!( char[]));
|
||
static assert( hasLvalueElements!(dchar[]));
|
||
|
||
auto c = chain([1, 2, 3], [4, 5, 6]);
|
||
static assert( hasLvalueElements!(typeof(c)));
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
// bugfix 6336
|
||
struct S { immutable int value; }
|
||
static assert( isInputRange!(S[]));
|
||
static assert( hasLvalueElements!(S[]));
|
||
}
|
||
|
||
/**
|
||
Yields `true` if `R` has a `length` member that returns a value of `size_t`
|
||
type. `R` does not have to be a range. If `R` is a range, algorithms in the
|
||
standard library are only guaranteed to support `length` with type `size_t`.
|
||
|
||
Note that `length` is an optional primitive as no range must implement it. Some
|
||
ranges do not store their length explicitly, some cannot compute it without
|
||
actually exhausting the range (e.g. socket streams), and some other ranges may
|
||
be infinite.
|
||
|
||
Although narrow string types (`char[]`, `wchar[]`, and their qualified
|
||
derivatives) do define a `length` property, `hasLength` yields `false` for them.
|
||
This is because a narrow string's length does not reflect the number of
|
||
characters, but instead the number of encoding units, and as such is not useful
|
||
with range-oriented algorithms. To use strings as random-access ranges with
|
||
length, use $(REF representation, std, string) or $(REF byCodeUnit, std, utf).
|
||
*/
|
||
template hasLength(R)
|
||
{
|
||
static if (is(typeof(((R* r) => r.length)(null)) Length))
|
||
enum bool hasLength = is(Length == size_t) &&
|
||
!(isAutodecodableString!R && !isAggregateType!R);
|
||
else
|
||
enum bool hasLength = false;
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
static assert(!hasLength!(char[]));
|
||
static assert( hasLength!(int[]));
|
||
static assert( hasLength!(inout(int)[]));
|
||
|
||
struct A { size_t length() { return 0; } }
|
||
struct B { @property size_t length() { return 0; } }
|
||
static assert( hasLength!(A));
|
||
static assert( hasLength!(B));
|
||
}
|
||
|
||
// test combinations which are invalid on some platforms
|
||
@safe unittest
|
||
{
|
||
struct A { ulong length; }
|
||
struct B { @property uint length() { return 0; } }
|
||
|
||
static if (is(size_t == uint))
|
||
{
|
||
static assert(!hasLength!(A));
|
||
static assert(hasLength!(B));
|
||
}
|
||
else static if (is(size_t == ulong))
|
||
{
|
||
static assert(hasLength!(A));
|
||
static assert(!hasLength!(B));
|
||
}
|
||
}
|
||
|
||
// test combinations which are invalid on all platforms
|
||
@safe unittest
|
||
{
|
||
struct A { long length; }
|
||
struct B { int length; }
|
||
struct C { ubyte length; }
|
||
struct D { char length; }
|
||
static assert(!hasLength!(A));
|
||
static assert(!hasLength!(B));
|
||
static assert(!hasLength!(C));
|
||
static assert(!hasLength!(D));
|
||
}
|
||
|
||
/**
|
||
Returns `true` if `R` is an infinite input range. An
|
||
infinite input range is an input range that has a statically-defined
|
||
enumerated member called `empty` that is always `false`,
|
||
for example:
|
||
|
||
----
|
||
struct MyInfiniteRange
|
||
{
|
||
enum bool empty = false;
|
||
...
|
||
}
|
||
----
|
||
*/
|
||
|
||
template isInfinite(R)
|
||
{
|
||
static if (isInputRange!R && __traits(compiles, { enum e = R.empty; }))
|
||
enum bool isInfinite = !R.empty;
|
||
else
|
||
enum bool isInfinite = false;
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
import std.range : Repeat;
|
||
static assert(!isInfinite!(int[]));
|
||
static assert( isInfinite!(Repeat!(int)));
|
||
}
|
||
|
||
/**
|
||
Returns `true` if `R` offers a slicing operator with integral boundaries
|
||
that returns a forward range type.
|
||
|
||
For finite ranges, the result of `opSlice` must be of the same type as the
|
||
original range type. If the range defines `opDollar`, then it must support
|
||
subtraction.
|
||
|
||
For infinite ranges, when $(I not) using `opDollar`, the result of `opSlice`
|
||
may be a forward range of any type. However, when using `opDollar`, the result
|
||
of `opSlice` must be of the same type as the original range type.
|
||
|
||
The following expression must be true for `hasSlicing` to be `true`:
|
||
|
||
----
|
||
isForwardRange!R
|
||
&& !(isAutodecodableString!R && !isAggregateType!R)
|
||
&& is(typeof((R r) { return r[1 .. 1].length; } (R.init)) == size_t)
|
||
&& (is(typeof(lvalueOf!R[1 .. 1]) == R) || isInfinite!R)
|
||
&& (!is(typeof(lvalueOf!R[0 .. $])) || is(typeof(lvalueOf!R[0 .. $]) == R))
|
||
&& (!is(typeof(lvalueOf!R[0 .. $])) || isInfinite!R
|
||
|| is(typeof(lvalueOf!R[0 .. $ - 1]) == R))
|
||
&& is(typeof((ref R r)
|
||
{
|
||
static assert(isForwardRange!(typeof(r[1 .. 2])));
|
||
}));
|
||
----
|
||
*/
|
||
enum bool hasSlicing(R) = isForwardRange!R
|
||
&& !(isAutodecodableString!R && !isAggregateType!R)
|
||
&& is(typeof((R r) { return r[1 .. 1].length; } (R.init)) == size_t)
|
||
&& (is(typeof(lvalueOf!R[1 .. 1]) == R) || isInfinite!R)
|
||
&& (!is(typeof(lvalueOf!R[0 .. $])) || is(typeof(lvalueOf!R[0 .. $]) == R))
|
||
&& (!is(typeof(lvalueOf!R[0 .. $])) || isInfinite!R
|
||
|| is(typeof(lvalueOf!R[0 .. $ - 1]) == R))
|
||
&& is(typeof((ref R r)
|
||
{
|
||
static assert(isForwardRange!(typeof(r[1 .. 2])));
|
||
}));
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
import std.range : takeExactly;
|
||
static assert( hasSlicing!(int[]));
|
||
static assert( hasSlicing!(const(int)[]));
|
||
static assert(!hasSlicing!(const int[]));
|
||
static assert( hasSlicing!(inout(int)[]));
|
||
static assert(!hasSlicing!(inout int []));
|
||
static assert( hasSlicing!(immutable(int)[]));
|
||
static assert(!hasSlicing!(immutable int[]));
|
||
static assert(!hasSlicing!string);
|
||
static assert( hasSlicing!dstring);
|
||
|
||
enum rangeFuncs = "@property int front();" ~
|
||
"void popFront();" ~
|
||
"@property bool empty();" ~
|
||
"@property auto save() { return this; }" ~
|
||
"@property size_t length();";
|
||
|
||
struct A { mixin(rangeFuncs); int opSlice(size_t, size_t); }
|
||
struct B { mixin(rangeFuncs); B opSlice(size_t, size_t); }
|
||
struct C { mixin(rangeFuncs); @disable this(); C opSlice(size_t, size_t); }
|
||
struct D { mixin(rangeFuncs); int[] opSlice(size_t, size_t); }
|
||
static assert(!hasSlicing!(A));
|
||
static assert( hasSlicing!(B));
|
||
static assert( hasSlicing!(C));
|
||
static assert(!hasSlicing!(D));
|
||
|
||
struct InfOnes
|
||
{
|
||
enum empty = false;
|
||
void popFront() {}
|
||
@property int front() { return 1; }
|
||
@property InfOnes save() { return this; }
|
||
auto opSlice(size_t i, size_t j) { return takeExactly(this, j - i); }
|
||
auto opSlice(size_t i, Dollar d) { return this; }
|
||
|
||
struct Dollar {}
|
||
Dollar opDollar() const { return Dollar.init; }
|
||
}
|
||
|
||
static assert(hasSlicing!InfOnes);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=24348
|
||
@safe unittest
|
||
{
|
||
static struct Slice
|
||
{
|
||
size_t length;
|
||
bool empty() => length == 0;
|
||
int front() => 0;
|
||
void popFront() { --length; }
|
||
Slice save() => this;
|
||
}
|
||
|
||
static struct InfZeros
|
||
{
|
||
enum empty = false;
|
||
int front() => 0;
|
||
void popFront() {}
|
||
InfZeros save() => this;
|
||
|
||
Slice opIndex(size_t[2] bounds)
|
||
{
|
||
size_t i = bounds[0], j = bounds[1];
|
||
size_t length = i <= j ? j - i : 0;
|
||
return Slice(length);
|
||
}
|
||
|
||
size_t[2] opSlice(size_t dim : 0)(size_t i, size_t j) => [i, j];
|
||
}
|
||
|
||
static assert(hasSlicing!InfZeros);
|
||
}
|
||
|
||
/**
|
||
This is a best-effort implementation of `length` for any kind of
|
||
range.
|
||
|
||
If `hasLength!Range`, simply returns `range.length` without
|
||
checking `upTo` (when specified).
|
||
|
||
Otherwise, walks the range through its length and returns the number
|
||
of elements seen. Performes $(BIGOH n) evaluations of `range.empty`
|
||
and `range.popFront()`, where `n` is the effective length of $(D
|
||
range).
|
||
|
||
The `upTo` parameter is useful to "cut the losses" in case
|
||
the interest is in seeing whether the range has at least some number
|
||
of elements. If the parameter `upTo` is specified, stops if $(D
|
||
upTo) steps have been taken and returns `upTo`.
|
||
|
||
Infinite ranges are compatible, provided the parameter `upTo` is
|
||
specified, in which case the implementation simply returns upTo.
|
||
*/
|
||
auto walkLength(Range)(Range range)
|
||
if (isInputRange!Range && !isInfinite!Range)
|
||
{
|
||
static if (hasLength!Range)
|
||
return range.length;
|
||
else
|
||
{
|
||
size_t result;
|
||
static if (autodecodeStrings && isNarrowString!Range)
|
||
{
|
||
import std.utf : codeUnitLimit;
|
||
result = range.length;
|
||
foreach (const i, const c; range)
|
||
{
|
||
if (c >= codeUnitLimit!Range)
|
||
{
|
||
result = i;
|
||
break;
|
||
}
|
||
}
|
||
range = range[result .. $];
|
||
}
|
||
for ( ; !range.empty ; range.popFront() )
|
||
++result;
|
||
return result;
|
||
}
|
||
}
|
||
/// ditto
|
||
auto walkLength(Range)(Range range, const size_t upTo)
|
||
if (isInputRange!Range)
|
||
{
|
||
static if (hasLength!Range)
|
||
return range.length;
|
||
else static if (isInfinite!Range)
|
||
return upTo;
|
||
else
|
||
{
|
||
size_t result;
|
||
static if (autodecodeStrings && isNarrowString!Range)
|
||
{
|
||
import std.utf : codeUnitLimit;
|
||
result = upTo > range.length ? range.length : upTo;
|
||
foreach (const i, const c; range[0 .. result])
|
||
{
|
||
if (c >= codeUnitLimit!Range)
|
||
{
|
||
result = i;
|
||
break;
|
||
}
|
||
}
|
||
range = range[result .. $];
|
||
}
|
||
for ( ; result < upTo && !range.empty ; range.popFront() )
|
||
++result;
|
||
return result;
|
||
}
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
import std.range : iota;
|
||
|
||
assert(10.iota.walkLength == 10);
|
||
// iota has a length function, and therefore the
|
||
// doesn't have to be walked, and the upTo
|
||
// parameter is ignored
|
||
assert(10.iota.walkLength(5) == 10);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
import std.algorithm.iteration : filter;
|
||
import std.range : recurrence, take;
|
||
|
||
//hasLength Range
|
||
int[] a = [ 1, 2, 3 ];
|
||
assert(walkLength(a) == 3);
|
||
assert(walkLength(a, 0) == 3);
|
||
assert(walkLength(a, 2) == 3);
|
||
assert(walkLength(a, 4) == 3);
|
||
|
||
//Forward Range
|
||
auto b = filter!"true"([1, 2, 3, 4]);
|
||
assert(b.walkLength() == 4);
|
||
assert(b.walkLength(0) == 0);
|
||
assert(b.walkLength(2) == 2);
|
||
assert(b.walkLength(4) == 4);
|
||
assert(b.walkLength(6) == 4);
|
||
|
||
//Infinite Range
|
||
auto fibs = recurrence!"a[n-1] + a[n-2]"(1, 1);
|
||
assert(!__traits(compiles, fibs.walkLength()));
|
||
assert(fibs.take(10).walkLength() == 10);
|
||
assert(fibs.walkLength(55) == 55);
|
||
}
|
||
|
||
/**
|
||
`popFrontN` eagerly advances `r` itself (not a copy) up to `n` times
|
||
(by calling `r.popFront`). `popFrontN` takes `r` by `ref`,
|
||
so it mutates the original range. Completes in $(BIGOH 1) steps for ranges
|
||
that support slicing and have length.
|
||
Completes in $(BIGOH n) time for all other ranges.
|
||
|
||
`popBackN` behaves the same as `popFrontN` but instead removes
|
||
elements from the back of the (bidirectional) range instead of the front.
|
||
|
||
Returns:
|
||
How much `r` was actually advanced, which may be less than `n` if
|
||
`r` did not have at least `n` elements.
|
||
|
||
See_Also: $(REF drop, std, range), $(REF dropBack, std, range)
|
||
*/
|
||
size_t popFrontN(Range)(ref Range r, size_t n)
|
||
if (isInputRange!Range)
|
||
{
|
||
static if (hasLength!Range)
|
||
{
|
||
n = cast(size_t) (n < r.length ? n : r.length);
|
||
}
|
||
|
||
static if (hasSlicing!Range && is(typeof(r = r[n .. $])))
|
||
{
|
||
r = r[n .. $];
|
||
}
|
||
else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar.
|
||
{
|
||
r = r[n .. r.length];
|
||
}
|
||
else
|
||
{
|
||
static if (hasLength!Range)
|
||
{
|
||
foreach (i; 0 .. n)
|
||
r.popFront();
|
||
}
|
||
else
|
||
{
|
||
foreach (i; 0 .. n)
|
||
{
|
||
if (r.empty) return i;
|
||
r.popFront();
|
||
}
|
||
}
|
||
}
|
||
return n;
|
||
}
|
||
|
||
/// ditto
|
||
size_t popBackN(Range)(ref Range r, size_t n)
|
||
if (isBidirectionalRange!Range)
|
||
{
|
||
static if (hasLength!Range)
|
||
{
|
||
n = cast(size_t) (n < r.length ? n : r.length);
|
||
}
|
||
|
||
static if (hasSlicing!Range && is(typeof(r = r[0 .. $ - n])))
|
||
{
|
||
r = r[0 .. $ - n];
|
||
}
|
||
else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar.
|
||
{
|
||
r = r[0 .. r.length - n];
|
||
}
|
||
else
|
||
{
|
||
static if (hasLength!Range)
|
||
{
|
||
foreach (i; 0 .. n)
|
||
r.popBack();
|
||
}
|
||
else
|
||
{
|
||
foreach (i; 0 .. n)
|
||
{
|
||
if (r.empty) return i;
|
||
r.popBack();
|
||
}
|
||
}
|
||
}
|
||
return n;
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
int[] a = [ 1, 2, 3, 4, 5 ];
|
||
a.popFrontN(2);
|
||
assert(a == [ 3, 4, 5 ]);
|
||
a.popFrontN(7);
|
||
assert(a == [ ]);
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
import std.algorithm.comparison : equal;
|
||
import std.range : iota;
|
||
auto LL = iota(1L, 7L);
|
||
auto r = popFrontN(LL, 2);
|
||
assert(equal(LL, [3L, 4L, 5L, 6L]));
|
||
assert(r == 2);
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
int[] a = [ 1, 2, 3, 4, 5 ];
|
||
a.popBackN(2);
|
||
assert(a == [ 1, 2, 3 ]);
|
||
a.popBackN(7);
|
||
assert(a == [ ]);
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
import std.algorithm.comparison : equal;
|
||
import std.range : iota;
|
||
auto LL = iota(1L, 7L);
|
||
auto r = popBackN(LL, 2);
|
||
assert(equal(LL, [1L, 2L, 3L, 4L]));
|
||
assert(r == 2);
|
||
}
|
||
|
||
/**
|
||
Eagerly advances `r` itself (not a copy) exactly `n` times (by
|
||
calling `r.popFront`). `popFrontExactly` takes `r` by `ref`,
|
||
so it mutates the original range. Completes in $(BIGOH 1) steps for ranges
|
||
that support slicing, and have either length or are infinite.
|
||
Completes in $(BIGOH n) time for all other ranges.
|
||
|
||
Note: Unlike $(LREF popFrontN), `popFrontExactly` will assume that the
|
||
range holds at least `n` elements. This makes `popFrontExactly`
|
||
faster than `popFrontN`, but it also means that if `range` does
|
||
not contain at least `n` elements, it will attempt to call `popFront`
|
||
on an empty range, which is undefined behavior. So, only use
|
||
`popFrontExactly` when it is guaranteed that `range` holds at least
|
||
`n` elements.
|
||
|
||
`popBackExactly` will behave the same but instead removes elements from
|
||
the back of the (bidirectional) range instead of the front.
|
||
|
||
See_Also: $(REF dropExactly, std, range), $(REF dropBackExactly, std, range)
|
||
*/
|
||
void popFrontExactly(Range)(ref Range r, size_t n)
|
||
if (isInputRange!Range)
|
||
{
|
||
static if (hasLength!Range)
|
||
assert(n <= r.length, "range is smaller than amount of items to pop");
|
||
|
||
static if (hasSlicing!Range && is(typeof(r = r[n .. $])))
|
||
r = r[n .. $];
|
||
else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar.
|
||
r = r[n .. r.length];
|
||
else
|
||
foreach (i; 0 .. n)
|
||
r.popFront();
|
||
}
|
||
|
||
/// ditto
|
||
void popBackExactly(Range)(ref Range r, size_t n)
|
||
if (isBidirectionalRange!Range)
|
||
{
|
||
static if (hasLength!Range)
|
||
assert(n <= r.length, "range is smaller than amount of items to pop");
|
||
|
||
static if (hasSlicing!Range && is(typeof(r = r[0 .. $ - n])))
|
||
r = r[0 .. $ - n];
|
||
else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar.
|
||
r = r[0 .. r.length - n];
|
||
else
|
||
foreach (i; 0 .. n)
|
||
r.popBack();
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
import std.algorithm.comparison : equal;
|
||
import std.algorithm.iteration : filterBidirectional;
|
||
|
||
auto a = [1, 2, 3];
|
||
a.popFrontExactly(1);
|
||
assert(a == [2, 3]);
|
||
a.popBackExactly(1);
|
||
assert(a == [2]);
|
||
|
||
string s = "日本語";
|
||
s.popFrontExactly(1);
|
||
assert(s == "本語");
|
||
s.popBackExactly(1);
|
||
assert(s == "本");
|
||
|
||
auto bd = filterBidirectional!"true"([1, 2, 3]);
|
||
bd.popFrontExactly(1);
|
||
assert(bd.equal([2, 3]));
|
||
bd.popBackExactly(1);
|
||
assert(bd.equal([2]));
|
||
}
|
||
|
||
/**
|
||
Moves the front of `r` out and returns it.
|
||
|
||
If `r.front` is a struct with a destructor or copy constructor defined, it
|
||
is reset to its `.init` value after its value is moved. Otherwise, it is
|
||
left unchanged.
|
||
|
||
In either case, `r.front` is left in a destroyable state that does not
|
||
allocate any resources.
|
||
*/
|
||
ElementType!R moveFront(R)(R r)
|
||
{
|
||
static if (is(typeof(&r.moveFront)))
|
||
{
|
||
return r.moveFront();
|
||
}
|
||
else static if (!hasElaborateCopyConstructor!(ElementType!R))
|
||
{
|
||
return r.front;
|
||
}
|
||
else static if (is(typeof(&(r.front())) == ElementType!R*))
|
||
{
|
||
import std.algorithm.mutation : move;
|
||
return move(r.front);
|
||
}
|
||
else
|
||
{
|
||
static assert(0,
|
||
"Cannot move front of a range with a postblit and an rvalue front.");
|
||
}
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
auto a = [ 1, 2, 3 ];
|
||
assert(moveFront(a) == 1);
|
||
assert(a.length == 3);
|
||
|
||
// define a perfunctory input range
|
||
struct InputRange
|
||
{
|
||
enum bool empty = false;
|
||
enum int front = 7;
|
||
void popFront() {}
|
||
int moveFront() { return 43; }
|
||
}
|
||
InputRange r;
|
||
// calls r.moveFront
|
||
assert(moveFront(r) == 43);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
struct R
|
||
{
|
||
@property ref int front() { static int x = 42; return x; }
|
||
this(this){}
|
||
}
|
||
R r;
|
||
assert(moveFront(r) == 42);
|
||
}
|
||
|
||
/**
|
||
Moves the back of `r` out and returns it. Leaves `r.back` in a
|
||
destroyable state that does not allocate any resources (usually equal
|
||
to its `.init` value).
|
||
*/
|
||
ElementType!R moveBack(R)(R r)
|
||
{
|
||
static if (is(typeof(&r.moveBack)))
|
||
{
|
||
return r.moveBack();
|
||
}
|
||
else static if (!hasElaborateCopyConstructor!(ElementType!R))
|
||
{
|
||
return r.back;
|
||
}
|
||
else static if (is(typeof(&(r.back())) == ElementType!R*))
|
||
{
|
||
import std.algorithm.mutation : move;
|
||
return move(r.back);
|
||
}
|
||
else
|
||
{
|
||
static assert(0,
|
||
"Cannot move back of a range with a postblit and an rvalue back.");
|
||
}
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
struct TestRange
|
||
{
|
||
int payload = 5;
|
||
@property bool empty() { return false; }
|
||
@property TestRange save() { return this; }
|
||
@property ref int front() return { return payload; }
|
||
@property ref int back() return { return payload; }
|
||
void popFront() { }
|
||
void popBack() { }
|
||
}
|
||
static assert(isBidirectionalRange!TestRange);
|
||
TestRange r;
|
||
auto x = moveBack(r);
|
||
assert(x == 5);
|
||
}
|
||
|
||
/**
|
||
Moves element at index `i` of `r` out and returns it. Leaves $(D
|
||
r[i]) in a destroyable state that does not allocate any resources
|
||
(usually equal to its `.init` value).
|
||
*/
|
||
ElementType!R moveAt(R)(R r, size_t i)
|
||
{
|
||
static if (is(typeof(&r.moveAt)))
|
||
{
|
||
return r.moveAt(i);
|
||
}
|
||
else static if (!hasElaborateCopyConstructor!(ElementType!(R)))
|
||
{
|
||
return r[i];
|
||
}
|
||
else static if (is(typeof(&r[i]) == ElementType!R*))
|
||
{
|
||
import std.algorithm.mutation : move;
|
||
return move(r[i]);
|
||
}
|
||
else
|
||
{
|
||
static assert(0,
|
||
"Cannot move element of a range with a postblit and rvalue elements.");
|
||
}
|
||
}
|
||
|
||
///
|
||
@safe unittest
|
||
{
|
||
auto a = [1,2,3,4];
|
||
foreach (idx, it; a)
|
||
{
|
||
assert(it == moveAt(a, idx));
|
||
}
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
import std.internal.test.dummyrange;
|
||
|
||
foreach (DummyType; AllDummyRanges)
|
||
{
|
||
auto d = DummyType.init;
|
||
assert(moveFront(d) == 1);
|
||
|
||
static if (isBidirectionalRange!DummyType)
|
||
{
|
||
assert(moveBack(d) == 10);
|
||
}
|
||
|
||
static if (isRandomAccessRange!DummyType)
|
||
{
|
||
assert(moveAt(d, 2) == 3);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
Implements the range interface primitive `empty` for types that
|
||
obey $(LREF hasLength) property and for narrow strings. Due to the
|
||
fact that nonmember functions can be called with the first argument
|
||
using the dot notation, `a.empty` is equivalent to `empty(a)`.
|
||
*/
|
||
@property bool empty(T)(auto ref scope T a)
|
||
if (is(typeof(a.length) : size_t))
|
||
{
|
||
return !a.length;
|
||
}
|
||
|
||
///
|
||
@safe pure nothrow unittest
|
||
{
|
||
auto a = [ 1, 2, 3 ];
|
||
assert(!a.empty);
|
||
assert(a[3 .. $].empty);
|
||
|
||
int[string] b;
|
||
assert(b.empty);
|
||
b["zero"] = 0;
|
||
assert(!b.empty);
|
||
}
|
||
|
||
/**
|
||
Implements the range interface primitive `save` for built-in
|
||
arrays. Due to the fact that nonmember functions can be called with
|
||
the first argument using the dot notation, `array.save` is
|
||
equivalent to `save(array)`. The function does not duplicate the
|
||
content of the array, it simply returns its argument.
|
||
*/
|
||
@property inout(T)[] save(T)(return scope inout(T)[] a) @safe pure nothrow @nogc
|
||
{
|
||
return a;
|
||
}
|
||
|
||
///
|
||
@safe pure nothrow unittest
|
||
{
|
||
auto a = [ 1, 2, 3 ];
|
||
auto b = a.save;
|
||
assert(b is a);
|
||
}
|
||
|
||
/**
|
||
Implements the range interface primitive `popFront` for built-in
|
||
arrays. Due to the fact that nonmember functions can be called with
|
||
the first argument using the dot notation, `array.popFront` is
|
||
equivalent to `popFront(array)`. For $(GLOSSARY narrow strings),
|
||
`popFront` automatically advances to the next $(GLOSSARY code
|
||
point).
|
||
*/
|
||
void popFront(T)(scope ref inout(T)[] a) @safe pure nothrow @nogc
|
||
if (!isAutodecodableString!(T[]) && !is(T[] == void[]))
|
||
{
|
||
assert(a.length, "Attempting to popFront() past the end of an array of " ~ T.stringof);
|
||
a = a[1 .. $];
|
||
}
|
||
|
||
///
|
||
@safe pure nothrow unittest
|
||
{
|
||
auto a = [ 1, 2, 3 ];
|
||
a.popFront();
|
||
assert(a == [ 2, 3 ]);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
static assert(!is(typeof({ int[4] a; popFront(a); })));
|
||
static assert(!is(typeof({ immutable int[] a; popFront(a); })));
|
||
static assert(!is(typeof({ void[] a; popFront(a); })));
|
||
}
|
||
|
||
/// ditto
|
||
void popFront(C)(scope ref inout(C)[] str) @trusted pure nothrow
|
||
if (isAutodecodableString!(C[]))
|
||
{
|
||
import std.algorithm.comparison : min;
|
||
|
||
assert(str.length, "Attempting to popFront() past the end of an array of " ~ C.stringof);
|
||
|
||
static if (is(immutable C == immutable char))
|
||
{
|
||
static immutable ubyte[] charWidthTab = [
|
||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1
|
||
];
|
||
|
||
immutable c = str[0];
|
||
immutable charWidth = c < 192 ? 1 : charWidthTab.ptr[c - 192];
|
||
str = str.ptr[min(str.length, charWidth) .. str.length];
|
||
}
|
||
else static if (is(immutable C == immutable wchar))
|
||
{
|
||
immutable u = str[0];
|
||
immutable seqLen = 1 + (u >= 0xD800 && u <= 0xDBFF);
|
||
str = str.ptr[min(seqLen, str.length) .. str.length];
|
||
}
|
||
else static assert(0, "Bad template constraint.");
|
||
}
|
||
|
||
@safe pure unittest
|
||
{
|
||
import std.meta : AliasSeq;
|
||
|
||
static foreach (S; AliasSeq!(string, wstring, dstring))
|
||
{{
|
||
S s = "\xC2\xA9hello";
|
||
s.popFront();
|
||
assert(s == "hello");
|
||
|
||
S str = "hello\U00010143\u0100\U00010143";
|
||
foreach (dchar c; ['h', 'e', 'l', 'l', 'o', '\U00010143', '\u0100', '\U00010143'])
|
||
{
|
||
assert(str.front == c);
|
||
str.popFront();
|
||
}
|
||
assert(str.empty);
|
||
|
||
static assert(!is(typeof({ immutable S a; popFront(a); })));
|
||
static assert(!is(typeof({ typeof(S.init[0])[4] a; popFront(a); })));
|
||
}}
|
||
|
||
C[] _eatString(C)(C[] str)
|
||
{
|
||
while (!str.empty)
|
||
str.popFront();
|
||
|
||
return str;
|
||
}
|
||
enum checkCTFE = _eatString("ウェブサイト@La_Verité.com");
|
||
static assert(checkCTFE.empty);
|
||
enum checkCTFEW = _eatString("ウェブサイト@La_Verité.com"w);
|
||
static assert(checkCTFEW.empty);
|
||
}
|
||
|
||
// https://issues.dlang.org/show_bug.cgi?id=16090
|
||
@safe unittest
|
||
{
|
||
string s = "\u00E4";
|
||
assert(s.length == 2);
|
||
s = s[0 .. 1];
|
||
assert(s.length == 1);
|
||
s.popFront;
|
||
assert(s.empty);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
wstring s = "\U00010000";
|
||
assert(s.length == 2);
|
||
s = s[0 .. 1];
|
||
assert(s.length == 1);
|
||
s.popFront;
|
||
assert(s.empty);
|
||
}
|
||
|
||
/**
|
||
Implements the range interface primitive `popBack` for built-in
|
||
arrays. Due to the fact that nonmember functions can be called with
|
||
the first argument using the dot notation, `array.popBack` is
|
||
equivalent to `popBack(array)`. For $(GLOSSARY narrow strings), $(D
|
||
popFront) automatically eliminates the last $(GLOSSARY code point).
|
||
*/
|
||
void popBack(T)(scope ref inout(T)[] a) @safe pure nothrow @nogc
|
||
if (!isAutodecodableString!(T[]) && !is(T[] == void[]))
|
||
{
|
||
assert(a.length);
|
||
a = a[0 .. $ - 1];
|
||
}
|
||
|
||
///
|
||
@safe pure nothrow unittest
|
||
{
|
||
auto a = [ 1, 2, 3 ];
|
||
a.popBack();
|
||
assert(a == [ 1, 2 ]);
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
static assert(!is(typeof({ immutable int[] a; popBack(a); })));
|
||
static assert(!is(typeof({ int[4] a; popBack(a); })));
|
||
static assert(!is(typeof({ void[] a; popBack(a); })));
|
||
}
|
||
|
||
/// ditto
|
||
void popBack(T)(scope ref inout(T)[] a) @safe pure
|
||
if (isAutodecodableString!(T[]))
|
||
{
|
||
import std.utf : strideBack;
|
||
assert(a.length, "Attempting to popBack() past the front of an array of " ~ T.stringof);
|
||
a = a[0 .. $ - strideBack(a, $)];
|
||
}
|
||
|
||
@safe pure unittest
|
||
{
|
||
import std.meta : AliasSeq;
|
||
|
||
static foreach (S; AliasSeq!(string, wstring, dstring))
|
||
{{
|
||
S s = "hello\xE2\x89\xA0";
|
||
s.popBack();
|
||
assert(s == "hello");
|
||
S s3 = "\xE2\x89\xA0";
|
||
auto c = s3.back;
|
||
assert(c == cast(dchar)'\u2260');
|
||
s3.popBack();
|
||
assert(s3 == "");
|
||
|
||
S str = "\U00010143\u0100\U00010143hello";
|
||
foreach (dchar ch; ['o', 'l', 'l', 'e', 'h', '\U00010143', '\u0100', '\U00010143'])
|
||
{
|
||
assert(str.back == ch);
|
||
str.popBack();
|
||
}
|
||
assert(str.empty);
|
||
|
||
static assert(!is(typeof({ immutable S a; popBack(a); })));
|
||
static assert(!is(typeof({ typeof(S.init[0])[4] a; popBack(a); })));
|
||
}}
|
||
}
|
||
|
||
/**
|
||
EXPERIMENTAL: to try out removing autodecoding, set the version
|
||
`NoAutodecodeStrings`. Most things are expected to fail with this version
|
||
currently.
|
||
*/
|
||
version (NoAutodecodeStrings)
|
||
{
|
||
enum autodecodeStrings = false;
|
||
}
|
||
else
|
||
{
|
||
///
|
||
enum autodecodeStrings = true;
|
||
}
|
||
|
||
/**
|
||
Implements the range interface primitive `front` for built-in
|
||
arrays. Due to the fact that nonmember functions can be called with
|
||
the first argument using the dot notation, `array.front` is
|
||
equivalent to `front(array)`. For $(GLOSSARY narrow strings), $(D
|
||
front) automatically returns the first $(GLOSSARY code point) as _a $(D
|
||
dchar).
|
||
*/
|
||
@property ref inout(T) front(T)(return scope inout(T)[] a) @safe pure nothrow @nogc
|
||
if (!isAutodecodableString!(T[]) && !is(T[] == void[]))
|
||
{
|
||
assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof);
|
||
return a[0];
|
||
}
|
||
|
||
///
|
||
@safe pure nothrow unittest
|
||
{
|
||
int[] a = [ 1, 2, 3 ];
|
||
assert(a.front == 1);
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
auto a = [ 1, 2 ];
|
||
a.front = 4;
|
||
assert(a.front == 4);
|
||
assert(a == [ 4, 2 ]);
|
||
|
||
immutable b = [ 1, 2 ];
|
||
assert(b.front == 1);
|
||
|
||
int[2] c = [ 1, 2 ];
|
||
assert(c.front == 1);
|
||
}
|
||
|
||
/// ditto
|
||
@property dchar front(T)(scope const(T)[] a) @safe pure
|
||
if (isAutodecodableString!(T[]))
|
||
{
|
||
import std.utf : decode;
|
||
assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof);
|
||
size_t i = 0;
|
||
return decode(a, i);
|
||
}
|
||
|
||
/**
|
||
Implements the range interface primitive `back` for built-in
|
||
arrays. Due to the fact that nonmember functions can be called with
|
||
the first argument using the dot notation, `array.back` is
|
||
equivalent to `back(array)`. For $(GLOSSARY narrow strings), $(D
|
||
back) automatically returns the last $(GLOSSARY code point) as _a $(D
|
||
dchar).
|
||
*/
|
||
@property ref inout(T) back(T)(return scope inout(T)[] a) @safe pure nothrow @nogc
|
||
if (!isAutodecodableString!(T[]) && !is(T[] == void[]))
|
||
{
|
||
assert(a.length, "Attempting to fetch the back of an empty array of " ~ T.stringof);
|
||
return a[$ - 1];
|
||
}
|
||
|
||
///
|
||
@safe pure nothrow unittest
|
||
{
|
||
int[] a = [ 1, 2, 3 ];
|
||
assert(a.back == 3);
|
||
a.back += 4;
|
||
assert(a.back == 7);
|
||
}
|
||
|
||
@safe pure nothrow unittest
|
||
{
|
||
immutable b = [ 1, 2, 3 ];
|
||
assert(b.back == 3);
|
||
|
||
int[3] c = [ 1, 2, 3 ];
|
||
assert(c.back == 3);
|
||
}
|
||
|
||
/// ditto
|
||
// Specialization for strings
|
||
@property dchar back(T)(scope const(T)[] a) @safe pure
|
||
if (isAutodecodableString!(T[]))
|
||
{
|
||
import std.utf : decode, strideBack;
|
||
assert(a.length, "Attempting to fetch the back of an empty array of " ~ T.stringof);
|
||
size_t i = a.length - strideBack(a, a.length);
|
||
return decode(a, i);
|
||
}
|
||
|
||
/*
|
||
Implements `length` for a range by forwarding it to `member`.
|
||
*/
|
||
package(std) mixin template ImplementLength(alias member)
|
||
{
|
||
static if (hasLength!(typeof(member)))
|
||
{
|
||
@property auto length()
|
||
{
|
||
return member.length;
|
||
}
|
||
alias opDollar = length;
|
||
}
|
||
}
|
||
|
||
@safe unittest
|
||
{
|
||
import std.meta : AliasSeq;
|
||
|
||
foreach (alias E; AliasSeq!(noreturn, const(noreturn), immutable(noreturn) ))
|
||
{
|
||
alias R = E[];
|
||
|
||
static assert(isInputRange!R);
|
||
static assert(isForwardRange!R);
|
||
static assert(isBidirectionalRange!R);
|
||
static assert(isRandomAccessRange!R);
|
||
}
|
||
|
||
static assert(isOutputRange!(noreturn[], noreturn));
|
||
}
|