blob: 8f6c3bf568e572faea9094f35982b1c0da5c3018 [file] [log] [blame]
// Written in the D programming language.
/**
A one-stop shop for converting values from one type to another.
$(SCRIPT inhibitQuickIndex = 1;)
$(DIVC quickindex,
$(BOOKTABLE,
$(TR $(TH Category) $(TH Functions))
$(TR $(TD Generic) $(TD
$(LREF asOriginalType)
$(LREF castFrom)
$(LREF parse)
$(LREF to)
$(LREF toChars)
))
$(TR $(TD Strings) $(TD
$(LREF text)
$(LREF wtext)
$(LREF dtext)
$(LREF hexString)
))
$(TR $(TD Numeric) $(TD
$(LREF octal)
$(LREF roundTo)
$(LREF signed)
$(LREF unsigned)
))
$(TR $(TD Exceptions) $(TD
$(LREF ConvException)
$(LREF ConvOverflowException)
))
))
Copyright: Copyright The D Language Foundation 2007-.
License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
Authors: $(HTTP digitalmars.com, Walter Bright),
$(HTTP erdani.org, Andrei Alexandrescu),
Shin Fujishiro,
Adam D. Ruppe,
Kenji Hara
Source: $(PHOBOSSRC std/conv.d)
*/
module std.conv;
public import std.ascii : LetterCase;
import std.meta;
import std.range;
import std.traits;
import std.typecons : Flag, Yes, No, tuple, isTuple;
// Same as std.string.format, but "self-importing".
// Helps reduce code and imports, particularly in static asserts.
// Also helps with missing imports errors.
package template convFormat()
{
import std.format : format;
alias convFormat = format;
}
/* ************* Exceptions *************** */
/**
* Thrown on conversion errors.
*/
class ConvException : Exception
{
import std.exception : basicExceptionCtors;
///
mixin basicExceptionCtors;
}
///
@safe unittest
{
import std.exception : assertThrown;
assertThrown!ConvException(to!int("abc"));
}
private auto convError(S, T)(S source, string fn = __FILE__, size_t ln = __LINE__)
{
string msg;
if (source.empty)
msg = "Unexpected end of input when converting from type " ~ S.stringof ~ " to type " ~ T.stringof;
else
{
ElementType!S el = source.front;
if (el == '\n')
msg = text("Unexpected '\\n' when converting from type " ~ S.stringof ~ " to type " ~ T.stringof);
else
msg = text("Unexpected '", el,
"' when converting from type " ~ S.stringof ~ " to type " ~ T.stringof);
}
return new ConvException(msg, fn, ln);
}
private auto convError(S, T)(S source, int radix, string fn = __FILE__, size_t ln = __LINE__)
{
string msg;
if (source.empty)
msg = text("Unexpected end of input when converting from type " ~ S.stringof ~ " base ", radix,
" to type " ~ T.stringof);
else
msg = text("Unexpected '", source.front,
"' when converting from type " ~ S.stringof ~ " base ", radix,
" to type " ~ T.stringof);
return new ConvException(msg, 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
{
T toStr(T, S)(S src)
if (isSomeString!T)
{
// workaround for https://issues.dlang.org/show_bug.cgi?id=14198
static if (is(S == bool) && is(typeof({ T s = "string"; })))
{
return src ? "true" : "false";
}
else
{
import std.array : appender;
import std.format.spec : FormatSpec;
import std.format.write : formatValue;
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(immutable S == immutable typeof(null))) && isExactSomeString!T;
}
}
/**
* 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);
}
}
///
@safe unittest
{
import std.exception : assertThrown;
assertThrown!ConvOverflowException(to!ubyte(1_000_000));
}
/**
The `to` template converts a value from one type _to another.
The source type is deduced and the target type must be specified, for example the
expression `to!int(42.0)` converts the number 42 from
`double` _to `int`. The conversion is "safe", i.e.,
it checks for overflow; `to!int(4.2e10)` would throw the
`ConvOverflowException` exception. Overflow checks are only
inserted when necessary, e.g., `to!double(42)` does not do
any checking because any `int` fits in a `double`.
Conversions from string _to numeric types differ from the C equivalents
`atoi()` and `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 -))
For conversion _to unsigned types, the grammar recognized is:
$(PRE $(I UnsignedInteger):
$(I DecimalDigit)
$(I DecimalDigit) $(I UnsignedInteger))
*/
template to(T)
{
T to(A...)(A args)
if (A.length > 0)
{
return toImpl!T(args);
}
// Fix issue 6175
T to(S)(ref S arg)
if (isStaticArray!S)
{
return toImpl!T(arg);
}
// Fix issue 16108
T to(S)(ref S arg)
if (isAggregateType!S && !isCopyable!S)
{
return toImpl!T(arg);
}
}
/**
* Converting a value _to its own type (useful mostly for generic code)
* simply returns its argument.
*/
@safe pure unittest
{
int a = 42;
int b = to!int(a);
double 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 `roundTo`.)
*/
@safe pure unittest
{
import std.exception : assertThrown;
int a = 420;
assert(to!long(a) == a);
assertThrown!ConvOverflowException(to!byte(a));
assert(to!int(4.2e6) == 4200000);
assertThrown!ConvOverflowException(to!uint(-3.14));
assert(to!uint(3.14) == 3);
assert(to!uint(3.99) == 3);
assert(to!int(-3.99) == -3);
}
/**
* When converting strings _to numeric types, note that the D hexadecimal and binary
* literals are not handled. Neither the prefixes that indicate the base, nor the
* horizontal bar used _to separate groups of digits are recognized. This also
* applies to the suffixes that indicate the type.
*
* _To work around this, you can specify a radix for conversions involving numbers.
*/
@safe pure unittest
{
auto str = to!string(42, 16);
assert(str == "2A");
auto i = to!int(str, 16);
assert(i == 42);
}
/**
* 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 `real` (when
* `real` is 80-bit, e.g. on Intel machines).
*/
@safe pure unittest
{
// 2^24 - 1, largest proper integer representable as float
int a = 16_777_215;
assert(to!int(to!float(a)) == a);
assert(to!int(to!float(-a)) == -a);
}
/**
Conversion from string types to char types enforces the input
to consist of a single code point, and said code point must
fit in the target type. Otherwise, $(LREF ConvException) is thrown.
*/
@safe pure unittest
{
import std.exception : assertThrown;
assert(to!char("a") == 'a');
assertThrown(to!char("ñ")); // 'ñ' does not fit into a char
assert(to!wchar("ñ") == 'ñ');
assertThrown(to!wchar("😃")); // '😃' does not fit into a wchar
assert(to!dchar("😃") == '😃');
// Using wstring or dstring as source type does not affect the result
assert(to!char("a"w) == 'a');
assert(to!char("a"d) == 'a');
// Two code points cannot be converted to a single one
assertThrown(to!char("ab"));
}
/**
* 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.
*/
@safe pure unittest
{
import std.string : split;
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.
*
* This conversion works because `to!short` applies _to an `int`, `to!wstring`
* applies _to a `string`, `to!string` applies _to a `double`, and
* `to!(double[])` applies _to an `int[]`. The conversion might throw an
* exception because `to!short` might fail the range check.
*/
@safe unittest
{
int[string][double[int[]]] a;
auto b = to!(short[wstring][string[double[]]])(a);
}
/**
* Object-to-object conversions by dynamic casting throw exception when
* the source is non-null and the target is null.
*/
@safe pure unittest
{
import std.exception : assertThrown;
// 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));
}
/**
* Stringize conversion from all types is supported.
* $(UL
* $(LI String _to string conversion works for any two string types having
* (`char`, `wchar`, `dchar`) character widths and any
* combination of qualifiers (mutable, `const`, or `immutable`).)
* $(LI Converts array (other than strings) _to string.
* Each element is converted by calling `to!T`.)
* $(LI Associative array _to string conversion.
* Each element is converted by calling `to!T`.)
* $(LI Object _to string conversion calls `toString` against the object or
* returns `"null"` if the object is null.)
* $(LI Struct _to string conversion calls `toString` against the struct if
* it is defined.)
* $(LI For structs that do not define `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 converted to `"true"` or `"false"`.)
* $(LI `char`, `wchar`, `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 convert the pointer to a `size_t` value.
* If pointer is `char*`, treat it as C-style strings.
* In that case, this function is `@system`.))
* See $(REF formatValue, std,format) on how toString should be defined.
*/
@system pure unittest // @system due to cast and ptr
{
// Conversion representing dynamic/static array with string
long[] a = [ 1, 3, 5 ];
assert(to!string(a) == "[1, 3, 5]");
// Conversion representing associative array with string
int[string] associativeArray = ["0":1, "1":2];
assert(to!string(associativeArray) == `["0":1, "1":2]` ||
to!string(associativeArray) == `["1":2, "0":1]`);
// char* to string conversion
assert(to!string(cast(char*) null) == "");
assert(to!string("foo\0".ptr) == "foo");
// Conversion reinterpreting void array to string
auto w = "abcx"w;
const(void)[] b = w;
assert(b.length == 8);
auto c = to!(wchar[])(b);
assert(c == "abcx");
}
// 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");
}
// Test `scope` inference of parameters of `text`
@safe unittest
{
static struct S
{
int* x; // make S a type with pointers
string toString() const scope
{
return "S";
}
}
scope S s;
assert(text("a", s) == "aS");
}
// 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;
static foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong))
{
assertThrown!ConvException(to!T(" 0"));
assertThrown!ConvException(to!T(" 0", 8));
}
static foreach (T; AliasSeq!(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]"));
}
// https://issues.dlang.org/show_bug.cgi?id=20623
@safe pure nothrow unittest
{
// static class C
// {
// override string toString() const
// {
// return "C()";
// }
// }
static struct S
{
bool b;
int i;
float f;
int[] a;
int[int] aa;
S* p;
// C c; // TODO: Fails because of hasToString
void fun() inout
{
static foreach (const idx; 0 .. this.tupleof.length)
{
{
const _ = this.tupleof[idx].to!string();
}
}
}
}
}
/**
If the source type is implicitly convertible to the target type, $(D
to) simply performs the implicit conversion.
*/
private 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;
}
// https://issues.dlang.org/show_bug.cgi?id=9523: Allow identity enum conversion
@safe pure nothrow unittest
{
enum E { a }
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);
}
// https://issues.dlang.org/show_bug.cgi?id=6377
@safe pure unittest
{
import std.exception;
// Conversion between same size
static foreach (S; AliasSeq!(byte, short, int, long))
{{
alias U = Unsigned!S;
static foreach (Sint; AliasSeq!(S, const S, immutable S))
static foreach (Uint; AliasSeq!(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
static foreach (i, S1; AliasSeq!(byte, short, int, long))
static foreach ( S2; AliasSeq!(byte, short, int, long)[i+1..$])
{{
alias U1 = Unsigned!S1;
alias U2 = Unsigned!S2;
static assert(U1.sizeof < S2.sizeof);
// small unsigned to big signed
static foreach (Uint; AliasSeq!(U1, const U1, immutable U1))
static foreach (Sint; AliasSeq!(S2, const S2, immutable S2))
{{
Uint un = Uint.max;
assertNotThrown(to!Sint(un));
assert(to!Sint(un) == un);
}}
// big unsigned to small signed
static foreach (Uint; AliasSeq!(U2, const U2, immutable U2))
static foreach (Sint; AliasSeq!(S1, const S1, immutable S1))
{{
Uint un = Uint.max;
assertThrown(to!Sint(un));
}}
static assert(S1.sizeof < U2.sizeof);
// small signed to big unsigned
static foreach (Sint; AliasSeq!(S1, const S1, immutable S1))
static foreach (Uint; AliasSeq!(U2, const U2, immutable U2))
{{
Sint sn = -1;
assertThrown!ConvOverflowException(to!Uint(sn));
}}
// big signed to small unsigned
static foreach (Sint; AliasSeq!(S2, const S2, immutable S2))
static foreach (Uint; AliasSeq!(U1, const U1, immutable U1))
{{
Sint sn = -1;
assertThrown!ConvOverflowException(to!Uint(sn));
}}
}}
}
// https://issues.dlang.org/show_bug.cgi?id=13551
private T toImpl(T, S)(S value)
if (isTuple!T)
{
T t;
static foreach (i; 0 .. T.length)
{
t[i] = value[i].to!(typeof(T[i]));
}
return t;
}
@safe unittest
{
import std.typecons : Tuple;
auto test = ["10", "20", "30"];
assert(test.to!(Tuple!(int, int, int)) == Tuple!(int, int, int)(10, 20, 30));
auto test1 = [1, 2];
assert(test1.to!(Tuple!(int, int)) == Tuple!(int, int)(1, 2));
auto test2 = [1.0, 2.0, 3.0];
assert(test2.to!(Tuple!(int, int, int)) == Tuple!(int, int, int)(1, 2, 3));
}
/*
Converting static arrays forwards to their dynamic counterparts.
*/
private T toImpl(T, S)(ref S s)
if (isStaticArray!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.
*/
private 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); }
}
}
cast(void) 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, `T(value)` is used.)
$(LI If target type is class, $(D new T(value)) is used.))
*/
private T toImpl(T, S)(S value)
if (!isImplicitlyConvertible!(S, T) &&
is(T == struct) && is(typeof(T(value))))
{
return T(value);
}
// https://issues.dlang.org/show_bug.cgi?id=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);
}
// https://issues.dlang.org/show_bug.cgi?id=6808
@safe pure unittest
{
static struct FakeBigInt
{
this(string s) @safe pure {}
}
string s = "101";
auto i3 = to!FakeBigInt(s);
}
/// ditto
private 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.
*/
private 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;
}
// 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 {}
static foreach (m1; 0 .. 5) // enumerate modifiers
static foreach (m2; 0 .. 5) // ditto
{{
alias srcmod = AddModifier!m1;
alias tgtmod = AddModifier!m2;
// 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
}
}}
}
/**
Handles type _to string conversions
*/
private 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 && isSomeChar!(PointerTarget!S))
{
// This is unsafe because we cannot guarantee that the pointer is null terminated.
return () @system {
static if (is(S : const(char)*))
import core.stdc.string : strlen;
else
size_t strlen(S s) nothrow
{
S p = s;
while (*p++) {}
return p-s-1;
}
return toImpl!T(value ? 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.array : appender;
import std.format.spec : FormatSpec;
import std.format.write : formatValue;
//Default case, delegate to format
//Note: we don't call toStr directly, to avoid duplicate work.
auto app = appender!T();
app.put("cast(" ~ S.stringof ~ ")");
FormatSpec!char f;
formatValue(app, cast(OriginalType!S) value, f);
return app.data;
}
else
{
// other non-string values runs formatting
return toStr!T(value);
}
}
// https://issues.dlang.org/show_bug.cgi?id=14042
@system unittest
{
immutable(char)* ptr = "hello".ptr;
auto result = ptr.to!(char[]);
}
// https://issues.dlang.org/show_bug.cgi?id=8384
@system unittest
{
void test1(T)(T lp, string cmp)
{
static foreach (e; AliasSeq!(char, wchar, dchar))
{
test2!(e[])(lp, cmp);
test2!(const(e)[])(lp, cmp);
test2!(immutable(e)[])(lp, cmp);
}
}
void test2(D, S)(S lp, string cmp)
{
assert(to!string(to!D(lp)) == cmp);
}
static foreach (e; AliasSeq!("Hello, world!", "Hello, world!"w, "Hello, world!"d))
{
test1(e, "Hello, world!");
test1(e.ptr, "Hello, world!");
}
static foreach (e; AliasSeq!("", ""w, ""d))
{
test1(e, "");
test1(e.ptr, "");
}
}
/*
To string conversion for non copy-able structs
*/
private T toImpl(T, S)(ref S value)
if (!(isImplicitlyConvertible!(S, T) &&
!isEnumStrToStr!(S, T) && !isNullToStr!(S, T)) &&
!isInfinite!S && isExactSomeString!T && !isCopyable!S && !isStaticArray!S)
{
import std.array : appender;
import std.format.spec : FormatSpec;
import std.format.write : formatValue;
auto w = appender!T();
FormatSpec!(ElementEncodingType!T) f;
formatValue(w, value, f);
return w.data;
}
// https://issues.dlang.org/show_bug.cgi?id=16108
@safe unittest
{
static struct A
{
int val;
bool flag;
string toString() { return text(val, ":", flag); }
@disable this(this);
}
auto a = A();
assert(to!string(a) == "0:false");
static struct B
{
int val;
bool flag;
@disable this(this);
}
auto b = B();
assert(to!string(b) == "B(0, false)");
}
// https://issues.dlang.org/show_bug.cgi?id=20070
@safe unittest
{
void writeThem(T)(ref inout(T) them)
{
assert(them.to!string == "[1, 2, 3, 4]");
}
const(uint)[4] vals = [ 1, 2, 3, 4 ];
writeThem(vals);
}
/*
Check whether type `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: }
}));
}
//
@safe 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 = AliasSeq!(char, wchar, dchar);
foreach (LhsC; Chars)
{
alias LhStrings = AliasSeq!(LhsC[], const(LhsC)[], immutable(LhsC)[]);
foreach (Lhs; LhStrings)
{
foreach (RhsC; Chars)
{
alias RhStrings = AliasSeq!(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 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 =
AliasSeq!( 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);
}
assert(s2 == "foo");
}
@safe pure nothrow unittest
{
import std.exception;
// Conversion representing integer values with string
static foreach (Int; AliasSeq!(ubyte, ushort, uint, ulong))
{
assert(to!string(Int(0)) == "0");
assert(to!string(Int(9)) == "9");
assert(to!string(Int(123)) == "123");
}
static foreach (Int; AliasSeq!(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 unittest // sprintf issue
{
double[2] a = [ 1.5, 2.5 ];
assert(to!string(a) == "[1.5, 2.5]");
}
@safe unittest
{
// Conversion representing class object with string
class A
{
override string toString() @safe const { return "an A"; }
}
A a;
assert(to!string(a) == "null");
a = new A;
assert(to!string(a) == "an A");
// https://issues.dlang.org/show_bug.cgi?id=7660
class C { override string toString() @safe const { return "C"; } }
struct S { C c; alias c this; }
S s; s.c = new C();
assert(to!string(s) == "C");
}
@safe 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>");
}
@safe 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
// base type is signed (https://issues.dlang.org/show_bug.cgi?id=7909)
enum EI : int { a = -1, b = 0, c = 1 }
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" }
static foreach (E; AliasSeq!(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);
}
@safe 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");
static foreach (S; AliasSeq!(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);
}}
static foreach (S; AliasSeq!(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 private T toImpl(T, S)(S value, uint radix, LetterCase letterCase = LetterCase.upper)
if (isIntegral!S &&
isExactSomeString!T)
in
{
assert(radix >= 2 && radix <= 36, "radix must be in range [2,36]");
}
do
{
alias EEType = Unqual!(ElementEncodingType!T);
T toStringRadixConvert(size_t bufLen)(uint runtimeRadix = 0)
{
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
{
div = cast(S)(mValue / runtimeRadix );
mod = cast(ubyte)(mValue % runtimeRadix);
mod += mod < 10 ? '0' : baseChar - 10;
buffer[--index] = cast(char) mod;
mValue = div;
} while (mValue);
return cast(T) buffer[index .. $].dup;
}
import std.array : array;
switch (radix)
{
case 10:
// The (value+0) is so integral promotions happen to the type
return toChars!(10, EEType)(value + 0).array;
case 16:
// The unsigned(unsigned(value)+0) is so unsigned integral promotions happen to the type
if (letterCase == letterCase.upper)
return toChars!(16, EEType, LetterCase.upper)(unsigned(unsigned(value) + 0)).array;
else
return toChars!(16, EEType, LetterCase.lower)(unsigned(unsigned(value) + 0)).array;
case 2:
return toChars!(2, EEType)(unsigned(unsigned(value) + 0)).array;
case 8:
return toChars!(8, EEType)(unsigned(unsigned(value) + 0)).array;
default:
return toStringRadixConvert!(S.sizeof * 6)(radix);
}
}
@safe pure nothrow unittest
{
static foreach (Int; AliasSeq!(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");
}
static foreach (Int; AliasSeq!(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.
*/
private 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))
{
static if (isFloatingPoint!S && isIntegral!T)
{
import std.math.traits : isNaN;
if (value.isNaN) throw new ConvException("Input was NaN");
}
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,
"minimum value of T must be smaller than 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);
}
@safe 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);
}
@safe unittest
{
import std.exception;
import std.math.traits : isNaN;
double d = double.nan;
float f = to!float(d);
assert(f.isNaN);
assert(to!double(f).isNaN);
assertThrown!ConvException(to!int(d));
assertThrown!ConvException(to!int(f));
auto ex = collectException(d.to!int);
assert(ex.msg == "Input was NaN");
}
/**
Array-to-array conversion (except when target is a string type)
converts each element in turn by using `to`.
*/
private T toImpl(T, S)(scope 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 (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 ]);
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 https://issues.dlang.org/show_bug.cgi?id=8264
struct Wrap
{
string wrap;
alias wrap this;
}
Wrap[] warr = to!(Wrap[])(["foo", "bar"]); // should work
// https://issues.dlang.org/show_bug.cgi?id=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 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.
*/
private T toImpl(T, S)(S value)
if (!isImplicitlyConvertible!(S, T) && 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)] = to!(Unqual!V2)(v1);
}
// Cast back to original type
return () @trusted { return cast(T) result; }();
}
@safe 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);
}
// https://issues.dlang.org/show_bug.cgi?id=8705, from doc
@safe unittest
{
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));
}
@system 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);
}
@safe unittest
{
import std.algorithm.comparison : equal;
import std.array : byPair;
int[int] a;
assert(a.to!(int[int]) == a);
assert(a.to!(const(int)[int]).byPair.equal(a.byPair));
}
@safe pure unittest
{
static void testIntegralToFloating(Integral, Floating)()
{
Integral a = 42;
auto b = to!Floating(a);
assert(a == b);
assert(a == to!Integral(b));
}
static void testFloatingToIntegral(Floating, Integral)()
{
import std.math : floatTraits, RealFormat;
bool convFails(Source, Target, E)(Source src)
{
try
cast(void) 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
|| floatTraits!Floating.realFormat == RealFormat.ieeeExtended53);
}
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
|| floatTraits!Floating.realFormat == RealFormat.ieeeExtended53);
a = 0.0 + Integral.max;
assert(to!Integral(a) == Integral.max
|| Floating.sizeof <= Integral.sizeof
|| floatTraits!Floating.realFormat == RealFormat.ieeeExtended53);
++a; // no more representable as an Integral
assert(convFails!(Floating, Integral, ConvOverflowException)(a)
|| Floating.sizeof <= Integral.sizeof
|| floatTraits!Floating.realFormat == RealFormat.ieeeExtended53);
// 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);
}
}
alias AllInts = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong);
alias AllFloats = AliasSeq!(float, double, real);
alias AllNumerics = AliasSeq!(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
static if (real.mant_dig >= 64)
{
ulong c = 18_446_744_073_709_551_615UL; // 2^64 - 1
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 unittest
{
alias AllInts = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong);
alias AllFloats = AliasSeq!(float, double, real);
alias AllNumerics = AliasSeq!(AllInts, AllFloats);
// test conversions to string
{
foreach (T; AllNumerics)
{
T a = 42;
string s = to!string(a);
assert(s == "42", s);
wstring ws = to!wstring(a);
assert(ws == "42"w, to!string(ws));
dstring ds = to!dstring(a);
assert(ds == "42"d, to!string(ds));
// 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 == "Test1");
}
/**
String, or string-like input range, 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.))
*/
private T toImpl(T, S)(S value)
if (isInputRange!S && isSomeChar!(ElementEncodingType!S) &&
!isExactSomeString!T && is(typeof(parse!T(value))) &&
// issue 20539
!(is(T == enum) && is(typeof(value == OriginalType!T.init)) && !isSomeString!(OriginalType!T)))
{
scope(success)
{
if (!value.empty)
{
throw convError!(S, T)(value);
}
}
return parse!T(value);
}
/// ditto
private T toImpl(T, S)(S value, uint radix)
if (isSomeFiniteCharInputRange!S &&
isIntegral!T && is(typeof(parse!T(value, radix))))
{
scope(success)
{
if (!value.empty)
{
throw convError!(S, T)(value);
}
}
return parse!T(value, radix);
}
@safe pure unittest
{
// https://issues.dlang.org/show_bug.cgi?id=6668
// ensure no collaterals thrown
try { to!uint("-1"); }
catch (ConvException e) { assert(e.next is null); }
}
@safe pure unittest
{
static foreach (Str; AliasSeq!(string, wstring, dstring))
{{
Str a = "123";
assert(to!int(a) == 123);
assert(to!double(a) == 123);
}}
// https://issues.dlang.org/show_bug.cgi?id=6255
auto n = to!int("FF", 16);
assert(n == 255);
}
// https://issues.dlang.org/show_bug.cgi?id=15800
@safe unittest
{
import std.utf : byCodeUnit, byChar, byWchar, byDchar;
assert(to!int(byCodeUnit("10")) == 10);
assert(to!int(byCodeUnit("10"), 10) == 10);
assert(to!int(byCodeUnit("10"w)) == 10);
assert(to!int(byCodeUnit("10"w), 10) == 10);
assert(to!int(byChar("10")) == 10);
assert(to!int(byChar("10"), 10) == 10);
assert(to!int(byWchar("10")) == 10);
assert(to!int(byWchar("10"), 10) == 10);
assert(to!int(byDchar("10")) == 10);
assert(to!int(byDchar("10"), 10) == 10);
}
/**
String, or string-like input range, to char type not directly
supported by parse parses the first dchar of the source.
Returns: the first code point of the input range, converted
to type T.
Throws: ConvException if the input range contains more than
a single code point, or if the code point does not
fit into a code unit of type T.
*/
private T toImpl(T, S)(S value)
if (isSomeChar!T && !is(typeof(parse!T(value))) &&
is(typeof(parse!dchar(value))))
{
import std.utf : encode;
immutable dchar codepoint = parse!dchar(value);
if (!value.empty)
throw new ConvException(convFormat("Cannot convert \"%s\" to %s because it " ~
"contains more than a single code point.",
value, T.stringof));
T[dchar.sizeof / T.sizeof] decodedCodepoint;
if (encode(decodedCodepoint, codepoint) != 1)
throw new ConvException(convFormat("First code point '%s' of \"%s\" does not fit into a " ~
"single %s code unit", codepoint, value, T.stringof));
return decodedCodepoint[0];
}
@safe pure unittest
{
import std.exception : assertThrown;
assert(toImpl!wchar("a") == 'a');
assert(toImpl!char("a"d) == 'a');
assert(toImpl!char("a"w) == 'a');
assert(toImpl!wchar("a"d) == 'a');
assertThrown!ConvException(toImpl!wchar("ab"));
assertThrown!ConvException(toImpl!char("😃"d));
}
/**
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.
*/
private 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]]);
}
// https://issues.dlang.org/show_bug.cgi?id=20539
@safe pure unittest
{
import std.exception : assertNotThrown;
// To test that the bug is fixed it is required that the struct is static,
// otherwise, the frame pointer makes the test pass even if the bug is not
// fixed.
static struct A
{
auto opEquals(U)(U)
{
return true;
}
}
enum ColorA
{
red = A()
}
assertNotThrown("xxx".to!ColorA);
// This is a guard for the future.
struct B
{
auto opEquals(U)(U)
{
return true;
}
}
enum ColorB
{
red = B()
}
assertNotThrown("xxx".to!ColorB);
}
/***************************************************************
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 core.math : abs = fabs;
import std.math.exponential : log2;
import std.math.rounding : trunc;
static assert(isFloatingPoint!Source);
static assert(isIntegral!Target);
// If value >= 2 ^^ (real.mant_dig - 1), the number is an integer
// and adding 0.5 won't work, but we allready know, that we do
// not have to round anything.
if (log2(abs(value)) >= real.mant_dig - 1)
return to!Target(value);
return to!Target(trunc(value + (value < 0 ? -0.5L : 0.5L)));
}
}
///
@safe 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);
}
@safe unittest
{
import std.exception;
// boundary values
static foreach (Int; AliasSeq!(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));
}
}
@safe unittest
{
import std.exception;
assertThrown!ConvException(roundTo!int(float.init));
auto ex = collectException(roundTo!int(float.init));
assert(ex.msg == "Input was NaN");
}
// https://issues.dlang.org/show_bug.cgi?id=5232
@safe pure unittest
{
static if (real.mant_dig >= 64)
ulong maxOdd = ulong.max;
else
ulong maxOdd = (1UL << real.mant_dig) - 1;
real r1 = maxOdd;
assert(roundTo!ulong(r1) == maxOdd);
real r2 = maxOdd - 1;
assert(roundTo!ulong(r2) == maxOdd - 1);
real r3 = maxOdd / 2;
assert(roundTo!ulong(r3) == maxOdd / 2);
real r4 = maxOdd / 2 + 1;
assert(roundTo!ulong(r4) == maxOdd / 2 + 1);
// this is only an issue on computers where real == double
long l = -((1L << double.mant_dig) - 1);
double r5 = l;
assert(roundTo!long(r5) == l);
}
/**
The `parse` family of functions works quite like the `to`
family, except that:
$(OL
$(LI It only works with character ranges as input.)
$(LI It takes the input by reference. (This means that rvalues - such
as string literals - are not accepted: use `to` instead.))
$(LI It advances the input to the position following the conversion.)
$(LI It does not throw if it could not convert the entire input.))
This overload converts a character input range to a `bool`.
Params:
Target = the type to convert to
source = the lvalue of an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
doCount = the flag for deciding to report the number of consumed characters
Returns:
$(UL
$(LI A `bool` if `doCount` is set to `No.doCount`)
$(LI A `tuple` containing a `bool` and a `size_t` if `doCount` is set to `Yes.doCount`))
Throws:
A $(LREF ConvException) if the range does not represent a `bool`.
Note:
All character input range conversions using $(LREF to) are forwarded
to `parse` and do not require lvalues.
*/
auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source source)
if (isInputRange!Source &&
isSomeChar!(ElementType!Source) &&
is(immutable Target == immutable bool))
{
import std.ascii : toLower;
static if (isNarrowString!Source)
{
import std.string : representation;
auto s = source.representation;
}
else
{
alias s = source;
}
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();
}
static if (isNarrowString!Source)
source = cast(Source) s;
static if (doCount)
{
if (result)
return tuple!("data", "count")(result, 4);
return tuple!("data", "count")(result, 5);
}
else
{
return result;
}
}
}
Lerr:
throw parseError("bool should be case-insensitive 'true' or 'false'");
}
///
@safe unittest
{
import std.typecons : Flag, Yes, No;
auto s = "true";
bool b = parse!bool(s);
assert(b);
auto s2 = "true";
bool b2 = parse!(bool, string, No.doCount)(s2);
assert(b2);
auto s3 = "true";
auto b3 = parse!(bool, string, Yes.doCount)(s3);
assert(b3.data && b3.count == 4);
auto s4 = "falSE";
auto b4 = parse!(bool, string, Yes.doCount)(s4);
assert(!b4.data && b4.count == 5);
}
@safe unittest
{
import std.algorithm.comparison : equal;
import std.exception;
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));
}
}
/**
Parses a character $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
to an integral value.
Params:
Target = the integral type to convert to
s = the lvalue of an input range
doCount = the flag for deciding to report the number of consumed characters
Returns:
$(UL
$(LI A number of type `Target` if `doCount` is set to `No.doCount`)
$(LI A `tuple` containing a number of type `Target` and a `size_t` if `doCount` is set to `Yes.doCount`))
Throws:
A $(LREF ConvException) If an overflow occurred during conversion or
if no character of the input was meaningfully converted.
*/
auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref scope 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), Source, Yes.doCount)(s);
auto result = (() @trusted => cast (Target) v.data)();
if (result == v.data)
{
static if (doCount)
{
return tuple!("data", "count")(result, v.count);
}
else
{
return result;
}
}
throw new ConvOverflowException("Overflow in integral conversion");
}
else
{
// int or larger types
static if (Target.min < 0)
bool sign = false;
else
enum bool sign = false;
enum char maxLastDigit = Target.min < 0 ? 7 : 5;
uint c;
static if (isNarrowString!Source)
{
import std.string : representation;
auto source = s.representation;
}
else
{
alias source = s;
}
size_t count = 0;
if (source.empty)
goto Lerr;
c = source.front;
static if (Target.min < 0)
{
switch (c)
{
case '-':
sign = true;
goto case '+';
case '+':
++count;
source.popFront();
if (source.empty)
goto Lerr;
c = source.front;
break;
default:
break;
}
}
c -= '0';
if (c <= 9)
{
Target v = cast(Target) c;
++count;
source.popFront();
while (!source.empty)
{
c = cast(typeof(c)) (source.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);
++count;
source.popFront();
}
else
throw new ConvOverflowException("Overflow in integral conversion");
}
if (sign)
v = -v;
static if (isNarrowString!Source)
s = s[$-source.length..$];
static if (doCount)
{
return tuple!("data", "count")(v, count);
}
else
{
return v;
}
}
Lerr:
static if (isNarrowString!Source)
throw convError!(Source, Target)(cast(Source) source);
else
throw convError!(Source, Target)(source);
}
}
///
@safe pure unittest
{
import std.typecons : Flag, Yes, No;
string s = "123";
auto a = parse!int(s);
assert(a == 123);
string s1 = "123";
auto a1 = parse!(int, string, Yes.doCount)(s1);
assert(a1.data == 123 && a1.count == 3);
// parse only accepts lvalues
static assert(!__traits(compiles, parse!int("123")));
}
///
@safe pure unittest
{
import std.string : tr;
import std.typecons : Flag, Yes, No;
string test = "123 \t 76.14";
auto a = parse!uint(test);
assert(a == 123);
assert(test == " \t 76.14"); // parse bumps string
test = tr(test, " \t\n\r", "", "d"); // skip ws
assert(test == "76.14");
auto b = parse!double(test);
assert(b == 76.14);
assert(test == "");
string test2 = "123 \t 76.14";
auto a2 = parse!(uint, string, Yes.doCount)(test2);
assert(a2.data == 123 && a2.count == 3);
assert(test2 == " \t 76.14");// parse bumps string
test2 = tr(test2, " \t\n\r", "", "d"); // skip ws
assert(test2 == "76.14");
auto b2 = parse!(double, string, Yes.doCount)(test2);
assert(b2.data == 76.14 && b2.count == 5);
assert(test2 == "");
}
@safe pure unittest
{
static foreach (Int; AliasSeq!(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;
immutable string[] errors =
[
"",
"-",
"+",
"-+",
" ",
" 0",
"0 ",
"- 0",
"1-",
"xx",
"123h",
"-+1",
"--1",
"+-1",
"++1",
];
immutable string[] unsignedErrors =
[
"+5",
"-78",
];
// parsing error check
static foreach (Int; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong))
{
foreach (j, s; errors)
assertThrown!ConvException(to!Int(s));
// parse!SomeUnsigned cannot parse head sign.
static if (isUnsigned!Int)
{
foreach (j, s; unsignedErrors)
assertThrown!ConvException(to!Int(s));
}
}
immutable string[] positiveOverflowErrors =
[
"128", // > byte.max
"256", // > ubyte.max
"32768", // > short.max
"65536", // > ushort.max
"2147483648", // > int.max
"4294967296", // > uint.max
"9223372036854775808", // > long.max
"18446744073709551616", // > ulong.max
];
// positive overflow check
static foreach (i, Int; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong))
{
foreach (j, s; positiveOverflowErrors[i..$])
assertThrown!ConvOverflowException(to!Int(s));
}
immutable string[] negativeOverflowErrors =
[
"-129", // < byte.min
"-32769", // < short.min
"-2147483649", // < int.min
"-9223372036854775809", // < long.min
];
// negative overflow check
static foreach (i, Int; AliasSeq!(byte, short, int, long))
{
foreach (j, s; negativeOverflowErrors[i..$])
assertThrown!ConvOverflowException(to!Int(s));
}
}
@safe pure unittest
{
void checkErrMsg(string input, dchar charInMsg, dchar charNotInMsg)
{
try
{
int x = input.to!int();
assert(false, "Invalid conversion did not throw");
}
catch (ConvException e)
{
// Ensure error message contains failing character, not the character
// beyond.
import std.algorithm.searching : canFind;
assert( e.msg.canFind(charInMsg) &&
!e.msg.canFind(charNotInMsg));
}
catch (Exception e)
{
assert(false, "Did not throw ConvException");
}
}
checkErrMsg("@$", '@', '$');
checkErrMsg("@$123", '@', '$');
checkErrMsg("1@$23", '@', '$');
checkErrMsg("1@$", '@', '$');
checkErrMsg("1@$2", '@', '$');
checkErrMsg("12@$", '@', '$');
}
@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"); });
assertCTFEable!({ string s = "1234abc"; assert(parse!( int, string, Yes.doCount)(s) ==
tuple( 1234, 4) && s == "abc"); });
assertCTFEable!({ string s = "-1234abc"; assert(parse!( int, string, Yes.doCount)(s) ==
tuple(-1234, 5) && s == "abc"); });
assertCTFEable!({ string s = "1234abc"; assert(parse!(uint, string, Yes.doCount)(s) ==
tuple( 1234 ,4) && s == "abc"); });
}
// https://issues.dlang.org/show_bug.cgi?id=13931
@safe pure unittest
{
import std.exception;
assertThrown!ConvOverflowException("-21474836480".to!int());
assertThrown!ConvOverflowException("-92233720368547758080".to!long());
}
// https://issues.dlang.org/show_bug.cgi?id=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);
auto input2 = StrInputRange("777");
assert(parse!(int, StrInputRange, Yes.doCount)(input2) == tuple(777, 3));
}
// https://issues.dlang.org/show_bug.cgi?id=9621
@safe pure unittest
{
string s1 = "[ \"\\141\", \"\\0\", \"\\41\", \"\\418\" ]";
assert(parse!(string[])(s1) == ["a", "\0", "!", "!8"]);
s1 = "[ \"\\141\", \"\\0\", \"\\41\", \"\\418\" ]";
auto len = s1.length;
assert(parse!(string[], string, Yes.doCount)(s1) == tuple(["a", "\0", "!", "!8"], len));
}
/// ditto
auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source source, uint radix)
if (isSomeChar!(ElementType!Source) &&
isIntegral!Target && !is(Target == enum))
in
{
assert(radix >= 2 && radix <= 36, "radix must be in range [2,36]");
}
do
{
import core.checkedint : mulu, addu;
import std.exception : enforce;
if (radix == 10)
{
return parse!(Target, Source, doCount)(source);
}
enforce!ConvException(!source.empty, "s must not be empty in integral parse");
immutable uint beyond = (radix < 10 ? '0' : 'a'-10) + radix;
Target v = 0;
static if (isNarrowString!Source)
{
import std.string : representation;
scope s = source.representation;
}
else
{
alias s = source;
}
size_t count = 0;
auto found = false;
do
{
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);
enforce!ConvOverflowException(!overflow && nextv <= Target.max, "Overflow in integral conversion");
v = cast(Target) nextv;
++count;
s.popFront();
found = true;
} while (!s.empty);
if (!found)
{
static if (isNarrowString!Source)
throw convError!(Source, Target)(cast(Source) source);
else
throw convError!(Source, Target)(source);
}
static if (isNarrowString!Source)
source = source[$ - s.length .. $];
static if (doCount)
{
return tuple!("data", "count")(v, count);
}
else
{
return v;
}
}
@safe pure unittest
{
string s; // parse doesn't accept rvalues
foreach (i; 2 .. 37)
{
assert(parse!int(s = "0", i) == 0);
assert(parse!int(s = "1", i) == 1);
assert(parse!byte(s = "10", i) == i);
assert(parse!(int, string, Yes.doCount)(s = "0", i) == tuple(0, 1));
assert(parse!(int, string, Yes.doCount)(s = "1", i) == tuple(1, 1));
assert(parse!(byte, string, Yes.doCount)(s = "10", i) == tuple(i, 2));
}
assert(parse!int(s = "0011001101101", 2) == 0b0011001101101);
assert(parse!int(s = "765", 8) == octal!765);
assert(parse!int(s = "000135", 8) == octal!"135");
assert(parse!int(s = "fCDe", 16) == 0xfcde);
// https://issues.dlang.org/show_bug.cgi?id=6609
assert(parse!int(s = "-42", 10) == -42);
assert(parse!ubyte(s = "ff", 16) == 0xFF);
}
// https://issues.dlang.org/show_bug.cgi?id=7302
@safe pure unittest
{
import std.range : cycle;
auto r = cycle("2A!");
auto u = parse!uint(r, 16);
assert(u == 42);
assert(r.front == '!');
auto r2 = cycle("2A!");
auto u2 = parse!(uint, typeof(r2), Yes.doCount)(r2, 16);
assert(u2.data == 42 && u2.count == 2);
assert(r2.front == '!');
}
// https://issues.dlang.org/show_bug.cgi?id=13163
@safe pure unittest
{
import std.exception;
foreach (s; ["fff", "123"])
assertThrown!ConvOverflowException(s.parse!ubyte(16));
}
// https://issues.dlang.org/show_bug.cgi?id=17282
@safe pure unittest
{
auto str = "0=\x00\x02\x55\x40&\xff\xf0\n\x00\x04\x55\x40\xff\xf0~4+10\n";
assert(parse!uint(str) == 0);
str = "0=\x00\x02\x55\x40&\xff\xf0\n\x00\x04\x55\x40\xff\xf0~4+10\n";
assert(parse!(uint, string, Yes.doCount)(str) == tuple(0, 1));
}
// https://issues.dlang.org/show_bug.cgi?id=18248
@safe pure unittest
{
import std.exception : assertThrown;
auto str = ";";
assertThrown(str.parse!uint(16));
assertThrown(str.parse!(uint, string, Yes.doCount)(16));
}
/**
* Takes a string representing an `enum` type and returns that type.
*
* Params:
* Target = the `enum` type to convert to
* s = the lvalue of the range to _parse
* doCount = the flag for deciding to report the number of consumed characters
*
* Returns:
$(UL
* $(LI An `enum` of type `Target` if `doCount` is set to `No.doCount`)
* $(LI A `tuple` containing an `enum` of type `Target` and a `size_t` if `doCount` is set to `Yes.doCount`))
*
* Throws:
* A $(LREF ConvException) if type `Target` does not have a member
* represented by `s`.
*/
auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source s)
if (isSomeString!Source && !is(Source == enum) &&
is(Target == enum))
{
import std.algorithm.searching : startsWith;
import std.traits : Unqual, EnumMembers;
Unqual!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 .. $];
static if (doCount)
{
return tuple!("data", "count")(result, longest_match);
}
else
{
return result;
}
}
throw new ConvException(
Target.stringof ~ " does not have a member named '"
~ to!string(s) ~ "'");
}
///
@safe unittest
{
import std.typecons : Flag, Yes, No, tuple;
enum EnumType : bool { a = true, b = false, c = a }
auto str = "a";
assert(parse!EnumType(str) == EnumType.a);
auto str2 = "a";
assert(parse!(EnumType, string, No.doCount)(str2) == EnumType.a);
auto str3 = "a";
assert(parse!(EnumType, string, Yes.doCount)(str3) == tuple(EnumType.a, 1));
}
@safe 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" }
static foreach (E; AliasSeq!(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);
assert(to!(const E)("a") == E.a);
assert(to!(immutable E)("a") == E.a);
assert(to!(shared E)("a") == E.a);
assertThrown!ConvException(to!E("d"));
}
}
// https://issues.dlang.org/show_bug.cgi?id=4744
@safe pure unittest
{
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");
auto s2 = "member1111";
assert(parse!(A, string, No.doCount)(s2) == A.member111 && s2 == "1");
auto s3 = "member1111";
assert(parse!(A, string, Yes.doCount)(s3) == tuple(A.member111, 9) && s3 == "1");
}
/**
* Parses a character range to a floating point number.
*
* Params:
* Target = a floating point type
* source = the lvalue of the range to _parse
* doCount = the flag for deciding to report the number of consumed characters
*
* Returns:
$(UL
* $(LI A floating point number of type `Target` if `doCount` is set to `No.doCount`)
* $(LI A `tuple` containing a floating point number of·type `Target` and a `size_t`
* if `doCount` is set to `Yes.doCount`))
*
* Throws:
* A $(LREF ConvException) if `source` is empty, if no number could be
* parsed, or if an overflow occurred.
*/
auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source source)
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;
static if (isNarrowString!Source)
{
import std.string : representation;
scope p = source.representation;
}
else
{
alias p = source;
}
void advanceSource()
{
static if (isNarrowString!Source)
source = source[$ - p.length .. $];
}
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 ];
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 \"", source, "\"."), fn, ln);
}
enforce(!p.empty, bailOut());
size_t count = 0;
bool sign = false;
switch (p.front)
{
case '-':
sign = true;
++count;
p.popFront();
enforce(!p.empty, bailOut());
if (toLower(p.front) == 'i')
goto case 'i';
break;
case '+':
++count;
p.popFront();
enforce(!p.empty, bailOut());
break;
case 'i': case 'I':
// inf
++count;
p.popFront();
enforce(!p.empty && toUpper(p.front) == 'N',
bailOut("error converting input to floating point"));
++count;
p.popFront();
enforce(!p.empty && toUpper(p.front) == 'F',
bailOut("error converting input to floating point"));
// skip past the last 'f'
++count;
p.popFront();
advanceSource();
static if (doCount)
{
return tuple!("data", "count")(sign ? -Target.infinity : Target.infinity, count);
}
else
{
return sign ? -Target.infinity : Target.infinity;
}
default: {}
}
bool isHex = false;
bool startsWithZero = p.front == '0';
if (startsWithZero)
{
++count;
p.popFront();
if (p.empty)
{
advanceSource();
static if (doCount)
{
return tuple!("data", "count")(cast (Target) (sign ? -0.0 : 0.0), count);
}
else
{
return sign ? -0.0 : 0.0;
}
}
isHex = p.front == 'x' || p.front == 'X';
if (isHex)
{
++count;
p.popFront();
}
}
else if (toLower(p.front) == 'n')
{
// nan
++count;
p.popFront();
enforce(!p.empty && toUpper(p.front) == 'A',
bailOut("error converting input to floating point"));
++count;
p.popFront();
enforce(!p.empty && toUpper(p.front) == 'N',
bailOut("error converting input to floating point"));
// skip past the last 'n'
++count;
p.popFront();
advanceSource();
static if (doCount)
{
return tuple!("data", "count")(Target.nan, count);
}
else
{
return typeof(return).nan;
}
}
/*
* The following algorithm consists of 2 steps:
* 1) parseDigits processes the textual input into msdec and possibly
* lsdec/msscale variables, followed by the exponent parser which sets
* exp below.
* Hex: input is 0xaaaaa...p+000... where aaaa is the mantissa in hex
* and 000 is the exponent in decimal format with base 2.
* Decimal: input is 0.00333...p+000... where 0.0033 is the mantissa
* in decimal and 000 is the exponent in decimal format with base 10.
* 2) Convert msdec/lsdec and exp into native real format
*/
real ldval = 0.0;
char dot = 0; /* if decimal point has been seen */
int exp = 0;
ulong msdec = 0, lsdec = 0;
ulong msscale = 1;
bool sawDigits;
enum { hex, decimal }
// sets msdec, lsdec/msscale, and sawDigits by parsing the mantissa digits
void parseDigits(alias FloatFormat)()
{
static if (FloatFormat == hex)
{
enum uint base = 16;
enum ulong msscaleMax = 0x1000_0000_0000_0000UL; // largest power of 16 a ulong holds
enum ubyte expIter = 4; // iterate the base-2 exponent by 4 for every hex digit
alias checkDigit = isHexDigit;
/*
* convert letter to binary representation: First clear bit
* to convert lower space chars to upperspace, then -('A'-10)
* converts letter A to 10, letter B to 11, ...
*/
alias convertDigit = (int x) => isAlpha(x) ? ((x & ~0x20) - ('A' - 10)) : x - '0';
sawDigits = false;
}
else static if (FloatFormat == decimal)
{
enum uint base = 10;
enum ulong msscaleMax = 10_000_000_000_000_000_000UL; // largest power of 10 a ulong holds
enum ubyte expIter = 1; // iterate the base-10 exponent once for every decimal digit
alias checkDigit = isDigit;
alias convertDigit = (int x) => x - '0';
// Used to enforce that any mantissa digits are present
sawDigits = startsWithZero;
}
else
static assert(false, "Unrecognized floating-point format used.");
while (!p.empty)
{
int i = p.front;
while (checkDigit(i))
{
sawDigits = true; /* must have at least 1 digit */
i = convertDigit(i);
if (msdec < (ulong.max - base)/base)
{
// For base 16: Y = ... + y3*16^3 + y2*16^2 + y1*16^1 + y0*16^0
msdec = msdec * base + i;
}
else if (msscale < msscaleMax)
{
lsdec = lsdec * base + i;
msscale *= base;
}
else
{
exp += expIter;
}
exp -= dot;
++count;
p.popFront();
if (p.empty)
break;
i = p.front;
if (i == '_')
{
++count;
p.popFront();
if (p.empty)
break;
i = p.front;
}
}
if (i == '.' && !dot)
{
++count;
p.popFront();
dot += expIter;
}
else
break;
}
// Have we seen any mantissa digits so far?
enforce(sawDigits, bailOut("no digits seen"));
static if (FloatFormat == hex)
enforce(!p.empty && (p.front == 'p' || p.front == 'P'),
bailOut("Floating point parsing: exponent is required"));
}
if (isHex)
parseDigits!hex;
else
parseDigits!decimal;
if (isHex || (!p.empty && (p.front == 'e' || p.front == 'E')))
{
char sexp = 0;
int e = 0;
++count;
p.popFront();
enforce(!p.empty, new ConvException("Unexpected end of input"));
switch (p.front)
{
case '-': sexp++;
goto case;
case '+': ++count;
p.popFront();
break;
default: {}
}
sawDigits = false;
while (!p.empty && isDigit(p.front))
{
if (e < 0x7FFFFFFF / 10 - 10) // prevent integer overflow
{
e = e * 10 + p.front - '0';
}
++count;
p.popFront();
sawDigits = true;
}
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 (isHex)
{
import core.math : ldexp;
// Exponent is power of 2, not power of 10
ldval = ldexp(ldval,exp);
}
else 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++;
}
}
// if overflow occurred
enforce(ldval != real.infinity, new ConvException("Range error"));
advanceSource();
static if (doCount)
{
return tuple!("data", "count")(cast (Target) (sign ? -ldval : ldval), count);
}
else
{
return cast (Target) (sign ? -ldval : ldval);
}
}
///
@safe unittest
{
import std.math.operations : isClose;
import std.math.traits : isNaN, isInfinity;
import std.typecons : Flag, Yes, No;
auto str = "123.456";
assert(parse!double(str).isClose(123.456));
auto str2 = "123.456";
assert(parse!(double, string, No.doCount)(str2).isClose(123.456));
auto str3 = "123.456";
auto r = parse!(double, string, Yes.doCount)(str3);
assert(r.data.isClose(123.456));
assert(r.count == 7);
auto str4 = "-123.456";
r = parse!(double, string, Yes.doCount)(str4);
assert(r.data.isClose(-123.456));
assert(r.count == 8);
auto str5 = "+123.456";
r = parse!(double, string, Yes.doCount)(str5);
assert(r.data.isClose(123.456));
assert(r.count == 8);
auto str6 = "inf0";
r = parse!(double, string, Yes.doCount)(str6);
assert(isInfinity(r.data) && r.count == 3 && str6 == "0");
auto str7 = "-0";
auto r2 = parse!(float, string, Yes.doCount)(str7);
assert(r2.data.isClose(0.0) && r2.count == 2);
auto str8 = "nan";
auto r3 = parse!(real, string, Yes.doCount)(str8);
assert(isNaN(r3.data) && r3.count == 3);
}
@safe unittest
{
import std.exception;
import std.math.traits : isNaN, isInfinity;
import std.math.algebraic : 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;
}
static foreach (Float; AliasSeq!(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-2L));
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));
auto z = real.max / 2L;
static assert(is(typeof(z) == real));
assert(!isNaN(z));
assert(!isInfinity(z));
string a = to!string(z);
real b = to!real(a);
string c = to!string(b);
assert(c == a, "\n" ~ c ~ "\n" ~ a);
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));
version (NetBSD)
{
// NetBSD notice
// to!string returns 3.3621e-4932L. It is less than real.min_normal and it is subnormal value
// Simple C code
// long double rd = 3.3621e-4932L;
// printf("%Le\n", rd);
// has unexpected result: 1.681050e-4932
//
// Bug report: http://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=50937
}
else
{
assert(to!string(r) == to!string(real.min_normal));
}
r = to!real(to!string(real.max));
assert(to!string(r) == to!string(real.max));
real pi = 3.1415926535897932384626433832795028841971693993751L;
string fullPrecision = "3.1415926535897932384626433832795028841971693993751";
assert(feq(parse!real(fullPrecision), pi, 2*real.epsilon));
string fullPrecision2 = "3.1415926535897932384626433832795028841971693993751";
assert(feq(parse!(real, string, No.doCount)(fullPrecision2), pi, 2*real.epsilon));
string fullPrecision3= "3.1415926535897932384626433832795028841971693993751";
auto len = fullPrecision3.length;
auto res = parse!(real, string, Yes.doCount)(fullPrecision3);
assert(feq(res.data, pi, 2*real.epsilon));
assert(res.count == len);
real x = 0x1.FAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAAFAAFAFAFAFAFAFAFAP-252L;
string full = "0x1.FAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAAFAAFAFAFAFAFAFAFAP-252";
assert(parse!real(full) == x);
string full2 = "0x1.FAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAAFAAFAFAFAFAFAFAFAP-252";
assert(parse!(real, string, No.doCount)(full2) == x);
string full3 = "0x1.FAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAAFAAFAFAFAFAFAFAFAP-252";
auto len2 = full3.length;
assert(parse!(real, string, Yes.doCount)(full3) == tuple(x, len2));
}
// Tests for the double implementation
@system unittest
{
// @system because strtod is not @safe.
import std.math : floatTraits, RealFormat;
static if (floatTraits!real.realFormat == RealFormat.ieeeDouble)
{
import core.stdc.stdlib, std.exception, std.math;
//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);
s = "0x1A_BCDE_F012_3456p10";
auto len = s.length;
assert(parse!(real, string, Yes.doCount)(s) == tuple(x, len));
//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(isClose(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(isClose(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(isClose(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(isClose(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);
s = "0x1FFFFFFFFFFFFFp-2000";
len = s.length;
assert(parse!(real, string, Yes.doCount)(s) == tuple(x, len));
}
}
@system unittest
{
import core.stdc.errno;
import core.stdc.stdlib;
import std.math : floatTraits, RealFormat;
errno = 0; // In case it was set by another unittest in a different module.
struct longdouble
{
static if (floatTraits!real.realFormat == RealFormat.ieeeQuadruple)
{
ushort[8] value;
}
else static if (floatTraits!real.realFormat == RealFormat.ieeeExtended ||
floatTraits!real.realFormat == RealFormat.ieeeExtended53)
{
ushort[5] value;
}
else static if (floatTraits!real.realFormat == RealFormat.ieeeDouble)
{
ushort[4] value;
}
else
static assert(false, "Not implemented");
}
real ld;
longdouble x;
real ld1;
longdouble x1;
int i;
static if (floatTraits!real.realFormat == RealFormat.ieeeQuadruple)
enum s = "0x1.FFFFFFFFFFFFFFFFFFFFFFFFFFFFp-16382";
else static if (floatTraits!real.realFormat == RealFormat.ieeeExtended)
enum s = "0x1.FFFFFFFFFFFFFFFEp-16382";
else static if (floatTraits!real.realFormat == RealFormat.ieeeExtended53)
enum s = "0x1.FFFFFFFFFFFFFFFEp-16382";
else static if (floatTraits!real.realFormat == RealFormat.ieeeDouble)
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;
static if (floatTraits!real.realFormat == RealFormat.ieeeExtended)
{
version (CRuntime_Microsoft)
ld1 = 0x1.FFFFFFFFFFFFFFFEp-16382L; // strtold currently mapped to strtod
else
ld1 = strtold(s.ptr, null);
}
else static if (floatTraits!real.realFormat == RealFormat.ieeeExtended53)
ld1 = 0x1.FFFFFFFFFFFFFFFEp-16382L; // strtold rounds to 53 bits.
else
ld1 = strtold(s.ptr, null);
x1 = *cast(longdouble *)&ld1;
assert(x1 == x && ld1 == ld);
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;
}
@safe pure unittest
{
import std.exception;
// https://issues.dlang.org/show_bug.cgi?id=4959
{
auto s = "0 ";
auto x = parse!double(s);
assert(s == " ");
assert(x == 0.0);
}
{
auto s = "0 ";
auto x = parse!(double, string, Yes.doCount)(s);
assert(s == " ");
assert(x == tuple(0.0, 1));
}
// https://issues.dlang.org/show_bug.cgi?id=3369
assert(to!float("inf") == float.infinity);
assert(to!float("-inf") == -float.infinity);
// https://issues.dlang.org/show_bug.cgi?id=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
// https://issues.dlang.org/show_bug.cgi?id=6258
assertThrown!ConvException(to!real("-"));
assertThrown!ConvException(to!real("in"));
// https://issues.dlang.org/show_bug.cgi?id=7055
assertThrown!ConvException(to!float("INF2"));
//extra stress testing
auto ssOK = ["1.", "1.1.1", "1.e5", "2e1e", "2a", "2e1_1", "3.4_",
"inf", "-inf", "infa", "-infa", "inf2e2", "-inf2e2",
"nan", "-NAN", "+NaN", "-nAna", "NAn2e2", "-naN2e2"];
auto ssKO = ["", " ", "2e", "2e+", "2e-", "2ee", "2e++1", "2e--1", "2e_1",
"+inf", "-in", "I", "+N", "-NaD", "0x3.F"];
foreach (s; ssOK)
parse!double(s);
foreach (s; ssKO)
assertThrown!ConvException(parse!double(s));
}
/**
Parsing one character off a range returns the first element and calls `popFront`.
Params:
Target = the type to convert to
s = the lvalue of an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
doCount = the flag for deciding to report the number of consumed characters
Returns:
$(UL
$(LI A character of type `Target` if `doCount` is set to `No.doCount`)
$(LI A `tuple` containing a character of type `Target` and a `size_t` if `doCount` is set to `Yes.doCount`))
Throws:
A $(LREF ConvException) if the range is empty.
*/
auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source s)
if (isSomeString!Source && !is(Source == enum) &&
staticIndexOf!(immutable Target, immutable dchar, immutable ElementEncodingType!Source) >= 0)
{
if (s.empty)
throw convError!(Source, Target)(s);
static if (is(immutable Target == immutable dchar))
{
Target result = s.front;
s.popFront();
static if (doCount)
{
return tuple!("data", "count")(result, 1);
}
else
{
return result;
}
}
else
{
// Special case: okay so parse a Char off a Char[]
Target result = s[0];
s = s[1 .. $];
static if (doCount)
{
return tuple!("data", "count")(result, 1);
}
else
{
return result;
}
}
}
@safe pure unittest
{
static foreach (Str; AliasSeq!(string, wstring, dstring))
{
static foreach (Char; AliasSeq!(char, wchar, dchar))
{{
static if (is(immutable Char == immutable dchar) ||
Char.sizeof == ElementEncodingType!Str.sizeof)
{
Str s = "aaa";
assert(parse!Char(s) == 'a');
assert(s == "aa");
assert(parse!(Char, typeof(s), No.doCount)(s) == 'a');
assert(s == "a");
assert(parse!(Char, typeof(s), Yes.doCount)(s) == tuple('a', 1) && s == "");
}
}}
}
}
/// ditto
auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(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();
static if (doCount)
{
return tuple!("data", "count")(result, 1);
}
else
{
return result;
}
}
///
@safe pure unittest
{
import std.typecons : Flag, Yes, No;
auto s = "Hello, World!";
char first = parse!char(s);
assert(first == 'H');
assert(s == "ello, World!");
char second = parse!(char, string, No.doCount)(s);
assert(second == 'e');
assert(s == "llo, World!");
auto third = parse!(char, string, Yes.doCount)(s);
assert(third.data == 'l' && third.count == 1);
assert(s == "lo, World!");
}
/*
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);
f = "False killer whale"d;
assert(parse!(bool, dstring, Yes.doCount)(f) == tuple(false, 5));
assert(f == " killer whale"d);
auto m = "maybe";
assertThrown!ConvException(parse!bool(m));
assertThrown!ConvException(parse!(bool, string, Yes.doCount)(m));
assert(m == "maybe"); // m shouldn't change on failure
auto s = "true";
auto b = parse!(const(bool))(s);
assert(b == true);
}
/**
Parsing a character range to `typeof(null)` returns `null` if the range
spells `"null"`. This function is case insensitive.
Params:
Target = the type to convert to
s = the lvalue of an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
doCount = the flag for deciding to report the number of consumed characters
Returns:
$(UL
$(LI `null` if `doCount` is set to `No.doCount`)
$(LI A `tuple` containing `null` and a `size_t` if `doCount` is set to `Yes.doCount`))
Throws:
A $(LREF ConvException) if the range doesn't represent `null`.
*/
auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source s)
if (isInputRange!Source &&
isSomeChar!(ElementType!Source) &&
is(immutable Target == immutable 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();
}
static if (doCount)
{
return tuple!("data", "count")(null, 4);
}
else
{
return null;
}
}
///
@safe pure unittest
{
import std.exception : assertThrown;
import std.typecons : Flag, Yes, No;
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 s3 = "nuLlNULl";
assert(parse!(NullType, string, No.doCount)(s3) is null);
auto r = parse!(NullType, string, Yes.doCount)(s3);
assert(r.data is null && r.count == 4);
auto m = "maybe";
assertThrown!ConvException(parse!NullType(m));
assertThrown!ConvException(parse!(NullType, string, Yes.doCount)(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 auto skipWS(R, Flag!"doCount" doCount = No.doCount)(ref R r)
{
import std.ascii : isWhite;
static if (isSomeString!R)
{
//Implementation inspired from stripLeft.
foreach (i, c; r)
{
if (!isWhite(c))
{
r = r[i .. $];
static if (doCount)
{
return i;
}
else
{
return;
}
}
}
auto len = r.length;
r = r[0 .. 0]; //Empty string with correct type.
static if (doCount)
{
return len;
}
else
{
return;
}
}
else
{
size_t i = 0;
for (; !r.empty && isWhite(r.front); r.popFront(), ++i)
{ }
static if (doCount)
{
return i;
}
}
}
/**
* Parses an array from a string given the left bracket (default $(D
* '[')), right bracket (default `']'`), and element separator (by
* default `','`). A trailing separator is allowed.
*
* Params:
* s = The string to parse
* lbracket = the character that starts the array
* rbracket = the character that ends the array
* comma = the character that separates the elements of the array
* doCount = the flag for deciding to report the number of consumed characters
*
* Returns:
$(UL
* $(LI An array of type `Target` if `doCount` is set to `No.doCount`)
* $(LI A `tuple` containing an array of type `Target` and a `size_t` if `doCount` is set to `Yes.doCount`))
*/
auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source s, dchar lbracket = '[',
dchar rbracket = ']', dchar comma = ',')
if (isSomeString!Source && !is(Source == enum) &&
isDynamicArray!Target && !is(Target == enum))
{
import std.array : appender;
auto result = appender!Target();
parseCheck!s(lbracket);
size_t count = 1 + skipWS!(Source, Yes.doCount)(s);
if (s.empty)
throw convError!(Source, Target)(s);
if (s.front == rbracket)
{
s.popFront();
static if (doCount)
{
return tuple!("data", "count")(result.data, ++count);
}
else
{
return result.data;
}
}
for (;; s.popFront(), count += 1 + skipWS!(Source, Yes.doCount)(s))
{
if (!s.empty && s.front == rbracket)
break;
auto r = parseElement!(WideElementType!Target, Source, Yes.doCount)(s);
result ~= r.data;
count += r.count + skipWS!(Source, Yes.doCount)(s);
if (s.empty)
throw convError!(Source, Target)(s);
if (s.front != comma)
break;
}
parseCheck!s(rbracket);
static if (doCount)
{
return tuple!("data", "count")(result.data, ++count);
}
else
{
return result.data;
}
}
///
@safe pure unittest
{
import std.typecons : Flag, Yes, No;
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"]);
auto s3 = `[['h', 'e', 'l', 'l', 'o'], "world"]`;
auto len3 = s3.length;
auto a3 = parse!(string[], string, Yes.doCount)(s3);
assert(a3.data == ["hello", "world"]);
assert(a3.count == len3);
}
// https://issues.dlang.org/show_bug.cgi?id=9615
@safe unittest
{
import std.typecons : Flag, Yes, No, tuple;
string s0 = "[1,2, ]";
string s1 = "[1,2, \t\v\r\n]";
string s2 = "[1,2]";
assert(s0.parse!(int[]) == [1,2]);
assert(s1.parse!(int[]) == [1,2]);
assert(s2.parse!(int[]) == [1,2]);
s0 = "[1,2, ]";
auto len0 = s0.length;
s1 = "[1,2, \t\v\r\n]";
auto len1 = s1.length;
s2 = "[1,2]";
auto len2 = s2.length;
assert(s0.parse!(int[], string, Yes.doCount) == tuple([1,2], len0));
assert(s1.parse!(int[], string, Yes.doCount) == tuple([1,2], len1));
assert(s2.parse!(int[], string, Yes.doCount) == tuple([1,2], len2));
string s3 = `["a","b",]`;
string s4 = `["a","b"]`;
assert(s3.parse!(string[]) == ["a","b"]);
assert(s4.parse!(string[]) == ["a","b"]);
s3 = `["a","b",]`;
auto len3 = s3.length;
assert(s3.parse!(string[], string, Yes.doCount) == tuple(["a","b"], len3));
s3 = `[ ]`;
assert(tuple([], s3.length) == s3.parse!(string[], string, Yes.doCount));
import std.exception : assertThrown;
string s5 = "[,]";
string s6 = "[, \t,]";
assertThrown!ConvException(parse!(string[])(s5));
assertThrown!ConvException(parse!(int[])(s6));
s5 = "[,]";
s6 = "[,·\t,]";
assertThrown!ConvException(parse!(string[], string, Yes.doCount)(s5));
assertThrown!ConvException(parse!(string[], string, Yes.doCount)(s6));
}
@safe unittest
{
int[] a = [1, 2, 3, 4, 5];
auto s = to!string(a);
assert(to!(int[])(s) == a);
}
@safe unittest
{
int[][] a = [ [1, 2] , [3], [4, 5] ];
auto s = to!string(a);
assert(to!(int[][])(s) == a);
}
@safe 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
{
import std.exception;
import std.typecons : Flag, Yes, No;
//Check proper failure
auto s = "[ 1 , 2 , 3 ]";
auto s2 = s.save;
foreach (i ; 0 .. s.length-1)
{
auto ss = s[0 .. i];
assertThrown!ConvException(parse!(int[])(ss));
assertThrown!ConvException(parse!(int[], string, Yes.doCount)(ss));
}
int[] arr = parse!(int[])(s);
auto arr2 = parse!(int[], string, Yes.doCount)(s2);
arr = arr2.data;
}
@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"
];
string s3 = s1.save;
assert(s2 == parse!(string[])(s1));
assert(s1.empty);
assert(tuple(s2, s3.length) == parse!(string[], string, Yes.doCount)(s3));
}
/// ditto
auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(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);
size_t count = 1 + skipWS!(Source, Yes.doCount)(s);
if (s.empty)
throw convError!(Source, Target)(s);
if (s.front == rbracket)
{
static if (result.length != 0)
goto Lmanyerr;
else
{
s.popFront();
static if (doCount)
{
return tuple!("data", "count")(result, ++count);
}
else
{
return result;
}
}
}
for (size_t i = 0; ; s.popFront(), count += 1 + skipWS!(Source, Yes.doCount)(s))
{
if (i == result.length)
goto Lmanyerr;
auto r = parseElement!(ElementType!Target, Source, Yes.doCount)(s);
result[i++] = r.data;
count += r.count + skipWS!(Source, Yes.doCount)(s);
if (s.empty)
throw convError!(Source, Target)(s);
if (s.front != comma)
{
if (i != result.length)
goto Lfewerr;
break;
}
}
parseCheck!s(rbracket);
static if (doCount)
{
return tuple!("data", "count")(result, ++count);
}
else
{
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]);
s1 = "[1,2,3,4]";
assert(tuple([1,2,3,4], s1.length) == parse!(int[4], string, Yes.doCount)(s1));
auto s2 = "[[1],[2,3],[4]]";
auto sa2 = parse!(int[][3])(s2);
assert(sa2 == [[1],[2,3],[4]]);
s2 = "[[1],[2,3],[4]]";
assert(tuple([[1],[2,3],[4]], s2.length) == parse!(int[][3], string, Yes.doCount)(s2));
auto s3 = "[1,2,3]";
assertThrown!ConvException(parse!(int[4])(s3));
assertThrown!ConvException(parse!(int[4], string, Yes.doCount)(s3));
auto s4 = "[1,2,3,4,5]";
assertThrown!ConvException(parse!(int[4])(s4));
assertThrown!ConvException(parse!(int[4], string, Yes.doCount)(s4));
}
/**
* Parses an associative array from a string given the left bracket (default $(D
* '[')), right bracket (default `']'`), key-value separator (default $(D
* ':')), and element seprator (by default `','`).
*
* Params:
* s = the string to parse
* lbracket = the character that starts the associative array
* rbracket = the character that ends the associative array
* keyval = the character that associates the key with the value
* comma = the character that separates the elements of the associative array
* doCount = the flag for deciding to report the number of consumed characters
*
* Returns:
$(UL
* $(LI An associative array of type `Target` if `doCount` is set to `No.doCount`)
* $(LI A `tuple` containing an associative array of type `Target` and a `size_t`
* if `doCount` is set to `Yes.doCount`))
*/