libphobos: Merge upstream phobos 0faae92d6
Phobos changes: - Import phobos v2.111.0-beta.1. - Added `bitCast' function to `std.conv'. - Added `readfln' and `File.readfln' functions to `std.stdio'. - New procedural API for `std.sumtype'. libphobos/ChangeLog: * src/MERGE: Merge upstream phobos 0faae92d6. * testsuite/libphobos.phobos/std_array.d: Regenerate. * testsuite/libphobos.phobos/std_conv.d: Regenerate. * testsuite/libphobos.phobos/std_functional.d: Regenerate. * testsuite/libphobos.phobos/std_sumtype.d: Regenerate.
This commit is contained in:
parent
6e4045513d
commit
d63b52e059
26 changed files with 1773 additions and 426 deletions
|
@ -1,4 +1,4 @@
|
|||
1b242048c9db88c52cb0df6cd50c2b7455bedc01
|
||||
0faae92d62bdc1cc1982f0e9c65830ece1677289
|
||||
|
||||
The first line of this file holds the git revision number of the last
|
||||
merge done from the dlang/phobos repository.
|
||||
|
|
|
@ -446,35 +446,21 @@ if (fun.length >= 1)
|
|||
auto map(Range)(Range r)
|
||||
if (isInputRange!(Unqual!Range))
|
||||
{
|
||||
import std.meta : AliasSeq, staticMap;
|
||||
import std.meta : staticMap;
|
||||
import std.functional : adjoin;
|
||||
|
||||
alias RE = ElementType!(Range);
|
||||
static if (fun.length > 1)
|
||||
|
||||
alias _funs = staticMap!(unaryFun, fun);
|
||||
alias _fun = adjoin!_funs;
|
||||
|
||||
// Once https://issues.dlang.org/show_bug.cgi?id=5710 is fixed
|
||||
// accross all compilers (as of 2020-04, it wasn't fixed in LDC and GDC),
|
||||
// this validation loop can be moved into a template.
|
||||
foreach (f; _funs)
|
||||
{
|
||||
import std.functional : adjoin;
|
||||
import std.meta : staticIndexOf;
|
||||
|
||||
alias _funs = staticMap!(unaryFun, fun);
|
||||
alias _fun = adjoin!_funs;
|
||||
|
||||
// Once https://issues.dlang.org/show_bug.cgi?id=5710 is fixed
|
||||
// accross all compilers (as of 2020-04, it wasn't fixed in LDC and GDC),
|
||||
// this validation loop can be moved into a template.
|
||||
foreach (f; _funs)
|
||||
{
|
||||
static assert(!is(typeof(f(RE.init)) == void),
|
||||
static assert(!is(typeof(f(RE.init)) == void),
|
||||
"Mapping function(s) must not return void: " ~ _funs.stringof);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
alias _fun = unaryFun!fun;
|
||||
alias _funs = AliasSeq!(_fun);
|
||||
|
||||
// Do the validation separately for single parameters due to
|
||||
// https://issues.dlang.org/show_bug.cgi?id=15777.
|
||||
static assert(!is(typeof(_fun(RE.init)) == void),
|
||||
"Mapping function(s) must not return void: " ~ _funs.stringof);
|
||||
}
|
||||
|
||||
return MapResult!(_fun, Range)(r);
|
||||
|
|
|
@ -3571,18 +3571,11 @@ See_Also: $(LREF appender)
|
|||
struct Appender(A)
|
||||
if (isDynamicArray!A)
|
||||
{
|
||||
import core.memory : GC;
|
||||
import std.format.spec : FormatSpec;
|
||||
|
||||
private alias T = ElementEncodingType!A;
|
||||
|
||||
private struct Data
|
||||
{
|
||||
size_t capacity;
|
||||
Unqual!T[] arr;
|
||||
bool tryExtendBlock = false;
|
||||
}
|
||||
|
||||
private Data* _data;
|
||||
InPlaceAppender!A* impl;
|
||||
|
||||
/**
|
||||
* Constructs an `Appender` with a given array. Note that this does not copy the
|
||||
|
@ -3590,27 +3583,17 @@ if (isDynamicArray!A)
|
|||
* 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
|
||||
this(A arr) @safe
|
||||
{
|
||||
// initialize to a given array.
|
||||
_data = new Data;
|
||||
_data.arr = cast(Unqual!T[]) arr; //trusted
|
||||
impl = new InPlaceAppender!A(arr);
|
||||
}
|
||||
|
||||
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)))
|
||||
private void ensureInit() @safe
|
||||
{
|
||||
if (impl is null)
|
||||
{
|
||||
immutable cap = arr.capacity; //trusted
|
||||
// Replace with "GC.setAttr( Not Appendable )" once pure (and fixed)
|
||||
if (cap > arr.length)
|
||||
arr.length = cap;
|
||||
impl = new InPlaceAppender!A;
|
||||
}
|
||||
_data.capacity = arr.length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3623,14 +3606,10 @@ if (isDynamicArray!A)
|
|||
*/
|
||||
void reserve(size_t newCapacity)
|
||||
{
|
||||
if (_data)
|
||||
if (newCapacity != 0)
|
||||
{
|
||||
if (newCapacity > _data.capacity)
|
||||
ensureAddable(newCapacity - _data.arr.length);
|
||||
}
|
||||
else
|
||||
{
|
||||
ensureAddable(newCapacity);
|
||||
ensureInit();
|
||||
impl.reserve(newCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3641,11 +3620,11 @@ if (isDynamicArray!A)
|
|||
*/
|
||||
@property size_t capacity() const
|
||||
{
|
||||
return _data ? _data.capacity : 0;
|
||||
return impl ? impl.capacity : 0;
|
||||
}
|
||||
|
||||
/// Returns: The number of elements appended.
|
||||
@property size_t length() const => _data ? _data.arr.length : 0;
|
||||
@property size_t length() const => (impl is null) ? 0 : impl.length;
|
||||
|
||||
/**
|
||||
* Use opSlice() from now on.
|
||||
|
@ -3653,29 +3632,219 @@ if (isDynamicArray!A)
|
|||
*/
|
||||
@property inout(T)[] data() inout
|
||||
{
|
||||
return this[];
|
||||
return opSlice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The managed array.
|
||||
*/
|
||||
@property inout(T)[] opSlice() inout @trusted
|
||||
@property inout(T)[] opSlice() inout @safe
|
||||
{
|
||||
return impl ? impl.opSlice() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (InPlaceAppender!A.canPutItem!U)
|
||||
{
|
||||
ensureInit();
|
||||
impl.put(item);
|
||||
}
|
||||
|
||||
// Const fixing hack.
|
||||
void put(Range)(Range items)
|
||||
if (InPlaceAppender!A.canPutConstRange!Range)
|
||||
{
|
||||
if (!items.empty)
|
||||
{
|
||||
ensureInit();
|
||||
impl.put(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 (InPlaceAppender!A.canPutRange!Range)
|
||||
{
|
||||
if (!items.empty)
|
||||
{
|
||||
ensureInit();
|
||||
impl.put(items);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() @safe pure nothrow
|
||||
{
|
||||
if (impl)
|
||||
{
|
||||
impl.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) @safe pure
|
||||
{
|
||||
import std.exception : enforce;
|
||||
if (impl)
|
||||
{
|
||||
impl.shrinkTo(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
|
||||
{
|
||||
return InPlaceAppender!A.toStringImpl(Unqual!(typeof(this)).stringof, impl ? impl.data : null);
|
||||
}
|
||||
|
||||
/// ditto
|
||||
template toString(Writer)
|
||||
if (isOutputRange!(Writer, char))
|
||||
{
|
||||
void toString(scope ref Writer w, scope const ref FormatSpec!char fmt) const
|
||||
{
|
||||
InPlaceAppender!A.toStringImpl(Unqual!(typeof(this)).stringof, impl ? impl.data : null, w, fmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
@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);
|
||||
app2.put([ 4, 5, 6 ]);
|
||||
assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]);
|
||||
}
|
||||
|
||||
package(std) struct InPlaceAppender(A)
|
||||
if (isDynamicArray!A)
|
||||
{
|
||||
import core.memory : GC;
|
||||
import std.format.spec : FormatSpec;
|
||||
|
||||
private alias T = ElementEncodingType!A;
|
||||
|
||||
private
|
||||
{
|
||||
size_t _capacity;
|
||||
Unqual!T[] arr;
|
||||
bool tryExtendBlock = false;
|
||||
}
|
||||
|
||||
@disable this(ref InPlaceAppender);
|
||||
|
||||
this(A arrIn) @trusted
|
||||
{
|
||||
arr = cast(Unqual!T[]) arrIn; //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(arrIn.length = size_t.max)))
|
||||
{
|
||||
immutable cap = arrIn.capacity; //trusted
|
||||
// Replace with "GC.setAttr( Not Appendable )" once pure (and fixed)
|
||||
if (cap > arrIn.length)
|
||||
arrIn.length = cap;
|
||||
}
|
||||
_capacity = arrIn.length;
|
||||
}
|
||||
|
||||
void reserve(size_t newCapacity)
|
||||
{
|
||||
if (newCapacity > _capacity)
|
||||
ensureAddable(newCapacity - arr.length);
|
||||
}
|
||||
|
||||
@property size_t capacity() const
|
||||
{
|
||||
return _capacity;
|
||||
}
|
||||
|
||||
@property size_t length() const => arr.length;
|
||||
|
||||
@property inout(T)[] data() inout
|
||||
{
|
||||
return this[];
|
||||
}
|
||||
|
||||
inout(T)[] opSlice() inout @trusted
|
||||
{
|
||||
/* @trusted operation:
|
||||
* casting Unqual!T[] to inout(T)[]
|
||||
*/
|
||||
return cast(typeof(return))(_data ? _data.arr : null);
|
||||
return cast(typeof(return)) arr;
|
||||
}
|
||||
|
||||
// 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 len = arr.length;
|
||||
immutable reqlen = len + nelems;
|
||||
|
||||
if (_data.capacity >= reqlen)
|
||||
if (_capacity >= reqlen)
|
||||
return;
|
||||
|
||||
// need to increase capacity
|
||||
|
@ -3683,17 +3852,17 @@ if (isDynamicArray!A)
|
|||
{
|
||||
static if (__traits(compiles, new Unqual!T[1]))
|
||||
{
|
||||
_data.arr.length = reqlen;
|
||||
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;
|
||||
arr = arr[0 .. _capacity];
|
||||
foreach (i; _capacity .. reqlen)
|
||||
arr ~= Unqual!T.init;
|
||||
}
|
||||
_data.arr = _data.arr[0 .. len];
|
||||
_data.capacity = reqlen;
|
||||
arr = arr[0 .. len];
|
||||
_capacity = reqlen;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -3701,11 +3870,11 @@ if (isDynamicArray!A)
|
|||
// 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);
|
||||
auto newlen = appenderNewCapacity!(T.sizeof)(_capacity, reqlen);
|
||||
// first, try extending the current block
|
||||
if (_data.tryExtendBlock)
|
||||
if (tryExtendBlock)
|
||||
{
|
||||
immutable u = (() @trusted => GC.extend(_data.arr.ptr, nelems * T.sizeof, (newlen - len) * T.sizeof))();
|
||||
immutable u = (() @trusted => GC.extend(arr.ptr, nelems * T.sizeof, (newlen - len) * T.sizeof))();
|
||||
if (u)
|
||||
{
|
||||
// extend worked, update the capacity
|
||||
|
@ -3714,11 +3883,10 @@ if (isDynamicArray!A)
|
|||
// at large unused blocks.
|
||||
static if (hasIndirections!T)
|
||||
{
|
||||
immutable addedSize = u - (_data.capacity * T.sizeof);
|
||||
() @trusted { memset(_data.arr.ptr + _data.capacity, 0, addedSize); }();
|
||||
immutable addedSize = u - (_capacity * T.sizeof);
|
||||
() @trusted { memset(arr.ptr + _capacity, 0, addedSize); }();
|
||||
}
|
||||
|
||||
_data.capacity = u / T.sizeof;
|
||||
_capacity = u / T.sizeof;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -3732,11 +3900,11 @@ if (isDynamicArray!A)
|
|||
~ "available pointer range");
|
||||
|
||||
auto bi = (() @trusted => GC.qalloc(nbytes, blockAttribute!T))();
|
||||
_data.capacity = bi.size / T.sizeof;
|
||||
_capacity = bi.size / T.sizeof;
|
||||
import core.stdc.string : memcpy;
|
||||
if (len)
|
||||
() @trusted { memcpy(bi.base, _data.arr.ptr, len * T.sizeof); }();
|
||||
|
||||
_data.arr = (() @trusted => (cast(Unqual!T*) bi.base)[0 .. len])();
|
||||
() @trusted { memcpy(bi.base, arr.ptr, len * T.sizeof); }();
|
||||
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
|
||||
|
@ -3747,7 +3915,7 @@ if (isDynamicArray!A)
|
|||
memset(bi.base + (len * T.sizeof), 0, (newlen - len) * T.sizeof);
|
||||
}();
|
||||
|
||||
_data.tryExtendBlock = true;
|
||||
tryExtendBlock = true;
|
||||
// leave the old data, for safety reasons
|
||||
}
|
||||
}
|
||||
|
@ -3763,13 +3931,13 @@ if (isDynamicArray!A)
|
|||
enum bool canPutConstRange =
|
||||
isInputRange!(Unqual!Range) &&
|
||||
!isInputRange!Range &&
|
||||
is(typeof(Appender.init.put(Range.init.front)));
|
||||
is(typeof(InPlaceAppender.init.put(Range.init.front)));
|
||||
}
|
||||
private template canPutRange(Range)
|
||||
{
|
||||
enum bool canPutRange =
|
||||
isInputRange!Range &&
|
||||
is(typeof(Appender.init.put(Range.init.front)));
|
||||
is(typeof(InPlaceAppender.init.put(Range.init.front)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3798,13 +3966,13 @@ if (isDynamicArray!A)
|
|||
import core.lifetime : emplace;
|
||||
|
||||
ensureAddable(1);
|
||||
immutable len = _data.arr.length;
|
||||
immutable len = arr.length;
|
||||
|
||||
auto bigData = (() @trusted => _data.arr.ptr[0 .. len + 1])();
|
||||
auto bigData = (() @trusted => 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;
|
||||
arr = bigData;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3848,16 +4016,16 @@ if (isDynamicArray!A)
|
|||
auto bigDataFun(size_t extra)
|
||||
{
|
||||
ensureAddable(extra);
|
||||
return (() @trusted => _data.arr.ptr[0 .. _data.arr.length + extra])();
|
||||
return (() @trusted => arr.ptr[0 .. arr.length + extra])();
|
||||
}
|
||||
auto bigData = bigDataFun(items.length);
|
||||
|
||||
immutable len = _data.arr.length;
|
||||
immutable len = arr.length;
|
||||
immutable newlen = bigData.length;
|
||||
|
||||
alias UT = Unqual!T;
|
||||
|
||||
static if (is(typeof(_data.arr[] = items[])) &&
|
||||
static if (is(typeof(arr[] = items[])) &&
|
||||
!hasElaborateAssign!UT && isAssignable!(UT, ElementEncodingType!Range))
|
||||
{
|
||||
bigData[len .. newlen] = items[];
|
||||
|
@ -3873,7 +4041,7 @@ if (isDynamicArray!A)
|
|||
}
|
||||
|
||||
//We do this at the end, in case of exceptions
|
||||
_data.arr = bigData;
|
||||
arr = bigData;
|
||||
}
|
||||
else static if (isSomeChar!T && isSomeChar!(ElementType!Range) &&
|
||||
!is(immutable T == immutable ElementType!Range))
|
||||
|
@ -3916,10 +4084,7 @@ if (isDynamicArray!A)
|
|||
*/
|
||||
void clear() @trusted pure nothrow
|
||||
{
|
||||
if (_data)
|
||||
{
|
||||
_data.arr = _data.arr.ptr[0 .. 0];
|
||||
}
|
||||
arr = arr.ptr[0 .. 0];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3931,13 +4096,8 @@ if (isDynamicArray!A)
|
|||
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");
|
||||
enforce(newlength <= arr.length, "Attempting to shrink Appender with newlength > length");
|
||||
arr = arr.ptr[0 .. newlength];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3952,13 +4112,18 @@ if (isDynamicArray!A)
|
|||
* Returns:
|
||||
* A `string` if `writer` is not set; `void` otherwise.
|
||||
*/
|
||||
string toString()() const
|
||||
auto toString() const
|
||||
{
|
||||
return toStringImpl(Unqual!(typeof(this)).stringof, data);
|
||||
}
|
||||
|
||||
static auto toStringImpl(string typeName, const T[] arr)
|
||||
{
|
||||
import std.format.spec : singleSpec;
|
||||
|
||||
auto app = appender!string();
|
||||
InPlaceAppender!string app;
|
||||
auto spec = singleSpec("%s");
|
||||
immutable len = _data ? _data.arr.length : 0;
|
||||
immutable len = arr.length;
|
||||
// different reserve lengths because each element in a
|
||||
// non-string-like array uses two extra characters for `, `.
|
||||
static if (isSomeString!A)
|
||||
|
@ -3971,25 +4136,25 @@ if (isDynamicArray!A)
|
|||
// length, as it assumes each element is only one char
|
||||
app.reserve((len * 3) + 25);
|
||||
}
|
||||
toString(app, spec);
|
||||
toStringImpl(typeName, arr, app, spec);
|
||||
return app.data;
|
||||
}
|
||||
|
||||
import std.format.spec : FormatSpec;
|
||||
|
||||
/// ditto
|
||||
template toString(Writer)
|
||||
void toString(Writer)(scope ref Writer w, scope const ref FormatSpec!char fmt) const
|
||||
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, ')');
|
||||
}
|
||||
toStringImpl(Unqual!(typeof(this)).stringof, data, w, fmt);
|
||||
}
|
||||
|
||||
static void toStringImpl(Writer)(string typeName, const T[] data, scope ref Writer w,
|
||||
scope const ref FormatSpec!char fmt)
|
||||
{
|
||||
import std.format.write : formatValue;
|
||||
import std.range.primitives : put;
|
||||
put(w, typeName);
|
||||
put(w, '(');
|
||||
formatValue(w, data, fmt);
|
||||
put(w, ')');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4032,6 +4197,16 @@ if (isDynamicArray!A)
|
|||
assert(app3[] == "Appender!(int[])(0001, 0002, 0003)");
|
||||
}
|
||||
|
||||
@safe pure unittest
|
||||
{
|
||||
auto app = appender!(char[])();
|
||||
app ~= "hello";
|
||||
app.clear;
|
||||
// not a promise, just nothing else exercises capacity
|
||||
// and this is the expected sort of behaviour
|
||||
assert(app.capacity >= 5);
|
||||
}
|
||||
|
||||
// https://issues.dlang.org/show_bug.cgi?id=17251
|
||||
@safe pure nothrow unittest
|
||||
{
|
||||
|
@ -4294,6 +4469,24 @@ unittest
|
|||
assert(app2.capacity >= 5);
|
||||
}
|
||||
|
||||
/++
|
||||
Convenience function that returns a $(LREF InPlaceAppender) instance,
|
||||
optionally initialized with `array`.
|
||||
+/
|
||||
package(std) InPlaceAppender!A inPlaceAppender(A)()
|
||||
if (isDynamicArray!A)
|
||||
{
|
||||
return InPlaceAppender!A(null);
|
||||
}
|
||||
/// ditto
|
||||
package(std) InPlaceAppender!(E[]) inPlaceAppender(A : E[], E)(auto ref A array)
|
||||
{
|
||||
static assert(!isStaticArray!A || __traits(isRef, array),
|
||||
"Cannot create InPlaceAppender from an rvalue static array");
|
||||
|
||||
return InPlaceAppender!(E[])(array);
|
||||
}
|
||||
|
||||
/++
|
||||
Convenience function that returns an $(LREF Appender) instance,
|
||||
optionally initialized with `array`.
|
||||
|
|
|
@ -265,10 +265,11 @@ public:
|
|||
}
|
||||
else static if (op=="*")
|
||||
{
|
||||
if (y == 0)
|
||||
if (y == 0 || data.isZero())
|
||||
{
|
||||
sign = false;
|
||||
data = 0UL;
|
||||
return this;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -361,6 +362,29 @@ public:
|
|||
return this;
|
||||
}
|
||||
|
||||
// https://issues.dlang.org/show_bug.cgi?id=10565
|
||||
@safe unittest
|
||||
{
|
||||
// Test cases from the issue
|
||||
BigInt a = BigInt("0");
|
||||
BigInt b = BigInt("-0");
|
||||
BigInt c = BigInt("0") * -1;
|
||||
BigInt d = BigInt("0") * -42;
|
||||
BigInt e = BigInt("0"); e *= -1;
|
||||
BigInt f = BigInt(c);
|
||||
BigInt g = BigInt("0") * cast(byte) -1;
|
||||
BigInt h = BigInt("0"); h *= BigInt("-1");
|
||||
BigInt i = BigInt("0"); i -= 2 * i;
|
||||
BigInt j = BigInt("0"); j = -j;
|
||||
// All of these should be zero and not negative
|
||||
auto values = [a, b, c, d, e, f, g, h, i, j];
|
||||
foreach (val; values)
|
||||
{
|
||||
assert(val == 0, "BigInt value should be equal to zero");
|
||||
assert(!(val < 0), "BigInt zero should not be negative");
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
@safe unittest
|
||||
{
|
||||
|
|
|
@ -2078,7 +2078,7 @@ struct ProperCompare
|
|||
/**
|
||||
|
||||
Hook that reserves a special value as a "Not a Number" representative. For
|
||||
signed integrals, the reserved value is `T.min`. For signed integrals, the
|
||||
signed integrals, the reserved value is `T.min`. For unsigned integrals, the
|
||||
reserved value is `T.max`.
|
||||
|
||||
The default value of a $(D Checked!(X, WithNaN)) is its NaN value, so care must
|
||||
|
|
|
@ -451,7 +451,7 @@ iterating over the container are never invalidated.
|
|||
|
||||
Returns: The number of elements inserted
|
||||
|
||||
Complexity: $(BIGOH log(n))
|
||||
Complexity: $(BIGOH m), where `m` is the length of `stuff`
|
||||
*/
|
||||
size_t insertFront(Stuff)(Stuff stuff)
|
||||
{
|
||||
|
|
|
@ -13,6 +13,7 @@ $(TR $(TD Generic) $(TD
|
|||
$(LREF parse)
|
||||
$(LREF to)
|
||||
$(LREF toChars)
|
||||
$(LREF bitCast)
|
||||
))
|
||||
$(TR $(TD Strings) $(TD
|
||||
$(LREF text)
|
||||
|
@ -6047,3 +6048,38 @@ package enum toCtString(ulong n) = n.stringof[0 .. $ - "LU".length];
|
|||
assert(toCtString!0 == "0");
|
||||
assert(toCtString!123456 == "123456");
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the raw bits of a value and reinterprets them as a different type.
|
||||
*
|
||||
* Params:
|
||||
* T = the new type.
|
||||
* value = the value to reinterpret.
|
||||
*
|
||||
* Returns: a reference to the reinterpreted value.
|
||||
*/
|
||||
pragma(inline, true)
|
||||
ref T bitCast(T, S)(ref S value)
|
||||
if (T.sizeof <= S.sizeof)
|
||||
{
|
||||
return *cast(T*) &value;
|
||||
}
|
||||
|
||||
///
|
||||
@safe unittest
|
||||
{
|
||||
uint n = 0xDEADBEEF;
|
||||
|
||||
version (LittleEndian)
|
||||
assert(n.bitCast!(ubyte[4]) == [0xEF, 0xBE, 0xAD, 0xDE]);
|
||||
version (BigEndian)
|
||||
assert(n.bitCast!(ubyte[4]) == [0xDE, 0xAD, 0xBE, 0xEF]);
|
||||
}
|
||||
|
||||
// Sizes must be compatible
|
||||
@safe unittest
|
||||
{
|
||||
uint n;
|
||||
|
||||
assert(!__traits(compiles, n.bitCast!ulong));
|
||||
}
|
||||
|
|
|
@ -166,7 +166,6 @@ public:
|
|||
Thread.sleep(usecs(1));
|
||||
|
||||
sw.reset();
|
||||
assert(sw.peek() < msecs(1));
|
||||
assert(sw._timeStarted > before);
|
||||
assert(sw._timeStarted <= MonoTime.currTime);
|
||||
}
|
||||
|
|
|
@ -1476,37 +1476,40 @@ if (is(T == float) || is(T == double)
|
|||
|
||||
assertCTFEable!(
|
||||
{
|
||||
// log2 is broken for x87-reals on some computers in CTFE
|
||||
// the following tests excludes these computers from the tests
|
||||
// (https://issues.dlang.org/show_bug.cgi?id=21757)
|
||||
enum test = cast(int) log2(3.05e2312L);
|
||||
static if (real.mant_dig == 64 && test == 7681)
|
||||
static if (real.mant_dig == 64) // 80 bit reals
|
||||
{
|
||||
auto f = FormatSpec!dchar("");
|
||||
f.spec = 'e';
|
||||
assert(printFloat(real.infinity, f) == "inf");
|
||||
assert(printFloat(10.0L, f) == "1.000000e+01");
|
||||
assert(printFloat(2.6080L, f) == "2.608000e+00");
|
||||
assert(printFloat(3.05e2312L, f) == "3.050000e+2312");
|
||||
// log2 is broken for x87-reals on some computers in CTFE
|
||||
// the following tests excludes these computers from the tests
|
||||
// (https://issues.dlang.org/show_bug.cgi?id=21757)
|
||||
enum test = cast(int) log2(3.05e2312L);
|
||||
static if (test == 7681)
|
||||
{
|
||||
auto f = FormatSpec!dchar("");
|
||||
f.spec = 'e';
|
||||
assert(printFloat(real.infinity, f) == "inf");
|
||||
assert(printFloat(10.0L, f) == "1.000000e+01");
|
||||
assert(printFloat(2.6080L, f) == "2.608000e+00");
|
||||
assert(printFloat(3.05e2312L, f) == "3.050000e+2312");
|
||||
|
||||
f.precision = 60;
|
||||
assert(printFloat(2.65e-54L, f) ==
|
||||
"2.650000000000000000059009987400547013941028940935296547599415e-54");
|
||||
f.precision = 60;
|
||||
assert(printFloat(2.65e-54L, f) ==
|
||||
"2.650000000000000000059009987400547013941028940935296547599415e-54");
|
||||
|
||||
/*
|
||||
commented out, because CTFE is currently too slow for 5000 digits with extreme values
|
||||
/*
|
||||
commented out, because CTFE is currently too slow for 5000 digits with extreme values
|
||||
|
||||
f.precision = 5000;
|
||||
auto result2 = printFloat(1.2119e-4822L, f);
|
||||
assert(result2.length == 5008);
|
||||
assert(result2[$ - 20 .. $] == "60729486595339e-4822");
|
||||
auto result3 = printFloat(real.min_normal, f);
|
||||
assert(result3.length == 5008);
|
||||
assert(result3[$ - 20 .. $] == "20781410082267e-4932");
|
||||
auto result4 = printFloat(real.min_normal.nextDown, f);
|
||||
assert(result4.length == 5008);
|
||||
assert(result4[$ - 20 .. $] == "81413263331006e-4932");
|
||||
*/
|
||||
f.precision = 5000;
|
||||
auto result2 = printFloat(1.2119e-4822L, f);
|
||||
assert(result2.length == 5008);
|
||||
assert(result2[$ - 20 .. $] == "60729486595339e-4822");
|
||||
auto result3 = printFloat(real.min_normal, f);
|
||||
assert(result3.length == 5008);
|
||||
assert(result3[$ - 20 .. $] == "20781410082267e-4932");
|
||||
auto result4 = printFloat(real.min_normal.nextDown, f);
|
||||
assert(result4.length == 5008);
|
||||
assert(result4[$ - 20 .. $] == "81413263331006e-4932");
|
||||
*/
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2149,39 +2152,42 @@ if (is(T == float) || is(T == double)
|
|||
|
||||
assertCTFEable!(
|
||||
{
|
||||
// log2 is broken for x87-reals on some computers in CTFE
|
||||
// the following tests excludes these computers from the tests
|
||||
// (https://issues.dlang.org/show_bug.cgi?id=21757)
|
||||
enum test = cast(int) log2(3.05e2312L);
|
||||
static if (real.mant_dig == 64 && test == 7681)
|
||||
static if (real.mant_dig == 64) // 80 bit reals
|
||||
{
|
||||
auto f = FormatSpec!dchar("");
|
||||
f.spec = 'f';
|
||||
assert(printFloat(real.infinity, f) == "inf");
|
||||
assert(printFloat(10.0L, f) == "10.000000");
|
||||
assert(printFloat(2.6080L, f) == "2.608000");
|
||||
auto result1 = printFloat(3.05e2312L, f);
|
||||
assert(result1.length == 2320);
|
||||
assert(result1[0 .. 20] == "30499999999999999999");
|
||||
// log2 is broken for x87-reals on some computers in CTFE
|
||||
// the following tests excludes these computers from the tests
|
||||
// (https://issues.dlang.org/show_bug.cgi?id=21757)
|
||||
enum test = cast(int) log2(3.05e2312L);
|
||||
static if (test == 7681)
|
||||
{
|
||||
auto f = FormatSpec!dchar("");
|
||||
f.spec = 'f';
|
||||
assert(printFloat(real.infinity, f) == "inf");
|
||||
assert(printFloat(10.0L, f) == "10.000000");
|
||||
assert(printFloat(2.6080L, f) == "2.608000");
|
||||
auto result1 = printFloat(3.05e2312L, f);
|
||||
assert(result1.length == 2320);
|
||||
assert(result1[0 .. 20] == "30499999999999999999");
|
||||
|
||||
f.precision = 60;
|
||||
assert(printFloat(2.65e-54L, f) ==
|
||||
"0.000000000000000000000000000000000000000000000000000002650000");
|
||||
f.precision = 60;
|
||||
assert(printFloat(2.65e-54L, f) ==
|
||||
"0.000000000000000000000000000000000000000000000000000002650000");
|
||||
|
||||
/*
|
||||
commented out, because CTFE is currently too slow for 5000 digits with extreme values
|
||||
/*
|
||||
commented out, because CTFE is currently too slow for 5000 digits with extreme values
|
||||
|
||||
f.precision = 5000;
|
||||
auto result2 = printFloat(1.2119e-4822L, f);
|
||||
assert(result2.length == 5002);
|
||||
assert(result2[$ - 20 .. $] == "60076763752233836613");
|
||||
auto result3 = printFloat(real.min_normal, f);
|
||||
assert(result3.length == 5002);
|
||||
assert(result3[$ - 20 .. $] == "47124010882722980874");
|
||||
auto result4 = printFloat(real.min_normal.nextDown, f);
|
||||
assert(result4.length == 5002);
|
||||
assert(result4[$ - 20 .. $] == "52925846892214823939");
|
||||
*/
|
||||
f.precision = 5000;
|
||||
auto result2 = printFloat(1.2119e-4822L, f);
|
||||
assert(result2.length == 5002);
|
||||
assert(result2[$ - 20 .. $] == "60076763752233836613");
|
||||
auto result3 = printFloat(real.min_normal, f);
|
||||
assert(result3.length == 5002);
|
||||
assert(result3[$ - 20 .. $] == "47124010882722980874");
|
||||
auto result4 = printFloat(real.min_normal.nextDown, f);
|
||||
assert(result4.length == 5002);
|
||||
assert(result4[$ - 20 .. $] == "52925846892214823939");
|
||||
*/
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2830,37 +2836,40 @@ if (is(T == float) || is(T == double)
|
|||
|
||||
assertCTFEable!(
|
||||
{
|
||||
// log2 is broken for x87-reals on some computers in CTFE
|
||||
// the following tests excludes these computers from the tests
|
||||
// (https://issues.dlang.org/show_bug.cgi?id=21757)
|
||||
enum test = cast(int) log2(3.05e2312L);
|
||||
static if (real.mant_dig == 64 && test == 7681)
|
||||
static if (real.mant_dig == 64) // 80 bit reals
|
||||
{
|
||||
auto f = FormatSpec!dchar("");
|
||||
f.spec = 'g';
|
||||
assert(printFloat(real.infinity, f) == "inf");
|
||||
assert(printFloat(10.0L, f) == "10");
|
||||
assert(printFloat(2.6080L, f) == "2.608");
|
||||
assert(printFloat(3.05e2312L, f) == "3.05e+2312");
|
||||
// log2 is broken for x87-reals on some computers in CTFE
|
||||
// the following tests excludes these computers from the tests
|
||||
// (https://issues.dlang.org/show_bug.cgi?id=21757)
|
||||
enum test = cast(int) log2(3.05e2312L);
|
||||
static if (test == 7681)
|
||||
{
|
||||
auto f = FormatSpec!dchar("");
|
||||
f.spec = 'g';
|
||||
assert(printFloat(real.infinity, f) == "inf");
|
||||
assert(printFloat(10.0L, f) == "10");
|
||||
assert(printFloat(2.6080L, f) == "2.608");
|
||||
assert(printFloat(3.05e2312L, f) == "3.05e+2312");
|
||||
|
||||
f.precision = 60;
|
||||
assert(printFloat(2.65e-54L, f) ==
|
||||
"2.65000000000000000005900998740054701394102894093529654759941e-54");
|
||||
f.precision = 60;
|
||||
assert(printFloat(2.65e-54L, f) ==
|
||||
"2.65000000000000000005900998740054701394102894093529654759941e-54");
|
||||
|
||||
/*
|
||||
commented out, because CTFE is currently too slow for 5000 digits with extreme values
|
||||
/*
|
||||
commented out, because CTFE is currently too slow for 5000 digits with extreme values
|
||||
|
||||
f.precision = 5000;
|
||||
auto result2 = printFloat(1.2119e-4822L, f);
|
||||
assert(result2.length == 5007);
|
||||
assert(result2[$ - 20 .. $] == "26072948659534e-4822");
|
||||
auto result3 = printFloat(real.min_normal, f);
|
||||
assert(result3.length == 5007);
|
||||
assert(result3[$ - 20 .. $] == "72078141008227e-4932");
|
||||
auto result4 = printFloat(real.min_normal.nextDown, f);
|
||||
assert(result4.length == 5007);
|
||||
assert(result4[$ - 20 .. $] == "48141326333101e-4932");
|
||||
*/
|
||||
f.precision = 5000;
|
||||
auto result2 = printFloat(1.2119e-4822L, f);
|
||||
assert(result2.length == 5007);
|
||||
assert(result2[$ - 20 .. $] == "26072948659534e-4822");
|
||||
auto result3 = printFloat(real.min_normal, f);
|
||||
assert(result3.length == 5007);
|
||||
assert(result3[$ - 20 .. $] == "72078141008227e-4932");
|
||||
auto result4 = printFloat(real.min_normal.nextDown, f);
|
||||
assert(result4.length == 5007);
|
||||
assert(result4[$ - 20 .. $] == "48141326333101e-4932");
|
||||
*/
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ package(std.format):
|
|||
|
||||
void skipData(Range, Char)(ref Range input, scope const ref FormatSpec!Char spec)
|
||||
{
|
||||
import std.ascii : isDigit;
|
||||
import std.ascii : isDigit, isWhite;
|
||||
import std.range.primitives : empty, front, popFront;
|
||||
|
||||
switch (spec.spec)
|
||||
|
@ -33,6 +33,9 @@ void skipData(Range, Char)(ref Range input, scope const ref FormatSpec!Char spec
|
|||
case 'd':
|
||||
if (input.front == '+' || input.front == '-') input.popFront();
|
||||
goto case 'u';
|
||||
case 's':
|
||||
while (!input.empty && !isWhite(input.front)) input.popFront();
|
||||
break;
|
||||
case 'u':
|
||||
while (!input.empty && isDigit(input.front)) input.popFront();
|
||||
break;
|
||||
|
|
|
@ -902,13 +902,14 @@ if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
|
|||
{
|
||||
import std.math.exponential : log2;
|
||||
|
||||
// log2 is broken for x87-reals on some computers in CTFE
|
||||
// the following test excludes these computers from the test
|
||||
// (https://issues.dlang.org/show_bug.cgi?id=21757)
|
||||
enum test = cast(int) log2(3.05e2312L);
|
||||
static if (real.mant_dig == 64 && test == 7681) // 80 bit reals
|
||||
static if (real.mant_dig == 64) // 80 bit reals
|
||||
{
|
||||
static assert(format!"%e"(real.max) == "1.189731e+4932");
|
||||
// log2 is broken for x87-reals on some computers in CTFE
|
||||
// the following test excludes these computers from the test
|
||||
// (https://issues.dlang.org/show_bug.cgi?id=21757)
|
||||
enum test = cast(int) log2(3.05e2312L);
|
||||
static if (test == 7681)
|
||||
static assert(format!"%e"(real.max) == "1.189731e+4932");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -377,6 +377,16 @@ if (!isType!fmt && isSomeString!(typeof(fmt)))
|
|||
assert(t[0] == 1 && t[1] == 2.125);
|
||||
}
|
||||
|
||||
@safe pure unittest
|
||||
{
|
||||
string hello;
|
||||
string world;
|
||||
|
||||
assert("hello ignore world".formattedRead("%s %*s %s", hello, world) == 2);
|
||||
assert(hello == "hello");
|
||||
assert(world == "world");
|
||||
}
|
||||
|
||||
// https://issues.dlang.org/show_bug.cgi?id=23600
|
||||
@safe pure unittest
|
||||
{
|
||||
|
|
|
@ -51,6 +51,9 @@ $(TR $(TH Function Name) $(TH Description)
|
|||
$(TR $(TD $(LREF bind))
|
||||
$(TD Passes the fields of a struct as arguments to a function.
|
||||
))
|
||||
$(TR $(TD $(LREF ctEval))
|
||||
$(TD Enforces the evaluation of an expression during compile-time.
|
||||
))
|
||||
))
|
||||
|
||||
Copyright: Copyright Andrei Alexandrescu 2008 - 2009.
|
||||
|
@ -1886,16 +1889,21 @@ private template buildDelegate(F)
|
|||
|
||||
@safe unittest
|
||||
{
|
||||
static int inc(ref uint num) {
|
||||
static int inc(ref int num) {
|
||||
num++;
|
||||
return 8675309;
|
||||
}
|
||||
|
||||
uint myNum = 0x1337;
|
||||
struct S1 { int opCall() { inc(myNum); return myNum; } }
|
||||
static assert(!is(typeof(&s1.opCall) == delegate));
|
||||
struct S1
|
||||
{
|
||||
static int myNum = 0x1337;
|
||||
static int opCall() { inc(myNum); return myNum; }
|
||||
}
|
||||
|
||||
S1 s1;
|
||||
auto getvals1 = toDelegate(s1);
|
||||
static assert(!is(typeof(&s1.opCall) == delegate));
|
||||
static assert( is(typeof(toDelegate(s1)) == delegate));
|
||||
assert(getvals1() == 0x1338);
|
||||
}
|
||||
|
||||
|
@ -1924,15 +1932,18 @@ private template buildDelegate(F)
|
|||
assert(getvali() == 3);
|
||||
|
||||
struct S1 { int opCall() { inc(myNum); return myNum; } }
|
||||
static assert(!is(typeof(&s1.opCall) == delegate));
|
||||
S1 s1;
|
||||
auto getvals1 = toDelegate(s1);
|
||||
static assert(is(typeof(&s1.opCall) == delegate));
|
||||
static assert(is(typeof(getvals1) == delegate));
|
||||
assert(&s1.opCall is getvals1);
|
||||
assert(getvals1() == 4);
|
||||
|
||||
struct S2 { static int opCall() { return 123456; } }
|
||||
static assert(!is(typeof(&S2.opCall) == delegate));
|
||||
S2 s2;
|
||||
auto getvals2 =&S2.opCall;
|
||||
auto getvals2 = toDelegate(s2);
|
||||
static assert(!is(typeof(&S2.opCall) == delegate));
|
||||
static assert( is(typeof(getvals2) == delegate));
|
||||
assert(getvals2() == 123456);
|
||||
|
||||
/* test for attributes */
|
||||
|
@ -2167,3 +2178,50 @@ template bind(alias fun)
|
|||
static assert(!__traits(isRef, x));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces the evaluation of an expression during compile-time.
|
||||
*
|
||||
* Computes the value of an expression during compilation (CTFE).
|
||||
*
|
||||
* This is useful for call chains in functional programming
|
||||
* where declaring an `enum` constant would require splitting
|
||||
* the pipeline.
|
||||
*
|
||||
* Params:
|
||||
* expr = expression to evaluate
|
||||
* See_also:
|
||||
* $(LINK https://dlang.org/spec/function.html#interpretation)
|
||||
*/
|
||||
enum ctEval(alias expr) = expr;
|
||||
|
||||
///
|
||||
@safe unittest
|
||||
{
|
||||
import std.math : abs;
|
||||
|
||||
// No explicit `enum` needed.
|
||||
float result = ctEval!(abs(-3));
|
||||
assert(result == 3);
|
||||
|
||||
// Can be statically asserted.
|
||||
static assert(ctEval!(abs(-4)) == 4);
|
||||
static assert(ctEval!(abs( 9)) == 9);
|
||||
}
|
||||
|
||||
///
|
||||
@safe unittest
|
||||
{
|
||||
import core.stdc.math : round;
|
||||
import std.conv : to;
|
||||
import std.math : abs, PI, sin;
|
||||
|
||||
// `round` from the C standard library cannot be interpreted at compile
|
||||
// time, because it has no available source code. However the function
|
||||
// calls preceding `round` can be evaluated during compile time.
|
||||
int result = ctEval!(abs(sin(1.0)) * 180 / PI)
|
||||
.round()
|
||||
.to!int();
|
||||
|
||||
assert(result == 48);
|
||||
}
|
||||
|
|
|
@ -610,6 +610,23 @@ private template optionValidator(A...)
|
|||
alias optionValidator = message;
|
||||
}
|
||||
|
||||
private void handleConversion(R)(string option, string value, R* receiver,
|
||||
size_t idx, string file = __FILE__, size_t line = __LINE__)
|
||||
{
|
||||
import std.conv : to, ConvException;
|
||||
import std.format : format;
|
||||
try
|
||||
{
|
||||
*receiver = to!(typeof(*receiver))(value);
|
||||
}
|
||||
catch (ConvException e)
|
||||
{
|
||||
throw new ConvException(format("Argument '%s' at position '%u' could "
|
||||
~ "not be converted to type '%s' as required by option '%s'.",
|
||||
value, idx, R.stringof, option), e, file, line);
|
||||
}
|
||||
}
|
||||
|
||||
@safe pure unittest
|
||||
{
|
||||
alias P = void*;
|
||||
|
@ -864,7 +881,7 @@ private bool handleOption(R)(string option, R receiver, ref string[] args,
|
|||
if (val.length)
|
||||
{
|
||||
// parse '--b=true/false'
|
||||
*receiver = to!(typeof(*receiver))(val);
|
||||
handleConversion(option, val, receiver, i);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -888,15 +905,22 @@ private bool handleOption(R)(string option, R receiver, ref string[] args,
|
|||
val = args[i];
|
||||
args = args[0 .. i] ~ args[i + 1 .. $];
|
||||
}
|
||||
static if (is(typeof(*receiver) == enum))
|
||||
static if (is(typeof(*receiver) == enum) ||
|
||||
is(typeof(*receiver) == string))
|
||||
{
|
||||
*receiver = to!(typeof(*receiver))(val);
|
||||
handleConversion(option, val, receiver, i);
|
||||
}
|
||||
else static if (is(typeof(*receiver) : real))
|
||||
{
|
||||
// numeric receiver
|
||||
if (incremental) ++*receiver;
|
||||
else *receiver = to!(typeof(*receiver))(val);
|
||||
if (incremental)
|
||||
{
|
||||
++*receiver;
|
||||
}
|
||||
else
|
||||
{
|
||||
handleConversion(option, val, receiver, i);
|
||||
}
|
||||
}
|
||||
else static if (is(typeof(*receiver) == string))
|
||||
{
|
||||
|
@ -936,12 +960,18 @@ private bool handleOption(R)(string option, R receiver, ref string[] args,
|
|||
|
||||
if (arraySep == "")
|
||||
{
|
||||
*receiver ~= to!E(val);
|
||||
E tmp;
|
||||
handleConversion(option, val, &tmp, i);
|
||||
*receiver ~= tmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (elem; val.splitter(arraySep).map!(a => to!E(a))())
|
||||
*receiver ~= elem;
|
||||
foreach (elem; val.splitter(arraySep))
|
||||
{
|
||||
E tmp;
|
||||
handleConversion(option, elem, &tmp, i);
|
||||
*receiver ~= tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
else static if (isAssociativeArray!(typeof(*receiver)))
|
||||
|
@ -961,7 +991,14 @@ private bool handleOption(R)(string option, R receiver, ref string[] args,
|
|||
~ to!string(assignChar) ~ "' in argument '" ~ input ~ "'.");
|
||||
auto key = input[0 .. j];
|
||||
auto value = input[j + 1 .. $];
|
||||
return tuple(to!K(key), to!V(value));
|
||||
|
||||
K k;
|
||||
handleConversion("", key, &k, 0);
|
||||
|
||||
V v;
|
||||
handleConversion("", value, &v, 0);
|
||||
|
||||
return tuple(k,v);
|
||||
}
|
||||
|
||||
static void setHash(Range)(R receiver, Range range)
|
||||
|
@ -1946,3 +1983,59 @@ void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt, st
|
|||
~ "information.\n";
|
||||
assert(wanted == helpMsg);
|
||||
}
|
||||
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
import std.conv : ConvException;
|
||||
import std.string : indexOf;
|
||||
|
||||
enum UniqueIdentifer {
|
||||
a,
|
||||
b
|
||||
}
|
||||
|
||||
UniqueIdentifer a;
|
||||
|
||||
auto args = ["prog", "--foo", "HELLO"];
|
||||
try
|
||||
{
|
||||
auto t = getopt(args, "foo|f", &a);
|
||||
assert(false, "Must not be reached, as \"HELLO\" cannot be converted"
|
||||
~ " to enum A.");
|
||||
}
|
||||
catch (ConvException e)
|
||||
{
|
||||
string str = () @trusted { return e.toString(); }();
|
||||
assert(str.indexOf("HELLO") != -1);
|
||||
assert(str.indexOf("UniqueIdentifer") != -1);
|
||||
assert(str.indexOf("foo") != -1);
|
||||
}
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
import std.conv : ConvException;
|
||||
import std.string : indexOf;
|
||||
|
||||
int a;
|
||||
|
||||
auto args = ["prog", "--foo", "HELLO"];
|
||||
try
|
||||
{
|
||||
auto t = getopt(args, "foo|f", &a);
|
||||
assert(false, "Must not be reached, as \"HELLO\" cannot be converted"
|
||||
~ " to an int");
|
||||
}
|
||||
catch (ConvException e)
|
||||
{
|
||||
string str = () @trusted { return e.toString(); }();
|
||||
assert(str.indexOf("HELLO") != -1);
|
||||
assert(str.indexOf("int") != -1);
|
||||
assert(str.indexOf("foo") != -1);
|
||||
}
|
||||
|
||||
args = ["prog", "--foo", "1337"];
|
||||
getopt(args, "foo|f", &a);
|
||||
assert(a == 1337);
|
||||
}
|
||||
|
|
|
@ -1950,52 +1950,55 @@ if (isFloatingPoint!T)
|
|||
|
||||
alias F = floatTraits!real;
|
||||
|
||||
// log2 is broken for x87-reals on some computers in CTFE
|
||||
// the following test excludes these computers from the test
|
||||
// (https://issues.dlang.org/show_bug.cgi?id=21757)
|
||||
enum test = cast(int) log2(3.05e2312L);
|
||||
static if (F.realFormat == RealFormat.ieeeExtended && test == 7681)
|
||||
static if (F.realFormat == RealFormat.ieeeExtended)
|
||||
{
|
||||
enum r1 = 1.0L;
|
||||
enum bp1 = extractBitpattern(r1);
|
||||
static assert(bp1.mantissa == 0x8000_0000_0000_0000L);
|
||||
static assert(bp1.exponent == 0);
|
||||
static assert(bp1.negative == false);
|
||||
// log2 is broken for x87-reals on some computers in CTFE
|
||||
// the following test excludes these computers from the test
|
||||
// (https://issues.dlang.org/show_bug.cgi?id=21757)
|
||||
enum test = cast(int) log2(3.05e2312L);
|
||||
static if (test == 7681)
|
||||
{
|
||||
enum r1 = 1.0L;
|
||||
enum bp1 = extractBitpattern(r1);
|
||||
static assert(bp1.mantissa == 0x8000_0000_0000_0000L);
|
||||
static assert(bp1.exponent == 0);
|
||||
static assert(bp1.negative == false);
|
||||
|
||||
enum r2 = real.max;
|
||||
enum bp2 = extractBitpattern(r2);
|
||||
static assert(bp2.mantissa == 0xffff_ffff_ffff_ffffL);
|
||||
static assert(bp2.exponent == 16383);
|
||||
static assert(bp2.negative == false);
|
||||
enum r2 = real.max;
|
||||
enum bp2 = extractBitpattern(r2);
|
||||
static assert(bp2.mantissa == 0xffff_ffff_ffff_ffffL);
|
||||
static assert(bp2.exponent == 16383);
|
||||
static assert(bp2.negative == false);
|
||||
|
||||
enum r3 = -1.5432e-3333L;
|
||||
enum bp3 = extractBitpattern(r3);
|
||||
static assert(bp3.mantissa == 0xc768_a2c7_a616_cc22L);
|
||||
static assert(bp3.exponent == -11072);
|
||||
static assert(bp3.negative == true);
|
||||
enum r3 = -1.5432e-3333L;
|
||||
enum bp3 = extractBitpattern(r3);
|
||||
static assert(bp3.mantissa == 0xc768_a2c7_a616_cc22L);
|
||||
static assert(bp3.exponent == -11072);
|
||||
static assert(bp3.negative == true);
|
||||
|
||||
enum r4 = 0.0L.nextUp;
|
||||
enum bp4 = extractBitpattern(r4);
|
||||
static assert(bp4.mantissa == 0x0000_0000_0000_0001L);
|
||||
static assert(bp4.exponent == -16382);
|
||||
static assert(bp4.negative == false);
|
||||
enum r4 = 0.0L.nextUp;
|
||||
enum bp4 = extractBitpattern(r4);
|
||||
static assert(bp4.mantissa == 0x0000_0000_0000_0001L);
|
||||
static assert(bp4.exponent == -16382);
|
||||
static assert(bp4.negative == false);
|
||||
|
||||
enum r5 = -real.infinity;
|
||||
enum bp5 = extractBitpattern(r5);
|
||||
static assert(bp5.mantissa == 0);
|
||||
static assert(bp5.exponent == 16384);
|
||||
static assert(bp5.negative == true);
|
||||
enum r5 = -real.infinity;
|
||||
enum bp5 = extractBitpattern(r5);
|
||||
static assert(bp5.mantissa == 0);
|
||||
static assert(bp5.exponent == 16384);
|
||||
static assert(bp5.negative == true);
|
||||
|
||||
enum r6 = real.nan;
|
||||
enum bp6 = extractBitpattern(r6);
|
||||
static assert(bp6.mantissa != 0); // we don't guarantee payloads
|
||||
static assert(bp6.exponent == 16384);
|
||||
static assert(bp6.negative == false);
|
||||
enum r6 = real.nan;
|
||||
enum bp6 = extractBitpattern(r6);
|
||||
static assert(bp6.mantissa != 0); // we don't guarantee payloads
|
||||
static assert(bp6.exponent == 16384);
|
||||
static assert(bp6.negative == false);
|
||||
|
||||
enum r7 = nextDown(0x1p+16383L);
|
||||
enum bp7 = extractBitpattern(r7);
|
||||
static assert(bp7.mantissa == 0xffff_ffff_ffff_ffffL);
|
||||
static assert(bp7.exponent == 16382);
|
||||
static assert(bp7.negative == false);
|
||||
enum r7 = nextDown(0x1p+16383L);
|
||||
enum bp7 = extractBitpattern(r7);
|
||||
static assert(bp7.mantissa == 0xffff_ffff_ffff_ffffL);
|
||||
static assert(bp7.exponent == 16382);
|
||||
static assert(bp7.negative == false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4315,14 +4315,14 @@ version (Posix)
|
|||
import core.sys.posix.stdlib;
|
||||
}
|
||||
|
||||
private void toAStringz(in string[] a, const(char)**az)
|
||||
private const(char)** toAStringz(in string[] a)
|
||||
{
|
||||
import std.string : toStringz;
|
||||
foreach (string s; a)
|
||||
{
|
||||
*az++ = toStringz(s);
|
||||
}
|
||||
*az = null;
|
||||
auto p = (new const(char)*[1 + a.length]).ptr;
|
||||
foreach (i, string s; a)
|
||||
p[i] = toStringz(s);
|
||||
p[a.length] = null;
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
|
@ -4452,45 +4452,17 @@ extern(C)
|
|||
|
||||
private int execv_(in string pathname, in string[] argv)
|
||||
{
|
||||
import core.exception : OutOfMemoryError;
|
||||
import std.exception : enforce;
|
||||
auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length));
|
||||
enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process.");
|
||||
scope(exit) core.stdc.stdlib.free(argv_);
|
||||
|
||||
toAStringz(argv, argv_);
|
||||
|
||||
return execv(pathname.tempCString(), argv_);
|
||||
return execv(pathname.tempCString(), toAStringz(argv));
|
||||
}
|
||||
|
||||
private int execve_(in string pathname, in string[] argv, in string[] envp)
|
||||
{
|
||||
import core.exception : OutOfMemoryError;
|
||||
import std.exception : enforce;
|
||||
auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length));
|
||||
enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process.");
|
||||
scope(exit) core.stdc.stdlib.free(argv_);
|
||||
auto envp_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + envp.length));
|
||||
enforce!OutOfMemoryError(envp_ !is null, "Out of memory in std.process.");
|
||||
scope(exit) core.stdc.stdlib.free(envp_);
|
||||
|
||||
toAStringz(argv, argv_);
|
||||
toAStringz(envp, envp_);
|
||||
|
||||
return execve(pathname.tempCString(), argv_, envp_);
|
||||
return execve(pathname.tempCString(), toAStringz(argv), toAStringz(envp));
|
||||
}
|
||||
|
||||
private int execvp_(in string pathname, in string[] argv)
|
||||
{
|
||||
import core.exception : OutOfMemoryError;
|
||||
import std.exception : enforce;
|
||||
auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length));
|
||||
enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process.");
|
||||
scope(exit) core.stdc.stdlib.free(argv_);
|
||||
|
||||
toAStringz(argv, argv_);
|
||||
|
||||
return execvp(pathname.tempCString(), argv_);
|
||||
return execvp(pathname.tempCString(), toAStringz(argv));
|
||||
}
|
||||
|
||||
private int execvpe_(in string pathname, in string[] argv, in string[] envp)
|
||||
|
@ -4532,19 +4504,7 @@ version (Posix)
|
|||
}
|
||||
else version (Windows)
|
||||
{
|
||||
import core.exception : OutOfMemoryError;
|
||||
import std.exception : enforce;
|
||||
auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length));
|
||||
enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process.");
|
||||
scope(exit) core.stdc.stdlib.free(argv_);
|
||||
auto envp_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + envp.length));
|
||||
enforce!OutOfMemoryError(envp_ !is null, "Out of memory in std.process.");
|
||||
scope(exit) core.stdc.stdlib.free(envp_);
|
||||
|
||||
toAStringz(argv, argv_);
|
||||
toAStringz(envp, envp_);
|
||||
|
||||
return execvpe(pathname.tempCString(), argv_, envp_);
|
||||
return execvpe(pathname.tempCString(), toAStringz(argv), toAStringz(envp));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -1772,11 +1772,74 @@ else
|
|||
}
|
||||
}
|
||||
|
||||
version (linux)
|
||||
{
|
||||
// `getrandom()` was introduced in Linux 3.17.
|
||||
|
||||
// Shim for missing bindings in druntime
|
||||
version (none)
|
||||
import core.sys.linux.sys.random : getrandom;
|
||||
else
|
||||
{
|
||||
import core.sys.posix.sys.types : ssize_t;
|
||||
extern extern(C) ssize_t getrandom(
|
||||
void* buf,
|
||||
size_t buflen,
|
||||
uint flags,
|
||||
) @system nothrow @nogc;
|
||||
}
|
||||
}
|
||||
|
||||
version (Windows)
|
||||
{
|
||||
pragma(lib, "Bcrypt.lib");
|
||||
|
||||
private bool bcryptGenRandom(T)(out T result) @trusted
|
||||
{
|
||||
import core.sys.windows.windef : PUCHAR, ULONG;
|
||||
import core.sys.windows.ntdef : NT_SUCCESS;
|
||||
import core.sys.windows.bcrypt : BCryptGenRandom, BCRYPT_USE_SYSTEM_PREFERRED_RNG;
|
||||
|
||||
const gotRandom = BCryptGenRandom(
|
||||
null,
|
||||
cast(PUCHAR) &result,
|
||||
ULONG(T.sizeof),
|
||||
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
|
||||
);
|
||||
|
||||
return NT_SUCCESS(gotRandom);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A "good" seed for initializing random number engines. Initializing
|
||||
with $(D_PARAM unpredictableSeed) makes engines generate different
|
||||
random number sequences every run.
|
||||
|
||||
This function utilizes the system $(I cryptographically-secure pseudo-random
|
||||
number generator (CSPRNG)) or $(I pseudo-random number generator (PRNG))
|
||||
where available and implemented (currently `arc4random` on applicable BSD
|
||||
systems, `getrandom` on Linux or `BCryptGenRandom` on Windows) to generate
|
||||
“high quality” pseudo-random numbers – if possible.
|
||||
As a consequence, calling it may block under certain circumstances (typically
|
||||
during early boot when the system's entropy pool has not yet been
|
||||
initialized).
|
||||
|
||||
On x86 CPU models which support the `RDRAND` instruction, that will be used
|
||||
when no more specialized randomness source is implemented.
|
||||
|
||||
In the future, further platform-specific PRNGs may be incorporated.
|
||||
|
||||
Warning:
|
||||
$(B This function must not be used for cryptographic purposes.)
|
||||
Despite being implemented for certain targets, there are no guarantees
|
||||
that it sources its randomness from a CSPRNG.
|
||||
The implementation also includes a fallback option that provides very little
|
||||
randomness and is used when no better source of randomness is available or
|
||||
integrated on the target system.
|
||||
As written earlier, this function only aims to provide randomness for seeding
|
||||
ordinary (non-cryptographic) PRNG engines.
|
||||
|
||||
Returns:
|
||||
A single unsigned integer seed value, different on each successive call
|
||||
Note:
|
||||
|
@ -1788,7 +1851,37 @@ how excellent the source of entropy is.
|
|||
*/
|
||||
@property uint unpredictableSeed() @trusted nothrow @nogc
|
||||
{
|
||||
version (AnyARC4Random)
|
||||
version (linux)
|
||||
{
|
||||
uint buffer;
|
||||
|
||||
/*
|
||||
getrandom(2):
|
||||
If the _urandom_ source has been initialized, reads of up to
|
||||
256 bytes will always return as many bytes as requested and
|
||||
will not be interrupted by signals. No such guarantees apply
|
||||
for larger buffer sizes.
|
||||
*/
|
||||
static assert(buffer.sizeof <= 256);
|
||||
|
||||
const status = (() @trusted => getrandom(&buffer, buffer.sizeof, 0))();
|
||||
assert(status == buffer.sizeof);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
else version (Windows)
|
||||
{
|
||||
uint result;
|
||||
if (!bcryptGenRandom!uint(result))
|
||||
{
|
||||
version (none)
|
||||
return fallbackSeed();
|
||||
else
|
||||
assert(false, "BCryptGenRandom() failed.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else version (AnyARC4Random)
|
||||
{
|
||||
return arc4random();
|
||||
}
|
||||
|
@ -1837,7 +1930,37 @@ if (isUnsigned!UIntType)
|
|||
/// ditto
|
||||
@property UIntType unpredictableSeed() @nogc nothrow @trusted
|
||||
{
|
||||
version (AnyARC4Random)
|
||||
version (linux)
|
||||
{
|
||||
UIntType buffer;
|
||||
|
||||
/*
|
||||
getrandom(2):
|
||||
If the _urandom_ source has been initialized, reads of up to
|
||||
256 bytes will always return as many bytes as requested and
|
||||
will not be interrupted by signals. No such guarantees apply
|
||||
for larger buffer sizes.
|
||||
*/
|
||||
static assert(buffer.sizeof <= 256);
|
||||
|
||||
const status = (() @trusted => getrandom(&buffer, buffer.sizeof, 0))();
|
||||
assert(status == buffer.sizeof);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
else version (Windows)
|
||||
{
|
||||
UIntType result;
|
||||
if (!bcryptGenRandom!UIntType(result))
|
||||
{
|
||||
version (none)
|
||||
return fallbackSeed();
|
||||
else
|
||||
assert(false, "BCryptGenRandom() failed.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else version (AnyARC4Random)
|
||||
{
|
||||
static if (UIntType.sizeof <= uint.sizeof)
|
||||
{
|
||||
|
|
|
@ -31,7 +31,7 @@ $(BOOKTABLE ,
|
|||
$(TR $(TD $(LREF RandomAccessFinite))
|
||||
$(TD Wrapper for finite random-access ranges.
|
||||
))
|
||||
$(TR $(TD $(LREF RandomAccessAssignable))
|
||||
$(TR $(TD $(LREF RandomFiniteAssignable))
|
||||
$(TD Wrapper for finite random-access ranges with assignable elements.
|
||||
))
|
||||
$(TR $(TD $(LREF RandomAccessInfinite))
|
||||
|
|
|
@ -1042,7 +1042,7 @@ if (Ranges.length > 0 &&
|
|||
// We do this separately to avoid invoking `empty` needlessly.
|
||||
// While not recommended, a range may depend on side effects of
|
||||
// `empty` call.
|
||||
foreach (i, ref v; input) if (!v.empty)
|
||||
foreach (i, ref v; source) if (!v.empty)
|
||||
{
|
||||
frontIndex = i;
|
||||
static if (bidirectional) backIndex = i+1;
|
||||
|
@ -1056,7 +1056,7 @@ if (Ranges.length > 0 &&
|
|||
static foreach_reverse (i; 1 .. R.length + 1)
|
||||
{
|
||||
if (i <= frontIndex + 1) return;
|
||||
if (!input[i-1].empty)
|
||||
if (!source[i-1].empty)
|
||||
{
|
||||
backIndex = i;
|
||||
return;
|
||||
|
@ -11019,6 +11019,23 @@ auto only()()
|
|||
static assert(!__traits(compiles, () { r3[0] = 789; }));
|
||||
}
|
||||
|
||||
// https://github.com/dlang/phobos/issues/10561
|
||||
@safe unittest
|
||||
{
|
||||
static struct Range
|
||||
{
|
||||
private int i;
|
||||
|
||||
enum bool empty = false;
|
||||
int front() => i;
|
||||
void popFront() { ++i; }
|
||||
}
|
||||
import std.algorithm;
|
||||
|
||||
assert(Range().take(10).filter!"a>8".chain(only(100)).equal([9,100]));
|
||||
assert((new Range()).take(10).filter!"a>8".chain(only(100)).equal([9,100]));
|
||||
}
|
||||
|
||||
/**
|
||||
Iterate over `range` with an attached index variable.
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ $(TR $(TD Reading) $(TD
|
|||
$(MYREF chunks)
|
||||
$(MYREF lines)
|
||||
$(MYREF readf)
|
||||
$(MYREF readfln)
|
||||
$(MYREF readln)
|
||||
))
|
||||
$(TR $(TD Writing) $(TD
|
||||
|
@ -2094,6 +2095,85 @@ $(CONSOLE
|
|||
"Unexpected '\\n' when converting from type LockingTextReader to type int");
|
||||
}
|
||||
|
||||
/**
|
||||
Reads a line from the file and parses it using $(REF formattedRead, std,format,read).
|
||||
|
||||
Params:
|
||||
format = The $(MREF_ALTTEXT format string, std,format). When passed as a
|
||||
compile-time argument, the string will be statically checked against the
|
||||
argument types passed.
|
||||
data = Items to be read.
|
||||
|
||||
Returns: Same as `formattedRead`: the number of variables filled. If the
|
||||
input ends early, this number will be less that the number of variables
|
||||
provided.
|
||||
|
||||
Example:
|
||||
---
|
||||
// sum_rows.d
|
||||
void main()
|
||||
{
|
||||
import std.stdio;
|
||||
auto f = File("input");
|
||||
int a, b, c;
|
||||
while (f.readfln("%d %d %d", a, b, c) == 3)
|
||||
{
|
||||
writeln(a + b + c);
|
||||
}
|
||||
}
|
||||
---
|
||||
$(CONSOLE
|
||||
% cat << EOF > input
|
||||
1 2 3
|
||||
4 5 6
|
||||
7 8 9
|
||||
EOF
|
||||
% rdmd sum_rows.d
|
||||
6
|
||||
15
|
||||
24
|
||||
)
|
||||
*/
|
||||
uint readfln(alias format, Data...)(auto ref Data data)
|
||||
if (isSomeString!(typeof(format)))
|
||||
{
|
||||
import std.format : checkFormatException;
|
||||
|
||||
alias e = checkFormatException!(format, Data);
|
||||
static assert(!e, e);
|
||||
return this.readfln(format, data);
|
||||
}
|
||||
|
||||
/// ditto
|
||||
uint readfln(Data...)(scope const(char)[] format, auto ref Data data)
|
||||
{
|
||||
import std.format.read : formattedRead;
|
||||
import std.string : stripRight;
|
||||
|
||||
string line = this.readln.stripRight("\r\n");
|
||||
return formattedRead(line, format, data);
|
||||
}
|
||||
|
||||
@system unittest
|
||||
{
|
||||
static import std.file;
|
||||
|
||||
auto deleteme = testFilename();
|
||||
std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n");
|
||||
scope(exit) std.file.remove(deleteme);
|
||||
string s;
|
||||
auto f = File(deleteme);
|
||||
f.readfln!"%s"(s);
|
||||
assert(s == "hello", "["~s~"]");
|
||||
f.readfln("%s", s);
|
||||
assert(s == "world", "["~s~"]");
|
||||
|
||||
bool b1, b2;
|
||||
f.readfln("%s", b1);
|
||||
f.readfln("%s", b2);
|
||||
assert(b1 == true && b2 == false);
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a temporary file by calling $(CSTDIO tmpfile).
|
||||
Note that the created file has no $(LREF name).*/
|
||||
|
@ -4489,6 +4569,70 @@ if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) &&
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Reads a line from `stdin` and parses it using $(REF formattedRead, std,format,read).
|
||||
|
||||
Params:
|
||||
format = The $(MREF_ALTTEXT format string, std,format). When passed as a
|
||||
compile-time argument, the string will be statically checked against the
|
||||
argument types passed.
|
||||
data = Items to be read.
|
||||
|
||||
Returns: Same as `formattedRead`: the number of variables filled. If the
|
||||
input ends early, this number will be less that the number of variables
|
||||
provided.
|
||||
|
||||
Example:
|
||||
---
|
||||
// sum_rows.d
|
||||
void main()
|
||||
{
|
||||
import std.stdio;
|
||||
int a, b, c;
|
||||
while (readfln("%d %d %d", a, b, c) == 3)
|
||||
{
|
||||
writeln(a + b + c);
|
||||
}
|
||||
}
|
||||
---
|
||||
$(CONSOLE
|
||||
% cat << EOF > input
|
||||
1 2 3
|
||||
4 5 6
|
||||
7 8 9
|
||||
EOF
|
||||
% rdmd sum_rows.d < input
|
||||
6
|
||||
15
|
||||
24
|
||||
)
|
||||
*/
|
||||
uint readfln(alias format, Data...)(auto ref Data data)
|
||||
{
|
||||
import std.format : checkFormatException;
|
||||
|
||||
alias e = checkFormatException!(format, Data);
|
||||
static assert(!e, e);
|
||||
return .readfln(format, data);
|
||||
}
|
||||
|
||||
/// ditto
|
||||
uint readfln(Data...)(scope const(char)[] format, auto ref Data data)
|
||||
{
|
||||
return stdin.readfln(format, data);
|
||||
}
|
||||
|
||||
@system unittest
|
||||
{
|
||||
float f;
|
||||
string s;
|
||||
char c;
|
||||
int n;
|
||||
if (false) readfln("%f %s %c %d", f, s, c, n);
|
||||
if (false) readfln!"%f %s %c %d"(f, s, c, n);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Convenience function that forwards to `core.sys.posix.stdio.fopen`
|
||||
* (to `_wfopen` on Windows)
|
||||
|
|
|
@ -254,6 +254,8 @@ private enum hasPostblit(T) = __traits(hasPostblit, T);
|
|||
|
||||
private enum isInout(T) = is(T == inout);
|
||||
|
||||
private enum memberName(size_t tid) = "values_" ~ toCtString!tid;
|
||||
|
||||
/**
|
||||
* A [tagged union](https://en.wikipedia.org/wiki/Tagged_union) that can hold a
|
||||
* single value from any of a specified set of types.
|
||||
|
@ -290,45 +292,45 @@ private:
|
|||
|
||||
union Storage
|
||||
{
|
||||
// Workaround for https://issues.dlang.org/show_bug.cgi?id=20068
|
||||
template memberName(T)
|
||||
if (IndexOf!(T, Types) >= 0)
|
||||
{
|
||||
enum tid = IndexOf!(T, Types);
|
||||
mixin("enum memberName = `values_", toCtString!tid, "`;");
|
||||
}
|
||||
|
||||
static foreach (T; Types)
|
||||
static foreach (tid, T; Types)
|
||||
{
|
||||
mixin("T ", memberName!T, ";");
|
||||
/+
|
||||
Giving these fields individual names makes it possible to use brace
|
||||
initialization for Storage.
|
||||
+/
|
||||
mixin("T ", memberName!tid, ";");
|
||||
}
|
||||
}
|
||||
|
||||
Storage storage;
|
||||
Tag tag;
|
||||
static if (Types.length > 1)
|
||||
Tag tag;
|
||||
else
|
||||
enum Tag tag = 0;
|
||||
|
||||
/* Accesses the value stored in a SumType.
|
||||
/* Accesses the value stored in a SumType by its index.
|
||||
*
|
||||
* This method is memory-safe, provided that:
|
||||
*
|
||||
* 1. A SumType's tag is always accurate.
|
||||
* 2. A SumType cannot be assigned to in @safe code if that assignment
|
||||
* could cause unsafe aliasing.
|
||||
* 2. A SumType's value cannot be unsafely aliased in @safe code.
|
||||
*
|
||||
* All code that accesses a SumType's tag or storage directly, including
|
||||
* @safe code in this module, must be manually checked to ensure that it
|
||||
* does not violate either of the above requirements.
|
||||
*/
|
||||
@trusted
|
||||
ref inout(T) get(T)() inout
|
||||
if (IndexOf!(T, Types) >= 0)
|
||||
// Explicit return type omitted
|
||||
// Workaround for https://github.com/dlang/dmd/issues/20549
|
||||
ref getByIndex(size_t tid)() inout
|
||||
if (tid < Types.length)
|
||||
{
|
||||
enum tid = IndexOf!(T, Types);
|
||||
assert(tag == tid,
|
||||
"This `" ~ SumType.stringof ~
|
||||
"` does not contain a(n) `" ~ T.stringof ~ "`"
|
||||
"This `" ~ SumType.stringof ~ "`" ~
|
||||
"does not contain a(n) `" ~ Types[tid].stringof ~ "`"
|
||||
);
|
||||
return __traits(getMember, storage, Storage.memberName!T);
|
||||
return storage.tupleof[tid];
|
||||
}
|
||||
|
||||
public:
|
||||
|
@ -363,14 +365,15 @@ public:
|
|||
static if (isCopyable!T)
|
||||
{
|
||||
// Workaround for https://issues.dlang.org/show_bug.cgi?id=21542
|
||||
__traits(getMember, storage, Storage.memberName!T) = __ctfe ? value : forward!value;
|
||||
storage.tupleof[tid] = __ctfe ? value : forward!value;
|
||||
}
|
||||
else
|
||||
{
|
||||
__traits(getMember, storage, Storage.memberName!T) = forward!value;
|
||||
storage.tupleof[tid] = forward!value;
|
||||
}
|
||||
|
||||
tag = tid;
|
||||
static if (Types.length > 1)
|
||||
tag = tid;
|
||||
}
|
||||
|
||||
static if (isCopyable!(const(T)))
|
||||
|
@ -380,8 +383,9 @@ public:
|
|||
/// ditto
|
||||
this(const(T) value) const
|
||||
{
|
||||
__traits(getMember, storage, Storage.memberName!T) = value;
|
||||
tag = tid;
|
||||
storage.tupleof[tid] = value;
|
||||
static if (Types.length > 1)
|
||||
tag = tid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -397,8 +401,9 @@ public:
|
|||
/// ditto
|
||||
this(immutable(T) value) immutable
|
||||
{
|
||||
__traits(getMember, storage, Storage.memberName!T) = value;
|
||||
tag = tid;
|
||||
storage.tupleof[tid] = value;
|
||||
static if (Types.length > 1)
|
||||
tag = tid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -415,8 +420,9 @@ public:
|
|||
this(Value)(Value value) inout
|
||||
if (is(Value == DeducedParameterType!(inout(T))))
|
||||
{
|
||||
__traits(getMember, storage, Storage.memberName!T) = value;
|
||||
tag = tid;
|
||||
storage.tupleof[tid] = value;
|
||||
static if (Types.length > 1)
|
||||
tag = tid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -442,16 +448,16 @@ public:
|
|||
storage = other.match!((ref value) {
|
||||
alias OtherTypes = Map!(InoutOf, Types);
|
||||
enum tid = IndexOf!(typeof(value), OtherTypes);
|
||||
alias T = Types[tid];
|
||||
|
||||
mixin("inout(Storage) newStorage = { ",
|
||||
Storage.memberName!T, ": value",
|
||||
memberName!tid, ": value",
|
||||
" };");
|
||||
|
||||
return newStorage;
|
||||
});
|
||||
|
||||
tag = other.tag;
|
||||
static if (Types.length > 1)
|
||||
tag = other.tag;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -462,16 +468,17 @@ public:
|
|||
this(ref SumType other)
|
||||
{
|
||||
storage = other.match!((ref value) {
|
||||
alias T = typeof(value);
|
||||
enum tid = IndexOf!(typeof(value), Types);
|
||||
|
||||
mixin("Storage newStorage = { ",
|
||||
Storage.memberName!T, ": value",
|
||||
memberName!tid, ": value",
|
||||
" };");
|
||||
|
||||
return newStorage;
|
||||
});
|
||||
|
||||
tag = other.tag;
|
||||
static if (Types.length > 1)
|
||||
tag = other.tag;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -487,16 +494,16 @@ public:
|
|||
storage = other.match!((ref value) {
|
||||
alias OtherTypes = Map!(ConstOf, Types);
|
||||
enum tid = IndexOf!(typeof(value), OtherTypes);
|
||||
alias T = Types[tid];
|
||||
|
||||
mixin("const(Storage) newStorage = { ",
|
||||
Storage.memberName!T, ": value",
|
||||
memberName!tid, ": value",
|
||||
" };");
|
||||
|
||||
return newStorage;
|
||||
});
|
||||
|
||||
tag = other.tag;
|
||||
static if (Types.length > 1)
|
||||
tag = other.tag;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -512,16 +519,16 @@ public:
|
|||
storage = other.match!((ref value) {
|
||||
alias OtherTypes = Map!(ImmutableOf, Types);
|
||||
enum tid = IndexOf!(typeof(value), OtherTypes);
|
||||
alias T = Types[tid];
|
||||
|
||||
mixin("immutable(Storage) newStorage = { ",
|
||||
Storage.memberName!T, ": value",
|
||||
memberName!tid, ": value",
|
||||
" };");
|
||||
|
||||
return newStorage;
|
||||
});
|
||||
|
||||
tag = other.tag;
|
||||
static if (Types.length > 1)
|
||||
tag = other.tag;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -637,18 +644,19 @@ public:
|
|||
{
|
||||
// Workaround for https://issues.dlang.org/show_bug.cgi?id=21542
|
||||
mixin("Storage newStorage = { ",
|
||||
Storage.memberName!T, ": __ctfe ? rhs : forward!rhs",
|
||||
memberName!tid, ": __ctfe ? rhs : forward!rhs",
|
||||
" };");
|
||||
}
|
||||
else
|
||||
{
|
||||
mixin("Storage newStorage = { ",
|
||||
Storage.memberName!T, ": forward!rhs",
|
||||
memberName!tid, ": forward!rhs",
|
||||
" };");
|
||||
}
|
||||
|
||||
storage = newStorage;
|
||||
tag = tid;
|
||||
static if (Types.length > 1)
|
||||
tag = tid;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@ -1146,7 +1154,7 @@ version (D_BetterC) {} else
|
|||
alias MySum = SumType!(ubyte, void*[2]);
|
||||
|
||||
MySum x = [null, cast(void*) 0x12345678];
|
||||
void** p = &x.get!(void*[2])[1];
|
||||
void** p = &x.getByIndex!1[1];
|
||||
x = ubyte(123);
|
||||
|
||||
assert(*p != cast(void*) 0x12345678);
|
||||
|
@ -1178,8 +1186,8 @@ version (D_BetterC) {} else
|
|||
catch (Exception e) {}
|
||||
|
||||
assert(
|
||||
(x.tag == 0 && x.get!A.value == 123) ||
|
||||
(x.tag == 1 && x.get!B.value == 456)
|
||||
(x.tag == 0 && x.getByIndex!0.value == 123) ||
|
||||
(x.tag == 1 && x.getByIndex!1.value == 456)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1238,8 +1246,8 @@ version (D_BetterC) {} else
|
|||
SumType!(S[1]) x = [S(0)];
|
||||
SumType!(S[1]) y = x;
|
||||
|
||||
auto xval = x.get!(S[1])[0].n;
|
||||
auto yval = y.get!(S[1])[0].n;
|
||||
auto xval = x.getByIndex!0[0].n;
|
||||
auto yval = y.getByIndex!0[0].n;
|
||||
|
||||
assert(xval != yval);
|
||||
}
|
||||
|
@ -1324,8 +1332,8 @@ version (D_BetterC) {} else
|
|||
SumType!S y;
|
||||
y = x;
|
||||
|
||||
auto xval = x.get!S.n;
|
||||
auto yval = y.get!S.n;
|
||||
auto xval = x.getByIndex!0.n;
|
||||
auto yval = y.getByIndex!0.n;
|
||||
|
||||
assert(xval != yval);
|
||||
}
|
||||
|
@ -1399,8 +1407,8 @@ version (D_BetterC) {} else
|
|||
SumType!S x = S();
|
||||
SumType!S y = x;
|
||||
|
||||
auto xval = x.get!S.n;
|
||||
auto yval = y.get!S.n;
|
||||
auto xval = x.getByIndex!0.n;
|
||||
auto yval = y.getByIndex!0.n;
|
||||
|
||||
assert(xval != yval);
|
||||
}
|
||||
|
@ -1562,6 +1570,13 @@ version (D_BetterC) {} else
|
|||
enum result = test();
|
||||
}
|
||||
|
||||
// https://github.com/dlang/phobos/issues/10563
|
||||
// Do not waste space for tag if sumtype has only single type
|
||||
@safe unittest
|
||||
{
|
||||
static assert(SumType!int.sizeof == int.sizeof);
|
||||
}
|
||||
|
||||
/// True if `T` is an instance of the `SumType` template, otherwise false.
|
||||
private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...);
|
||||
|
||||
|
@ -1815,7 +1830,7 @@ class MatchException : Exception
|
|||
template canMatch(alias handler, Ts...)
|
||||
if (Ts.length > 0)
|
||||
{
|
||||
enum canMatch = is(typeof((ref Ts args) => handler(args)));
|
||||
enum canMatch = is(typeof(auto ref (ref Ts args) => handler(args)));
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -1840,6 +1855,21 @@ if (Ts.length > 0)
|
|||
assert(canMatch!(OverloadSet.fun, double));
|
||||
}
|
||||
|
||||
// Allows returning non-copyable types by ref
|
||||
// https://github.com/dlang/phobos/issues/10647
|
||||
@safe unittest
|
||||
{
|
||||
static struct NoCopy
|
||||
{
|
||||
@disable this(this);
|
||||
}
|
||||
|
||||
static NoCopy lvalue;
|
||||
static ref handler(int _) => lvalue;
|
||||
|
||||
assert(canMatch!(handler, int));
|
||||
}
|
||||
|
||||
// Like aliasSeqOf!(iota(n)), but works in BetterC
|
||||
private template Iota(size_t n)
|
||||
{
|
||||
|
@ -1872,10 +1902,10 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...)
|
|||
* argument's tag, so there's no need for TagTuple.
|
||||
*/
|
||||
enum handlerArgs(size_t caseId) =
|
||||
"args[0].get!(SumTypes[0].Types[" ~ toCtString!caseId ~ "])()";
|
||||
"args[0].getByIndex!(" ~ toCtString!caseId ~ ")()";
|
||||
|
||||
alias valueTypes(size_t caseId) =
|
||||
typeof(args[0].get!(SumTypes[0].Types[caseId])());
|
||||
typeof(args[0].getByIndex!(caseId)());
|
||||
|
||||
enum numCases = SumTypes[0].Types.length;
|
||||
}
|
||||
|
@ -1901,9 +1931,7 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...)
|
|||
|
||||
template getType(size_t i)
|
||||
{
|
||||
enum tid = tags[i];
|
||||
alias T = SumTypes[i].Types[tid];
|
||||
alias getType = typeof(args[i].get!T());
|
||||
alias getType = typeof(args[i].getByIndex!(tags[i])());
|
||||
}
|
||||
|
||||
alias valueTypes = Map!(getType, Iota!(tags.length));
|
||||
|
@ -2128,8 +2156,7 @@ private template handlerArgs(size_t caseId, typeCounts...)
|
|||
{
|
||||
handlerArgs = AliasSeq!(
|
||||
handlerArgs,
|
||||
"args[" ~ toCtString!i ~ "].get!(SumTypes[" ~ toCtString!i ~ "]" ~
|
||||
".Types[" ~ toCtString!(tags[i]) ~ "])(), "
|
||||
"args[" ~ toCtString!i ~ "].getByIndex!(" ~ toCtString!(tags[i]) ~ ")(), "
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2393,7 +2420,7 @@ version (D_Exceptions)
|
|||
(ref double d) { d *= 2; }
|
||||
);
|
||||
|
||||
assert(value.get!double.isClose(6.28));
|
||||
assert(value.getByIndex!1.isClose(6.28));
|
||||
}
|
||||
|
||||
// Unreachable handlers
|
||||
|
@ -2615,6 +2642,417 @@ version (D_Exceptions)
|
|||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a `SumType` contains a value of a given type.
|
||||
*
|
||||
* The types must match exactly, without implicit conversions.
|
||||
*
|
||||
* Params:
|
||||
* T = the type to check for.
|
||||
*/
|
||||
template has(T)
|
||||
{
|
||||
/**
|
||||
* The actual `has` function.
|
||||
*
|
||||
* Params:
|
||||
* self = the `SumType` to check.
|
||||
*
|
||||
* Returns: true if `self` contains a `T`, otherwise false.
|
||||
*/
|
||||
bool has(Self)(auto ref Self self)
|
||||
if (isSumType!Self)
|
||||
{
|
||||
return self.match!checkType;
|
||||
}
|
||||
|
||||
// Helper to avoid redundant template instantiations
|
||||
private bool checkType(Value)(ref Value value)
|
||||
{
|
||||
return is(Value == T);
|
||||
}
|
||||
}
|
||||
|
||||
/// Basic usage
|
||||
@safe unittest
|
||||
{
|
||||
SumType!(string, double) example = "hello";
|
||||
|
||||
assert( example.has!string);
|
||||
assert(!example.has!double);
|
||||
|
||||
// If T isn't part of the SumType, has!T will always return false.
|
||||
assert(!example.has!int);
|
||||
}
|
||||
|
||||
/// With type qualifiers
|
||||
@safe unittest
|
||||
{
|
||||
alias Example = SumType!(string, double);
|
||||
|
||||
Example m = "mutable";
|
||||
const Example c = "const";
|
||||
immutable Example i = "immutable";
|
||||
|
||||
assert( m.has!string);
|
||||
assert(!m.has!(const(string)));
|
||||
assert(!m.has!(immutable(string)));
|
||||
|
||||
assert(!c.has!string);
|
||||
assert( c.has!(const(string)));
|
||||
assert(!c.has!(immutable(string)));
|
||||
|
||||
assert(!i.has!string);
|
||||
assert(!i.has!(const(string)));
|
||||
assert( i.has!(immutable(string)));
|
||||
}
|
||||
|
||||
/// As a predicate
|
||||
version (D_BetterC) {} else
|
||||
@safe unittest
|
||||
{
|
||||
import std.algorithm.iteration : filter;
|
||||
import std.algorithm.comparison : equal;
|
||||
|
||||
alias Example = SumType!(string, double);
|
||||
|
||||
auto arr = [
|
||||
Example("foo"),
|
||||
Example(0),
|
||||
Example("bar"),
|
||||
Example(1),
|
||||
Example(2),
|
||||
Example("baz")
|
||||
];
|
||||
|
||||
auto strings = arr.filter!(has!string);
|
||||
auto nums = arr.filter!(has!double);
|
||||
|
||||
assert(strings.equal([Example("foo"), Example("bar"), Example("baz")]));
|
||||
assert(nums.equal([Example(0), Example(1), Example(2)]));
|
||||
}
|
||||
|
||||
// Non-copyable types
|
||||
@safe unittest
|
||||
{
|
||||
static struct NoCopy
|
||||
{
|
||||
@disable this(this);
|
||||
}
|
||||
|
||||
SumType!NoCopy x;
|
||||
|
||||
assert(x.has!NoCopy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accesses a `SumType`'s value.
|
||||
*
|
||||
* The value must be of the specified type. Use [has] to check.
|
||||
*
|
||||
* Params:
|
||||
* T = the type of the value being accessed.
|
||||
*/
|
||||
template get(T)
|
||||
{
|
||||
/**
|
||||
* The actual `get` function.
|
||||
*
|
||||
* Params:
|
||||
* self = the `SumType` whose value is being accessed.
|
||||
*
|
||||
* Returns: the `SumType`'s value.
|
||||
*/
|
||||
auto ref T get(Self)(auto ref Self self)
|
||||
if (isSumType!Self)
|
||||
{
|
||||
import std.typecons : No;
|
||||
|
||||
static if (__traits(isRef, self))
|
||||
return self.match!(getLvalue!(No.try_, T));
|
||||
else
|
||||
return self.match!(getRvalue!(No.try_, T));
|
||||
}
|
||||
}
|
||||
|
||||
/// Basic usage
|
||||
@safe unittest
|
||||
{
|
||||
SumType!(string, double) example1 = "hello";
|
||||
SumType!(string, double) example2 = 3.14;
|
||||
|
||||
assert(example1.get!string == "hello");
|
||||
assert(example2.get!double == 3.14);
|
||||
}
|
||||
|
||||
/// With type qualifiers
|
||||
@safe unittest
|
||||
{
|
||||
alias Example = SumType!(string, double);
|
||||
|
||||
Example m = "mutable";
|
||||
const(Example) c = "const";
|
||||
immutable(Example) i = "immutable";
|
||||
|
||||
assert(m.get!string == "mutable");
|
||||
assert(c.get!(const(string)) == "const");
|
||||
assert(i.get!(immutable(string)) == "immutable");
|
||||
}
|
||||
|
||||
/// As a predicate
|
||||
version (D_BetterC) {} else
|
||||
@safe unittest
|
||||
{
|
||||
import std.algorithm.iteration : map;
|
||||
import std.algorithm.comparison : equal;
|
||||
|
||||
alias Example = SumType!(string, double);
|
||||
|
||||
auto arr = [Example(0), Example(1), Example(2)];
|
||||
auto values = arr.map!(get!double);
|
||||
|
||||
assert(values.equal([0, 1, 2]));
|
||||
}
|
||||
|
||||
// Non-copyable types
|
||||
@safe unittest
|
||||
{
|
||||
static struct NoCopy
|
||||
{
|
||||
@disable this(this);
|
||||
}
|
||||
|
||||
SumType!NoCopy lvalue;
|
||||
auto rvalue() => SumType!NoCopy();
|
||||
|
||||
assert(lvalue.get!NoCopy == NoCopy());
|
||||
assert(rvalue.get!NoCopy == NoCopy());
|
||||
}
|
||||
|
||||
// Immovable rvalues
|
||||
@safe unittest
|
||||
{
|
||||
auto rvalue() => const(SumType!string)("hello");
|
||||
|
||||
assert(rvalue.get!(const(string)) == "hello");
|
||||
}
|
||||
|
||||
// Nontrivial rvalues at compile time
|
||||
@safe unittest
|
||||
{
|
||||
static struct ElaborateCopy
|
||||
{
|
||||
this(this) {}
|
||||
}
|
||||
|
||||
enum rvalue = SumType!ElaborateCopy();
|
||||
enum ctResult = rvalue.get!ElaborateCopy;
|
||||
|
||||
assert(ctResult == ElaborateCopy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to access a `SumType`'s value.
|
||||
*
|
||||
* If the `SumType` does not contain a value of the specified type, an
|
||||
* exception is thrown.
|
||||
*
|
||||
* Params:
|
||||
* T = the type of the value being accessed.
|
||||
*/
|
||||
version (D_Exceptions)
|
||||
template tryGet(T)
|
||||
{
|
||||
/**
|
||||
* The actual `tryGet` function.
|
||||
*
|
||||
* Params:
|
||||
* self = the `SumType` whose value is being accessed.
|
||||
*
|
||||
* Throws: `MatchException` if the value does not have the expected type.
|
||||
*
|
||||
* Returns: the `SumType`'s value.
|
||||
*/
|
||||
auto ref T tryGet(Self)(auto ref Self self)
|
||||
if (isSumType!Self)
|
||||
{
|
||||
import std.typecons : Yes;
|
||||
|
||||
static if (__traits(isRef, self))
|
||||
return self.match!(getLvalue!(Yes.try_, T));
|
||||
else
|
||||
return self.match!(getRvalue!(Yes.try_, T));
|
||||
}
|
||||
}
|
||||
|
||||
/// Basic usage
|
||||
version (D_Exceptions)
|
||||
@safe unittest
|
||||
{
|
||||
SumType!(string, double) example = "hello";
|
||||
|
||||
assert(example.tryGet!string == "hello");
|
||||
|
||||
double result = double.nan;
|
||||
try
|
||||
result = example.tryGet!double;
|
||||
catch (MatchException e)
|
||||
result = 0;
|
||||
|
||||
// Exception was thrown
|
||||
assert(result == 0);
|
||||
}
|
||||
|
||||
/// With type qualifiers
|
||||
version (D_Exceptions)
|
||||
@safe unittest
|
||||
{
|
||||
import std.exception : assertThrown;
|
||||
|
||||
const(SumType!(string, double)) example = "const";
|
||||
|
||||
// Qualifier mismatch; throws exception
|
||||
assertThrown!MatchException(example.tryGet!string);
|
||||
// Qualifier matches; no exception
|
||||
assert(example.tryGet!(const(string)) == "const");
|
||||
}
|
||||
|
||||
/// As a predicate
|
||||
version (D_BetterC) {} else
|
||||
@safe unittest
|
||||
{
|
||||
import std.algorithm.iteration : map, sum;
|
||||
import std.functional : pipe;
|
||||
import std.exception : assertThrown;
|
||||
|
||||
alias Example = SumType!(string, double);
|
||||
|
||||
auto arr1 = [Example(0), Example(1), Example(2)];
|
||||
auto arr2 = [Example("foo"), Example("bar"), Example("baz")];
|
||||
|
||||
alias trySum = pipe!(map!(tryGet!double), sum);
|
||||
|
||||
assert(trySum(arr1) == 0 + 1 + 2);
|
||||
assertThrown!MatchException(trySum(arr2));
|
||||
}
|
||||
|
||||
// Throws if requested type is impossible
|
||||
version (D_Exceptions)
|
||||
@safe unittest
|
||||
{
|
||||
import std.exception : assertThrown;
|
||||
|
||||
SumType!int x;
|
||||
|
||||
assertThrown!MatchException(x.tryGet!string);
|
||||
}
|
||||
|
||||
// Non-copyable types
|
||||
version (D_Exceptions)
|
||||
@safe unittest
|
||||
{
|
||||
static struct NoCopy
|
||||
{
|
||||
@disable this(this);
|
||||
}
|
||||
|
||||
SumType!NoCopy lvalue;
|
||||
auto rvalue() => SumType!NoCopy();
|
||||
|
||||
assert(lvalue.tryGet!NoCopy == NoCopy());
|
||||
assert(rvalue.tryGet!NoCopy == NoCopy());
|
||||
}
|
||||
|
||||
// Immovable rvalues
|
||||
version (D_Exceptions)
|
||||
@safe unittest
|
||||
{
|
||||
auto rvalue() => const(SumType!string)("hello");
|
||||
|
||||
assert(rvalue.tryGet!(const(string)) == "hello");
|
||||
}
|
||||
|
||||
// Nontrivial rvalues at compile time
|
||||
version (D_Exceptions)
|
||||
@safe unittest
|
||||
{
|
||||
static struct ElaborateCopy
|
||||
{
|
||||
this(this) {}
|
||||
}
|
||||
|
||||
enum rvalue = SumType!ElaborateCopy();
|
||||
enum ctResult = rvalue.tryGet!ElaborateCopy;
|
||||
|
||||
assert(ctResult == ElaborateCopy());
|
||||
}
|
||||
|
||||
private template failedGetMessage(Expected, Actual)
|
||||
{
|
||||
static if (Expected.stringof == Actual.stringof)
|
||||
{
|
||||
enum expectedStr = __traits(fullyQualifiedName, Expected);
|
||||
enum actualStr = __traits(fullyQualifiedName, Actual);
|
||||
}
|
||||
else
|
||||
{
|
||||
enum expectedStr = Expected.stringof;
|
||||
enum actualStr = Actual.stringof;
|
||||
}
|
||||
|
||||
enum failedGetMessage =
|
||||
"Tried to get `" ~ expectedStr ~ "`" ~
|
||||
" but found `" ~ actualStr ~ "`";
|
||||
}
|
||||
|
||||
private template getLvalue(Flag!"try_" try_, T)
|
||||
{
|
||||
ref T getLvalue(Value)(ref Value value)
|
||||
{
|
||||
static if (is(Value == T))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
static if (try_)
|
||||
throw new MatchException(failedGetMessage!(T, Value));
|
||||
else
|
||||
assert(false, failedGetMessage!(T, Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private template getRvalue(Flag!"try_" try_, T)
|
||||
{
|
||||
T getRvalue(Value)(ref Value value)
|
||||
{
|
||||
static if (is(Value == T))
|
||||
{
|
||||
import core.lifetime : move;
|
||||
|
||||
// Move if possible; otherwise fall back to copy
|
||||
static if (is(typeof(move(value))))
|
||||
{
|
||||
static if (isCopyable!Value)
|
||||
// Workaround for https://issues.dlang.org/show_bug.cgi?id=21542
|
||||
return __ctfe ? value : move(value);
|
||||
else
|
||||
return move(value);
|
||||
}
|
||||
else
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
static if (try_)
|
||||
throw new MatchException(failedGetMessage!(T, Value));
|
||||
else
|
||||
assert(false, failedGetMessage!(T, Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void destroyIfOwner(T)(ref T value)
|
||||
{
|
||||
static if (hasElaborateDestructor!T)
|
||||
|
|
|
@ -447,19 +447,6 @@ private:
|
|||
assert(ptr.bar.val == 7);
|
||||
}
|
||||
|
||||
// Used in Tuple.toString
|
||||
private template sharedToString(alias field)
|
||||
if (is(typeof(field) == shared))
|
||||
{
|
||||
static immutable sharedToString = typeof(field).stringof;
|
||||
}
|
||||
|
||||
private template sharedToString(alias field)
|
||||
if (!is(typeof(field) == shared))
|
||||
{
|
||||
alias sharedToString = field;
|
||||
}
|
||||
|
||||
private enum bool distinctFieldNames(names...) = __traits(compiles,
|
||||
{
|
||||
static foreach (__name; names)
|
||||
|
@ -1307,11 +1294,11 @@ if (distinctFieldNames!(Specs))
|
|||
* Returns:
|
||||
* The string representation of this `Tuple`.
|
||||
*/
|
||||
string toString()() const
|
||||
string toString()()
|
||||
{
|
||||
import std.array : appender;
|
||||
auto app = appender!string();
|
||||
this.toString((const(char)[] chunk) => app ~= chunk);
|
||||
toString((const(char)[] chunk) => app ~= chunk);
|
||||
return app.data;
|
||||
}
|
||||
|
||||
|
@ -1333,14 +1320,14 @@ if (distinctFieldNames!(Specs))
|
|||
* sink = A `char` accepting delegate
|
||||
* fmt = A $(REF FormatSpec, std,format)
|
||||
*/
|
||||
void toString(DG)(scope DG sink) const
|
||||
void toString(DG)(scope DG sink)
|
||||
{
|
||||
auto f = FormatSpec!char();
|
||||
toString(sink, f);
|
||||
}
|
||||
|
||||
/// ditto
|
||||
void toString(DG, Char)(scope DG sink, scope const ref FormatSpec!Char fmt) const
|
||||
void toString(DG, Char)(scope DG sink, scope const ref FormatSpec!Char fmt)
|
||||
{
|
||||
import std.format : format, FormatException;
|
||||
import std.format.write : formattedWrite;
|
||||
|
@ -1355,20 +1342,12 @@ if (distinctFieldNames!(Specs))
|
|||
{
|
||||
sink(fmt.sep);
|
||||
}
|
||||
// TODO: Change this once formattedWrite() works for shared objects.
|
||||
static if (is(Type == class) && is(Type == shared))
|
||||
{
|
||||
sink(Type.stringof);
|
||||
}
|
||||
else
|
||||
{
|
||||
formattedWrite(sink, fmt.nested, this.field[i]);
|
||||
}
|
||||
formattedWrite(sink, fmt.nested, this.field[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
formattedWrite(sink, fmt.nested, staticMap!(sharedToString, this.expand));
|
||||
formattedWrite(sink, fmt.nested, this.expand);
|
||||
}
|
||||
}
|
||||
else if (fmt.spec == 's')
|
||||
|
@ -1383,15 +1362,8 @@ if (distinctFieldNames!(Specs))
|
|||
{
|
||||
sink(separator);
|
||||
}
|
||||
// TODO: Change this once format() works for shared objects.
|
||||
static if (is(Type == class) && is(Type == shared))
|
||||
{
|
||||
sink(Type.stringof);
|
||||
}
|
||||
else
|
||||
{
|
||||
sink(format!("%(%s%)")(only(field[i])));
|
||||
}
|
||||
// Among other things, using "only" causes string-fields to be inside quotes in the result
|
||||
sink.formattedWrite!("%(%s%)")(only(field[i]));
|
||||
}
|
||||
sink(footer);
|
||||
}
|
||||
|
@ -1812,7 +1784,36 @@ private template ReverseTupleSpecs(T...)
|
|||
Tuple!(int, shared A) nosh;
|
||||
nosh[0] = 5;
|
||||
assert(nosh[0] == 5 && nosh[1] is null);
|
||||
assert(nosh.to!string == "Tuple!(int, shared(A))(5, shared(A))");
|
||||
|
||||
assert(nosh.to!string == "Tuple!(int, shared(A))(5, null)");
|
||||
}
|
||||
{
|
||||
// Shared, without fmt.sep
|
||||
import std.format;
|
||||
import std.algorithm.searching;
|
||||
static class A {int i = 1;}
|
||||
Tuple!(int, shared A) nosh;
|
||||
nosh[0] = 5;
|
||||
assert(nosh[0] == 5 && nosh[1] is null);
|
||||
|
||||
// needs trusted, because Object.toString() isn't @safe
|
||||
auto f = ()@trusted => format!("%(%s, %s%)")(nosh);
|
||||
assert(f() == "5, null");
|
||||
nosh[1] = new shared A();
|
||||
// Currently contains the mangled type name
|
||||
// 5, const(std.typecons.__unittest_L1750_C7.A)
|
||||
// This assert is not necessarily to prescribe this behaviour, only to signal if there is a breaking change.
|
||||
// See https://github.com/dlang/phobos/issues/9811
|
||||
auto s = f();
|
||||
assert(s.canFind("__unittest_L"));
|
||||
assert(s.endsWith(".A)"));
|
||||
}
|
||||
{
|
||||
static struct A {}
|
||||
Tuple!(int, shared A*) nosh;
|
||||
nosh[0] = 5;
|
||||
assert(nosh[0] == 5 && nosh[1] is null);
|
||||
assert(nosh.to!string == "Tuple!(int, shared(A*))(5, null)");
|
||||
}
|
||||
{
|
||||
Tuple!(int, string) t;
|
||||
|
@ -1821,6 +1822,40 @@ private template ReverseTupleSpecs(T...)
|
|||
assert(t[0] == 10 && t[1] == "str");
|
||||
assert(t.to!string == `Tuple!(int, string)(10, "str")`, t.to!string);
|
||||
}
|
||||
/* https://github.com/dlang/phobos/issues/9811
|
||||
* Note: This is just documenting current behaviour, dependent on `std.format` implementation
|
||||
* details. None of this is defined in a spec or should be regarded as rigid.
|
||||
*/
|
||||
{
|
||||
static struct X
|
||||
{
|
||||
/** Usually, toString() should be const where possible.
|
||||
* But as long as the tuple is also non-const, this will work
|
||||
*/
|
||||
string toString()
|
||||
{
|
||||
return "toString non-const";
|
||||
}
|
||||
}
|
||||
assert(tuple(X()).to!string == "Tuple!(X)(toString non-const)");
|
||||
const t = tuple(X());
|
||||
// This is an implementation detail of `format`
|
||||
// if the tuple is const, than non-const toString will not be called
|
||||
assert(t.to!string == "const(Tuple!(X))(const(X)())");
|
||||
|
||||
static struct X2
|
||||
{
|
||||
string toString() const /* const toString will work in more cases */
|
||||
{
|
||||
return "toString const";
|
||||
}
|
||||
}
|
||||
assert(tuple(X2()).to!string == "Tuple!(X2)(toString const)");
|
||||
const t2 = tuple(X2());
|
||||
// This is an implementation detail of `format`
|
||||
// if the tuple is const, than non-const toString will not be called
|
||||
assert(t2.to!string == "const(Tuple!(X2))(toString const)");
|
||||
}
|
||||
{
|
||||
Tuple!(int, "a", double, "b") x;
|
||||
static assert(x.a.offsetof == x[0].offsetof);
|
||||
|
|
|
@ -438,6 +438,23 @@
|
|||
assert(b == [1, 0, 0, 0, 5]);
|
||||
}
|
||||
|
||||
@safe pure nothrow unittest
|
||||
{
|
||||
import std.array;
|
||||
|
||||
auto app = appender!string();
|
||||
string b = "abcdefg";
|
||||
foreach (char c; b)
|
||||
app.put(c);
|
||||
assert(app[] == "abcdefg");
|
||||
|
||||
int[] a = [ 1, 2 ];
|
||||
auto app2 = appender(a);
|
||||
app2.put(3);
|
||||
app2.put([ 4, 5, 6 ]);
|
||||
assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]);
|
||||
}
|
||||
|
||||
@safe pure nothrow unittest
|
||||
{
|
||||
import std.array;
|
||||
|
|
|
@ -509,3 +509,15 @@
|
|||
assert(toChars!(16, char, LetterCase.upper)(255U).equal("FF"));
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
import std.conv;
|
||||
|
||||
uint n = 0xDEADBEEF;
|
||||
|
||||
version (LittleEndian)
|
||||
assert(n.bitCast!(ubyte[4]) == [0xEF, 0xBE, 0xAD, 0xDE]);
|
||||
version (BigEndian)
|
||||
assert(n.bitCast!(ubyte[4]) == [0xDE, 0xAD, 0xBE, 0xEF]);
|
||||
}
|
||||
|
||||
|
|
|
@ -358,3 +358,36 @@ pure @safe @nogc nothrow unittest
|
|||
assert(overForty.equal(["Bob", "Eve"]));
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
import std.functional;
|
||||
|
||||
import std.math : abs;
|
||||
|
||||
// No explicit `enum` needed.
|
||||
float result = ctEval!(abs(-3));
|
||||
assert(result == 3);
|
||||
|
||||
// Can be statically asserted.
|
||||
static assert(ctEval!(abs(-4)) == 4);
|
||||
static assert(ctEval!(abs( 9)) == 9);
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
import std.functional;
|
||||
|
||||
import core.stdc.math : round;
|
||||
import std.conv : to;
|
||||
import std.math : abs, PI, sin;
|
||||
|
||||
// `round` from the C standard library cannot be interpreted at compile
|
||||
// time, because it has no available source code. However the function
|
||||
// calls preceding `round` can be evaluated during compile time.
|
||||
int result = ctEval!(abs(sin(1.0)) * 180 / PI)
|
||||
.round()
|
||||
.to!int();
|
||||
|
||||
assert(result == 48);
|
||||
}
|
||||
|
||||
|
|
|
@ -299,3 +299,156 @@
|
|||
assert(!canMatch!(handleInt, string));
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
import std.sumtype;
|
||||
|
||||
SumType!(string, double) example = "hello";
|
||||
|
||||
assert( example.has!string);
|
||||
assert(!example.has!double);
|
||||
|
||||
// If T isn't part of the SumType, has!T will always return false.
|
||||
assert(!example.has!int);
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
import std.sumtype;
|
||||
|
||||
alias Example = SumType!(string, double);
|
||||
|
||||
Example m = "mutable";
|
||||
const Example c = "const";
|
||||
immutable Example i = "immutable";
|
||||
|
||||
assert( m.has!string);
|
||||
assert(!m.has!(const(string)));
|
||||
assert(!m.has!(immutable(string)));
|
||||
|
||||
assert(!c.has!string);
|
||||
assert( c.has!(const(string)));
|
||||
assert(!c.has!(immutable(string)));
|
||||
|
||||
assert(!i.has!string);
|
||||
assert(!i.has!(const(string)));
|
||||
assert( i.has!(immutable(string)));
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
import std.sumtype;
|
||||
|
||||
import std.algorithm.iteration : filter;
|
||||
import std.algorithm.comparison : equal;
|
||||
|
||||
alias Example = SumType!(string, double);
|
||||
|
||||
auto arr = [
|
||||
Example("foo"),
|
||||
Example(0),
|
||||
Example("bar"),
|
||||
Example(1),
|
||||
Example(2),
|
||||
Example("baz")
|
||||
];
|
||||
|
||||
auto strings = arr.filter!(has!string);
|
||||
auto nums = arr.filter!(has!double);
|
||||
|
||||
assert(strings.equal([Example("foo"), Example("bar"), Example("baz")]));
|
||||
assert(nums.equal([Example(0), Example(1), Example(2)]));
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
import std.sumtype;
|
||||
|
||||
SumType!(string, double) example1 = "hello";
|
||||
SumType!(string, double) example2 = 3.14;
|
||||
|
||||
assert(example1.get!string == "hello");
|
||||
assert(example2.get!double == 3.14);
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
import std.sumtype;
|
||||
|
||||
alias Example = SumType!(string, double);
|
||||
|
||||
Example m = "mutable";
|
||||
const(Example) c = "const";
|
||||
immutable(Example) i = "immutable";
|
||||
|
||||
assert(m.get!string == "mutable");
|
||||
assert(c.get!(const(string)) == "const");
|
||||
assert(i.get!(immutable(string)) == "immutable");
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
import std.sumtype;
|
||||
|
||||
import std.algorithm.iteration : map;
|
||||
import std.algorithm.comparison : equal;
|
||||
|
||||
alias Example = SumType!(string, double);
|
||||
|
||||
auto arr = [Example(0), Example(1), Example(2)];
|
||||
auto values = arr.map!(get!double);
|
||||
|
||||
assert(values.equal([0, 1, 2]));
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
import std.sumtype;
|
||||
|
||||
SumType!(string, double) example = "hello";
|
||||
|
||||
assert(example.tryGet!string == "hello");
|
||||
|
||||
double result = double.nan;
|
||||
try
|
||||
result = example.tryGet!double;
|
||||
catch (MatchException e)
|
||||
result = 0;
|
||||
|
||||
// Exception was thrown
|
||||
assert(result == 0);
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
import std.sumtype;
|
||||
|
||||
import std.exception : assertThrown;
|
||||
|
||||
const(SumType!(string, double)) example = "const";
|
||||
|
||||
// Qualifier mismatch; throws exception
|
||||
assertThrown!MatchException(example.tryGet!string);
|
||||
// Qualifier matches; no exception
|
||||
assert(example.tryGet!(const(string)) == "const");
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
import std.sumtype;
|
||||
|
||||
import std.algorithm.iteration : map, sum;
|
||||
import std.functional : pipe;
|
||||
import std.exception : assertThrown;
|
||||
|
||||
alias Example = SumType!(string, double);
|
||||
|
||||
auto arr1 = [Example(0), Example(1), Example(2)];
|
||||
auto arr2 = [Example("foo"), Example("bar"), Example("baz")];
|
||||
|
||||
alias trySum = pipe!(map!(tryGet!double), sum);
|
||||
|
||||
assert(trySum(arr1) == 0 + 1 + 2);
|
||||
assertThrown!MatchException(trySum(arr2));
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue