mirror of
https://github.com/dlang/phobos.git
synced 2025-04-26 21:22:20 +03:00
5547 lines
146 KiB
D
5547 lines
146 KiB
D
// Written in the D programming language.
|
|
|
|
/**
|
|
A one-stop shop for converting values from one type to another.
|
|
|
|
Copyright: Copyright Digital Mars 2007-.
|
|
|
|
License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
|
|
|
Authors: $(WEB digitalmars.com, Walter Bright),
|
|
$(WEB erdani.org, Andrei Alexandrescu),
|
|
Shin Fujishiro,
|
|
Adam D. Ruppe,
|
|
Kenji Hara
|
|
|
|
Source: $(PHOBOSSRC std/_conv.d)
|
|
|
|
Macros:
|
|
WIKI = Phobos/StdConv
|
|
|
|
*/
|
|
module std.conv;
|
|
|
|
public import std.ascii : LetterCase;
|
|
|
|
import std.range.primitives;
|
|
import std.traits;
|
|
import std.meta;
|
|
|
|
private string convFormat(Char, Args...)(in Char[] fmt, Args args)
|
|
{
|
|
import std.format : format;
|
|
return std.format.format(fmt, args);
|
|
}
|
|
|
|
/* ************* Exceptions *************** */
|
|
|
|
/**
|
|
* Thrown on conversion errors.
|
|
*/
|
|
class ConvException : Exception
|
|
{
|
|
@safe pure nothrow
|
|
this(string s, string fn = __FILE__, size_t ln = __LINE__)
|
|
{
|
|
super(s, fn, ln);
|
|
}
|
|
}
|
|
|
|
private string convError_unexpected(S)(S source)
|
|
{
|
|
return source.empty ? "end of input" : text("'", source.front, "'");
|
|
}
|
|
|
|
private auto convError(S, T)(S source, string fn = __FILE__, size_t ln = __LINE__)
|
|
{
|
|
return new ConvException(
|
|
text("Unexpected ", convError_unexpected(source),
|
|
" when converting from type "~S.stringof~" to type "~T.stringof),
|
|
fn, ln);
|
|
}
|
|
|
|
private auto convError(S, T)(S source, int radix, string fn = __FILE__, size_t ln = __LINE__)
|
|
{
|
|
return new ConvException(
|
|
text("Unexpected ", convError_unexpected(source),
|
|
" when converting from type "~S.stringof~" base ", radix,
|
|
" to type "~T.stringof),
|
|
fn, ln);
|
|
}
|
|
|
|
@safe pure/* nothrow*/ // lazy parameter bug
|
|
private auto parseError(lazy string msg, string fn = __FILE__, size_t ln = __LINE__)
|
|
{
|
|
return new ConvException(text("Can't parse string: ", msg), fn, ln);
|
|
}
|
|
|
|
private void parseCheck(alias source)(dchar c, string fn = __FILE__, size_t ln = __LINE__)
|
|
{
|
|
if (source.empty)
|
|
throw parseError(text("unexpected end of input when expecting", "\"", c, "\""));
|
|
if (source.front != c)
|
|
throw parseError(text("\"", c, "\" is missing"), fn, ln);
|
|
source.popFront();
|
|
}
|
|
|
|
private
|
|
{
|
|
template isImaginary(T)
|
|
{
|
|
enum bool isImaginary = staticIndexOf!(Unqual!T,
|
|
ifloat, idouble, ireal) >= 0;
|
|
}
|
|
template isComplex(T)
|
|
{
|
|
enum bool isComplex = staticIndexOf!(Unqual!T,
|
|
cfloat, cdouble, creal) >= 0;
|
|
}
|
|
template isNarrowInteger(T)
|
|
{
|
|
enum bool isNarrowInteger = staticIndexOf!(Unqual!T,
|
|
byte, ubyte, short, ushort) >= 0;
|
|
}
|
|
|
|
T toStr(T, S)(S src)
|
|
if (isSomeString!T)
|
|
{
|
|
// workaround for Bugzilla 14198
|
|
static if (is(S == bool) && is(typeof({ T s = "string"; })))
|
|
{
|
|
return src ? "true" : "false";
|
|
}
|
|
else
|
|
{
|
|
import std.format : FormatSpec, formatValue;
|
|
import std.array : appender;
|
|
|
|
auto w = appender!T();
|
|
FormatSpec!(ElementEncodingType!T) f;
|
|
formatValue(w, src, f);
|
|
return w.data;
|
|
}
|
|
}
|
|
|
|
template isExactSomeString(T)
|
|
{
|
|
enum isExactSomeString = isSomeString!T && !is(T == enum);
|
|
}
|
|
|
|
template isEnumStrToStr(S, T)
|
|
{
|
|
enum isEnumStrToStr = isImplicitlyConvertible!(S, T) &&
|
|
is(S == enum) && isExactSomeString!T;
|
|
}
|
|
template isNullToStr(S, T)
|
|
{
|
|
enum isNullToStr = isImplicitlyConvertible!(S, T) &&
|
|
(is(Unqual!S == typeof(null))) && isExactSomeString!T;
|
|
}
|
|
|
|
template isRawStaticArray(T, A...)
|
|
{
|
|
enum isRawStaticArray =
|
|
A.length == 0 &&
|
|
isStaticArray!T &&
|
|
!is(T == class) &&
|
|
!is(T == interface) &&
|
|
!is(T == struct) &&
|
|
!is(T == union);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thrown on conversion overflow errors.
|
|
*/
|
|
class ConvOverflowException : ConvException
|
|
{
|
|
@safe pure nothrow
|
|
this(string s, string fn = __FILE__, size_t ln = __LINE__)
|
|
{
|
|
super(s, fn, ln);
|
|
}
|
|
}
|
|
|
|
/**
|
|
|
|
The $(D_PARAM to) family of functions converts a value from type
|
|
$(D_PARAM Source) to type $(D_PARAM Target). The source type is
|
|
deduced and the target type must be specified, for example the
|
|
expression $(D_PARAM to!int(42.0)) converts the number 42 from
|
|
$(D_PARAM double) to $(D_PARAM int). The conversion is "safe", i.e.,
|
|
it checks for overflow; $(D_PARAM to!int(4.2e10)) would throw the
|
|
$(D_PARAM ConvOverflowException) exception. Overflow checks are only
|
|
inserted when necessary, e.g., $(D_PARAM to!double(42)) does not do
|
|
any checking because any int fits in a double.
|
|
|
|
Converting a value to its own type (useful mostly for generic code)
|
|
simply returns its argument.
|
|
|
|
Example:
|
|
-------------------------
|
|
int a = 42;
|
|
auto b = to!int(a); // b is int with value 42
|
|
auto c = to!double(3.14); // c is double with value 3.14
|
|
-------------------------
|
|
|
|
Converting among numeric types is a safe way to cast them around.
|
|
|
|
Conversions from floating-point types to integral types allow loss of
|
|
precision (the fractional part of a floating-point number). The
|
|
conversion is truncating towards zero, the same way a cast would
|
|
truncate. (To round a floating point value when casting to an
|
|
integral, use $(D_PARAM roundTo).)
|
|
|
|
Examples:
|
|
-------------------------
|
|
int a = 420;
|
|
auto b = to!long(a); // same as long b = a;
|
|
auto c = to!byte(a / 10); // fine, c = 42
|
|
auto d = to!byte(a); // throw ConvOverflowException
|
|
double e = 4.2e6;
|
|
auto f = to!int(e); // f == 4200000
|
|
e = -3.14;
|
|
auto g = to!uint(e); // fails: floating-to-integral negative overflow
|
|
e = 3.14;
|
|
auto h = to!uint(e); // h = 3
|
|
e = 3.99;
|
|
h = to!uint(a); // h = 3
|
|
e = -3.99;
|
|
f = to!int(a); // f = -3
|
|
-------------------------
|
|
|
|
Conversions from integral types to floating-point types always
|
|
succeed, but might lose accuracy. The largest integers with a
|
|
predecessor representable in floating-point format are 2^24-1 for
|
|
float, 2^53-1 for double, and 2^64-1 for $(D_PARAM real) (when
|
|
$(D_PARAM real) is 80-bit, e.g. on Intel machines).
|
|
|
|
Example:
|
|
-------------------------
|
|
int a = 16_777_215; // 2^24 - 1, largest proper integer representable as float
|
|
assert(to!int(to!float(a)) == a);
|
|
assert(to!int(to!float(-a)) == -a);
|
|
a += 2;
|
|
assert(to!int(to!float(a)) == a); // fails!
|
|
-------------------------
|
|
|
|
Conversions from string to numeric types differ from the C equivalents
|
|
$(D_PARAM atoi()) and $(D_PARAM atol()) by checking for overflow and
|
|
not allowing whitespace.
|
|
|
|
For conversion of strings to signed types, the grammar recognized is:
|
|
<pre>
|
|
$(I Integer): $(I Sign UnsignedInteger)
|
|
$(I UnsignedInteger)
|
|
$(I Sign):
|
|
$(B +)
|
|
$(B -)
|
|
</pre>
|
|
|
|
For conversion to unsigned types, the grammar recognized is:
|
|
<pre>
|
|
$(I UnsignedInteger):
|
|
$(I DecimalDigit)
|
|
$(I DecimalDigit) $(I UnsignedInteger)
|
|
</pre>
|
|
|
|
Converting an array to another array type works by converting each
|
|
element in turn. Associative arrays can be converted to associative
|
|
arrays as long as keys and values can in turn be converted.
|
|
|
|
Example:
|
|
-------------------------
|
|
int[] a = [1, 2, 3];
|
|
auto b = to!(float[])(a);
|
|
assert(b == [1.0f, 2, 3]);
|
|
string str = "1 2 3 4 5 6";
|
|
auto numbers = to!(double[])(split(str));
|
|
assert(numbers == [1.0, 2, 3, 4, 5, 6]);
|
|
int[string] c;
|
|
c["a"] = 1;
|
|
c["b"] = 2;
|
|
auto d = to!(double[wstring])(c);
|
|
assert(d["a"w] == 1 && d["b"w] == 2);
|
|
-------------------------
|
|
|
|
Conversions operate transitively, meaning that they work on arrays and
|
|
associative arrays of any complexity:
|
|
|
|
-------------------------
|
|
int[string][double[int[]]] a;
|
|
...
|
|
auto b = to!(short[wstring][string[double[]]])(a);
|
|
-------------------------
|
|
|
|
This conversion works because $(D_PARAM to!short) applies to an
|
|
$(D_PARAM int), $(D_PARAM to!wstring) applies to a $(D_PARAM
|
|
string), $(D_PARAM to!string) applies to a $(D_PARAM double), and
|
|
$(D_PARAM to!(double[])) applies to an $(D_PARAM int[]). The
|
|
conversion might throw an exception because $(D_PARAM to!short)
|
|
might fail the range check.
|
|
|
|
*/
|
|
|
|
/**
|
|
Entry point that dispatches to the appropriate conversion
|
|
primitive. Client code normally calls $(D _to!TargetType(value))
|
|
(and not some variant of $(D toImpl)).
|
|
*/
|
|
template to(T)
|
|
{
|
|
T to(A...)(A args)
|
|
if (!isRawStaticArray!A)
|
|
{
|
|
return toImpl!T(args);
|
|
}
|
|
|
|
// Fix issue 6175
|
|
T to(S)(ref S arg)
|
|
if (isRawStaticArray!S)
|
|
{
|
|
return toImpl!T(arg);
|
|
}
|
|
}
|
|
|
|
// Tests for issue 6175
|
|
@safe pure nothrow unittest
|
|
{
|
|
char[9] sarr = "blablabla";
|
|
auto darr = to!(char[])(sarr);
|
|
assert(sarr.ptr == darr.ptr);
|
|
assert(sarr.length == darr.length);
|
|
}
|
|
|
|
// Tests for issue 7348
|
|
@safe pure /+nothrow+/ unittest
|
|
{
|
|
assert(to!string(null) == "null");
|
|
assert(text(null) == "null");
|
|
}
|
|
|
|
// Tests for issue 11390
|
|
@safe pure /+nothrow+/ unittest
|
|
{
|
|
const(typeof(null)) ctn;
|
|
immutable(typeof(null)) itn;
|
|
assert(to!string(ctn) == "null");
|
|
assert(to!string(itn) == "null");
|
|
}
|
|
|
|
// Tests for issue 8729: do NOT skip leading WS
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
foreach (T; TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong))
|
|
{
|
|
assertThrown!ConvException(to!T(" 0"));
|
|
assertThrown!ConvException(to!T(" 0", 8));
|
|
}
|
|
foreach (T; TypeTuple!(float, double, real))
|
|
{
|
|
assertThrown!ConvException(to!T(" 0"));
|
|
}
|
|
|
|
assertThrown!ConvException(to!bool(" true"));
|
|
|
|
alias NullType = typeof(null);
|
|
assertThrown!ConvException(to!NullType(" null"));
|
|
|
|
alias ARR = int[];
|
|
assertThrown!ConvException(to!ARR(" [1]"));
|
|
|
|
alias AA = int[int];
|
|
assertThrown!ConvException(to!AA(" [1:1]"));
|
|
}
|
|
|
|
/**
|
|
If the source type is implicitly convertible to the target type, $(D
|
|
to) simply performs the implicit conversion.
|
|
*/
|
|
T toImpl(T, S)(S value)
|
|
if (isImplicitlyConvertible!(S, T) &&
|
|
!isEnumStrToStr!(S, T) && !isNullToStr!(S, T))
|
|
{
|
|
template isSignedInt(T)
|
|
{
|
|
enum isSignedInt = isIntegral!T && isSigned!T;
|
|
}
|
|
alias isUnsignedInt = isUnsigned;
|
|
|
|
// Conversion from integer to integer, and changing its sign
|
|
static if (isUnsignedInt!S && isSignedInt!T && S.sizeof == T.sizeof)
|
|
{ // unsigned to signed & same size
|
|
import std.exception : enforce;
|
|
enforce(value <= cast(S)T.max,
|
|
new ConvOverflowException("Conversion positive overflow"));
|
|
}
|
|
else static if (isSignedInt!S && isUnsignedInt!T)
|
|
{ // signed to unsigned
|
|
import std.exception : enforce;
|
|
enforce(0 <= value,
|
|
new ConvOverflowException("Conversion negative overflow"));
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
@safe pure nothrow unittest
|
|
{
|
|
enum E { a } // Issue 9523 - Allow identity enum conversion
|
|
auto e = to!E(E.a);
|
|
assert(e == E.a);
|
|
}
|
|
|
|
@safe pure nothrow unittest
|
|
{
|
|
int a = 42;
|
|
auto b = to!long(a);
|
|
assert(a == b);
|
|
}
|
|
|
|
// Tests for issue 6377
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
// Conversion between same size
|
|
foreach (S; TypeTuple!(byte, short, int, long))
|
|
(){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
|
|
alias U = Unsigned!S;
|
|
|
|
foreach (Sint; TypeTuple!(S, const S, immutable S))
|
|
foreach (Uint; TypeTuple!(U, const U, immutable U))
|
|
{
|
|
// positive overflow
|
|
Uint un = Uint.max;
|
|
assertThrown!ConvOverflowException(to!Sint(un),
|
|
text(Sint.stringof, ' ', Uint.stringof, ' ', un));
|
|
|
|
// negative overflow
|
|
Sint sn = -1;
|
|
assertThrown!ConvOverflowException(to!Uint(sn),
|
|
text(Sint.stringof, ' ', Uint.stringof, ' ', un));
|
|
}
|
|
}();
|
|
|
|
// Conversion between different size
|
|
foreach (i, S1; TypeTuple!(byte, short, int, long))
|
|
foreach ( S2; TypeTuple!(byte, short, int, long)[i+1..$])
|
|
(){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
|
|
alias U1 = Unsigned!S1;
|
|
alias U2 = Unsigned!S2;
|
|
|
|
static assert(U1.sizeof < S2.sizeof);
|
|
|
|
// small unsigned to big signed
|
|
foreach (Uint; TypeTuple!(U1, const U1, immutable U1))
|
|
foreach (Sint; TypeTuple!(S2, const S2, immutable S2))
|
|
{
|
|
Uint un = Uint.max;
|
|
assertNotThrown(to!Sint(un));
|
|
assert(to!Sint(un) == un);
|
|
}
|
|
|
|
// big unsigned to small signed
|
|
foreach (Uint; TypeTuple!(U2, const U2, immutable U2))
|
|
foreach (Sint; TypeTuple!(S1, const S1, immutable S1))
|
|
{
|
|
Uint un = Uint.max;
|
|
assertThrown(to!Sint(un));
|
|
}
|
|
|
|
static assert(S1.sizeof < U2.sizeof);
|
|
|
|
// small signed to big unsigned
|
|
foreach (Sint; TypeTuple!(S1, const S1, immutable S1))
|
|
foreach (Uint; TypeTuple!(U2, const U2, immutable U2))
|
|
{
|
|
Sint sn = -1;
|
|
assertThrown!ConvOverflowException(to!Uint(sn));
|
|
}
|
|
|
|
// big signed to small unsigned
|
|
foreach (Sint; TypeTuple!(S2, const S2, immutable S2))
|
|
foreach (Uint; TypeTuple!(U1, const U1, immutable U1))
|
|
{
|
|
Sint sn = -1;
|
|
assertThrown!ConvOverflowException(to!Uint(sn));
|
|
}
|
|
}();
|
|
}
|
|
|
|
/*
|
|
Converting static arrays forwards to their dynamic counterparts.
|
|
*/
|
|
T toImpl(T, S)(ref S s)
|
|
if (isRawStaticArray!S)
|
|
{
|
|
return toImpl!(T, typeof(s[0])[])(s);
|
|
}
|
|
|
|
@safe pure nothrow unittest
|
|
{
|
|
char[4] test = ['a', 'b', 'c', 'd'];
|
|
static assert(!isInputRange!(Unqual!(char[4])));
|
|
assert(to!string(test) == test);
|
|
}
|
|
|
|
/**
|
|
When source type supports member template function opCast, it is used.
|
|
*/
|
|
T toImpl(T, S)(S value)
|
|
if (!isImplicitlyConvertible!(S, T) &&
|
|
is(typeof(S.init.opCast!T()) : T) &&
|
|
!isExactSomeString!T &&
|
|
!is(typeof(T(value))))
|
|
{
|
|
return value.opCast!T();
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
static struct Test
|
|
{
|
|
struct T
|
|
{
|
|
this(S s) @safe pure { }
|
|
}
|
|
struct S
|
|
{
|
|
T opCast(U)() @safe pure { assert(false); }
|
|
}
|
|
}
|
|
to!(Test.T)(Test.S());
|
|
|
|
// make sure std.conv.to is doing the same thing as initialization
|
|
Test.S s;
|
|
Test.T t = s;
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
class B
|
|
{
|
|
T opCast(T)() { return 43; }
|
|
}
|
|
auto b = new B;
|
|
assert(to!int(b) == 43);
|
|
|
|
struct S
|
|
{
|
|
T opCast(T)() { return 43; }
|
|
}
|
|
auto s = S();
|
|
assert(to!int(s) == 43);
|
|
}
|
|
|
|
/**
|
|
When target type supports 'converting construction', it is used.
|
|
$(UL $(LI If target type is struct, $(D T(value)) is used.)
|
|
$(LI If target type is class, $(D new T(value)) is used.))
|
|
*/
|
|
T toImpl(T, S)(S value)
|
|
if (!isImplicitlyConvertible!(S, T) &&
|
|
is(T == struct) && is(typeof(T(value))))
|
|
{
|
|
return T(value);
|
|
}
|
|
|
|
// Bugzilla 3961
|
|
@safe pure unittest
|
|
{
|
|
struct Int
|
|
{
|
|
int x;
|
|
}
|
|
Int i = to!Int(1);
|
|
|
|
static struct Int2
|
|
{
|
|
int x;
|
|
this(int x) @safe pure { this.x = x; }
|
|
}
|
|
Int2 i2 = to!Int2(1);
|
|
|
|
static struct Int3
|
|
{
|
|
int x;
|
|
static Int3 opCall(int x) @safe pure
|
|
{
|
|
Int3 i;
|
|
i.x = x;
|
|
return i;
|
|
}
|
|
}
|
|
Int3 i3 = to!Int3(1);
|
|
}
|
|
|
|
// Bugzilla 6808
|
|
@safe pure unittest
|
|
{
|
|
static struct FakeBigInt
|
|
{
|
|
this(string s) @safe pure {}
|
|
}
|
|
|
|
string s = "101";
|
|
auto i3 = to!FakeBigInt(s);
|
|
}
|
|
|
|
/// ditto
|
|
T toImpl(T, S)(S value)
|
|
if (!isImplicitlyConvertible!(S, T) &&
|
|
is(T == class) && is(typeof(new T(value))))
|
|
{
|
|
return new T(value);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
static struct S
|
|
{
|
|
int x;
|
|
}
|
|
static class C
|
|
{
|
|
int x;
|
|
this(int x) @safe pure { this.x = x; }
|
|
}
|
|
|
|
static class B
|
|
{
|
|
int value;
|
|
this(S src) @safe pure { value = src.x; }
|
|
this(C src) @safe pure { value = src.x; }
|
|
}
|
|
|
|
S s = S(1);
|
|
auto b1 = to!B(s); // == new B(s)
|
|
assert(b1.value == 1);
|
|
|
|
C c = new C(2);
|
|
auto b2 = to!B(c); // == new B(c)
|
|
assert(b2.value == 2);
|
|
|
|
auto c2 = to!C(3); // == new C(3)
|
|
assert(c2.x == 3);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
struct S
|
|
{
|
|
class A
|
|
{
|
|
this(B b) @safe pure {}
|
|
}
|
|
class B : A
|
|
{
|
|
this() @safe pure { super(this); }
|
|
}
|
|
}
|
|
|
|
S.B b = new S.B();
|
|
S.A a = to!(S.A)(b); // == cast(S.A)b
|
|
// (do not run construction conversion like new S.A(b))
|
|
assert(b is a);
|
|
|
|
static class C : Object
|
|
{
|
|
this() @safe pure {}
|
|
this(Object o) @safe pure {}
|
|
}
|
|
|
|
Object oc = new C();
|
|
C a2 = to!C(oc); // == new C(a)
|
|
// Construction conversion overrides down-casting conversion
|
|
assert(a2 !is a); //
|
|
}
|
|
|
|
/**
|
|
Object-to-object conversions by dynamic casting throw exception when the source is
|
|
non-null and the target is null.
|
|
*/
|
|
T toImpl(T, S)(S value)
|
|
if (!isImplicitlyConvertible!(S, T) &&
|
|
(is(S == class) || is(S == interface)) && !is(typeof(value.opCast!T()) : T) &&
|
|
(is(T == class) || is(T == interface)) && !is(typeof(new T(value))))
|
|
{
|
|
static if (is(T == immutable))
|
|
{
|
|
// immutable <- immutable
|
|
enum isModConvertible = is(S == immutable);
|
|
}
|
|
else static if (is(T == const))
|
|
{
|
|
static if (is(T == shared))
|
|
{
|
|
// shared const <- shared
|
|
// shared const <- shared const
|
|
// shared const <- immutable
|
|
enum isModConvertible = is(S == shared) || is(S == immutable);
|
|
}
|
|
else
|
|
{
|
|
// const <- mutable
|
|
// const <- immutable
|
|
enum isModConvertible = !is(S == shared);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
static if (is(T == shared))
|
|
{
|
|
// shared <- shared mutable
|
|
enum isModConvertible = is(S == shared) && !is(S == const);
|
|
}
|
|
else
|
|
{
|
|
// (mutable) <- (mutable)
|
|
enum isModConvertible = is(Unqual!S == S);
|
|
}
|
|
}
|
|
static assert(isModConvertible, "Bad modifier conversion: "~S.stringof~" to "~T.stringof);
|
|
|
|
auto result = ()@trusted{ return cast(T) value; }();
|
|
if (!result && value)
|
|
{
|
|
throw new ConvException("Cannot convert object of static type "
|
|
~S.classinfo.name~" and dynamic type "~value.classinfo.name
|
|
~" to type "~T.classinfo.name);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
// Testing object conversions
|
|
class A {}
|
|
class B : A {}
|
|
class C : A {}
|
|
A a1 = new A, a2 = new B, a3 = new C;
|
|
assert(to!B(a2) is a2);
|
|
assert(to!C(a3) is a3);
|
|
assertThrown!ConvException(to!B(a3));
|
|
}
|
|
|
|
// Unittest for 6288
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
|
|
alias Identity(T) = T;
|
|
alias toConst(T) = const T;
|
|
alias toShared(T) = shared T;
|
|
alias toSharedConst(T) = shared const T;
|
|
alias toImmutable(T) = immutable T;
|
|
template AddModifier(int n) if (0 <= n && n < 5)
|
|
{
|
|
static if (n == 0) alias AddModifier = Identity;
|
|
else static if (n == 1) alias AddModifier = toConst;
|
|
else static if (n == 2) alias AddModifier = toShared;
|
|
else static if (n == 3) alias AddModifier = toSharedConst;
|
|
else static if (n == 4) alias AddModifier = toImmutable;
|
|
}
|
|
|
|
interface I {}
|
|
interface J {}
|
|
|
|
class A {}
|
|
class B : A {}
|
|
class C : B, I, J {}
|
|
class D : I {}
|
|
|
|
foreach (m1; TypeTuple!(0,1,2,3,4)) // enumerate modifiers
|
|
foreach (m2; TypeTuple!(0,1,2,3,4)) // ditto
|
|
(){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
|
|
alias srcmod = AddModifier!m1;
|
|
alias tgtmod = AddModifier!m2;
|
|
//pragma(msg, srcmod!Object, " -> ", tgtmod!Object, ", convertible = ",
|
|
// isImplicitlyConvertible!(srcmod!Object, tgtmod!Object));
|
|
|
|
// Compile time convertible equals to modifier convertible.
|
|
static if (isImplicitlyConvertible!(srcmod!Object, tgtmod!Object))
|
|
{
|
|
// Test runtime conversions: class to class, class to interface,
|
|
// interface to class, and interface to interface
|
|
|
|
// Check that the runtime conversion to succeed
|
|
srcmod!A ac = new srcmod!C();
|
|
srcmod!I ic = new srcmod!C();
|
|
assert(to!(tgtmod!C)(ac) !is null); // A(c) to C
|
|
assert(to!(tgtmod!I)(ac) !is null); // A(c) to I
|
|
assert(to!(tgtmod!C)(ic) !is null); // I(c) to C
|
|
assert(to!(tgtmod!J)(ic) !is null); // I(c) to J
|
|
|
|
// Check that the runtime conversion fails
|
|
srcmod!A ab = new srcmod!B();
|
|
srcmod!I id = new srcmod!D();
|
|
assertThrown(to!(tgtmod!C)(ab)); // A(b) to C
|
|
assertThrown(to!(tgtmod!I)(ab)); // A(b) to I
|
|
assertThrown(to!(tgtmod!C)(id)); // I(d) to C
|
|
assertThrown(to!(tgtmod!J)(id)); // I(d) to J
|
|
}
|
|
else
|
|
{
|
|
// Check that the conversion is rejected statically
|
|
static assert(!is(typeof(to!(tgtmod!C)(srcmod!A.init)))); // A to C
|
|
static assert(!is(typeof(to!(tgtmod!I)(srcmod!A.init)))); // A to I
|
|
static assert(!is(typeof(to!(tgtmod!C)(srcmod!I.init)))); // I to C
|
|
static assert(!is(typeof(to!(tgtmod!J)(srcmod!I.init)))); // I to J
|
|
}
|
|
}();
|
|
}
|
|
|
|
/**
|
|
Stringize conversion from all types is supported.
|
|
$(UL
|
|
$(LI String _to string conversion works for any two string types having
|
|
($(D char), $(D wchar), $(D dchar)) character widths and any
|
|
combination of qualifiers (mutable, $(D const), or $(D immutable)).)
|
|
$(LI Converts array (other than strings) to string.
|
|
Each element is converted by calling $(D to!T).)
|
|
$(LI Associative array to string conversion.
|
|
Each element is printed by calling $(D to!T).)
|
|
$(LI Object to string conversion calls $(D toString) against the object or
|
|
returns $(D "null") if the object is null.)
|
|
$(LI Struct to string conversion calls $(D toString) against the struct if
|
|
it is defined.)
|
|
$(LI For structs that do not define $(D toString), the conversion to string
|
|
produces the list of fields.)
|
|
$(LI Enumerated types are converted to strings as their symbolic names.)
|
|
$(LI Boolean values are printed as $(D "true") or $(D "false").)
|
|
$(LI $(D char), $(D wchar), $(D dchar) to a string type.)
|
|
$(LI Unsigned or signed integers to strings.
|
|
$(DL $(DT [special case])
|
|
$(DD Convert integral value to string in $(D_PARAM radix) radix.
|
|
radix must be a value from 2 to 36.
|
|
value is treated as a signed value only if radix is 10.
|
|
The characters A through Z are used to represent values 10 through 36
|
|
and their case is determined by the $(D_PARAM letterCase) parameter.)))
|
|
$(LI All floating point types to all string types.)
|
|
$(LI Pointer to string conversions prints the pointer as a $(D size_t) value.
|
|
If pointer is $(D char*), treat it as C-style strings.
|
|
In that case, this function is $(D @system).))
|
|
*/
|
|
T toImpl(T, S)(S value)
|
|
if (!(isImplicitlyConvertible!(S, T) &&
|
|
!isEnumStrToStr!(S, T) && !isNullToStr!(S, T)) &&
|
|
!isInfinite!S && isExactSomeString!T)
|
|
{
|
|
static if (isExactSomeString!S && value[0].sizeof == ElementEncodingType!T.sizeof)
|
|
{
|
|
// string-to-string with incompatible qualifier conversion
|
|
static if (is(ElementEncodingType!T == immutable))
|
|
{
|
|
// conversion (mutable|const) -> immutable
|
|
return value.idup;
|
|
}
|
|
else
|
|
{
|
|
// conversion (immutable|const) -> mutable
|
|
return value.dup;
|
|
}
|
|
}
|
|
else static if (isExactSomeString!S)
|
|
{
|
|
import std.array : appender;
|
|
// other string-to-string
|
|
//Use Appender directly instead of toStr, which also uses a formatedWrite
|
|
auto w = appender!T();
|
|
w.put(value);
|
|
return w.data;
|
|
}
|
|
else static if (isIntegral!S && !is(S == enum))
|
|
{
|
|
// other integral-to-string conversions with default radix
|
|
return toImpl!(T, S)(value, 10);
|
|
}
|
|
else static if (is(S == void[]) || is(S == const(void)[]) || is(S == immutable(void)[]))
|
|
{
|
|
import core.stdc.string : memcpy;
|
|
import std.exception : enforce;
|
|
// Converting void array to string
|
|
alias Char = Unqual!(ElementEncodingType!T);
|
|
auto raw = cast(const(ubyte)[]) value;
|
|
enforce(raw.length % Char.sizeof == 0,
|
|
new ConvException("Alignment mismatch in converting a "
|
|
~ S.stringof ~ " to a "
|
|
~ T.stringof));
|
|
auto result = new Char[raw.length / Char.sizeof];
|
|
()@trusted{ memcpy(result.ptr, value.ptr, value.length); }();
|
|
return cast(T) result;
|
|
}
|
|
else static if (isPointer!S && is(S : const(char)*))
|
|
{
|
|
import core.stdc.string : strlen;
|
|
// It is unsafe because we cannot guarantee that the pointer is null terminated.
|
|
return value ? cast(T) value[0 .. strlen(value)].dup : null;
|
|
}
|
|
else static if (isSomeString!T && is(S == enum))
|
|
{
|
|
static if (isSwitchable!(OriginalType!S) && EnumMembers!S.length <= 50)
|
|
{
|
|
switch(value)
|
|
{
|
|
foreach (member; NoDuplicates!(EnumMembers!S))
|
|
{
|
|
case member:
|
|
return to!T(enumRep!(immutable(T), S, member));
|
|
}
|
|
default:
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (member; EnumMembers!S)
|
|
{
|
|
if (value == member)
|
|
return to!T(enumRep!(immutable(T), S, member));
|
|
}
|
|
}
|
|
|
|
import std.format : FormatSpec, formatValue;
|
|
import std.array : appender;
|
|
|
|
//Default case, delegate to format
|
|
//Note: we don't call toStr directly, to avoid duplicate work.
|
|
auto app = appender!T();
|
|
app.put("cast(");
|
|
app.put(S.stringof);
|
|
app.put(')');
|
|
FormatSpec!char f;
|
|
formatValue(app, cast(OriginalType!S)value, f);
|
|
return app.data;
|
|
}
|
|
else
|
|
{
|
|
// other non-string values runs formatting
|
|
return toStr!T(value);
|
|
}
|
|
}
|
|
|
|
// Bugzilla 14042
|
|
unittest
|
|
{
|
|
immutable(char)* ptr = "hello".ptr;
|
|
auto result = ptr.to!(char[]);
|
|
}
|
|
|
|
/*
|
|
Check whether type $(D T) can be used in a switch statement.
|
|
This is useful for compile-time generation of switch case statements.
|
|
*/
|
|
private template isSwitchable(E)
|
|
{
|
|
enum bool isSwitchable = is(typeof({
|
|
switch (E.init) { default: }
|
|
}));
|
|
}
|
|
|
|
//
|
|
unittest
|
|
{
|
|
static assert(isSwitchable!int);
|
|
static assert(!isSwitchable!double);
|
|
static assert(!isSwitchable!real);
|
|
}
|
|
|
|
//Static representation of the index I of the enum S,
|
|
//In representation T.
|
|
//T must be an immutable string (avoids un-necessary initializations).
|
|
private template enumRep(T, S, S value)
|
|
if (is (T == immutable) && isExactSomeString!T && is(S == enum))
|
|
{
|
|
static T enumRep = toStr!T(value);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
void dg()
|
|
{
|
|
// string to string conversion
|
|
alias Chars = TypeTuple!(char, wchar, dchar);
|
|
foreach (LhsC; Chars)
|
|
{
|
|
alias LhStrings = TypeTuple!(LhsC[], const(LhsC)[], immutable(LhsC)[]);
|
|
foreach (Lhs; LhStrings)
|
|
{
|
|
foreach (RhsC; Chars)
|
|
{
|
|
alias RhStrings = TypeTuple!(RhsC[], const(RhsC)[], immutable(RhsC)[]);
|
|
foreach (Rhs; RhStrings)
|
|
{
|
|
Lhs s1 = to!Lhs("wyda");
|
|
Rhs s2 = to!Rhs(s1);
|
|
//writeln(Lhs.stringof, " -> ", Rhs.stringof);
|
|
assert(s1 == to!Lhs(s2));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (T; Chars)
|
|
{
|
|
foreach (U; Chars)
|
|
{
|
|
T[] s1 = to!(T[])("Hello, world!");
|
|
auto s2 = to!(U[])(s1);
|
|
assert(s1 == to!(T[])(s2));
|
|
auto s3 = to!(const(U)[])(s1);
|
|
assert(s1 == to!(T[])(s3));
|
|
auto s4 = to!(immutable(U)[])(s1);
|
|
assert(s1 == to!(T[])(s4));
|
|
}
|
|
}
|
|
}
|
|
dg();
|
|
assertCTFEable!dg;
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
// Conversion reinterpreting void array to string
|
|
auto a = "abcx"w;
|
|
const(void)[] b = a;
|
|
assert(b.length == 8);
|
|
|
|
auto c = to!(wchar[])(b);
|
|
assert(c == "abcx");
|
|
}
|
|
|
|
@system pure nothrow unittest
|
|
{
|
|
// char* to string conversion
|
|
assert(to!string(cast(char*) null) == "");
|
|
assert(to!string("foo\0".ptr) == "foo");
|
|
}
|
|
|
|
@safe pure /+nothrow+/ unittest
|
|
{
|
|
// Conversion representing bool value with string
|
|
bool b;
|
|
assert(to!string(b) == "false");
|
|
b = true;
|
|
assert(to!string(b) == "true");
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
// Conversion representing character value with string
|
|
alias AllChars =
|
|
TypeTuple!( char, const( char), immutable( char),
|
|
wchar, const(wchar), immutable(wchar),
|
|
dchar, const(dchar), immutable(dchar));
|
|
foreach (Char1; AllChars)
|
|
{
|
|
foreach (Char2; AllChars)
|
|
{
|
|
Char1 c = 'a';
|
|
assert(to!(Char2[])(c)[0] == c);
|
|
}
|
|
uint x = 4;
|
|
assert(to!(Char1[])(x) == "4");
|
|
}
|
|
|
|
string s = "foo";
|
|
string s2;
|
|
foreach (char c; s)
|
|
{
|
|
s2 ~= to!string(c);
|
|
}
|
|
//printf("%.*s", s2);
|
|
assert(s2 == "foo");
|
|
}
|
|
|
|
@safe pure nothrow unittest
|
|
{
|
|
import std.exception;
|
|
// Conversion representing integer values with string
|
|
|
|
foreach (Int; TypeTuple!(ubyte, ushort, uint, ulong))
|
|
{
|
|
assert(to!string(Int(0)) == "0");
|
|
assert(to!string(Int(9)) == "9");
|
|
assert(to!string(Int(123)) == "123");
|
|
}
|
|
|
|
foreach (Int; TypeTuple!(byte, short, int, long))
|
|
{
|
|
assert(to!string(Int(0)) == "0");
|
|
assert(to!string(Int(9)) == "9");
|
|
assert(to!string(Int(123)) == "123");
|
|
assert(to!string(Int(-0)) == "0");
|
|
assert(to!string(Int(-9)) == "-9");
|
|
assert(to!string(Int(-123)) == "-123");
|
|
assert(to!string(const(Int)(6)) == "6");
|
|
}
|
|
|
|
assert(wtext(int.max) == "2147483647"w);
|
|
assert(wtext(int.min) == "-2147483648"w);
|
|
assert(to!string(0L) == "0");
|
|
|
|
assertCTFEable!(
|
|
{
|
|
assert(to!string(1uL << 62) == "4611686018427387904");
|
|
assert(to!string(0x100000000) == "4294967296");
|
|
assert(to!string(-138L) == "-138");
|
|
});
|
|
}
|
|
|
|
@safe pure /+nothrow+/ unittest
|
|
{
|
|
// Conversion representing dynamic/static array with string
|
|
long[] b = [ 1, 3, 5 ];
|
|
auto s = to!string(b);
|
|
assert(to!string(b) == "[1, 3, 5]", s);
|
|
}
|
|
/*@safe pure */unittest // sprintf issue
|
|
{
|
|
double[2] a = [ 1.5, 2.5 ];
|
|
assert(to!string(a) == "[1.5, 2.5]");
|
|
}
|
|
|
|
/*@safe pure */unittest
|
|
{
|
|
// Conversion representing associative array with string
|
|
int[string] a = ["0":1, "1":2];
|
|
assert(to!string(a) == `["0":1, "1":2]` ||
|
|
to!string(a) == `["1":2, "0":1]`);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// Conversion representing class object with string
|
|
class A
|
|
{
|
|
override string toString() const { return "an A"; }
|
|
}
|
|
A a;
|
|
assert(to!string(a) == "null");
|
|
a = new A;
|
|
assert(to!string(a) == "an A");
|
|
|
|
// Bug 7660
|
|
class C { override string toString() const { return "C"; } }
|
|
struct S { C c; alias c this; }
|
|
S s; s.c = new C();
|
|
assert(to!string(s) == "C");
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// Conversion representing struct object with string
|
|
struct S1
|
|
{
|
|
string toString() { return "wyda"; }
|
|
}
|
|
assert(to!string(S1()) == "wyda");
|
|
|
|
struct S2
|
|
{
|
|
int a = 42;
|
|
float b = 43.5;
|
|
}
|
|
S2 s2;
|
|
assert(to!string(s2) == "S2(42, 43.5)");
|
|
|
|
// Test for issue 8080
|
|
struct S8080
|
|
{
|
|
short[4] data;
|
|
alias data this;
|
|
string toString() { return "<S>"; }
|
|
}
|
|
S8080 s8080;
|
|
assert(to!string(s8080) == "<S>");
|
|
}
|
|
|
|
/+nothrow+/ unittest
|
|
{
|
|
// Conversion representing enum value with string
|
|
enum EB : bool { a = true }
|
|
enum EU : uint { a = 0, b = 1, c = 2 } // base type is unsigned
|
|
enum EI : int { a = -1, b = 0, c = 1 } // base type is signed (bug 7909)
|
|
enum EF : real { a = 1.414, b = 1.732, c = 2.236 }
|
|
enum EC : char { a = 'x', b = 'y' }
|
|
enum ES : string { a = "aaa", b = "bbb" }
|
|
|
|
foreach (E; TypeTuple!(EB, EU, EI, EF, EC, ES))
|
|
{
|
|
assert(to! string(E.a) == "a"c);
|
|
assert(to!wstring(E.a) == "a"w);
|
|
assert(to!dstring(E.a) == "a"d);
|
|
}
|
|
|
|
// Test an value not corresponding to an enum member.
|
|
auto o = cast(EU)5;
|
|
assert(to! string(o) == "cast(EU)5"c);
|
|
assert(to!wstring(o) == "cast(EU)5"w);
|
|
assert(to!dstring(o) == "cast(EU)5"d);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
enum E
|
|
{
|
|
foo,
|
|
doo = foo, // check duplicate switch statements
|
|
bar,
|
|
}
|
|
|
|
//Test regression 12494
|
|
assert(to!string(E.foo) == "foo");
|
|
assert(to!string(E.doo) == "foo");
|
|
assert(to!string(E.bar) == "bar");
|
|
|
|
foreach (S; TypeTuple!(string, wstring, dstring, const(char[]), const(wchar[]), const(dchar[])))
|
|
{
|
|
auto s1 = to!S(E.foo);
|
|
auto s2 = to!S(E.foo);
|
|
assert(s1 == s2);
|
|
// ensure we don't allocate when it's unnecessary
|
|
assert(s1 is s2);
|
|
}
|
|
|
|
foreach (S; TypeTuple!(char[], wchar[], dchar[]))
|
|
{
|
|
auto s1 = to!S(E.foo);
|
|
auto s2 = to!S(E.foo);
|
|
assert(s1 == s2);
|
|
// ensure each mutable array is unique
|
|
assert(s1 !is s2);
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
@trusted pure T toImpl(T, S)(S value, uint radix, LetterCase letterCase = LetterCase.upper)
|
|
if (isIntegral!S &&
|
|
isExactSomeString!T)
|
|
in
|
|
{
|
|
assert(radix >= 2 && radix <= 36);
|
|
}
|
|
body
|
|
{
|
|
alias EEType = Unqual!(ElementEncodingType!T);
|
|
|
|
T toStringRadixConvert(size_t bufLen, uint radix = 0, bool neg = false)(uint runtimeRadix = 0)
|
|
{
|
|
static if (neg)
|
|
ulong div = void, mValue = unsigned(-value);
|
|
else
|
|
Unsigned!(Unqual!S) div = void, mValue = unsigned(value);
|
|
|
|
size_t index = bufLen;
|
|
EEType[bufLen] buffer = void;
|
|
char baseChar = letterCase == LetterCase.lower ? 'a' : 'A';
|
|
char mod = void;
|
|
|
|
do
|
|
{
|
|
static if (radix == 0)
|
|
{
|
|
div = cast(S)(mValue / runtimeRadix );
|
|
mod = cast(ubyte)(mValue % runtimeRadix);
|
|
mod += mod < 10 ? '0' : baseChar - 10;
|
|
}
|
|
else static if (radix > 10)
|
|
{
|
|
div = cast(S)(mValue / radix );
|
|
mod = cast(ubyte)(mValue % radix);
|
|
mod += mod < 10 ? '0' : baseChar - 10;
|
|
}
|
|
else
|
|
{
|
|
div = cast(S)(mValue / radix);
|
|
mod = mValue % radix + '0';
|
|
}
|
|
buffer[--index] = cast(char)mod;
|
|
mValue = div;
|
|
} while (mValue);
|
|
|
|
static if (neg)
|
|
{
|
|
buffer[--index] = '-';
|
|
}
|
|
return cast(T)buffer[index .. $].dup;
|
|
}
|
|
|
|
switch(radix)
|
|
{
|
|
case 10:
|
|
if (value < 0)
|
|
return toStringRadixConvert!(S.sizeof * 3 + 1, 10, true)();
|
|
else
|
|
return toStringRadixConvert!(S.sizeof * 3, 10)();
|
|
case 16:
|
|
return toStringRadixConvert!(S.sizeof * 2, 16)();
|
|
case 2:
|
|
return toStringRadixConvert!(S.sizeof * 8, 2)();
|
|
case 8:
|
|
return toStringRadixConvert!(S.sizeof * 3, 8)();
|
|
default:
|
|
return toStringRadixConvert!(S.sizeof * 6)(radix);
|
|
}
|
|
}
|
|
|
|
@safe pure nothrow unittest
|
|
{
|
|
foreach (Int; TypeTuple!(uint, ulong))
|
|
{
|
|
assert(to!string(Int(16), 16) == "10");
|
|
assert(to!string(Int(15), 2u) == "1111");
|
|
assert(to!string(Int(1), 2u) == "1");
|
|
assert(to!string(Int(0x1234AF), 16u) == "1234AF");
|
|
assert(to!string(Int(0x1234BCD), 16u, LetterCase.upper) == "1234BCD");
|
|
assert(to!string(Int(0x1234AF), 16u, LetterCase.lower) == "1234af");
|
|
}
|
|
|
|
foreach (Int; TypeTuple!(int, long))
|
|
{
|
|
assert(to!string(Int(-10), 10u) == "-10");
|
|
}
|
|
|
|
assert(to!string(byte(-10), 16) == "F6");
|
|
assert(to!string(long.min) == "-9223372036854775808");
|
|
assert(to!string(long.max) == "9223372036854775807");
|
|
}
|
|
|
|
|
|
/**
|
|
Narrowing numeric-numeric conversions throw when the value does not
|
|
fit in the narrower type.
|
|
*/
|
|
T toImpl(T, S)(S value)
|
|
if (!isImplicitlyConvertible!(S, T) &&
|
|
(isNumeric!S || isSomeChar!S || isBoolean!S) &&
|
|
(isNumeric!T || isSomeChar!T || isBoolean!T) && !is(T == enum))
|
|
{
|
|
enum sSmallest = mostNegative!S;
|
|
enum tSmallest = mostNegative!T;
|
|
static if (sSmallest < 0)
|
|
{
|
|
// possible underflow converting from a signed
|
|
static if (tSmallest == 0)
|
|
{
|
|
immutable good = value >= 0;
|
|
}
|
|
else
|
|
{
|
|
static assert(tSmallest < 0);
|
|
immutable good = value >= tSmallest;
|
|
}
|
|
if (!good)
|
|
throw new ConvOverflowException("Conversion negative overflow");
|
|
}
|
|
static if (S.max > T.max)
|
|
{
|
|
// possible overflow
|
|
if (value > T.max)
|
|
throw new ConvOverflowException("Conversion positive overflow");
|
|
}
|
|
return (ref value)@trusted{ return cast(T) value; }(value);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
|
|
dchar a = ' ';
|
|
assert(to!char(a) == ' ');
|
|
a = 300;
|
|
assert(collectException(to!char(a)));
|
|
|
|
dchar from0 = 'A';
|
|
char to0 = to!char(from0);
|
|
|
|
wchar from1 = 'A';
|
|
char to1 = to!char(from1);
|
|
|
|
char from2 = 'A';
|
|
char to2 = to!char(from2);
|
|
|
|
char from3 = 'A';
|
|
wchar to3 = to!wchar(from3);
|
|
|
|
char from4 = 'A';
|
|
dchar to4 = to!dchar(from4);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.exception;
|
|
|
|
// Narrowing conversions from enum -> integral should be allowed, but they
|
|
// should throw at runtime if the enum value doesn't fit in the target
|
|
// type.
|
|
enum E1 : ulong { A = 1, B = 1UL<<48, C = 0 }
|
|
assert(to!int(E1.A) == 1);
|
|
assert(to!bool(E1.A) == true);
|
|
assertThrown!ConvOverflowException(to!int(E1.B)); // E1.B overflows int
|
|
assertThrown!ConvOverflowException(to!bool(E1.B)); // E1.B overflows bool
|
|
assert(to!bool(E1.C) == false);
|
|
|
|
enum E2 : long { A = -1L<<48, B = -1<<31, C = 1<<31 }
|
|
assertThrown!ConvOverflowException(to!int(E2.A)); // E2.A overflows int
|
|
assertThrown!ConvOverflowException(to!uint(E2.B)); // E2.B overflows uint
|
|
assert(to!int(E2.B) == -1<<31); // but does not overflow int
|
|
assert(to!int(E2.C) == 1<<31); // E2.C does not overflow int
|
|
|
|
enum E3 : int { A = -1, B = 1, C = 255, D = 0 }
|
|
assertThrown!ConvOverflowException(to!ubyte(E3.A));
|
|
assertThrown!ConvOverflowException(to!bool(E3.A));
|
|
assert(to!byte(E3.A) == -1);
|
|
assert(to!byte(E3.B) == 1);
|
|
assert(to!ubyte(E3.C) == 255);
|
|
assert(to!bool(E3.B) == true);
|
|
assertThrown!ConvOverflowException(to!byte(E3.C));
|
|
assertThrown!ConvOverflowException(to!bool(E3.C));
|
|
assert(to!bool(E3.D) == false);
|
|
|
|
}
|
|
|
|
/**
|
|
Array-to-array conversion (except when target is a string type)
|
|
converts each element in turn by using $(D to).
|
|
*/
|
|
T toImpl(T, S)(S value)
|
|
if (!isImplicitlyConvertible!(S, T) &&
|
|
!isSomeString!S && isDynamicArray!S &&
|
|
!isExactSomeString!T && isArray!T)
|
|
{
|
|
alias E = typeof(T.init[0]);
|
|
|
|
static if (isStaticArray!T)
|
|
{
|
|
import std.exception : enforce;
|
|
auto res = to!(E[])(value);
|
|
enforce!ConvException(T.length == res.length,
|
|
convFormat("Length mismatch when converting to static array: %s vs %s", T.length, res.length));
|
|
return res[0 .. T.length];
|
|
}
|
|
else
|
|
{
|
|
import std.array : appender;
|
|
auto w = appender!(E[])();
|
|
w.reserve(value.length);
|
|
foreach (i, ref e; value)
|
|
{
|
|
w.put(to!E(e));
|
|
}
|
|
return w.data;
|
|
}
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
|
|
// array to array conversions
|
|
uint[] a = [ 1u, 2, 3 ];
|
|
auto b = to!(float[])(a);
|
|
assert(b == [ 1.0f, 2, 3 ]);
|
|
|
|
//auto c = to!(string[])(b);
|
|
//assert(c[0] == "1" && c[1] == "2" && c[2] == "3");
|
|
|
|
immutable(int)[3] d = [ 1, 2, 3 ];
|
|
b = to!(float[])(d);
|
|
assert(b == [ 1.0f, 2, 3 ]);
|
|
|
|
uint[][] e = [ a, a ];
|
|
auto f = to!(float[][])(e);
|
|
assert(f[0] == b && f[1] == b);
|
|
|
|
// Test for bug 8264
|
|
struct Wrap
|
|
{
|
|
string wrap;
|
|
alias wrap this;
|
|
}
|
|
Wrap[] warr = to!(Wrap[])(["foo", "bar"]); // should work
|
|
|
|
// Issue 12633
|
|
import std.conv : to;
|
|
const s2 = ["10", "20"];
|
|
|
|
immutable int[2] a3 = s2.to!(int[2]);
|
|
assert(a3 == [10, 20]);
|
|
|
|
// verify length mismatches are caught
|
|
immutable s4 = [1, 2, 3, 4];
|
|
foreach (i; [1, 4])
|
|
{
|
|
auto ex = collectException(s4[0 .. i].to!(int[2]));
|
|
assert(ex && ex.msg == "Length mismatch when converting to static array: 2 vs " ~ [cast(char)(i + '0')],
|
|
ex ? ex.msg : "Exception was not thrown!");
|
|
}
|
|
}
|
|
/*@safe pure */unittest
|
|
{
|
|
auto b = [ 1.0f, 2, 3 ];
|
|
|
|
auto c = to!(string[])(b);
|
|
assert(c[0] == "1" && c[1] == "2" && c[2] == "3");
|
|
}
|
|
|
|
/**
|
|
Associative array to associative array conversion converts each key
|
|
and each value in turn.
|
|
*/
|
|
T toImpl(T, S)(S value)
|
|
if (isAssociativeArray!S &&
|
|
isAssociativeArray!T && !is(T == enum))
|
|
{
|
|
/* This code is potentially unsafe.
|
|
*/
|
|
alias K2 = KeyType!T;
|
|
alias V2 = ValueType!T;
|
|
|
|
// While we are "building" the AA, we need to unqualify its values, and only re-qualify at the end
|
|
Unqual!V2[K2] result;
|
|
|
|
foreach (k1, v1; value)
|
|
{
|
|
// Cast values temporarily to Unqual!V2 to store them to result variable
|
|
result[to!K2(k1)] = cast(Unqual!V2) to!V2(v1);
|
|
}
|
|
// Cast back to original type
|
|
return cast(T)result;
|
|
}
|
|
|
|
@safe /*pure */unittest
|
|
{
|
|
// hash to hash conversions
|
|
int[string] a;
|
|
a["0"] = 1;
|
|
a["1"] = 2;
|
|
auto b = to!(double[dstring])(a);
|
|
assert(b["0"d] == 1 && b["1"d] == 2);
|
|
}
|
|
@safe /*pure */unittest // Bugzilla 8705, from doc
|
|
{
|
|
import std.exception;
|
|
int[string][double[int[]]] a;
|
|
auto b = to!(short[wstring][string[double[]]])(a);
|
|
a = [null:["hello":int.max]];
|
|
assertThrown!ConvOverflowException(to!(short[wstring][string[double[]]])(a));
|
|
}
|
|
unittest // Extra cases for AA with qualifiers conversion
|
|
{
|
|
int[][int[]] a;// = [[], []];
|
|
auto b = to!(immutable(short[])[immutable short[]])(a);
|
|
|
|
double[dstring][int[long[]]] c;
|
|
auto d = to!(immutable(short[immutable wstring])[immutable string[double[]]])(c);
|
|
}
|
|
|
|
private void testIntegralToFloating(Integral, Floating)()
|
|
{
|
|
Integral a = 42;
|
|
auto b = to!Floating(a);
|
|
assert(a == b);
|
|
assert(a == to!Integral(b));
|
|
}
|
|
|
|
private void testFloatingToIntegral(Floating, Integral)()
|
|
{
|
|
bool convFails(Source, Target, E)(Source src)
|
|
{
|
|
try
|
|
auto t = to!Target(src);
|
|
catch (E)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// convert some value
|
|
Floating a = 4.2e1;
|
|
auto b = to!Integral(a);
|
|
assert(is(typeof(b) == Integral) && b == 42);
|
|
// convert some negative value (if applicable)
|
|
a = -4.2e1;
|
|
static if (Integral.min < 0)
|
|
{
|
|
b = to!Integral(a);
|
|
assert(is(typeof(b) == Integral) && b == -42);
|
|
}
|
|
else
|
|
{
|
|
// no go for unsigned types
|
|
assert(convFails!(Floating, Integral, ConvOverflowException)(a));
|
|
}
|
|
// convert to the smallest integral value
|
|
a = 0.0 + Integral.min;
|
|
static if (Integral.min < 0)
|
|
{
|
|
a = -a; // -Integral.min not representable as an Integral
|
|
assert(convFails!(Floating, Integral, ConvOverflowException)(a)
|
|
|| Floating.sizeof <= Integral.sizeof);
|
|
}
|
|
a = 0.0 + Integral.min;
|
|
assert(to!Integral(a) == Integral.min);
|
|
--a; // no more representable as an Integral
|
|
assert(convFails!(Floating, Integral, ConvOverflowException)(a)
|
|
|| Floating.sizeof <= Integral.sizeof);
|
|
a = 0.0 + Integral.max;
|
|
// fwritefln(stderr, "%s a=%g, %s conv=%s", Floating.stringof, a,
|
|
// Integral.stringof, to!Integral(a));
|
|
assert(to!Integral(a) == Integral.max || Floating.sizeof <= Integral.sizeof);
|
|
++a; // no more representable as an Integral
|
|
assert(convFails!(Floating, Integral, ConvOverflowException)(a)
|
|
|| Floating.sizeof <= Integral.sizeof);
|
|
// convert a value with a fractional part
|
|
a = 3.14;
|
|
assert(to!Integral(a) == 3);
|
|
a = 3.99;
|
|
assert(to!Integral(a) == 3);
|
|
static if (Integral.min < 0)
|
|
{
|
|
a = -3.14;
|
|
assert(to!Integral(a) == -3);
|
|
a = -3.99;
|
|
assert(to!Integral(a) == -3);
|
|
}
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
alias AllInts = TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong);
|
|
alias AllFloats = TypeTuple!(float, double, real);
|
|
alias AllNumerics = TypeTuple!(AllInts, AllFloats);
|
|
// test with same type
|
|
{
|
|
foreach (T; AllNumerics)
|
|
{
|
|
T a = 42;
|
|
auto b = to!T(a);
|
|
assert(is(typeof(a) == typeof(b)) && a == b);
|
|
}
|
|
}
|
|
// test that floating-point numbers convert properly to largest ints
|
|
// see http://oregonstate.edu/~peterseb/mth351/docs/351s2001_fp80x87.html
|
|
// look for "largest fp integer with a predecessor"
|
|
{
|
|
// float
|
|
int a = 16_777_215; // 2^24 - 1
|
|
assert(to!int(to!float(a)) == a);
|
|
assert(to!int(to!float(-a)) == -a);
|
|
// double
|
|
long b = 9_007_199_254_740_991; // 2^53 - 1
|
|
assert(to!long(to!double(b)) == b);
|
|
assert(to!long(to!double(-b)) == -b);
|
|
// real
|
|
// @@@ BUG IN COMPILER @@@
|
|
// ulong c = 18_446_744_073_709_551_615UL; // 2^64 - 1
|
|
// assert(to!ulong(to!real(c)) == c);
|
|
// assert(to!ulong(-to!real(c)) == c);
|
|
}
|
|
// test conversions floating => integral
|
|
{
|
|
// AllInts[0 .. $ - 1] should be AllInts
|
|
// @@@ BUG IN COMPILER @@@
|
|
foreach (Integral; AllInts[0 .. $ - 1])
|
|
{
|
|
foreach (Floating; AllFloats)
|
|
{
|
|
testFloatingToIntegral!(Floating, Integral)();
|
|
}
|
|
}
|
|
}
|
|
// test conversion integral => floating
|
|
{
|
|
foreach (Integral; AllInts[0 .. $ - 1])
|
|
{
|
|
foreach (Floating; AllFloats)
|
|
{
|
|
testIntegralToFloating!(Integral, Floating)();
|
|
}
|
|
}
|
|
}
|
|
// test parsing
|
|
{
|
|
foreach (T; AllNumerics)
|
|
{
|
|
// from type immutable(char)[2]
|
|
auto a = to!T("42");
|
|
assert(a == 42);
|
|
// from type char[]
|
|
char[] s1 = "42".dup;
|
|
a = to!T(s1);
|
|
assert(a == 42);
|
|
// from type char[2]
|
|
char[2] s2;
|
|
s2[] = "42";
|
|
a = to!T(s2);
|
|
assert(a == 42);
|
|
// from type immutable(wchar)[2]
|
|
a = to!T("42"w);
|
|
assert(a == 42);
|
|
}
|
|
}
|
|
}
|
|
/*@safe pure */unittest
|
|
{
|
|
alias AllInts = TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong);
|
|
alias AllFloats = TypeTuple!(float, double, real);
|
|
alias AllNumerics = TypeTuple!(AllInts, AllFloats);
|
|
// test conversions to string
|
|
{
|
|
foreach (T; AllNumerics)
|
|
{
|
|
T a = 42;
|
|
assert(to!string(a) == "42");
|
|
//assert(to!wstring(a) == "42"w);
|
|
//assert(to!dstring(a) == "42"d);
|
|
// array test
|
|
// T[] b = new T[2];
|
|
// b[0] = 42;
|
|
// b[1] = 33;
|
|
// assert(to!string(b) == "[42,33]");
|
|
}
|
|
}
|
|
// test array to string conversion
|
|
foreach (T ; AllNumerics)
|
|
{
|
|
auto a = [to!T(1), 2, 3];
|
|
assert(to!string(a) == "[1, 2, 3]");
|
|
}
|
|
// test enum to int conversion
|
|
// enum Testing { Test1, Test2 };
|
|
// Testing t;
|
|
// auto a = to!string(t);
|
|
// assert(a == "0");
|
|
}
|
|
|
|
|
|
/**
|
|
String to non-string conversion runs parsing.
|
|
$(UL
|
|
$(LI When the source is a wide string, it is first converted to a narrow
|
|
string and then parsed.)
|
|
$(LI When the source is a narrow string, normal text parsing occurs.))
|
|
*/
|
|
T toImpl(T, S)(S value)
|
|
if ( isExactSomeString!S && isDynamicArray!S &&
|
|
!isExactSomeString!T && is(typeof(parse!T(value))))
|
|
{
|
|
scope(success)
|
|
{
|
|
if (value.length)
|
|
{
|
|
throw convError!(S, T)(value);
|
|
}
|
|
}
|
|
return parse!T(value);
|
|
}
|
|
|
|
/// ditto
|
|
T toImpl(T, S)(S value, uint radix)
|
|
if ( isExactSomeString!S && isDynamicArray!S &&
|
|
!isExactSomeString!T && is(typeof(parse!T(value, radix))))
|
|
{
|
|
scope(success)
|
|
{
|
|
if (value.length)
|
|
{
|
|
throw convError!(S, T)(value);
|
|
}
|
|
}
|
|
return parse!T(value, radix);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
// Issue 6668 - ensure no collaterals thrown
|
|
try { to!uint("-1"); }
|
|
catch (ConvException e) { assert(e.next is null); }
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
foreach (Str; TypeTuple!(string, wstring, dstring))
|
|
{
|
|
Str a = "123";
|
|
assert(to!int(a) == 123);
|
|
assert(to!double(a) == 123);
|
|
}
|
|
|
|
// 6255
|
|
auto n = to!int("FF", 16);
|
|
assert(n == 255);
|
|
}
|
|
|
|
/**
|
|
Convert a value that is implicitly convertible to the enum base type
|
|
into an Enum value. If the value does not match any enum member values
|
|
a ConvException is thrown.
|
|
Enums with floating-point or string base types are not supported.
|
|
*/
|
|
T toImpl(T, S)(S value)
|
|
if (is(T == enum) && !is(S == enum)
|
|
&& is(typeof(value == OriginalType!T.init))
|
|
&& !isFloatingPoint!(OriginalType!T) && !isSomeString!(OriginalType!T))
|
|
{
|
|
foreach (Member; EnumMembers!T)
|
|
{
|
|
if (Member == value)
|
|
return Member;
|
|
}
|
|
throw new ConvException(convFormat("Value (%s) does not match any member value of enum '%s'", value, T.stringof));
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
enum En8143 : int { A = 10, B = 20, C = 30, D = 20 }
|
|
enum En8143[][] m3 = to!(En8143[][])([[10, 30], [30, 10]]);
|
|
static assert(m3 == [[En8143.A, En8143.C], [En8143.C, En8143.A]]);
|
|
|
|
En8143 en1 = to!En8143(10);
|
|
assert(en1 == En8143.A);
|
|
assertThrown!ConvException(to!En8143(5)); // matches none
|
|
En8143[][] m1 = to!(En8143[][])([[10, 30], [30, 10]]);
|
|
assert(m1 == [[En8143.A, En8143.C], [En8143.C, En8143.A]]);
|
|
}
|
|
|
|
/***************************************************************
|
|
Rounded conversion from floating point to integral.
|
|
|
|
Rounded conversions do not work with non-integral target types.
|
|
*/
|
|
|
|
template roundTo(Target)
|
|
{
|
|
Target roundTo(Source)(Source value)
|
|
{
|
|
import std.math : trunc;
|
|
|
|
static assert(isFloatingPoint!Source);
|
|
static assert(isIntegral!Target);
|
|
return to!Target(trunc(value + (value < 0 ? -0.5L : 0.5L)));
|
|
}
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
assert(roundTo!int(3.14) == 3);
|
|
assert(roundTo!int(3.49) == 3);
|
|
assert(roundTo!int(3.5) == 4);
|
|
assert(roundTo!int(3.999) == 4);
|
|
assert(roundTo!int(-3.14) == -3);
|
|
assert(roundTo!int(-3.49) == -3);
|
|
assert(roundTo!int(-3.5) == -4);
|
|
assert(roundTo!int(-3.999) == -4);
|
|
assert(roundTo!(const int)(to!(const double)(-3.999)) == -4);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.exception;
|
|
// boundary values
|
|
foreach (Int; TypeTuple!(byte, ubyte, short, ushort, int, uint))
|
|
{
|
|
assert(roundTo!Int(Int.min - 0.4L) == Int.min);
|
|
assert(roundTo!Int(Int.max + 0.4L) == Int.max);
|
|
assertThrown!ConvOverflowException(roundTo!Int(Int.min - 0.5L));
|
|
assertThrown!ConvOverflowException(roundTo!Int(Int.max + 0.5L));
|
|
}
|
|
}
|
|
|
|
/***************************************************************
|
|
* The $(D_PARAM parse) family of functions works quite like the
|
|
* $(D_PARAM to) family, except that (1) it only works with character ranges
|
|
* as input, (2) takes the input by reference and advances it to
|
|
* the position following the conversion, and (3) does not throw if it
|
|
* could not convert the entire input. It still throws if an overflow
|
|
* occurred during conversion or if no character of the input
|
|
* was meaningfully converted.
|
|
*/
|
|
Target parse(Target, Source)(ref Source s)
|
|
if (isInputRange!Source &&
|
|
isSomeChar!(ElementType!Source) &&
|
|
is(Unqual!Target == bool))
|
|
{
|
|
import std.ascii : toLower;
|
|
if (!s.empty)
|
|
{
|
|
auto c1 = toLower(s.front);
|
|
bool result = (c1 == 't');
|
|
if (result || c1 == 'f')
|
|
{
|
|
s.popFront();
|
|
foreach (c; result ? "rue" : "alse")
|
|
{
|
|
if (s.empty || toLower(s.front) != c)
|
|
goto Lerr;
|
|
s.popFront();
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
Lerr:
|
|
throw parseError("bool should be case-insensitive 'true' or 'false'");
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
import std.string : munch;
|
|
string test = "123 \t 76.14";
|
|
auto a = parse!uint(test);
|
|
assert(a == 123);
|
|
assert(test == " \t 76.14"); // parse bumps string
|
|
munch(test, " \t\n\r"); // skip ws
|
|
assert(test == "76.14");
|
|
auto b = parse!double(test);
|
|
assert(b == 76.14);
|
|
assert(test == "");
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.exception;
|
|
import std.algorithm : equal;
|
|
struct InputString
|
|
{
|
|
string _s;
|
|
@property auto front() { return _s.front; }
|
|
@property bool empty() { return _s.empty; }
|
|
void popFront() { _s.popFront(); }
|
|
}
|
|
|
|
auto s = InputString("trueFALSETrueFalsetRUEfALSE");
|
|
assert(parse!bool(s) == true);
|
|
assert(s.equal("FALSETrueFalsetRUEfALSE"));
|
|
assert(parse!bool(s) == false);
|
|
assert(s.equal("TrueFalsetRUEfALSE"));
|
|
assert(parse!bool(s) == true);
|
|
assert(s.equal("FalsetRUEfALSE"));
|
|
assert(parse!bool(s) == false);
|
|
assert(s.equal("tRUEfALSE"));
|
|
assert(parse!bool(s) == true);
|
|
assert(s.equal("fALSE"));
|
|
assert(parse!bool(s) == false);
|
|
assert(s.empty);
|
|
|
|
foreach (ss; ["tfalse", "ftrue", "t", "f", "tru", "fals", ""])
|
|
{
|
|
s = InputString(ss);
|
|
assertThrown!ConvException(parse!bool(s));
|
|
}
|
|
}
|
|
|
|
Target parse(Target, Source)(ref Source s)
|
|
if (isSomeChar!(ElementType!Source) &&
|
|
isIntegral!Target && !is(Target == enum))
|
|
{
|
|
static if (Target.sizeof < int.sizeof)
|
|
{
|
|
// smaller types are handled like integers
|
|
auto v = .parse!(Select!(Target.min < 0, int, uint))(s);
|
|
auto result = ()@trusted{ return cast(Target) v; }();
|
|
if (result == v)
|
|
return result;
|
|
throw new ConvOverflowException("Overflow in integral conversion");
|
|
}
|
|
else
|
|
{
|
|
// Larger than int types
|
|
|
|
static if (Target.min < 0)
|
|
bool sign = 0;
|
|
else
|
|
enum bool sign = 0;
|
|
|
|
enum char maxLastDigit = Target.min < 0 ? 7 : 5;
|
|
Unqual!(typeof(s.front)) c;
|
|
|
|
if (s.empty)
|
|
goto Lerr;
|
|
|
|
c = s.front;
|
|
s.popFront();
|
|
static if (Target.min < 0)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '-':
|
|
sign = true;
|
|
goto case '+';
|
|
case '+':
|
|
if (s.empty)
|
|
goto Lerr;
|
|
c = s.front;
|
|
s.popFront();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
c -= '0';
|
|
if (c <= 9)
|
|
{
|
|
Target v = cast(Target)c;
|
|
while (!s.empty)
|
|
{
|
|
c = cast(typeof(c)) (s.front - '0');
|
|
if (c > 9)
|
|
break;
|
|
|
|
if (v >= 0 && (v < Target.max/10 ||
|
|
(v == Target.max/10 && c <= maxLastDigit + sign)))
|
|
{
|
|
// Note: `v` can become negative here in case of parsing
|
|
// the most negative value:
|
|
v = cast(Target) (v * 10 + c);
|
|
s.popFront();
|
|
}
|
|
else
|
|
throw new ConvOverflowException("Overflow in integral conversion");
|
|
}
|
|
|
|
if (sign)
|
|
v = -v;
|
|
return v;
|
|
}
|
|
Lerr:
|
|
throw convError!(Source, Target)(s);
|
|
}
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
string s = "123";
|
|
auto a = parse!int(s);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
foreach (Int; TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong))
|
|
{
|
|
{
|
|
assert(to!Int("0") == 0);
|
|
|
|
static if (isSigned!Int)
|
|
{
|
|
assert(to!Int("+0") == 0);
|
|
assert(to!Int("-0") == 0);
|
|
}
|
|
}
|
|
|
|
static if (Int.sizeof >= byte.sizeof)
|
|
{
|
|
assert(to!Int("6") == 6);
|
|
assert(to!Int("23") == 23);
|
|
assert(to!Int("68") == 68);
|
|
assert(to!Int("127") == 0x7F);
|
|
|
|
static if (isUnsigned!Int)
|
|
{
|
|
assert(to!Int("255") == 0xFF);
|
|
}
|
|
static if (isSigned!Int)
|
|
{
|
|
assert(to!Int("+6") == 6);
|
|
assert(to!Int("+23") == 23);
|
|
assert(to!Int("+68") == 68);
|
|
assert(to!Int("+127") == 0x7F);
|
|
|
|
assert(to!Int("-6") == -6);
|
|
assert(to!Int("-23") == -23);
|
|
assert(to!Int("-68") == -68);
|
|
assert(to!Int("-128") == -128);
|
|
}
|
|
}
|
|
|
|
static if (Int.sizeof >= short.sizeof)
|
|
{
|
|
assert(to!Int("468") == 468);
|
|
assert(to!Int("32767") == 0x7FFF);
|
|
|
|
static if (isUnsigned!Int)
|
|
{
|
|
assert(to!Int("65535") == 0xFFFF);
|
|
}
|
|
static if (isSigned!Int)
|
|
{
|
|
assert(to!Int("+468") == 468);
|
|
assert(to!Int("+32767") == 0x7FFF);
|
|
|
|
assert(to!Int("-468") == -468);
|
|
assert(to!Int("-32768") == -32768);
|
|
}
|
|
}
|
|
|
|
static if (Int.sizeof >= int.sizeof)
|
|
{
|
|
assert(to!Int("2147483647") == 0x7FFFFFFF);
|
|
|
|
static if (isUnsigned!Int)
|
|
{
|
|
assert(to!Int("4294967295") == 0xFFFFFFFF);
|
|
}
|
|
|
|
static if (isSigned!Int)
|
|
{
|
|
assert(to!Int("+2147483647") == 0x7FFFFFFF);
|
|
|
|
assert(to!Int("-2147483648") == -2147483648);
|
|
}
|
|
}
|
|
|
|
static if (Int.sizeof >= long.sizeof)
|
|
{
|
|
assert(to!Int("9223372036854775807") == 0x7FFFFFFFFFFFFFFF);
|
|
|
|
static if (isUnsigned!Int)
|
|
{
|
|
assert(to!Int("18446744073709551615") == 0xFFFFFFFFFFFFFFFF);
|
|
}
|
|
|
|
static if (isSigned!Int)
|
|
{
|
|
assert(to!Int("+9223372036854775807") == 0x7FFFFFFFFFFFFFFF);
|
|
|
|
assert(to!Int("-9223372036854775808") == 0x8000000000000000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
// parsing error check
|
|
foreach (Int; TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong))
|
|
{
|
|
{
|
|
immutable string[] errors1 =
|
|
[
|
|
"",
|
|
"-",
|
|
"+",
|
|
"-+",
|
|
" ",
|
|
" 0",
|
|
"0 ",
|
|
"- 0",
|
|
"1-",
|
|
"xx",
|
|
"123h",
|
|
"-+1",
|
|
"--1",
|
|
"+-1",
|
|
"++1",
|
|
];
|
|
foreach (j, s; errors1)
|
|
assertThrown!ConvException(to!Int(s));
|
|
}
|
|
|
|
// parse!SomeUnsigned cannot parse head sign.
|
|
static if (isUnsigned!Int)
|
|
{
|
|
immutable string[] errors2 =
|
|
[
|
|
"+5",
|
|
"-78",
|
|
];
|
|
foreach (j, s; errors2)
|
|
assertThrown!ConvException(to!Int(s));
|
|
}
|
|
}
|
|
|
|
// positive overflow check
|
|
foreach (i, Int; TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong))
|
|
{
|
|
immutable string[] errors =
|
|
[
|
|
"128", // > byte.max
|
|
"256", // > ubyte.max
|
|
"32768", // > short.max
|
|
"65536", // > ushort.max
|
|
"2147483648", // > int.max
|
|
"4294967296", // > uint.max
|
|
"9223372036854775808", // > long.max
|
|
"18446744073709551616", // > ulong.max
|
|
];
|
|
foreach (j, s; errors[i..$])
|
|
assertThrown!ConvOverflowException(to!Int(s));
|
|
}
|
|
|
|
// negative overflow check
|
|
foreach (i, Int; TypeTuple!(byte, short, int, long))
|
|
{
|
|
immutable string[] errors =
|
|
[
|
|
"-129", // < byte.min
|
|
"-32769", // < short.min
|
|
"-2147483649", // < int.min
|
|
"-9223372036854775809", // < long.min
|
|
];
|
|
foreach (j, s; errors[i..$])
|
|
assertThrown!ConvOverflowException(to!Int(s));
|
|
}
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
assertCTFEable!({ string s = "1234abc"; assert(parse! int(s) == 1234 && s == "abc"); });
|
|
assertCTFEable!({ string s = "-1234abc"; assert(parse! int(s) == -1234 && s == "abc"); });
|
|
assertCTFEable!({ string s = "1234abc"; assert(parse!uint(s) == 1234 && s == "abc"); });
|
|
}
|
|
|
|
// Issue 13931
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
|
|
assertThrown!ConvOverflowException("-21474836480".to!int());
|
|
assertThrown!ConvOverflowException("-92233720368547758080".to!long());
|
|
}
|
|
|
|
// Issue 14396
|
|
@safe pure unittest
|
|
{
|
|
struct StrInputRange
|
|
{
|
|
this (string s) { str = s; }
|
|
char front() const @property { return str[front_index]; }
|
|
char popFront() { return str[front_index++]; }
|
|
bool empty() const @property { return str.length <= front_index; }
|
|
string str;
|
|
size_t front_index = 0;
|
|
}
|
|
auto input = StrInputRange("777");
|
|
assert(parse!int(input) == 777);
|
|
}
|
|
|
|
/// ditto
|
|
Target parse(Target, Source)(ref Source s, uint radix)
|
|
if (isSomeChar!(ElementType!Source) &&
|
|
isIntegral!Target && !is(Target == enum))
|
|
in
|
|
{
|
|
assert(radix >= 2 && radix <= 36);
|
|
}
|
|
body
|
|
{
|
|
import core.checkedint : mulu, addu;
|
|
if (radix == 10)
|
|
return parse!Target(s);
|
|
|
|
immutable uint beyond = (radix < 10 ? '0' : 'a'-10) + radix;
|
|
|
|
Target v = 0;
|
|
size_t atStart = true;
|
|
|
|
for (; !s.empty; s.popFront())
|
|
{
|
|
uint c = s.front;
|
|
if (c < '0')
|
|
break;
|
|
if (radix < 10)
|
|
{
|
|
if (c >= beyond)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (c > '9')
|
|
{
|
|
c |= 0x20;//poorman's tolower
|
|
if (c < 'a' || c >= beyond)
|
|
break;
|
|
c -= 'a'-10-'0';
|
|
}
|
|
}
|
|
|
|
bool overflow = false;
|
|
auto nextv = v.mulu(radix, overflow).addu(c - '0', overflow);
|
|
if (overflow || nextv > Target.max)
|
|
goto Loverflow;
|
|
v = cast(Target) nextv;
|
|
|
|
atStart = false;
|
|
}
|
|
if (atStart)
|
|
goto Lerr;
|
|
return v;
|
|
|
|
Loverflow:
|
|
throw new ConvOverflowException("Overflow in integral conversion");
|
|
Lerr:
|
|
throw convError!(Source, Target)(s, radix);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
// @@@BUG@@@ the size of China
|
|
// foreach (i; 2..37)
|
|
// {
|
|
// assert(parse!int("0",i) == 0);
|
|
// assert(parse!int("1",i) == 1);
|
|
// assert(parse!byte("10",i) == i);
|
|
// }
|
|
foreach (i; 2..37)
|
|
{
|
|
string s = "0";
|
|
assert(parse!int(s,i) == 0);
|
|
s = "1";
|
|
assert(parse!int(s,i) == 1);
|
|
s = "10";
|
|
assert(parse!byte(s,i) == i);
|
|
}
|
|
// Same @@@BUG@@@ as above
|
|
//assert(parse!int("0011001101101", 2) == 0b0011001101101);
|
|
// assert(parse!int("765",8) == 0765);
|
|
// assert(parse!int("fCDe",16) == 0xfcde);
|
|
auto s = "0011001101101";
|
|
assert(parse!int(s, 2) == 0b0011001101101);
|
|
s = "765";
|
|
assert(parse!int(s, 8) == octal!765);
|
|
s = "fCDe";
|
|
assert(parse!int(s, 16) == 0xfcde);
|
|
|
|
// 6609
|
|
s = "-42";
|
|
assert(parse!int(s, 10) == -42);
|
|
}
|
|
|
|
@safe pure unittest // bugzilla 7302
|
|
{
|
|
import std.range : cycle;
|
|
auto r = cycle("2A!");
|
|
auto u = parse!uint(r, 16);
|
|
assert(u == 42);
|
|
assert(r.front == '!');
|
|
}
|
|
|
|
@safe pure unittest // bugzilla 13163
|
|
{
|
|
import std.exception;
|
|
foreach (s; ["fff", "123"])
|
|
assertThrown!ConvOverflowException(s.parse!ubyte(16));
|
|
}
|
|
|
|
Target parse(Target, Source)(ref Source s)
|
|
if (isExactSomeString!Source &&
|
|
is(Target == enum))
|
|
{
|
|
import std.algorithm : startsWith;
|
|
Target result;
|
|
size_t longest_match = 0;
|
|
|
|
foreach (i, e; EnumMembers!Target)
|
|
{
|
|
auto ident = __traits(allMembers, Target)[i];
|
|
if (longest_match < ident.length && s.startsWith(ident))
|
|
{
|
|
result = e;
|
|
longest_match = ident.length ;
|
|
}
|
|
}
|
|
|
|
if (longest_match > 0)
|
|
{
|
|
s = s[longest_match .. $];
|
|
return result ;
|
|
}
|
|
|
|
throw new ConvException(
|
|
Target.stringof ~ " does not have a member named '"
|
|
~ to!string(s) ~ "'");
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.exception;
|
|
|
|
enum EB : bool { a = true, b = false, c = a }
|
|
enum EU { a, b, c }
|
|
enum EI { a = -1, b = 0, c = 1 }
|
|
enum EF : real { a = 1.414, b = 1.732, c = 2.236 }
|
|
enum EC : char { a = 'a', b = 'b', c = 'c' }
|
|
enum ES : string { a = "aaa", b = "bbb", c = "ccc" }
|
|
|
|
foreach (E; TypeTuple!(EB, EU, EI, EF, EC, ES))
|
|
{
|
|
assert(to!E("a"c) == E.a);
|
|
assert(to!E("b"w) == E.b);
|
|
assert(to!E("c"d) == E.c);
|
|
|
|
assertThrown!ConvException(to!E("d"));
|
|
}
|
|
}
|
|
|
|
@safe pure unittest // bugzilla 4744
|
|
{
|
|
enum A { member1, member11, member111 }
|
|
assert(to!A("member1" ) == A.member1 );
|
|
assert(to!A("member11" ) == A.member11 );
|
|
assert(to!A("member111") == A.member111);
|
|
auto s = "member1111";
|
|
assert(parse!A(s) == A.member111 && s == "1");
|
|
}
|
|
|
|
Target parse(Target, Source)(ref Source p)
|
|
if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) &&
|
|
isFloatingPoint!Target && !is(Target == enum))
|
|
{
|
|
import std.ascii : isDigit, isAlpha, toLower, toUpper, isHexDigit;
|
|
import std.exception : enforce;
|
|
import core.stdc.math : HUGE_VAL;
|
|
|
|
static immutable real[14] negtab =
|
|
[ 1e-4096L,1e-2048L,1e-1024L,1e-512L,1e-256L,1e-128L,1e-64L,1e-32L,
|
|
1e-16L,1e-8L,1e-4L,1e-2L,1e-1L,1.0L ];
|
|
static immutable real[13] postab =
|
|
[ 1e+4096L,1e+2048L,1e+1024L,1e+512L,1e+256L,1e+128L,1e+64L,1e+32L,
|
|
1e+16L,1e+8L,1e+4L,1e+2L,1e+1L ];
|
|
// static immutable string infinity = "infinity";
|
|
// static immutable string nans = "nans";
|
|
|
|
ConvException bailOut()(string msg = null, string fn = __FILE__, size_t ln = __LINE__)
|
|
{
|
|
if (msg == null)
|
|
msg = "Floating point conversion error";
|
|
return new ConvException(text(msg, " for input \"", p, "\"."), fn, ln);
|
|
}
|
|
|
|
enforce(!p.empty, bailOut());
|
|
|
|
char sign = 0; /* indicating + */
|
|
switch (p.front)
|
|
{
|
|
case '-':
|
|
sign++;
|
|
p.popFront();
|
|
enforce(!p.empty, bailOut());
|
|
if (toLower(p.front) == 'i')
|
|
goto case 'i';
|
|
enforce(!p.empty, bailOut());
|
|
break;
|
|
case '+':
|
|
p.popFront();
|
|
enforce(!p.empty, bailOut());
|
|
break;
|
|
case 'i': case 'I':
|
|
p.popFront();
|
|
enforce(!p.empty, bailOut());
|
|
if (toLower(p.front) == 'n')
|
|
{
|
|
p.popFront();
|
|
enforce(!p.empty, bailOut());
|
|
if (toLower(p.front) == 'f')
|
|
{
|
|
// 'inf'
|
|
p.popFront();
|
|
return sign ? -Target.infinity : Target.infinity;
|
|
}
|
|
}
|
|
goto default;
|
|
default: {}
|
|
}
|
|
|
|
bool isHex = false;
|
|
bool startsWithZero = p.front == '0';
|
|
if(startsWithZero)
|
|
{
|
|
p.popFront();
|
|
if(p.empty)
|
|
{
|
|
return (sign) ? -0.0 : 0.0;
|
|
}
|
|
|
|
isHex = p.front == 'x' || p.front == 'X';
|
|
}
|
|
|
|
real ldval = 0.0;
|
|
char dot = 0; /* if decimal point has been seen */
|
|
int exp = 0;
|
|
long msdec = 0, lsdec = 0;
|
|
ulong msscale = 1;
|
|
|
|
if (isHex)
|
|
{
|
|
int guard = 0;
|
|
int anydigits = 0;
|
|
uint ndigits = 0;
|
|
|
|
p.popFront();
|
|
while (!p.empty)
|
|
{
|
|
int i = p.front;
|
|
while (isHexDigit(i))
|
|
{
|
|
anydigits = 1;
|
|
i = isAlpha(i) ? ((i & ~0x20) - ('A' - 10)) : i - '0';
|
|
if (ndigits < 16)
|
|
{
|
|
msdec = msdec * 16 + i;
|
|
if (msdec)
|
|
ndigits++;
|
|
}
|
|
else if (ndigits == 16)
|
|
{
|
|
while (msdec >= 0)
|
|
{
|
|
exp--;
|
|
msdec <<= 1;
|
|
i <<= 1;
|
|
if (i & 0x10)
|
|
msdec |= 1;
|
|
}
|
|
guard = i << 4;
|
|
ndigits++;
|
|
exp += 4;
|
|
}
|
|
else
|
|
{
|
|
guard |= i;
|
|
exp += 4;
|
|
}
|
|
exp -= dot;
|
|
p.popFront();
|
|
if (p.empty)
|
|
break;
|
|
i = p.front;
|
|
if (i == '_')
|
|
{
|
|
p.popFront();
|
|
if (p.empty)
|
|
break;
|
|
i = p.front;
|
|
}
|
|
}
|
|
if (i == '.' && !dot)
|
|
{
|
|
p.popFront();
|
|
dot = 4;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
// Round up if (guard && (sticky || odd))
|
|
if (guard & 0x80 && (guard & 0x7F || msdec & 1))
|
|
{
|
|
msdec++;
|
|
if (msdec == 0) // overflow
|
|
{
|
|
msdec = 0x8000000000000000L;
|
|
exp++;
|
|
}
|
|
}
|
|
|
|
enforce(anydigits, bailOut());
|
|
enforce(!p.empty && (p.front == 'p' || p.front == 'P'),
|
|
bailOut("Floating point parsing: exponent is required"));
|
|
char sexp;
|
|
int e;
|
|
|
|
sexp = 0;
|
|
p.popFront();
|
|
if (!p.empty)
|
|
{
|
|
switch (p.front)
|
|
{
|
|
case '-': sexp++;
|
|
goto case;
|
|
case '+': p.popFront(); enforce(!p.empty,
|
|
new ConvException("Error converting input"~
|
|
" to floating point"));
|
|
break;
|
|
default: {}
|
|
}
|
|
}
|
|
ndigits = 0;
|
|
e = 0;
|
|
while (!p.empty && isDigit(p.front))
|
|
{
|
|
if (e < 0x7FFFFFFF / 10 - 10) // prevent integer overflow
|
|
{
|
|
e = e * 10 + p.front - '0';
|
|
}
|
|
p.popFront();
|
|
ndigits = 1;
|
|
}
|
|
exp += (sexp) ? -e : e;
|
|
enforce(ndigits, new ConvException("Error converting input"~
|
|
" to floating point"));
|
|
|
|
static if (real.mant_dig == 64)
|
|
{
|
|
if (msdec)
|
|
{
|
|
int e2 = 0x3FFF + 63;
|
|
|
|
// left justify mantissa
|
|
while (msdec >= 0)
|
|
{
|
|
msdec <<= 1;
|
|
e2--;
|
|
}
|
|
|
|
// Stuff mantissa directly into real
|
|
()@trusted{ *cast(long*)&ldval = msdec; }();
|
|
()@trusted{ (cast(ushort*)&ldval)[4] = cast(ushort) e2; }();
|
|
|
|
import std.math : ldexp;
|
|
|
|
// Exponent is power of 2, not power of 10
|
|
ldval = ldexp(ldval,exp);
|
|
}
|
|
}
|
|
else static if (real.mant_dig == 53)
|
|
{
|
|
if (msdec)
|
|
{
|
|
//Exponent bias + 52:
|
|
//After shifting 52 times left, exp must be 1
|
|
int e2 = 0x3FF + 52;
|
|
|
|
// right justify mantissa
|
|
// first 11 bits must be zero, rest is implied bit + mantissa
|
|
// shift one time less, do rounding, shift again
|
|
while ((msdec & 0xFFC0_0000_0000_0000) != 0)
|
|
{
|
|
msdec = ((cast(ulong)msdec) >> 1);
|
|
e2++;
|
|
}
|
|
|
|
//Have to shift one more time
|
|
//and do rounding
|
|
if((msdec & 0xFFE0_0000_0000_0000) != 0)
|
|
{
|
|
auto roundUp = (msdec & 0x1);
|
|
|
|
msdec = ((cast(ulong)msdec) >> 1);
|
|
e2++;
|
|
if(roundUp)
|
|
{
|
|
msdec += 1;
|
|
//If mantissa was 0b1111... and we added +1
|
|
//the mantissa should be 0b10000 (think of implicit bit)
|
|
//and the exponent increased
|
|
if((msdec & 0x0020_0000_0000_0000) != 0)
|
|
{
|
|
msdec = 0x0010_0000_0000_0000;
|
|
e2++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// left justify mantissa
|
|
// bit 11 must be 1
|
|
while ((msdec & 0x0010_0000_0000_0000) == 0)
|
|
{
|
|
msdec <<= 1;
|
|
e2--;
|
|
}
|
|
|
|
// Stuff mantissa directly into double
|
|
// (first including implicit bit)
|
|
()@trusted{ *cast(long *)&ldval = msdec; }();
|
|
//Store exponent, now overwriting implicit bit
|
|
()@trusted{ *cast(long *)&ldval &= 0x000F_FFFF_FFFF_FFFF; }();
|
|
()@trusted{ *cast(long *)&ldval |= ((e2 & 0xFFFUL) << 52); }();
|
|
|
|
import std.math : ldexp;
|
|
|
|
// Exponent is power of 2, not power of 10
|
|
ldval = ldexp(ldval,exp);
|
|
}
|
|
}
|
|
else
|
|
static assert(false, "Floating point format of real type not supported");
|
|
|
|
goto L6;
|
|
}
|
|
else // not hex
|
|
{
|
|
if (toUpper(p.front) == 'N' && !startsWithZero)
|
|
{
|
|
// nan
|
|
p.popFront();
|
|
enforce(!p.empty && toUpper(p.front) == 'A',
|
|
new ConvException("error converting input to floating point"));
|
|
p.popFront();
|
|
enforce(!p.empty && toUpper(p.front) == 'N',
|
|
new ConvException("error converting input to floating point"));
|
|
// skip past the last 'n'
|
|
p.popFront();
|
|
return typeof(return).nan;
|
|
}
|
|
|
|
bool sawDigits = startsWithZero;
|
|
|
|
while (!p.empty)
|
|
{
|
|
int i = p.front;
|
|
while (isDigit(i))
|
|
{
|
|
sawDigits = true; /* must have at least 1 digit */
|
|
if (msdec < (0x7FFFFFFFFFFFL-10)/10)
|
|
msdec = msdec * 10 + (i - '0');
|
|
else if (msscale < (0xFFFFFFFF-10)/10)
|
|
{
|
|
lsdec = lsdec * 10 + (i - '0');
|
|
msscale *= 10;
|
|
}
|
|
else
|
|
{
|
|
exp++;
|
|
}
|
|
exp -= dot;
|
|
p.popFront();
|
|
if (p.empty)
|
|
break;
|
|
i = p.front;
|
|
if (i == '_')
|
|
{
|
|
p.popFront();
|
|
if (p.empty)
|
|
break;
|
|
i = p.front;
|
|
}
|
|
}
|
|
if (i == '.' && !dot)
|
|
{
|
|
p.popFront();
|
|
dot++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
enforce(sawDigits, new ConvException("no digits seen"));
|
|
}
|
|
if (!p.empty && (p.front == 'e' || p.front == 'E'))
|
|
{
|
|
char sexp;
|
|
int e;
|
|
|
|
sexp = 0;
|
|
p.popFront();
|
|
enforce(!p.empty, new ConvException("Unexpected end of input"));
|
|
switch (p.front)
|
|
{
|
|
case '-': sexp++;
|
|
goto case;
|
|
case '+': p.popFront();
|
|
break;
|
|
default: {}
|
|
}
|
|
bool sawDigits = 0;
|
|
e = 0;
|
|
while (!p.empty && isDigit(p.front))
|
|
{
|
|
if (e < 0x7FFFFFFF / 10 - 10) // prevent integer overflow
|
|
{
|
|
e = e * 10 + p.front - '0';
|
|
}
|
|
p.popFront();
|
|
sawDigits = 1;
|
|
}
|
|
exp += (sexp) ? -e : e;
|
|
enforce(sawDigits, new ConvException("No digits seen."));
|
|
}
|
|
|
|
ldval = msdec;
|
|
if (msscale != 1) /* if stuff was accumulated in lsdec */
|
|
ldval = ldval * msscale + lsdec;
|
|
if (ldval)
|
|
{
|
|
uint u = 0;
|
|
int pow = 4096;
|
|
|
|
while (exp > 0)
|
|
{
|
|
while (exp >= pow)
|
|
{
|
|
ldval *= postab[u];
|
|
exp -= pow;
|
|
}
|
|
pow >>= 1;
|
|
u++;
|
|
}
|
|
while (exp < 0)
|
|
{
|
|
while (exp <= -pow)
|
|
{
|
|
ldval *= negtab[u];
|
|
enforce(ldval != 0, new ConvException("Range error"));
|
|
exp += pow;
|
|
}
|
|
pow >>= 1;
|
|
u++;
|
|
}
|
|
}
|
|
L6: // if overflow occurred
|
|
enforce(ldval != HUGE_VAL, new ConvException("Range error"));
|
|
|
|
L1:
|
|
return (sign) ? -ldval : ldval;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.exception;
|
|
import std.math : isNaN, fabs;
|
|
|
|
// Compare reals with given precision
|
|
bool feq(in real rx, in real ry, in real precision = 0.000001L)
|
|
{
|
|
if (rx == ry)
|
|
return 1;
|
|
|
|
if (isNaN(rx))
|
|
return cast(bool)isNaN(ry);
|
|
|
|
if (isNaN(ry))
|
|
return 0;
|
|
|
|
return cast(bool)(fabs(rx - ry) <= precision);
|
|
}
|
|
|
|
// Make given typed literal
|
|
F Literal(F)(F f)
|
|
{
|
|
return f;
|
|
}
|
|
|
|
foreach (Float; TypeTuple!(float, double, real))
|
|
{
|
|
assert(to!Float("123") == Literal!Float(123));
|
|
assert(to!Float("+123") == Literal!Float(+123));
|
|
assert(to!Float("-123") == Literal!Float(-123));
|
|
assert(to!Float("123e2") == Literal!Float(123e2));
|
|
assert(to!Float("123e+2") == Literal!Float(123e+2));
|
|
assert(to!Float("123e-2") == Literal!Float(123e-2));
|
|
assert(to!Float("123.") == Literal!Float(123.0));
|
|
assert(to!Float(".375") == Literal!Float(.375));
|
|
|
|
assert(to!Float("1.23375E+2") == Literal!Float(1.23375E+2));
|
|
|
|
assert(to!Float("0") is 0.0);
|
|
assert(to!Float("-0") is -0.0);
|
|
|
|
assert(isNaN(to!Float("nan")));
|
|
|
|
assertThrown!ConvException(to!Float("\x00"));
|
|
}
|
|
|
|
// min and max
|
|
float f = to!float("1.17549e-38");
|
|
assert(feq(cast(real)f, cast(real)1.17549e-38));
|
|
assert(feq(cast(real)f, cast(real)float.min_normal));
|
|
f = to!float("3.40282e+38");
|
|
assert(to!string(f) == to!string(3.40282e+38));
|
|
|
|
// min and max
|
|
double d = to!double("2.22508e-308");
|
|
assert(feq(cast(real)d, cast(real)2.22508e-308));
|
|
assert(feq(cast(real)d, cast(real)double.min_normal));
|
|
d = to!double("1.79769e+308");
|
|
assert(to!string(d) == to!string(1.79769e+308));
|
|
assert(to!string(d) == to!string(double.max));
|
|
|
|
assert(to!string(to!real(to!string(real.max / 2L))) == to!string(real.max / 2L));
|
|
|
|
// min and max
|
|
real r = to!real(to!string(real.min_normal));
|
|
assert(to!string(r) == to!string(real.min_normal));
|
|
r = to!real(to!string(real.max));
|
|
assert(to!string(r) == to!string(real.max));
|
|
}
|
|
|
|
//Tests for the double implementation
|
|
unittest
|
|
{
|
|
import core.stdc.stdlib, std.math;
|
|
static if(real.mant_dig == 53)
|
|
{
|
|
//Should be parsed exactly: 53 bit mantissa
|
|
string s = "0x1A_BCDE_F012_3456p10";
|
|
auto x = parse!real(s);
|
|
assert(x == 0x1A_BCDE_F012_3456p10L);
|
|
//1 bit is implicit
|
|
assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0xA_BCDE_F012_3456);
|
|
assert(strtod("0x1ABCDEF0123456p10", null) == x);
|
|
|
|
//Should be parsed exactly: 10 bit mantissa
|
|
s = "0x3FFp10";
|
|
x = parse!real(s);
|
|
assert(x == 0x03FFp10);
|
|
//1 bit is implicit
|
|
assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x000F_F800_0000_0000);
|
|
assert(strtod("0x3FFp10", null) == x);
|
|
|
|
//60 bit mantissa, round up
|
|
s = "0xFFF_FFFF_FFFF_FFFFp10";
|
|
x = parse!real(s);
|
|
assert(approxEqual(x, 0xFFF_FFFF_FFFF_FFFFp10));
|
|
//1 bit is implicit
|
|
assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x0000_0000_0000_0000);
|
|
assert(strtod("0xFFFFFFFFFFFFFFFp10", null) == x);
|
|
|
|
//60 bit mantissa, round down
|
|
s = "0xFFF_FFFF_FFFF_FF90p10";
|
|
x = parse!real(s);
|
|
assert(approxEqual(x, 0xFFF_FFFF_FFFF_FF90p10));
|
|
//1 bit is implicit
|
|
assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x000F_FFFF_FFFF_FFFF);
|
|
assert(strtod("0xFFFFFFFFFFFFF90p10", null) == x);
|
|
|
|
//61 bit mantissa, round up 2
|
|
s = "0x1F0F_FFFF_FFFF_FFFFp10";
|
|
x = parse!real(s);
|
|
assert(approxEqual(x, 0x1F0F_FFFF_FFFF_FFFFp10));
|
|
//1 bit is implicit
|
|
assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x000F_1000_0000_0000);
|
|
assert(strtod("0x1F0FFFFFFFFFFFFFp10", null) == x);
|
|
|
|
//61 bit mantissa, round down 2
|
|
s = "0x1F0F_FFFF_FFFF_FF10p10";
|
|
x = parse!real(s);
|
|
assert(approxEqual(x, 0x1F0F_FFFF_FFFF_FF10p10));
|
|
//1 bit is implicit
|
|
assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x000F_0FFF_FFFF_FFFF);
|
|
assert(strtod("0x1F0FFFFFFFFFFF10p10", null) == x);
|
|
|
|
//Huge exponent
|
|
s = "0x1F_FFFF_FFFF_FFFFp900";
|
|
x = parse!real(s);
|
|
assert(strtod("0x1FFFFFFFFFFFFFp900", null) == x);
|
|
|
|
//exponent too big -> converror
|
|
s = "";
|
|
assertThrown!ConvException(x = parse!real(s));
|
|
assert(strtod("0x1FFFFFFFFFFFFFp1024", null) == real.infinity);
|
|
|
|
//-exponent too big -> 0
|
|
s = "0x1FFFFFFFFFFFFFp-2000";
|
|
x = parse!real(s);
|
|
assert(x == 0);
|
|
assert(strtod("0x1FFFFFFFFFFFFFp-2000", null) == x);
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import core.stdc.errno;
|
|
import core.stdc.stdlib;
|
|
|
|
errno = 0; // In case it was set by another unittest in a different module.
|
|
struct longdouble
|
|
{
|
|
static if(real.mant_dig == 64)
|
|
{
|
|
ushort[5] value;
|
|
}
|
|
else static if(real.mant_dig == 53)
|
|
{
|
|
ushort[4] value;
|
|
}
|
|
else
|
|
static assert(false, "Not implemented");
|
|
}
|
|
|
|
real ld;
|
|
longdouble x;
|
|
real ld1;
|
|
longdouble x1;
|
|
int i;
|
|
|
|
static if(real.mant_dig == 64)
|
|
enum s = "0x1.FFFFFFFFFFFFFFFEp-16382";
|
|
else static if(real.mant_dig == 53)
|
|
enum s = "0x1.FFFFFFFFFFFFFFFEp-1000";
|
|
else
|
|
static assert(false, "Floating point format for real not supported");
|
|
|
|
auto s2 = s.idup;
|
|
ld = parse!real(s2);
|
|
assert(s2.empty);
|
|
x = *cast(longdouble *)&ld;
|
|
version (CRuntime_Microsoft)
|
|
ld1 = 0x1.FFFFFFFFFFFFFFFEp-16382L; // strtold currently mapped to strtod
|
|
else version (Android)
|
|
ld1 = 0x1.FFFFFFFFFFFFFFFEp-16382L; // strtold currently mapped to strtod
|
|
else
|
|
ld1 = strtold(s.ptr, null);
|
|
x1 = *cast(longdouble *)&ld1;
|
|
assert(x1 == x && ld1 == ld);
|
|
|
|
// for (i = 4; i >= 0; i--)
|
|
// {
|
|
// printf("%04x ", x.value[i]);
|
|
// }
|
|
// printf("\n");
|
|
assert(!errno);
|
|
|
|
s2 = "1.0e5";
|
|
ld = parse!real(s2);
|
|
assert(s2.empty);
|
|
x = *cast(longdouble *)&ld;
|
|
ld1 = strtold("1.0e5", null);
|
|
x1 = *cast(longdouble *)&ld1;
|
|
|
|
// for (i = 4; i >= 0; i--)
|
|
// {
|
|
// printf("%04x ", x.value[i]);
|
|
// }
|
|
// printf("\n");
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
|
|
// Bugzilla 4959
|
|
{
|
|
auto s = "0 ";
|
|
auto x = parse!double(s);
|
|
assert(s == " ");
|
|
assert(x == 0.0);
|
|
}
|
|
|
|
// Bugzilla 3369
|
|
assert(to!float("inf") == float.infinity);
|
|
assert(to!float("-inf") == -float.infinity);
|
|
|
|
// Bugzilla 6160
|
|
assert(6_5.536e3L == to!real("6_5.536e3")); // 2^16
|
|
assert(0x1000_000_000_p10 == to!real("0x1000_000_000_p10")); // 7.03687e+13
|
|
|
|
// Bugzilla 6258
|
|
assertThrown!ConvException(to!real("-"));
|
|
assertThrown!ConvException(to!real("in"));
|
|
|
|
// Bugzilla 7055
|
|
assertThrown!ConvException(to!float("INF2"));
|
|
|
|
//extra stress testing
|
|
auto ssOK = ["1.", "1.1.1", "1.e5", "2e1e", "2a", "2e1_1",
|
|
"inf", "-inf", "infa", "-infa", "inf2e2", "-inf2e2"];
|
|
auto ssKO = ["", " ", "2e", "2e+", "2e-", "2ee", "2e++1", "2e--1", "2e_1", "+inf"];
|
|
foreach (s; ssOK)
|
|
parse!double(s);
|
|
foreach (s; ssKO)
|
|
assertThrown!ConvException(parse!double(s));
|
|
}
|
|
|
|
/**
|
|
Parsing one character off a string returns the character and bumps the
|
|
string up one position.
|
|
*/
|
|
Target parse(Target, Source)(ref Source s)
|
|
if (isExactSomeString!Source &&
|
|
staticIndexOf!(Unqual!Target, dchar, Unqual!(ElementEncodingType!Source)) >= 0)
|
|
{
|
|
if (s.empty)
|
|
throw convError!(Source, Target)(s);
|
|
static if (is(Unqual!Target == dchar))
|
|
{
|
|
Target result = s.front;
|
|
s.popFront();
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
// Special case: okay so parse a Char off a Char[]
|
|
Target result = s[0];
|
|
s = s[1 .. $];
|
|
return result;
|
|
}
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
foreach (Str; TypeTuple!(string, wstring, dstring))
|
|
{
|
|
foreach (Char; TypeTuple!(char, wchar, dchar))
|
|
{
|
|
static if (is(Unqual!Char == dchar) ||
|
|
Char.sizeof == ElementEncodingType!Str.sizeof)
|
|
{
|
|
Str s = "aaa";
|
|
assert(parse!Char(s) == 'a');
|
|
assert(s == "aa");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Target parse(Target, Source)(ref Source s)
|
|
if (!isSomeString!Source && isInputRange!Source && isSomeChar!(ElementType!Source) &&
|
|
isSomeChar!Target && Target.sizeof >= ElementType!Source.sizeof && !is(Target == enum))
|
|
{
|
|
if (s.empty)
|
|
throw convError!(Source, Target)(s);
|
|
Target result = s.front;
|
|
s.popFront();
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
Tests for to!bool and parse!bool
|
|
*/
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
|
|
assert (to!bool("TruE") == true);
|
|
assert (to!bool("faLse"d) == false);
|
|
assertThrown!ConvException(to!bool("maybe"));
|
|
|
|
auto t = "TrueType";
|
|
assert (parse!bool(t) == true);
|
|
assert (t == "Type");
|
|
|
|
auto f = "False killer whale"d;
|
|
assert (parse!bool(f) == false);
|
|
assert (f == " killer whale"d);
|
|
|
|
auto m = "maybe";
|
|
assertThrown!ConvException(parse!bool(m));
|
|
assert (m == "maybe"); // m shouldn't change on failure
|
|
|
|
auto s = "true";
|
|
auto b = parse!(const(bool))(s);
|
|
assert(b == true);
|
|
}
|
|
|
|
// input range to null literal conversions
|
|
Target parse(Target, Source)(ref Source s)
|
|
if (isInputRange!Source &&
|
|
isSomeChar!(ElementType!Source) &&
|
|
is(Unqual!Target == typeof(null)))
|
|
{
|
|
import std.ascii : toLower;
|
|
foreach (c; "null")
|
|
{
|
|
if (s.empty || toLower(s.front) != c)
|
|
throw parseError("null should be case-insensitive 'null'");
|
|
s.popFront();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
|
|
alias NullType = typeof(null);
|
|
auto s1 = "null";
|
|
assert(parse!NullType(s1) is null);
|
|
assert(s1 == "");
|
|
|
|
auto s2 = "NUll"d;
|
|
assert(parse!NullType(s2) is null);
|
|
assert(s2 == "");
|
|
|
|
auto m = "maybe";
|
|
assertThrown!ConvException(parse!NullType(m));
|
|
assert(m == "maybe"); // m shouldn't change on failure
|
|
|
|
auto s = "NULL";
|
|
assert(parse!(const NullType)(s) is null);
|
|
}
|
|
|
|
//Used internally by parse Array/AA, to remove ascii whites
|
|
package void skipWS(R)(ref R r)
|
|
{
|
|
import std.ascii : isWhite;
|
|
static if (isSomeString!R)
|
|
{
|
|
//Implementation inspired from stripLeft.
|
|
foreach (i, dchar c; r)
|
|
{
|
|
if (!isWhite(c))
|
|
{
|
|
r = r[i .. $];
|
|
return;
|
|
}
|
|
}
|
|
r = r[0 .. 0]; //Empty string with correct type.
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
for (; !r.empty && isWhite(r.front); r.popFront())
|
|
{}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses an array from a string given the left bracket (default $(D
|
|
* '[')), right bracket (default $(D ']')), and element separator (by
|
|
* default $(D ',')).
|
|
*/
|
|
Target parse(Target, Source)(ref Source s, dchar lbracket = '[', dchar rbracket = ']', dchar comma = ',')
|
|
if (isExactSomeString!Source &&
|
|
isDynamicArray!Target && !is(Target == enum))
|
|
{
|
|
Target result;
|
|
|
|
parseCheck!s(lbracket);
|
|
skipWS(s);
|
|
if (s.empty)
|
|
throw convError!(Source, Target)(s);
|
|
if (s.front == rbracket)
|
|
{
|
|
s.popFront();
|
|
return result;
|
|
}
|
|
for (;; s.popFront(), skipWS(s))
|
|
{
|
|
result ~= parseElement!(ElementType!Target)(s);
|
|
skipWS(s);
|
|
if (s.empty)
|
|
throw convError!(Source, Target)(s);
|
|
if (s.front != comma)
|
|
break;
|
|
}
|
|
parseCheck!s(rbracket);
|
|
|
|
return result;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
int[] a = [1, 2, 3, 4, 5];
|
|
auto s = to!string(a);
|
|
assert(to!(int[])(s) == a);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
int[][] a = [ [1, 2] , [3], [4, 5] ];
|
|
auto s = to!string(a);
|
|
assert(to!(int[][])(s) == a);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
int[][][] ia = [ [[1,2],[3,4],[5]] , [[6],[],[7,8,9]] , [[]] ];
|
|
|
|
char[] s = to!(char[])(ia);
|
|
int[][][] ia2;
|
|
|
|
ia2 = to!(typeof(ia2))(s);
|
|
assert( ia == ia2);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
auto s1 = `[['h', 'e', 'l', 'l', 'o'], "world"]`;
|
|
auto a1 = parse!(string[])(s1);
|
|
assert(a1 == ["hello", "world"]);
|
|
|
|
auto s2 = `["aaa", "bbb", "ccc"]`;
|
|
auto a2 = parse!(string[])(s2);
|
|
assert(a2 == ["aaa", "bbb", "ccc"]);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
|
|
//Check proper failure
|
|
auto s = "[ 1 , 2 , 3 ]";
|
|
foreach (i ; 0..s.length-1)
|
|
{
|
|
auto ss = s[0 .. i];
|
|
assertThrown!ConvException(parse!(int[])(ss));
|
|
}
|
|
int[] arr = parse!(int[])(s);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
//Checks parsing of strings with escaped characters
|
|
string s1 = `[
|
|
"Contains a\0null!",
|
|
"tab\there",
|
|
"line\nbreak",
|
|
"backslash \\ slash / question \?",
|
|
"number \x35 five",
|
|
"unicode \u65E5 sun",
|
|
"very long \U000065E5 sun"
|
|
]`;
|
|
|
|
//Note: escaped characters purposefully replaced and isolated to guarantee
|
|
//there are no typos in the escape syntax
|
|
string[] s2 = [
|
|
"Contains a" ~ '\0' ~ "null!",
|
|
"tab" ~ '\t' ~ "here",
|
|
"line" ~ '\n' ~ "break",
|
|
"backslash " ~ '\\' ~ " slash / question ?",
|
|
"number 5 five",
|
|
"unicode 日 sun",
|
|
"very long 日 sun"
|
|
];
|
|
assert(s2 == parse!(string[])(s1));
|
|
assert(s1.empty);
|
|
}
|
|
|
|
/// ditto
|
|
Target parse(Target, Source)(ref Source s, dchar lbracket = '[', dchar rbracket = ']', dchar comma = ',')
|
|
if (isExactSomeString!Source &&
|
|
isStaticArray!Target && !is(Target == enum))
|
|
{
|
|
static if (hasIndirections!Target)
|
|
Target result = Target.init[0].init;
|
|
else
|
|
Target result = void;
|
|
|
|
parseCheck!s(lbracket);
|
|
skipWS(s);
|
|
if (s.empty)
|
|
throw convError!(Source, Target)(s);
|
|
if (s.front == rbracket)
|
|
{
|
|
static if (result.length != 0)
|
|
goto Lmanyerr;
|
|
else
|
|
{
|
|
s.popFront();
|
|
return result;
|
|
}
|
|
}
|
|
for (size_t i = 0; ; s.popFront(), skipWS(s))
|
|
{
|
|
if (i == result.length)
|
|
goto Lmanyerr;
|
|
result[i++] = parseElement!(ElementType!Target)(s);
|
|
skipWS(s);
|
|
if (s.empty)
|
|
throw convError!(Source, Target)(s);
|
|
if (s.front != comma)
|
|
{
|
|
if (i != result.length)
|
|
goto Lfewerr;
|
|
break;
|
|
}
|
|
}
|
|
parseCheck!s(rbracket);
|
|
|
|
return result;
|
|
|
|
Lmanyerr:
|
|
throw parseError(text("Too many elements in input, ", result.length, " elements expected."));
|
|
|
|
Lfewerr:
|
|
throw parseError(text("Too few elements in input, ", result.length, " elements expected."));
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
|
|
auto s1 = "[1,2,3,4]";
|
|
auto sa1 = parse!(int[4])(s1);
|
|
assert(sa1 == [1,2,3,4]);
|
|
|
|
auto s2 = "[[1],[2,3],[4]]";
|
|
auto sa2 = parse!(int[][3])(s2);
|
|
assert(sa2 == [[1],[2,3],[4]]);
|
|
|
|
auto s3 = "[1,2,3]";
|
|
assertThrown!ConvException(parse!(int[4])(s3));
|
|
|
|
auto s4 = "[1,2,3,4,5]";
|
|
assertThrown!ConvException(parse!(int[4])(s4));
|
|
}
|
|
|
|
/**
|
|
* Parses an associative array from a string given the left bracket (default $(D
|
|
* '[')), right bracket (default $(D ']')), key-value separator (default $(D
|
|
* ':')), and element seprator (by default $(D ',')).
|
|
*/
|
|
Target parse(Target, Source)(ref Source s, dchar lbracket = '[', dchar rbracket = ']', dchar keyval = ':', dchar comma = ',')
|
|
if (isExactSomeString!Source &&
|
|
isAssociativeArray!Target && !is(Target == enum))
|
|
{
|
|
alias KeyType = typeof(Target.init.keys[0]);
|
|
alias ValType = typeof(Target.init.values[0]);
|
|
|
|
Target result;
|
|
|
|
parseCheck!s(lbracket);
|
|
skipWS(s);
|
|
if (s.empty)
|
|
throw convError!(Source, Target)(s);
|
|
if (s.front == rbracket)
|
|
{
|
|
s.popFront();
|
|
return result;
|
|
}
|
|
for (;; s.popFront(), skipWS(s))
|
|
{
|
|
auto key = parseElement!KeyType(s);
|
|
skipWS(s);
|
|
parseCheck!s(keyval);
|
|
skipWS(s);
|
|
auto val = parseElement!ValType(s);
|
|
skipWS(s);
|
|
result[key] = val;
|
|
if (s.empty)
|
|
throw convError!(Source, Target)(s);
|
|
if (s.front != comma)
|
|
break;
|
|
}
|
|
parseCheck!s(rbracket);
|
|
|
|
return result;
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
auto s1 = "[1:10, 2:20, 3:30]";
|
|
auto aa1 = parse!(int[int])(s1);
|
|
assert(aa1 == [1:10, 2:20, 3:30]);
|
|
|
|
auto s2 = `["aaa":10, "bbb":20, "ccc":30]`;
|
|
auto aa2 = parse!(int[string])(s2);
|
|
assert(aa2 == ["aaa":10, "bbb":20, "ccc":30]);
|
|
|
|
auto s3 = `["aaa":[1], "bbb":[2,3], "ccc":[4,5,6]]`;
|
|
auto aa3 = parse!(int[][string])(s3);
|
|
assert(aa3 == ["aaa":[1], "bbb":[2,3], "ccc":[4,5,6]]);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
|
|
//Check proper failure
|
|
auto s = "[1:10, 2:20, 3:30]";
|
|
foreach (i ; 0 .. s.length-1)
|
|
{
|
|
auto ss = s[0 .. i];
|
|
assertThrown!ConvException(parse!(int[int])(ss));
|
|
}
|
|
int[int] aa = parse!(int[int])(s);
|
|
}
|
|
|
|
private dchar parseEscape(Source)(ref Source s)
|
|
if (isInputRange!Source && isSomeChar!(ElementType!Source))
|
|
{
|
|
parseCheck!s('\\');
|
|
if (s.empty)
|
|
throw parseError("Unterminated escape sequence");
|
|
|
|
dchar getHexDigit()(ref Source s_ = s) // workaround
|
|
{
|
|
import std.ascii : isAlpha, isHexDigit;
|
|
if (s_.empty)
|
|
throw parseError("Unterminated escape sequence");
|
|
s_.popFront();
|
|
if (s_.empty)
|
|
throw parseError("Unterminated escape sequence");
|
|
dchar c = s_.front;
|
|
if (!isHexDigit(c))
|
|
throw parseError("Hex digit is missing");
|
|
return isAlpha(c) ? ((c & ~0x20) - ('A' - 10)) : c - '0';
|
|
}
|
|
|
|
dchar result;
|
|
|
|
switch (s.front)
|
|
{
|
|
case '"': result = '\"'; break;
|
|
case '\'': result = '\''; break;
|
|
case '0': result = '\0'; break;
|
|
case '?': result = '\?'; break;
|
|
case '\\': result = '\\'; break;
|
|
case 'a': result = '\a'; break;
|
|
case 'b': result = '\b'; break;
|
|
case 'f': result = '\f'; break;
|
|
case 'n': result = '\n'; break;
|
|
case 'r': result = '\r'; break;
|
|
case 't': result = '\t'; break;
|
|
case 'v': result = '\v'; break;
|
|
case 'x':
|
|
result = getHexDigit() << 4;
|
|
result |= getHexDigit();
|
|
break;
|
|
case 'u':
|
|
result = getHexDigit() << 12;
|
|
result |= getHexDigit() << 8;
|
|
result |= getHexDigit() << 4;
|
|
result |= getHexDigit();
|
|
break;
|
|
case 'U':
|
|
result = getHexDigit() << 28;
|
|
result |= getHexDigit() << 24;
|
|
result |= getHexDigit() << 20;
|
|
result |= getHexDigit() << 16;
|
|
result |= getHexDigit() << 12;
|
|
result |= getHexDigit() << 8;
|
|
result |= getHexDigit() << 4;
|
|
result |= getHexDigit();
|
|
break;
|
|
default:
|
|
throw parseError("Unknown escape character " ~ to!string(s.front));
|
|
}
|
|
if (s.empty)
|
|
throw parseError("Unterminated escape sequence");
|
|
|
|
s.popFront();
|
|
|
|
return result;
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
string[] s1 = [
|
|
`\"`, `\'`, `\?`, `\\`, `\a`, `\b`, `\f`, `\n`, `\r`, `\t`, `\v`, //Normal escapes
|
|
//`\141`, //@@@9621@@@ Octal escapes.
|
|
`\x61`,
|
|
`\u65E5`, `\U00012456`
|
|
//`\&`, `\"`, //@@@9621@@@ Named Character Entities.
|
|
];
|
|
|
|
const(dchar)[] s2 = [
|
|
'\"', '\'', '\?', '\\', '\a', '\b', '\f', '\n', '\r', '\t', '\v', //Normal escapes
|
|
//'\141', //@@@9621@@@ Octal escapes.
|
|
'\x61',
|
|
'\u65E5', '\U00012456'
|
|
//'\&', '\"', //@@@9621@@@ Named Character Entities.
|
|
];
|
|
|
|
foreach (i ; 0 .. s1.length)
|
|
{
|
|
assert(s2[i] == parseEscape(s1[i]));
|
|
assert(s1[i].empty);
|
|
}
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.exception;
|
|
|
|
string[] ss = [
|
|
`hello!`, //Not an escape
|
|
`\`, //Premature termination
|
|
`\/`, //Not an escape
|
|
`\gggg`, //Not an escape
|
|
`\xzz`, //Not an hex
|
|
`\x0`, //Premature hex end
|
|
`\XB9`, //Not legal hex syntax
|
|
`\u!!`, //Not a unicode hex
|
|
`\777`, //Octal is larger than a byte //Note: Throws, but simply because octals are unsupported
|
|
`\u123`, //Premature hex end
|
|
`\U123123` //Premature hex end
|
|
];
|
|
foreach (s ; ss)
|
|
assertThrown!ConvException(parseEscape(s));
|
|
}
|
|
|
|
// Undocumented
|
|
Target parseElement(Target, Source)(ref Source s)
|
|
if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) &&
|
|
isExactSomeString!Target)
|
|
{
|
|
import std.array : appender;
|
|
auto result = appender!Target();
|
|
|
|
// parse array of chars
|
|
if (s.empty)
|
|
throw convError!(Source, Target)(s);
|
|
if (s.front == '[')
|
|
return parse!Target(s);
|
|
|
|
parseCheck!s('\"');
|
|
if (s.empty)
|
|
throw convError!(Source, Target)(s);
|
|
if (s.front == '\"')
|
|
{
|
|
s.popFront();
|
|
return result.data;
|
|
}
|
|
while (true)
|
|
{
|
|
if (s.empty)
|
|
throw parseError("Unterminated quoted string");
|
|
switch (s.front)
|
|
{
|
|
case '\"':
|
|
s.popFront();
|
|
return result.data;
|
|
case '\\':
|
|
result.put(parseEscape(s));
|
|
break;
|
|
default:
|
|
result.put(s.front);
|
|
s.popFront();
|
|
break;
|
|
}
|
|
}
|
|
assert(0);
|
|
}
|
|
|
|
// ditto
|
|
Target parseElement(Target, Source)(ref Source s)
|
|
if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) &&
|
|
isSomeChar!Target && !is(Target == enum))
|
|
{
|
|
Target c;
|
|
|
|
parseCheck!s('\'');
|
|
if (s.empty)
|
|
throw convError!(Source, Target)(s);
|
|
if (s.front != '\\')
|
|
{
|
|
c = s.front;
|
|
s.popFront();
|
|
}
|
|
else
|
|
c = parseEscape(s);
|
|
parseCheck!s('\'');
|
|
|
|
return c;
|
|
}
|
|
|
|
// ditto
|
|
Target parseElement(Target, Source)(ref Source s)
|
|
if (isInputRange!Source && isSomeChar!(ElementType!Source) &&
|
|
!isSomeString!Target && !isSomeChar!Target)
|
|
{
|
|
return parse!Target(s);
|
|
}
|
|
|
|
|
|
/***************************************************************
|
|
* Convenience functions for converting any number and types of
|
|
* arguments into _text (the three character widths).
|
|
*/
|
|
string text(T...)(T args) { return textImpl!string(args); }
|
|
///ditto
|
|
wstring wtext(T...)(T args) { return textImpl!wstring(args); }
|
|
///ditto
|
|
dstring dtext(T...)(T args) { return textImpl!dstring(args); }
|
|
|
|
private S textImpl(S, U...)(U args)
|
|
{
|
|
static if (U.length == 0)
|
|
{
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
auto result = to!S(args[0]);
|
|
foreach (arg; args[1 .. $])
|
|
result ~= to!S(arg);
|
|
return result;
|
|
}
|
|
}
|
|
///
|
|
unittest
|
|
{
|
|
assert( text(42, ' ', 1.5, ": xyz") == "42 1.5: xyz"c);
|
|
assert(wtext(42, ' ', 1.5, ": xyz") == "42 1.5: xyz"w);
|
|
assert(dtext(42, ' ', 1.5, ": xyz") == "42 1.5: xyz"d);
|
|
}
|
|
unittest
|
|
{
|
|
assert(text() is null);
|
|
assert(wtext() is null);
|
|
assert(dtext() is null);
|
|
}
|
|
|
|
|
|
/***************************************************************
|
|
The $(D octal) facility is intended as an experimental facility to
|
|
replace _octal literals starting with $(D '0'), which many find
|
|
confusing. Using $(D octal!177) or $(D octal!"177") instead of $(D
|
|
0177) as an _octal literal makes code clearer and the intent more
|
|
visible. If use of this facility becomes predominant, a future
|
|
version of the language may deem old-style _octal literals deprecated.
|
|
|
|
The rules for strings are the usual for literals: If it can fit in an
|
|
$(D int), it is an $(D int). Otherwise, it is a $(D long). But, if the
|
|
user specifically asks for a $(D long) with the $(D L) suffix, always
|
|
give the $(D long). Give an unsigned iff it is asked for with the $(D
|
|
U) or $(D u) suffix. _Octals created from integers preserve the type
|
|
of the passed-in integral.
|
|
*/
|
|
@property int octal(string num)()
|
|
if((octalFitsInInt!(num) && !literalIsLong!(num)) && !literalIsUnsigned!(num))
|
|
{
|
|
return octal!(int, num);
|
|
}
|
|
|
|
/// Ditto
|
|
@property long octal(string num)()
|
|
if((!octalFitsInInt!(num) || literalIsLong!(num)) && !literalIsUnsigned!(num))
|
|
{
|
|
return octal!(long, num);
|
|
}
|
|
|
|
/// Ditto
|
|
@property uint octal(string num)()
|
|
if((octalFitsInInt!(num) && !literalIsLong!(num)) && literalIsUnsigned!(num))
|
|
{
|
|
return octal!(int, num);
|
|
}
|
|
|
|
/// Ditto
|
|
@property ulong octal(string num)()
|
|
if((!octalFitsInInt!(num) || literalIsLong!(num)) && literalIsUnsigned!(num))
|
|
{
|
|
return octal!(long, num);
|
|
}
|
|
|
|
/// Ditto
|
|
template octal(alias s)
|
|
if (isIntegral!(typeof(s)))
|
|
{
|
|
enum auto octal = octal!(typeof(s), to!string(s));
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
// same as 0177
|
|
auto x = octal!177;
|
|
// octal is a compile-time device
|
|
enum y = octal!160;
|
|
// Create an unsigned octal
|
|
auto z = octal!"1_000_000u";
|
|
}
|
|
|
|
/*
|
|
Takes a string, num, which is an octal literal, and returns its
|
|
value, in the type T specified.
|
|
*/
|
|
@property T octal(T, string num)()
|
|
if (isOctalLiteral!num)
|
|
{
|
|
ulong pow = 1;
|
|
T value = 0;
|
|
|
|
for (int pos = num.length - 1; pos >= 0; pos--)
|
|
{
|
|
char s = num[pos];
|
|
if (s < '0' || s > '7') // we only care about digits; skip the rest
|
|
// safe to skip - this is checked out in the assert so these
|
|
// are just suffixes
|
|
continue;
|
|
|
|
value += pow * (s - '0');
|
|
pow *= 8;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
int a = octal!(int, "10");
|
|
|
|
assert(a == 8);
|
|
}
|
|
|
|
/*
|
|
Take a look at int.max and int.max+1 in octal and the logic for this
|
|
function follows directly.
|
|
*/
|
|
template octalFitsInInt(string octalNum)
|
|
{
|
|
// note it is important to strip the literal of all
|
|
// non-numbers. kill the suffix and underscores lest they mess up
|
|
// the number of digits here that we depend on.
|
|
enum bool octalFitsInInt = strippedOctalLiteral(octalNum).length < 11 ||
|
|
strippedOctalLiteral(octalNum).length == 11 &&
|
|
strippedOctalLiteral(octalNum)[0] == '1';
|
|
}
|
|
|
|
string strippedOctalLiteral(string original)
|
|
{
|
|
string stripped = "";
|
|
foreach (c; original)
|
|
if (c >= '0' && c <= '7')
|
|
stripped ~= c;
|
|
return stripped;
|
|
}
|
|
|
|
template literalIsLong(string num)
|
|
{
|
|
static if (num.length > 1)
|
|
// can be xxL or xxLu according to spec
|
|
enum literalIsLong = (num[$-1] == 'L' || num[$-2] == 'L');
|
|
else
|
|
enum literalIsLong = false;
|
|
}
|
|
|
|
template literalIsUnsigned(string num)
|
|
{
|
|
static if (num.length > 1)
|
|
// can be xxU or xxUL according to spec
|
|
enum literalIsUnsigned = (num[$-1] == 'u' || num[$-2] == 'u')
|
|
// both cases are allowed too
|
|
|| (num[$-1] == 'U' || num[$-2] == 'U');
|
|
else
|
|
enum literalIsUnsigned = false;
|
|
}
|
|
|
|
/*
|
|
Returns if the given string is a correctly formatted octal literal.
|
|
|
|
The format is specified in lex.html. The leading zero is allowed, but
|
|
not required.
|
|
*/
|
|
bool isOctalLiteralString(string num)
|
|
{
|
|
if (num.length == 0)
|
|
return false;
|
|
|
|
// Must start with a number. To avoid confusion, literals that
|
|
// start with a '0' are not allowed
|
|
if (num[0] == '0' && num.length > 1)
|
|
return false;
|
|
if (num[0] < '0' || num[0] > '7')
|
|
return false;
|
|
|
|
foreach (i, c; num)
|
|
{
|
|
if ((c < '0' || c > '7') && c != '_') // not a legal character
|
|
{
|
|
if (i < num.length - 2)
|
|
return false;
|
|
else // gotta check for those suffixes
|
|
{
|
|
if (c != 'U' && c != 'u' && c != 'L')
|
|
return false;
|
|
if (i != num.length - 1)
|
|
{
|
|
// if we're not the last one, the next one must
|
|
// also be a suffix to be valid
|
|
char c2 = num[$-1];
|
|
if (c2 != 'U' && c2 != 'u' && c2 != 'L')
|
|
return false; // spam at the end of the string
|
|
if (c2 == c)
|
|
return false; // repeats are disallowed
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Returns true if the given compile time string is an octal literal.
|
|
*/
|
|
template isOctalLiteral(string num)
|
|
{
|
|
enum bool isOctalLiteral = isOctalLiteralString(num);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// ensure that you get the right types, even with embedded underscores
|
|
auto w = octal!"100_000_000_000";
|
|
static assert(!is(typeof(w) == int));
|
|
auto w2 = octal!"1_000_000_000";
|
|
static assert(is(typeof(w2) == int));
|
|
|
|
static assert(octal!"45" == 37);
|
|
static assert(octal!"0" == 0);
|
|
static assert(octal!"7" == 7);
|
|
static assert(octal!"10" == 8);
|
|
static assert(octal!"666" == 438);
|
|
|
|
static assert(octal!45 == 37);
|
|
static assert(octal!0 == 0);
|
|
static assert(octal!7 == 7);
|
|
static assert(octal!10 == 8);
|
|
static assert(octal!666 == 438);
|
|
|
|
static assert(octal!"66_6" == 438);
|
|
|
|
static assert(octal!2520046213 == 356535435);
|
|
static assert(octal!"2520046213" == 356535435);
|
|
|
|
static assert(octal!17777777777 == int.max);
|
|
|
|
static assert(!__traits(compiles, octal!823));
|
|
|
|
static assert(!__traits(compiles, octal!"823"));
|
|
|
|
static assert(!__traits(compiles, octal!"_823"));
|
|
static assert(!__traits(compiles, octal!"spam"));
|
|
static assert(!__traits(compiles, octal!"77%"));
|
|
|
|
int a;
|
|
long b;
|
|
|
|
// biggest value that should fit in an it
|
|
static assert(__traits(compiles, a = octal!"17777777777"));
|
|
// should not fit in the int
|
|
static assert(!__traits(compiles, a = octal!"20000000000"));
|
|
// ... but should fit in a long
|
|
static assert(__traits(compiles, b = octal!"20000000000"));
|
|
|
|
static assert(!__traits(compiles, a = octal!"1L"));
|
|
|
|
// this should pass, but it doesn't, since the int converter
|
|
// doesn't pass along its suffix to helper templates
|
|
|
|
//static assert(!__traits(compiles, a = octal!1L));
|
|
|
|
static assert(__traits(compiles, b = octal!"1L"));
|
|
static assert(__traits(compiles, b = octal!1L));
|
|
}
|
|
|
|
/+
|
|
emplaceRef is a package function for phobos internal use. It works like
|
|
emplace, but takes its argument by ref (as opposed to "by pointer").
|
|
|
|
This makes it easier to use, easier to be safe, and faster in a non-inline
|
|
build.
|
|
|
|
Furthermore, emplaceRef optionally takes a type paremeter, which specifies
|
|
the type we want to build. This helps to build qualified objects on mutable
|
|
buffer, without breaking the type system with unsafe casts.
|
|
+/
|
|
package ref UT emplaceRef(UT, Args...)(ref UT chunk, auto ref Args args)
|
|
if (is(UT == Unqual!UT))
|
|
{
|
|
return emplaceImpl!UT(chunk, args);
|
|
}
|
|
// ditto
|
|
package ref UT emplaceRef(T, UT, Args...)(ref UT chunk, auto ref Args args)
|
|
if (is(UT == Unqual!T) && !is(T == UT))
|
|
{
|
|
return emplaceImpl!T(chunk, args);
|
|
}
|
|
|
|
|
|
private template emplaceImpl(T)
|
|
{
|
|
alias UT = Unqual!T;
|
|
|
|
ref UT emplaceImpl()(ref UT chunk)
|
|
{
|
|
static assert (is(typeof({static T i;})),
|
|
convFormat("Cannot emplace a %1$s because %1$s.this() is annotated with @disable.", T.stringof));
|
|
|
|
return emplaceInitializer(chunk);
|
|
}
|
|
|
|
static if (!is(T == struct))
|
|
ref UT emplaceImpl(Arg)(ref UT chunk, auto ref Arg arg)
|
|
{
|
|
static assert(is(typeof({T t = arg;})),
|
|
convFormat("%s cannot be emplaced from a %s.", T.stringof, Arg.stringof));
|
|
|
|
static if (isStaticArray!T)
|
|
{
|
|
alias UArg = Unqual!Arg;
|
|
alias E = ElementEncodingType!(typeof(T.init[]));
|
|
alias UE = Unqual!E;
|
|
enum N = T.length;
|
|
|
|
static if (is(Arg : T))
|
|
{
|
|
//Matching static array
|
|
static if (!hasElaborateAssign!UT && isAssignable!(UT, Arg))
|
|
chunk = arg;
|
|
else static if (is(UArg == UT))
|
|
{
|
|
import core.stdc.string : memcpy;
|
|
memcpy(&chunk, &arg, T.sizeof);
|
|
static if (hasElaborateCopyConstructor!T)
|
|
typeid(T).postblit(cast(void*)&chunk);
|
|
}
|
|
else
|
|
.emplaceImpl!T(chunk, cast(T)arg);
|
|
}
|
|
else static if (is(Arg : E[]))
|
|
{
|
|
//Matching dynamic array
|
|
static if (!hasElaborateAssign!UT && is(typeof(chunk[] = arg[])))
|
|
chunk[] = arg[];
|
|
else static if (is(Unqual!(ElementEncodingType!Arg) == UE))
|
|
{
|
|
import core.stdc.string : memcpy;
|
|
assert(N == chunk.length, "Array length missmatch in emplace");
|
|
memcpy(cast(void*)&chunk, arg.ptr, T.sizeof);
|
|
static if (hasElaborateCopyConstructor!T)
|
|
typeid(T).postblit(cast(void*)&chunk);
|
|
}
|
|
else
|
|
.emplaceImpl!T(chunk, cast(E[])arg);
|
|
}
|
|
else static if (is(Arg : E))
|
|
{
|
|
//Case matching single element to array.
|
|
static if (!hasElaborateAssign!UT && is(typeof(chunk[] = arg)))
|
|
chunk[] = arg;
|
|
else static if (is(UArg == Unqual!E))
|
|
{
|
|
import core.stdc.string : memcpy;
|
|
//Note: We copy everything, and then postblit just once.
|
|
//This is as exception safe as what druntime can provide us.
|
|
foreach(i; 0 .. N)
|
|
memcpy(cast(void*)&(chunk[i]), &arg, E.sizeof);
|
|
static if (hasElaborateCopyConstructor!T)
|
|
typeid(T).postblit(cast(void*)&chunk);
|
|
}
|
|
else
|
|
//Alias this. Coerce.
|
|
.emplaceImpl!T(chunk, cast(E)arg);
|
|
}
|
|
else static if (is(typeof(.emplaceImpl!E(chunk[0], arg))))
|
|
{
|
|
//Final case for everything else:
|
|
//Types that don't match (int to uint[2])
|
|
//Recursion for multidimensions
|
|
static if (!hasElaborateAssign!UT && is(typeof(chunk[] = arg)))
|
|
chunk[] = arg;
|
|
else
|
|
foreach(i; 0 .. N)
|
|
.emplaceImpl!E(chunk[i], arg);
|
|
}
|
|
else
|
|
static assert(0, convFormat("Sorry, this implementation doesn't know how to emplace a %s with a %s", T.stringof, Arg.stringof));
|
|
|
|
return chunk;
|
|
}
|
|
else
|
|
{
|
|
chunk = arg;
|
|
return chunk;
|
|
}
|
|
}
|
|
// ditto
|
|
static if (is(T == struct))
|
|
ref UT emplaceImpl(Args...)(ref UT chunk, auto ref Args args)
|
|
{
|
|
static if (Args.length == 1 && is(Args[0] : T) &&
|
|
is (typeof({T t = args[0];})) //Check for legal postblit
|
|
)
|
|
{
|
|
static if (is(Unqual!T == Unqual!(Args[0])))
|
|
{
|
|
//Types match exactly: we postblit
|
|
static if (!hasElaborateAssign!UT && isAssignable!(UT, T))
|
|
chunk = args[0];
|
|
else
|
|
{
|
|
import core.stdc.string : memcpy;
|
|
memcpy(&chunk, &args[0], T.sizeof);
|
|
static if (hasElaborateCopyConstructor!T)
|
|
typeid(T).postblit(&chunk);
|
|
}
|
|
}
|
|
else
|
|
//Alias this. Coerce to type T.
|
|
.emplaceImpl!T(chunk, cast(T)args[0]);
|
|
}
|
|
else static if (is(typeof(chunk.__ctor(args))))
|
|
{
|
|
// T defines a genuine constructor accepting args
|
|
// Go the classic route: write .init first, then call ctor
|
|
emplaceInitializer(chunk);
|
|
chunk.__ctor(args);
|
|
}
|
|
else static if (is(typeof(T.opCall(args))))
|
|
{
|
|
//Can be built calling opCall
|
|
emplaceOpCaller(chunk, args); //emplaceOpCaller is deprecated
|
|
}
|
|
else static if (is(typeof(T(args))))
|
|
{
|
|
// Struct without constructor that has one matching field for
|
|
// each argument. Individually emplace each field
|
|
emplaceInitializer(chunk);
|
|
foreach (i, ref field; chunk.tupleof[0 .. Args.length])
|
|
{
|
|
alias Field = typeof(field);
|
|
alias UField = Unqual!Field;
|
|
static if (is(Field == UField))
|
|
.emplaceImpl!Field(field, args[i]);
|
|
else
|
|
.emplaceImpl!Field(*cast(Unqual!Field*)&field, args[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//We can't emplace. Try to diagnose a disabled postblit.
|
|
static assert(!(Args.length == 1 && is(Args[0] : T)),
|
|
convFormat("Cannot emplace a %1$s because %1$s.this(this) is annotated with @disable.", T.stringof));
|
|
|
|
//We can't emplace.
|
|
static assert(false,
|
|
convFormat("%s cannot be emplaced from %s.", T.stringof, Args[].stringof));
|
|
}
|
|
|
|
return chunk;
|
|
}
|
|
}
|
|
//emplace helper functions
|
|
private ref T emplaceInitializer(T)(ref T chunk) @trusted pure nothrow
|
|
{
|
|
static if (!hasElaborateAssign!T && isAssignable!T)
|
|
chunk = T.init;
|
|
else
|
|
{
|
|
import core.stdc.string : memcpy;
|
|
static immutable T init = T.init;
|
|
memcpy(&chunk, &init, T.sizeof);
|
|
}
|
|
return chunk;
|
|
}
|
|
private deprecated("Using static opCall for emplace is deprecated. Plase use emplace(chunk, T(args)) instead.")
|
|
ref T emplaceOpCaller(T, Args...)(ref T chunk, auto ref Args args)
|
|
{
|
|
static assert (is(typeof({T t = T.opCall(args);})),
|
|
convFormat("%s.opCall does not return adequate data for construction.", T.stringof));
|
|
return emplaceImpl!T(chunk, chunk.opCall(args));
|
|
}
|
|
|
|
|
|
// emplace
|
|
/**
|
|
Given a pointer $(D chunk) to uninitialized memory (but already typed
|
|
as $(D T)), constructs an object of non-$(D class) type $(D T) at that
|
|
address.
|
|
|
|
Returns: A pointer to the newly constructed object (which is the same
|
|
as $(D chunk)).
|
|
*/
|
|
T* emplace(T)(T* chunk) @safe pure nothrow
|
|
{
|
|
emplaceImpl!T(*chunk);
|
|
return chunk;
|
|
}
|
|
|
|
/**
|
|
Given a pointer $(D chunk) to uninitialized memory (but already typed
|
|
as a non-class type $(D T)), constructs an object of type $(D T) at
|
|
that address from arguments $(D args).
|
|
|
|
This function can be $(D @trusted) if the corresponding constructor of
|
|
$(D T) is $(D @safe).
|
|
|
|
Returns: A pointer to the newly constructed object (which is the same
|
|
as $(D chunk)).
|
|
*/
|
|
T* emplace(T, Args...)(T* chunk, auto ref Args args)
|
|
if (!is(T == struct) && Args.length == 1)
|
|
{
|
|
emplaceImpl!T(*chunk, args);
|
|
return chunk;
|
|
}
|
|
/// ditto
|
|
T* emplace(T, Args...)(T* chunk, auto ref Args args)
|
|
if (is(T == struct))
|
|
{
|
|
emplaceImpl!T(*chunk, args);
|
|
return chunk;
|
|
}
|
|
|
|
version(unittest) private struct __conv_EmplaceTest
|
|
{
|
|
int i = 3;
|
|
this(int i)
|
|
{
|
|
assert(this.i == 3 && i == 5);
|
|
this.i = i;
|
|
}
|
|
this(int i, ref int j)
|
|
{
|
|
assert(i == 5 && j == 6);
|
|
this.i = i;
|
|
++j;
|
|
}
|
|
|
|
@disable:
|
|
this();
|
|
this(this);
|
|
void opAssign();
|
|
}
|
|
|
|
version(unittest) private class __conv_EmplaceTestClass
|
|
{
|
|
int i = 3;
|
|
this(int i) @nogc @safe pure nothrow
|
|
{
|
|
assert(this.i == 3 && i == 5);
|
|
this.i = i;
|
|
}
|
|
this(int i, ref int j) @nogc @safe pure nothrow
|
|
{
|
|
assert(i == 5 && j == 6);
|
|
this.i = i;
|
|
++j;
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
struct S { @disable this(); }
|
|
S s = void;
|
|
static assert(!__traits(compiles, emplace(&s)));
|
|
static assert( __traits(compiles, emplace(&s, S.init)));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
interface I {}
|
|
class K : I {}
|
|
|
|
K k = void;
|
|
emplace(&k);
|
|
assert(k is null);
|
|
|
|
I i = void;
|
|
emplace(&i);
|
|
assert(i is null);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
static struct S {int i = 5;}
|
|
S[2] s2 = void;
|
|
emplace(&s2);
|
|
assert(s2[0].i == 5 && s2[1].i == 5);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
struct S1
|
|
{}
|
|
|
|
struct S2
|
|
{
|
|
void opAssign(S2);
|
|
}
|
|
|
|
S1 s1 = void;
|
|
S2 s2 = void;
|
|
S1[2] as1 = void;
|
|
S2[2] as2 = void;
|
|
emplace(&s1);
|
|
emplace(&s2);
|
|
emplace(&as1);
|
|
emplace(&as2);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
static struct S1
|
|
{
|
|
this(this) @disable;
|
|
}
|
|
static struct S2
|
|
{
|
|
this() @disable;
|
|
}
|
|
S1[2] ss1 = void;
|
|
S2[2] ss2 = void;
|
|
static assert( __traits(compiles, emplace(&ss1)));
|
|
static assert(!__traits(compiles, emplace(&ss2)));
|
|
S1 s1 = S1.init;
|
|
S2 s2 = S2.init;
|
|
static assert(!__traits(compiles, emplace(&ss1, s1)));
|
|
static assert( __traits(compiles, emplace(&ss2, s2)));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
struct S
|
|
{
|
|
immutable int i;
|
|
}
|
|
S s = void;
|
|
S[2] ss1 = void;
|
|
S[2] ss2 = void;
|
|
emplace(&s, 5);
|
|
emplace(&ss1, s);
|
|
emplace(&ss2, ss1);
|
|
}
|
|
|
|
//Start testing emplace-args here
|
|
|
|
unittest
|
|
{
|
|
int a;
|
|
int b = 42;
|
|
assert(*emplace!int(&a, b) == 42);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
interface I {}
|
|
class K : I {}
|
|
|
|
K k = null, k2 = new K;
|
|
assert(k !is k2);
|
|
emplace!K(&k, k2);
|
|
assert(k is k2);
|
|
|
|
I i = null;
|
|
assert(i !is k);
|
|
emplace!I(&i, k);
|
|
assert(i is k);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
static struct S
|
|
{
|
|
int i = 5;
|
|
void opAssign(S){assert(0);}
|
|
}
|
|
S[2] sa = void;
|
|
S[2] sb;
|
|
emplace(&sa, sb);
|
|
assert(sa[0].i == 5 && sa[1].i == 5);
|
|
}
|
|
|
|
//Start testing emplace-struct here
|
|
|
|
// Test constructor branch
|
|
unittest
|
|
{
|
|
struct S
|
|
{
|
|
double x = 5, y = 6;
|
|
this(int a, int b)
|
|
{
|
|
assert(x == 5 && y == 6);
|
|
x = a;
|
|
y = b;
|
|
}
|
|
}
|
|
|
|
auto s1 = new void[S.sizeof];
|
|
auto s2 = S(42, 43);
|
|
assert(*emplace!S(cast(S*) s1.ptr, s2) == s2);
|
|
assert(*emplace!S(cast(S*) s1, 44, 45) == S(44, 45));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
__conv_EmplaceTest k = void;
|
|
emplace(&k, 5);
|
|
assert(k.i == 5);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
int var = 6;
|
|
__conv_EmplaceTest k = void;
|
|
emplace(&k, 5, var);
|
|
assert(k.i == 5);
|
|
assert(var == 7);
|
|
}
|
|
|
|
// Test matching fields branch
|
|
unittest
|
|
{
|
|
struct S { uint n; }
|
|
S s;
|
|
emplace!S(&s, 2U);
|
|
assert(s.n == 2);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
struct S { int a, b; this(int){} }
|
|
S s;
|
|
static assert(!__traits(compiles, emplace!S(&s, 2, 3)));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
struct S { int a, b = 7; }
|
|
S s1 = void, s2 = void;
|
|
|
|
emplace!S(&s1, 2);
|
|
assert(s1.a == 2 && s1.b == 7);
|
|
|
|
emplace!S(&s2, 2, 3);
|
|
assert(s2.a == 2 && s2.b == 3);
|
|
}
|
|
|
|
//opAssign
|
|
unittest
|
|
{
|
|
static struct S
|
|
{
|
|
int i = 5;
|
|
void opAssign(int){assert(0);}
|
|
void opAssign(S){assert(0);}
|
|
}
|
|
S sa1 = void;
|
|
S sa2 = void;
|
|
S sb1 = S(1);
|
|
emplace(&sa1, sb1);
|
|
emplace(&sa2, 2);
|
|
assert(sa1.i == 1);
|
|
assert(sa2.i == 2);
|
|
}
|
|
|
|
//postblit precedence
|
|
unittest
|
|
{
|
|
//Works, but breaks in "-w -O" because of @@@9332@@@.
|
|
//Uncomment test when 9332 is fixed.
|
|
static struct S
|
|
{
|
|
int i;
|
|
|
|
this(S other){assert(false);}
|
|
this(int i){this.i = i;}
|
|
this(this){}
|
|
}
|
|
S a = void;
|
|
assert(is(typeof({S b = a;}))); //Postblit
|
|
assert(is(typeof({S b = S(a);}))); //Constructor
|
|
auto b = S(5);
|
|
emplace(&a, b);
|
|
assert(a.i == 5);
|
|
|
|
static struct S2
|
|
{
|
|
int* p;
|
|
this(const S2){};
|
|
}
|
|
static assert(!is(immutable S2 : S2));
|
|
S2 s2 = void;
|
|
immutable is2 = (immutable S2).init;
|
|
emplace(&s2, is2);
|
|
}
|
|
|
|
//nested structs and postblit
|
|
unittest
|
|
{
|
|
static struct S
|
|
{
|
|
int* p;
|
|
this(int i){p = [i].ptr;}
|
|
this(this)
|
|
{
|
|
if (p)
|
|
p = [*p].ptr;
|
|
}
|
|
}
|
|
static struct SS
|
|
{
|
|
S s;
|
|
void opAssign(const SS)
|
|
{
|
|
assert(0);
|
|
}
|
|
}
|
|
SS ssa = void;
|
|
SS ssb = SS(S(5));
|
|
emplace(&ssa, ssb);
|
|
assert(*ssa.s.p == 5);
|
|
assert(ssa.s.p != ssb.s.p);
|
|
}
|
|
|
|
//disabled postblit
|
|
unittest
|
|
{
|
|
static struct S1
|
|
{
|
|
int i;
|
|
@disable this(this);
|
|
}
|
|
S1 s1 = void;
|
|
static assert( __traits(compiles, emplace(&s1, 1)));
|
|
static assert(!__traits(compiles, emplace(&s1, S1.init)));
|
|
|
|
static struct S2
|
|
{
|
|
int i;
|
|
@disable this(this);
|
|
this(ref S2){}
|
|
}
|
|
S2 s2 = void;
|
|
static assert(!__traits(compiles, emplace(&s2, 1)));
|
|
static assert( __traits(compiles, emplace(&s2, S2.init)));
|
|
|
|
static struct SS1
|
|
{
|
|
S1 s;
|
|
}
|
|
SS1 ss1 = void;
|
|
static assert( __traits(compiles, emplace(&ss1)));
|
|
static assert(!__traits(compiles, emplace(&ss1, SS1.init)));
|
|
|
|
static struct SS2
|
|
{
|
|
S2 s;
|
|
}
|
|
SS2 ss2 = void;
|
|
static assert( __traits(compiles, emplace(&ss2)));
|
|
static assert(!__traits(compiles, emplace(&ss2, SS2.init)));
|
|
|
|
|
|
// SS1 sss1 = s1; //This doesn't compile
|
|
// SS1 sss1 = SS1(s1); //This doesn't compile
|
|
// So emplace shouldn't compile either
|
|
static assert(!__traits(compiles, emplace(&sss1, s1)));
|
|
static assert(!__traits(compiles, emplace(&sss2, s2)));
|
|
}
|
|
|
|
//Imutability
|
|
unittest
|
|
{
|
|
//Castable immutability
|
|
{
|
|
static struct S1
|
|
{
|
|
int i;
|
|
}
|
|
static assert(is( immutable(S1) : S1));
|
|
S1 sa = void;
|
|
auto sb = immutable(S1)(5);
|
|
emplace(&sa, sb);
|
|
assert(sa.i == 5);
|
|
}
|
|
//Un-castable immutability
|
|
{
|
|
static struct S2
|
|
{
|
|
int* p;
|
|
}
|
|
static assert(!is(immutable(S2) : S2));
|
|
S2 sa = void;
|
|
auto sb = immutable(S2)(null);
|
|
assert(!__traits(compiles, emplace(&sa, sb)));
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
static struct S
|
|
{
|
|
immutable int i;
|
|
immutable(int)* j;
|
|
}
|
|
S s = void;
|
|
emplace(&s, 1, null);
|
|
emplace(&s, 2, &s.i);
|
|
assert(s is S(2, &s.i));
|
|
}
|
|
|
|
//Context pointer
|
|
unittest
|
|
{
|
|
int i = 0;
|
|
{
|
|
struct S1
|
|
{
|
|
void foo(){++i;}
|
|
}
|
|
S1 sa = void;
|
|
S1 sb;
|
|
emplace(&sa, sb);
|
|
sa.foo();
|
|
assert(i == 1);
|
|
}
|
|
{
|
|
struct S2
|
|
{
|
|
void foo(){++i;}
|
|
this(this){}
|
|
}
|
|
S2 sa = void;
|
|
S2 sb;
|
|
emplace(&sa, sb);
|
|
sa.foo();
|
|
assert(i == 2);
|
|
}
|
|
|
|
////NOTE: THESE WILL COMPILE
|
|
////But will not correctly emplace the context pointer
|
|
////The problem lies with voldemort, and not emplace.
|
|
//{
|
|
// struct S3
|
|
// {
|
|
// int k;
|
|
// void foo(){++i;}
|
|
// }
|
|
//}
|
|
//S3 s3 = void;
|
|
//emplace(&s3); //S3.init has no context pointer information
|
|
//emplace(&s3, 1); //No way to obtain context pointer once inside emplace
|
|
}
|
|
|
|
//Alias this
|
|
unittest
|
|
{
|
|
static struct S
|
|
{
|
|
int i;
|
|
}
|
|
//By Ref
|
|
{
|
|
static struct SS1
|
|
{
|
|
int j;
|
|
S s;
|
|
alias s this;
|
|
}
|
|
S s = void;
|
|
SS1 ss = SS1(1, S(2));
|
|
emplace(&s, ss);
|
|
assert(s.i == 2);
|
|
}
|
|
//By Value
|
|
{
|
|
static struct SS2
|
|
{
|
|
int j;
|
|
S s;
|
|
S foo() @property{return s;}
|
|
alias foo this;
|
|
}
|
|
S s = void;
|
|
SS2 ss = SS2(1, S(2));
|
|
emplace(&s, ss);
|
|
assert(s.i == 2);
|
|
}
|
|
}
|
|
version(unittest)
|
|
{
|
|
//Ambiguity
|
|
struct __std_conv_S
|
|
{
|
|
int i;
|
|
this(__std_conv_SS ss) {assert(0);}
|
|
static opCall(__std_conv_SS ss)
|
|
{
|
|
__std_conv_S s; s.i = ss.j;
|
|
return s;
|
|
}
|
|
}
|
|
struct __std_conv_SS
|
|
{
|
|
int j;
|
|
__std_conv_S s;
|
|
ref __std_conv_S foo() return @property {s.i = j; return s;}
|
|
alias foo this;
|
|
}
|
|
static assert(is(__std_conv_SS : __std_conv_S));
|
|
unittest
|
|
{
|
|
__std_conv_S s = void;
|
|
__std_conv_SS ss = __std_conv_SS(1);
|
|
|
|
__std_conv_S sTest1 = ss; //this calls "SS alias this" (and not "S.this(SS)")
|
|
emplace(&s, ss); //"alias this" should take precedence in emplace over "opCall"
|
|
assert(s.i == 1);
|
|
}
|
|
}
|
|
|
|
//Nested classes
|
|
unittest
|
|
{
|
|
class A{}
|
|
static struct S
|
|
{
|
|
A a;
|
|
}
|
|
S s1 = void;
|
|
S s2 = S(new A);
|
|
emplace(&s1, s2);
|
|
assert(s1.a is s2.a);
|
|
}
|
|
|
|
//safety & nothrow & CTFE
|
|
unittest
|
|
{
|
|
//emplace should be safe for anything with no elaborate opassign
|
|
static struct S1
|
|
{
|
|
int i;
|
|
}
|
|
static struct S2
|
|
{
|
|
int i;
|
|
this(int j)@safe nothrow{i = j;}
|
|
}
|
|
|
|
int i;
|
|
S1 s1 = void;
|
|
S2 s2 = void;
|
|
|
|
auto pi = &i;
|
|
auto ps1 = &s1;
|
|
auto ps2 = &s2;
|
|
|
|
void foo() @safe nothrow
|
|
{
|
|
emplace(pi);
|
|
emplace(pi, 5);
|
|
emplace(ps1);
|
|
emplace(ps1, 5);
|
|
emplace(ps1, S1.init);
|
|
emplace(ps2);
|
|
emplace(ps2, 5);
|
|
emplace(ps2, S2.init);
|
|
}
|
|
|
|
T bar(T)() @property
|
|
{
|
|
T t/+ = void+/; //CTFE void illegal
|
|
emplace(&t, 5);
|
|
return t;
|
|
}
|
|
enum a = bar!int;
|
|
enum b = bar!S1;
|
|
enum c = bar!S2;
|
|
}
|
|
|
|
|
|
unittest
|
|
{
|
|
struct S
|
|
{
|
|
int[2] get(){return [1, 2];}
|
|
alias get this;
|
|
}
|
|
struct SS
|
|
{
|
|
int[2] ii;
|
|
}
|
|
struct ISS
|
|
{
|
|
int[2] ii;
|
|
}
|
|
S s;
|
|
SS ss = void;
|
|
ISS iss = void;
|
|
emplace(&ss, s);
|
|
emplace(&iss, s);
|
|
assert(ss.ii == [1, 2]);
|
|
assert(iss.ii == [1, 2]);
|
|
}
|
|
|
|
//disable opAssign
|
|
unittest
|
|
{
|
|
static struct S
|
|
{
|
|
@disable void opAssign(S);
|
|
}
|
|
S s;
|
|
emplace(&s, S.init);
|
|
}
|
|
|
|
//opCall
|
|
unittest
|
|
{
|
|
int i;
|
|
//Without constructor
|
|
{
|
|
static struct S1
|
|
{
|
|
int i;
|
|
static S1 opCall(int*){assert(0);}
|
|
}
|
|
S1 s = void;
|
|
static assert(!__traits(compiles, emplace(&s, 1)));
|
|
static assert( __traits(compiles, emplace(&s, &i))); //(works, but deprected)
|
|
}
|
|
//With constructor
|
|
{
|
|
static struct S2
|
|
{
|
|
int i = 0;
|
|
static S2 opCall(int*){assert(0);}
|
|
static S2 opCall(int){assert(0);}
|
|
this(int i){this.i = i;}
|
|
}
|
|
S2 s = void;
|
|
static assert( __traits(compiles, emplace(&s, 1))); //(works, but deprected)
|
|
static assert( __traits(compiles, emplace(&s, &i))); //(works, but deprected)
|
|
emplace(&s, 1);
|
|
assert(s.i == 1);
|
|
}
|
|
//With postblit ambiguity
|
|
{
|
|
static struct S3
|
|
{
|
|
int i = 0;
|
|
static S3 opCall(ref S3){assert(0);}
|
|
}
|
|
S3 s = void;
|
|
static assert( __traits(compiles, emplace(&s, S3.init)));
|
|
}
|
|
}
|
|
|
|
unittest //@@@9559@@@
|
|
{
|
|
import std.algorithm : map;
|
|
import std.typecons : Nullable;
|
|
alias I = Nullable!int;
|
|
auto ints = [0, 1, 2].map!(i => i & 1 ? I.init : I(i))();
|
|
auto asArray = std.array.array(ints);
|
|
}
|
|
|
|
unittest //http://forum.dlang.org/thread/nxbdgtdlmwscocbiypjs@forum.dlang.org
|
|
{
|
|
import std.array : array;
|
|
import std.datetime : SysTime, UTC;
|
|
import std.math : isNaN;
|
|
|
|
static struct A
|
|
{
|
|
double i;
|
|
}
|
|
|
|
static struct B
|
|
{
|
|
invariant()
|
|
{
|
|
if(j == 0)
|
|
assert(a.i.isNaN(), "why is 'j' zero?? and i is not NaN?");
|
|
else
|
|
assert(!a.i.isNaN());
|
|
}
|
|
SysTime when; // comment this line avoid the breakage
|
|
int j;
|
|
A a;
|
|
}
|
|
|
|
B b1 = B.init;
|
|
assert(&b1); // verify that default eyes invariants are ok;
|
|
|
|
auto b2 = B(SysTime(0, UTC()), 1, A(1));
|
|
assert(&b2);
|
|
auto b3 = B(SysTime(0, UTC()), 1, A(1));
|
|
assert(&b3);
|
|
|
|
auto arr = [b2, b3];
|
|
|
|
assert(arr[0].j == 1);
|
|
assert(arr[1].j == 1);
|
|
auto a2 = arr.array(); // << bang, invariant is raised, also if b2 and b3 are good
|
|
}
|
|
|
|
//static arrays
|
|
unittest
|
|
{
|
|
static struct S
|
|
{
|
|
int[2] ii;
|
|
}
|
|
static struct IS
|
|
{
|
|
immutable int[2] ii;
|
|
}
|
|
int[2] ii;
|
|
S s = void;
|
|
IS ims = void;
|
|
ubyte ub = 2;
|
|
emplace(&s, ub);
|
|
emplace(&s, ii);
|
|
emplace(&ims, ub);
|
|
emplace(&ims, ii);
|
|
uint[2] uu;
|
|
static assert(!__traits(compiles, {S ss = S(uu);}));
|
|
static assert(!__traits(compiles, emplace(&s, uu)));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
int[2] sii;
|
|
int[2] sii2;
|
|
uint[2] uii;
|
|
uint[2] uii2;
|
|
emplace(&sii, 1);
|
|
emplace(&sii, 1U);
|
|
emplace(&uii, 1);
|
|
emplace(&uii, 1U);
|
|
emplace(&sii, sii2);
|
|
//emplace(&sii, uii2); //Sorry, this implementation doesn't know how to...
|
|
//emplace(&uii, sii2); //Sorry, this implementation doesn't know how to...
|
|
emplace(&uii, uii2);
|
|
emplace(&sii, sii2[]);
|
|
//emplace(&sii, uii2[]); //Sorry, this implementation doesn't know how to...
|
|
//emplace(&uii, sii2[]); //Sorry, this implementation doesn't know how to...
|
|
emplace(&uii, uii2[]);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
bool allowDestruction = false;
|
|
struct S
|
|
{
|
|
int i;
|
|
this(this){}
|
|
~this(){assert(allowDestruction);}
|
|
}
|
|
S s = S(1);
|
|
S[2] ss1 = void;
|
|
S[2] ss2 = void;
|
|
S[2] ss3 = void;
|
|
emplace(&ss1, s);
|
|
emplace(&ss2, ss1);
|
|
emplace(&ss3, ss2[]);
|
|
assert(ss1[1] == s);
|
|
assert(ss2[1] == s);
|
|
assert(ss3[1] == s);
|
|
allowDestruction = true;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
//Checks postblit, construction, and context pointer
|
|
int count = 0;
|
|
struct S
|
|
{
|
|
this(this)
|
|
{
|
|
++count;
|
|
}
|
|
~this()
|
|
{
|
|
--count;
|
|
}
|
|
}
|
|
|
|
S s;
|
|
{
|
|
S[4] ss = void;
|
|
emplace(&ss, s);
|
|
assert(count == 4);
|
|
}
|
|
assert(count == 0);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
struct S
|
|
{
|
|
int i;
|
|
}
|
|
S s;
|
|
S[2][2][2] sss = void;
|
|
emplace(&sss, s);
|
|
}
|
|
|
|
unittest //Constness
|
|
{
|
|
import std.stdio;
|
|
|
|
int a = void;
|
|
emplaceRef!(const int)(a, 5);
|
|
|
|
immutable i = 5;
|
|
const(int)* p = void;
|
|
emplaceRef!(const int*)(p, &i);
|
|
|
|
struct S
|
|
{
|
|
int* p;
|
|
}
|
|
alias IS = immutable(S);
|
|
S s = void;
|
|
emplaceRef!IS(s, IS());
|
|
S[2] ss = void;
|
|
emplaceRef!(IS[2])(ss, IS());
|
|
|
|
IS[2] iss = IS.init;
|
|
emplaceRef!(IS[2])(ss, iss);
|
|
emplaceRef!(IS[2])(ss, iss[]);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
int i;
|
|
emplaceRef(i);
|
|
emplaceRef!int(i);
|
|
emplaceRef(i, 5);
|
|
emplaceRef!int(i, 5);
|
|
}
|
|
|
|
private void testEmplaceChunk(void[] chunk, size_t typeSize, size_t typeAlignment, string typeName) @nogc pure nothrow
|
|
{
|
|
assert(chunk.length >= typeSize, "emplace: Chunk size too small.");
|
|
assert((cast(size_t)chunk.ptr) % typeAlignment == 0, "emplace: Chunk is not aligned.");
|
|
}
|
|
|
|
/**
|
|
Given a raw memory area $(D chunk), constructs an object of $(D class)
|
|
type $(D T) at that address. The constructor is passed the arguments
|
|
$(D Args). The $(D chunk) must be as least as large as $(D T) needs
|
|
and should have an alignment multiple of $(D T)'s alignment. (The size
|
|
of a $(D class) instance is obtained by using $(D
|
|
__traits(classInstanceSize, T))).
|
|
|
|
This function can be $(D @trusted) if the corresponding constructor of
|
|
$(D T) is $(D @safe).
|
|
|
|
Returns: A pointer to the newly constructed object.
|
|
*/
|
|
T emplace(T, Args...)(void[] chunk, auto ref Args args)
|
|
if (is(T == class))
|
|
{
|
|
enum classSize = __traits(classInstanceSize, T);
|
|
testEmplaceChunk(chunk, classSize, classInstanceAlignment!T, T.stringof);
|
|
auto result = cast(T) chunk.ptr;
|
|
|
|
// Initialize the object in its pre-ctor state
|
|
chunk[0 .. classSize] = typeid(T).init[];
|
|
|
|
// Call the ctor if any
|
|
static if (is(typeof(result.__ctor(args))))
|
|
{
|
|
// T defines a genuine constructor accepting args
|
|
// Go the classic route: write .init first, then call ctor
|
|
result.__ctor(args);
|
|
}
|
|
else
|
|
{
|
|
static assert(args.length == 0 && !is(typeof(&T.__ctor)),
|
|
"Don't know how to initialize an object of type "
|
|
~ T.stringof ~ " with arguments " ~ Args.stringof);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@nogc pure nothrow unittest
|
|
{
|
|
int var = 6;
|
|
ubyte[__traits(classInstanceSize, __conv_EmplaceTestClass)] buf;
|
|
auto k = emplace!__conv_EmplaceTestClass(buf, 5, var);
|
|
assert(k.i == 5);
|
|
assert(var == 7);
|
|
}
|
|
|
|
/**
|
|
Given a raw memory area $(D chunk), constructs an object of non-$(D
|
|
class) type $(D T) at that address. The constructor is passed the
|
|
arguments $(D args), if any. The $(D chunk) must be as least as large
|
|
as $(D T) needs and should have an alignment multiple of $(D T)'s
|
|
alignment.
|
|
|
|
This function can be $(D @trusted) if the corresponding constructor of
|
|
$(D T) is $(D @safe).
|
|
|
|
Returns: A pointer to the newly constructed object.
|
|
*/
|
|
T* emplace(T, Args...)(void[] chunk, auto ref Args args)
|
|
if (!is(T == class))
|
|
{
|
|
testEmplaceChunk(chunk, T.sizeof, T.alignof, T.stringof);
|
|
return emplace(cast(T*) chunk.ptr, args);
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
struct S
|
|
{
|
|
int a, b;
|
|
}
|
|
auto p = new void[S.sizeof];
|
|
S s;
|
|
s.a = 42;
|
|
s.b = 43;
|
|
auto s1 = emplace!S(p, s);
|
|
assert(s1.a == 42 && s1.b == 43);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
int var = 6;
|
|
auto k = emplace!__conv_EmplaceTest(new void[__conv_EmplaceTest.sizeof], 5, var);
|
|
assert(k.i == 5);
|
|
assert(var == 7);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
class A
|
|
{
|
|
int x = 5;
|
|
int y = 42;
|
|
this(int z)
|
|
{
|
|
assert(x == 5 && y == 42);
|
|
x = y = z;
|
|
}
|
|
}
|
|
void[] buf;
|
|
|
|
static byte[__traits(classInstanceSize, A)] sbuf;
|
|
buf = sbuf[];
|
|
auto a = emplace!A(buf, 55);
|
|
assert(a.x == 55 && a.y == 55);
|
|
|
|
// emplace in bigger buffer
|
|
buf = new byte[](__traits(classInstanceSize, A) + 10);
|
|
a = emplace!A(buf, 55);
|
|
assert(a.x == 55 && a.y == 55);
|
|
|
|
// need ctor args
|
|
static assert(!is(typeof(emplace!A(buf))));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.algorithm : equal, map;
|
|
// Check fix for http://d.puremagic.com/issues/show_bug.cgi?id=2971
|
|
assert(equal(map!(to!int)(["42", "34", "345"]), [42, 34, 345]));
|
|
}
|
|
|
|
// Undocumented for the time being
|
|
void toTextRange(T, W)(T value, W writer)
|
|
if (isIntegral!T && isOutputRange!(W, char))
|
|
{
|
|
char[value.sizeof * 4] buffer = void;
|
|
uint i = cast(uint) (buffer.length - 1);
|
|
|
|
bool negative = value < 0;
|
|
Unqual!(Unsigned!T) v = negative ? -value : value;
|
|
|
|
while (v >= 10)
|
|
{
|
|
auto c = cast(uint) (v % 10);
|
|
v /= 10;
|
|
buffer[i--] = cast(char) (c + '0');
|
|
}
|
|
|
|
buffer[i] = cast(char) (v + '0'); //hexDigits[cast(uint) v];
|
|
if (negative)
|
|
buffer[--i] = '-';
|
|
put(writer, buffer[i .. $]);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.array : appender;
|
|
auto result = appender!(char[])();
|
|
toTextRange(-1, result);
|
|
assert(result.data == "-1");
|
|
}
|
|
|
|
|
|
/**
|
|
Returns the corresponding _unsigned value for $(D x) (e.g. if $(D x) has type
|
|
$(D int), it returns $(D cast(uint) x)). The advantage compared to the cast
|
|
is that you do not need to rewrite the cast if $(D x) later changes type
|
|
(e.g from $(D int) to $(D long)).
|
|
|
|
Note that the result is always mutable even if the original type was const
|
|
or immutable. In order to retain the constness, use $(XREF traits, Unsigned).
|
|
*/
|
|
auto unsigned(T)(T x) if (isIntegral!T)
|
|
{
|
|
return cast(Unqual!(Unsigned!T))x;
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
immutable int s = 42;
|
|
auto u1 = unsigned(s); //not qualified
|
|
static assert(is(typeof(u1) == uint));
|
|
Unsigned!(typeof(s)) u2 = unsigned(s); //same qualification
|
|
static assert(is(typeof(u2) == immutable uint));
|
|
immutable u3 = unsigned(s); //explicitly qualified
|
|
}
|
|
|
|
unittest
|
|
{
|
|
foreach(T; TypeTuple!(byte, ubyte))
|
|
{
|
|
static assert(is(typeof(unsigned(cast(T)1)) == ubyte));
|
|
static assert(is(typeof(unsigned(cast(const T)1)) == ubyte));
|
|
static assert(is(typeof(unsigned(cast(immutable T)1)) == ubyte));
|
|
}
|
|
|
|
foreach(T; TypeTuple!(short, ushort))
|
|
{
|
|
static assert(is(typeof(unsigned(cast(T)1)) == ushort));
|
|
static assert(is(typeof(unsigned(cast(const T)1)) == ushort));
|
|
static assert(is(typeof(unsigned(cast(immutable T)1)) == ushort));
|
|
}
|
|
|
|
foreach(T; TypeTuple!(int, uint))
|
|
{
|
|
static assert(is(typeof(unsigned(cast(T)1)) == uint));
|
|
static assert(is(typeof(unsigned(cast(const T)1)) == uint));
|
|
static assert(is(typeof(unsigned(cast(immutable T)1)) == uint));
|
|
}
|
|
|
|
foreach(T; TypeTuple!(long, ulong))
|
|
{
|
|
static assert(is(typeof(unsigned(cast(T)1)) == ulong));
|
|
static assert(is(typeof(unsigned(cast(const T)1)) == ulong));
|
|
static assert(is(typeof(unsigned(cast(immutable T)1)) == ulong));
|
|
}
|
|
}
|
|
|
|
auto unsigned(T)(T x) if (isSomeChar!T)
|
|
{
|
|
// All characters are unsigned
|
|
static assert(T.min == 0);
|
|
return cast(Unqual!T) x;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
foreach(T; TypeTuple!(char, wchar, dchar))
|
|
{
|
|
static assert(is(typeof(unsigned(cast(T)'A')) == T));
|
|
static assert(is(typeof(unsigned(cast(const T)'A')) == T));
|
|
static assert(is(typeof(unsigned(cast(immutable T)'A')) == T));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Returns the corresponding _signed value for $(D x) (e.g. if $(D x) has type
|
|
$(D uint), it returns $(D cast(int) x)). The advantage compared to the cast
|
|
is that you do not need to rewrite the cast if $(D x) later changes type
|
|
(e.g from $(D uint) to $(D ulong)).
|
|
|
|
Note that the result is always mutable even if the original type was const
|
|
or immutable. In order to retain the constness, use $(XREF traits, Signed).
|
|
*/
|
|
auto signed(T)(T x) if (isIntegral!T)
|
|
{
|
|
return cast(Unqual!(Signed!T))x;
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
immutable uint u = 42;
|
|
auto s1 = signed(u); //not qualified
|
|
static assert(is(typeof(s1) == int));
|
|
Signed!(typeof(u)) s2 = signed(u); //same qualification
|
|
static assert(is(typeof(s2) == immutable int));
|
|
immutable s3 = signed(u); //explicitly qualified
|
|
}
|
|
|
|
unittest
|
|
{
|
|
foreach(T; TypeTuple!(byte, ubyte))
|
|
{
|
|
static assert(is(typeof(signed(cast(T)1)) == byte));
|
|
static assert(is(typeof(signed(cast(const T)1)) == byte));
|
|
static assert(is(typeof(signed(cast(immutable T)1)) == byte));
|
|
}
|
|
|
|
foreach(T; TypeTuple!(short, ushort))
|
|
{
|
|
static assert(is(typeof(signed(cast(T)1)) == short));
|
|
static assert(is(typeof(signed(cast(const T)1)) == short));
|
|
static assert(is(typeof(signed(cast(immutable T)1)) == short));
|
|
}
|
|
|
|
foreach(T; TypeTuple!(int, uint))
|
|
{
|
|
static assert(is(typeof(signed(cast(T)1)) == int));
|
|
static assert(is(typeof(signed(cast(const T)1)) == int));
|
|
static assert(is(typeof(signed(cast(immutable T)1)) == int));
|
|
}
|
|
|
|
foreach(T; TypeTuple!(long, ulong))
|
|
{
|
|
static assert(is(typeof(signed(cast(T)1)) == long));
|
|
static assert(is(typeof(signed(cast(const T)1)) == long));
|
|
static assert(is(typeof(signed(cast(immutable T)1)) == long));
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// issue 10874
|
|
enum Test { a = 0 }
|
|
ulong l = 0;
|
|
auto t = l.to!Test;
|
|
}
|
|
|
|
/**
|
|
A wrapper on top of the built-in cast operator that allows one to restrict
|
|
casting of the original type of the value.
|
|
|
|
A common issue with using a raw cast is that it may silently continue to
|
|
compile even if the value's type has changed during refactoring,
|
|
which breaks the initial assumption about the cast.
|
|
|
|
Params:
|
|
From = The type to cast from. The programmer must ensure it is legal
|
|
to make this cast.
|
|
*/
|
|
template castFrom(From)
|
|
{
|
|
/**
|
|
Params:
|
|
To = The type _to cast _to.
|
|
value = The value _to cast. It must be of type $(D From),
|
|
otherwise a compile-time error is emitted.
|
|
|
|
Returns:
|
|
the value after the cast, returned by reference if possible.
|
|
*/
|
|
auto ref to(To, T)(auto ref T value) @system
|
|
{
|
|
static assert (
|
|
is(From == T),
|
|
"the value to cast is not of specified type '" ~ From.stringof ~
|
|
"', it is of type '" ~ T.stringof ~ "'"
|
|
);
|
|
|
|
static assert (
|
|
is(typeof(cast(To)value)),
|
|
"can't cast from '" ~ From.stringof ~ "' to '" ~ To.stringof ~ "'"
|
|
);
|
|
|
|
return cast(To) value;
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
// Regular cast, which has been verified to be legal by the programmer:
|
|
{
|
|
long x;
|
|
auto y = cast(int) x;
|
|
}
|
|
|
|
// However this will still compile if 'x' is changed to be a pointer:
|
|
{
|
|
long* x;
|
|
auto y = cast(int) x;
|
|
}
|
|
|
|
// castFrom provides a more reliable alternative to casting:
|
|
{
|
|
long x;
|
|
auto y = castFrom!long.to!int(x);
|
|
}
|
|
|
|
// Changing the type of 'x' will now issue a compiler error,
|
|
// allowing bad casts to be caught before it's too late:
|
|
{
|
|
long* x;
|
|
static assert (
|
|
!__traits(compiles, castFrom!long.to!int(x))
|
|
);
|
|
|
|
// if cast is still needed, must be changed to:
|
|
auto y = castFrom!(long*).to!int(x);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Check the correctness of a string for $(D hexString).
|
|
The result is true if and only if the input string is composed of whitespace
|
|
characters (\f\n\r\t\v lineSep paraSep nelSep) and
|
|
an even number of hexadecimal digits (regardless of the case).
|
|
*/
|
|
private bool isHexLiteral(String)(in String hexData)
|
|
{
|
|
import std.ascii : isHexDigit;
|
|
import std.uni : lineSep, paraSep, nelSep;
|
|
size_t i;
|
|
foreach(const dchar c; hexData)
|
|
{
|
|
switch (c)
|
|
{
|
|
case ' ':
|
|
case '\t':
|
|
case '\v':
|
|
case '\f':
|
|
case '\r':
|
|
case '\n':
|
|
case lineSep:
|
|
case paraSep:
|
|
case nelSep:
|
|
continue;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
if (c.isHexDigit)
|
|
++i;
|
|
else
|
|
return false;
|
|
}
|
|
return !(i & 1);
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
// test all the hex digits
|
|
static assert( ("0123456789abcdefABCDEF").isHexLiteral);
|
|
// empty or white strings are not valid
|
|
static assert( "\r\n\t".isHexLiteral);
|
|
// but are accepted if the count of hex digits is even
|
|
static assert( "A\r\n\tB".isHexLiteral);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.ascii;
|
|
// empty/whites
|
|
static assert( "".isHexLiteral);
|
|
static assert( " \r".isHexLiteral);
|
|
static assert( whitespace.isHexLiteral);
|
|
static assert( ""w.isHexLiteral);
|
|
static assert( " \r"w.isHexLiteral);
|
|
static assert( ""d.isHexLiteral);
|
|
static assert( " \r"d.isHexLiteral);
|
|
static assert( "\u2028\u2029\u0085"d.isHexLiteral);
|
|
// odd x strings
|
|
static assert( !("5" ~ whitespace).isHexLiteral);
|
|
static assert( !"123".isHexLiteral);
|
|
static assert( !"1A3".isHexLiteral);
|
|
static assert( !"1 23".isHexLiteral);
|
|
static assert( !"\r\n\tC".isHexLiteral);
|
|
static assert( !"123"w.isHexLiteral);
|
|
static assert( !"1A3"w.isHexLiteral);
|
|
static assert( !"1 23"w.isHexLiteral);
|
|
static assert( !"\r\n\tC"w.isHexLiteral);
|
|
static assert( !"123"d.isHexLiteral);
|
|
static assert( !"1A3"d.isHexLiteral);
|
|
static assert( !"1 23"d.isHexLiteral);
|
|
static assert( !"\r\n\tC"d.isHexLiteral);
|
|
// even x strings with invalid charset
|
|
static assert( !"12gG".isHexLiteral);
|
|
static assert( !"2A 3q".isHexLiteral);
|
|
static assert( !"12gG"w.isHexLiteral);
|
|
static assert( !"2A 3q"w.isHexLiteral);
|
|
static assert( !"12gG"d.isHexLiteral);
|
|
static assert( !"2A 3q"d.isHexLiteral);
|
|
// valid x strings
|
|
static assert( ("5A" ~ whitespace).isHexLiteral);
|
|
static assert( ("5A 01A C FF de 1b").isHexLiteral);
|
|
static assert( ("0123456789abcdefABCDEF").isHexLiteral);
|
|
static assert( (" 012 34 5 6789 abcd ef\rAB\nCDEF").isHexLiteral);
|
|
static assert( ("5A 01A C FF de 1b"w).isHexLiteral);
|
|
static assert( ("0123456789abcdefABCDEF"w).isHexLiteral);
|
|
static assert( (" 012 34 5 6789 abcd ef\rAB\nCDEF"w).isHexLiteral);
|
|
static assert( ("5A 01A C FF de 1b"d).isHexLiteral);
|
|
static assert( ("0123456789abcdefABCDEF"d).isHexLiteral);
|
|
static assert( (" 012 34 5 6789 abcd ef\rAB\nCDEF"d).isHexLiteral);
|
|
// library version allows what's pointed by issue 10454
|
|
static assert( ("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").isHexLiteral);
|
|
}
|
|
|
|
/**
|
|
Converts a hex literal to a string at compile time.
|
|
|
|
Takes a string made of hexadecimal digits and returns
|
|
the matching string by converting each pair of digits to a character.
|
|
The input string can also include white characters, which can be used
|
|
to keep the literal string readable in the source code.
|
|
|
|
The function is intended to replace the hexadecimal literal strings
|
|
starting with $(D 'x'), which could be removed to simplify the core language.
|
|
|
|
Params:
|
|
hexData = string to be converted.
|
|
|
|
Returns:
|
|
a $(D string), a $(D wstring) or a $(D dstring), according to the type of hexData.
|
|
*/
|
|
template hexString(string hexData)
|
|
if (hexData.isHexLiteral)
|
|
{
|
|
immutable hexString = hexStrImpl(hexData);
|
|
}
|
|
|
|
/// ditto
|
|
template hexString(wstring hexData)
|
|
if (hexData.isHexLiteral)
|
|
{
|
|
immutable hexString = hexStrImpl(hexData);
|
|
}
|
|
|
|
/// ditto
|
|
template hexString(dstring hexData)
|
|
if (hexData.isHexLiteral)
|
|
{
|
|
immutable hexString = hexStrImpl(hexData);
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
// conversion at compile time
|
|
auto string1 = hexString!"304A314B";
|
|
assert(string1 == "0J1K");
|
|
auto string2 = hexString!"304A314B"w;
|
|
assert(string2 == "0J1K"w);
|
|
auto string3 = hexString!"304A314B"d;
|
|
assert(string3 == "0J1K"d);
|
|
}
|
|
|
|
/*
|
|
Takes a hexadecimal string literal and returns its representation.
|
|
hexData is granted to be a valid string by the caller.
|
|
C is granted to be a valid char type by the caller.
|
|
*/
|
|
@safe nothrow pure
|
|
private auto hexStrImpl(String)(String hexData)
|
|
{
|
|
import std.ascii;
|
|
alias Unqual!(ElementEncodingType!String) C;
|
|
C[] result;
|
|
result.length = hexData.length / 2;
|
|
size_t cnt;
|
|
ubyte v;
|
|
foreach(c; hexData)
|
|
{
|
|
if (c.isHexDigit)
|
|
{
|
|
ubyte x;
|
|
if (c >= '0' && c <= '9')
|
|
x = cast(ubyte)(c - '0');
|
|
else if (c >= 'a' && c <= 'f')
|
|
x = cast(ubyte)(c - ('a' - 10));
|
|
else if (c >= 'A' && c <= 'F')
|
|
x = cast(ubyte)(c - ('A' - 10));
|
|
if (cnt & 1)
|
|
{
|
|
v = cast(ubyte)((v << 4) | x);
|
|
result[cnt / 2] = v;
|
|
}
|
|
else
|
|
v = x;
|
|
++cnt;
|
|
}
|
|
}
|
|
result.length = cnt / 2;
|
|
return result;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// compile time
|
|
assert(hexString!"46 47 48 49 4A 4B" == "FGHIJK");
|
|
assert(hexString!"30\r\n\t\f\v31 32 33 32 31 30" == "0123210");
|
|
assert(hexString!"ab cd" == hexString!"ABCD");
|
|
}
|