997 lines
23 KiB
D
997 lines
23 KiB
D
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/**
|
|
* This module provides functions for converting between different types.
|
|
*
|
|
* Copyright: Eugene Wissner 2017-2018.
|
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
|
* Mozilla Public License, v. 2.0).
|
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
|
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/conv.d,
|
|
* tanya/conv.d)
|
|
*/
|
|
module tanya.conv;
|
|
|
|
import tanya.container.string;
|
|
import tanya.format;
|
|
import tanya.memory;
|
|
import tanya.memory.op;
|
|
import tanya.meta.trait;
|
|
import tanya.meta.transform;
|
|
import tanya.range.array;
|
|
import tanya.range.primitive;
|
|
|
|
version (unittest)
|
|
{
|
|
import tanya.test.assertion;
|
|
}
|
|
|
|
/**
|
|
* Constructs a new object of type $(D_PARAM T) in $(D_PARAM memory) with the
|
|
* given arguments.
|
|
*
|
|
* If $(D_PARAM T) is a $(D_KEYWORD class), emplace returns a class reference
|
|
* of type $(D_PARAM T), otherwise a pointer to the constructed object is
|
|
* returned.
|
|
*
|
|
* If $(D_PARAM T) is a nested class inside another class, $(D_PARAM outer)
|
|
* should be an instance of the outer class.
|
|
*
|
|
* $(D_PARAM args) are arguments for the constructor of $(D_PARAM T). If
|
|
* $(D_PARAM T) isn't an aggregate type and doesn't have a constructor,
|
|
* $(D_PARAM memory) can be initialized to `args[0]` if `Args.length == 1`,
|
|
* `Args[0]` should be implicitly convertible to $(D_PARAM T) then.
|
|
*
|
|
* Params:
|
|
* T = Constructed type.
|
|
* U = Type of the outer class if $(D_PARAM T) is a nested class.
|
|
* Args = Types of the constructor arguments if $(D_PARAM T) has a constructor
|
|
* or the type of the initial value.
|
|
* outer = Outer class instance if $(D_PARAM T) is a nested class.
|
|
* args = Constructor arguments if $(D_PARAM T) has a constructor or the
|
|
* initial value.
|
|
*
|
|
* Returns: New instance of type $(D_PARAM T) constructed in $(D_PARAM memory).
|
|
*
|
|
* Precondition: `memory.length == stateSize!T`.
|
|
* Postcondition: $(D_PARAM memory) and the result point to the same memory.
|
|
*/
|
|
T emplace(T, U, Args...)(void[] memory, U outer, auto ref Args args)
|
|
if (!isAbstractClass!T && isInnerClass!T && is(typeof(T.outer) == U))
|
|
in
|
|
{
|
|
assert(memory.length >= stateSize!T);
|
|
}
|
|
out (result)
|
|
{
|
|
assert(memory.ptr is (() @trusted => cast(void*) result)());
|
|
}
|
|
do
|
|
{
|
|
copy(typeid(T).initializer, memory);
|
|
|
|
auto result = (() @trusted => cast(T) memory.ptr)();
|
|
result.outer = outer;
|
|
|
|
static if (is(typeof(result.__ctor(args))))
|
|
{
|
|
result.__ctor(args);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// ditto
|
|
T emplace(T, Args...)(void[] memory, auto ref Args args)
|
|
if (is(T == class) && !isAbstractClass!T && !isInnerClass!T)
|
|
in
|
|
{
|
|
assert(memory.length == stateSize!T);
|
|
}
|
|
out (result)
|
|
{
|
|
assert(memory.ptr is (() @trusted => cast(void*) result)());
|
|
}
|
|
do
|
|
{
|
|
copy(typeid(T).initializer, memory);
|
|
|
|
auto result = (() @trusted => cast(T) memory.ptr)();
|
|
static if (is(typeof(result.__ctor(args))))
|
|
{
|
|
result.__ctor(args);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
import tanya.memory : stateSize;
|
|
|
|
class C
|
|
{
|
|
int i = 5;
|
|
class Inner
|
|
{
|
|
int i;
|
|
|
|
this(int param) pure nothrow @safe @nogc
|
|
{
|
|
this.i = param;
|
|
}
|
|
}
|
|
}
|
|
ubyte[stateSize!C] memory1;
|
|
ubyte[stateSize!(C.Inner)] memory2;
|
|
|
|
auto c = emplace!C(memory1);
|
|
assert(c.i == 5);
|
|
|
|
auto inner = emplace!(C.Inner)(memory2, c, 8);
|
|
assert(c.i == 5);
|
|
assert(inner.i == 8);
|
|
assert(inner.outer is c);
|
|
}
|
|
|
|
/// ditto
|
|
T* emplace(T, Args...)(void[] memory, auto ref Args args)
|
|
if (!isAggregateType!T && (Args.length <= 1))
|
|
in
|
|
{
|
|
assert(memory.length >= T.sizeof);
|
|
}
|
|
out (result)
|
|
{
|
|
assert(memory.ptr is result);
|
|
}
|
|
do
|
|
{
|
|
auto result = (() @trusted => cast(T*) memory.ptr)();
|
|
static if (Args.length == 1)
|
|
{
|
|
*result = T(args[0]);
|
|
}
|
|
else
|
|
{
|
|
*result = T.init;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// ditto
|
|
T* emplace(T, Args...)(void[] memory, auto ref Args args)
|
|
if (!isPolymorphicType!T && isAggregateType!T)
|
|
in
|
|
{
|
|
assert(memory.length >= T.sizeof);
|
|
}
|
|
out (result)
|
|
{
|
|
assert(memory.ptr is result);
|
|
}
|
|
do
|
|
{
|
|
auto result = (() @trusted => cast(T*) memory.ptr)();
|
|
static if (!hasElaborateAssign!T && isAssignable!T)
|
|
{
|
|
*result = T.init;
|
|
}
|
|
else
|
|
{
|
|
static const T init = T.init;
|
|
copy((cast(void*) &init)[0 .. T.sizeof], memory);
|
|
}
|
|
|
|
static if (Args.length == 0)
|
|
{
|
|
static assert(is(typeof({ static T t; })),
|
|
"Default constructor is disabled");
|
|
}
|
|
else static if (is(typeof(T(args))))
|
|
{
|
|
*result = T(args);
|
|
}
|
|
else static if (is(typeof(result.__ctor(args))))
|
|
{
|
|
result.__ctor(args);
|
|
}
|
|
else
|
|
{
|
|
static assert(false,
|
|
"Unable to construct value with the given arguments");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
ubyte[4] memory;
|
|
|
|
auto i = emplace!int(memory);
|
|
static assert(is(typeof(i) == int*));
|
|
assert(*i == 0);
|
|
|
|
i = emplace!int(memory, 5);
|
|
assert(*i == 5);
|
|
|
|
static struct S
|
|
{
|
|
int i;
|
|
@disable this();
|
|
@disable this(this);
|
|
this(int i) @nogc nothrow pure @safe
|
|
{
|
|
this.i = i;
|
|
}
|
|
}
|
|
auto s = emplace!S(memory, 8);
|
|
static assert(is(typeof(s) == S*));
|
|
assert(s.i == 8);
|
|
}
|
|
|
|
// Handles "Cannot access frame pointer" error.
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
struct F
|
|
{
|
|
~this() @nogc nothrow pure @safe
|
|
{
|
|
}
|
|
}
|
|
static assert(is(typeof(emplace!F((void[]).init))));
|
|
}
|
|
|
|
/**
|
|
* Thrown if a type conversion fails.
|
|
*/
|
|
final class ConvException : Exception
|
|
{
|
|
/**
|
|
* Params:
|
|
* msg = The message for the exception.
|
|
* file = The file where the exception occurred.
|
|
* line = The line number where the exception occurred.
|
|
* next = The previous exception in the chain of exceptions, if any.
|
|
*/
|
|
this(string msg,
|
|
string file = __FILE__,
|
|
size_t line = __LINE__,
|
|
Throwable next = null) @nogc @safe pure nothrow
|
|
{
|
|
super(msg, file, line, next);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Converts a string $(D_PARAM range) into an integral value of type
|
|
* $(D_PARAM T) in $(D_PARAM base).
|
|
*
|
|
* The convertion stops when $(D_PARAM range) is empty of if the next character
|
|
* cannot be converted because it is not a digit (with respect to the
|
|
* $(D_PARAM base)) or if the reading the next character would cause integer
|
|
* overflow. The function returns the value converted so far then. The front
|
|
* element of the $(D_PARAM range) points to the first character cannot be
|
|
* converted or $(D_PARAM range) is empty if the whole string could be
|
|
* converted.
|
|
*
|
|
* Base must be between 2 and 36 inclursive. Default base is 10.
|
|
*
|
|
* The function doesn't handle the sign (+ or -) or number prefixes (like 0x).
|
|
*/
|
|
package T readIntegral(T, R)(ref R range, const ubyte base = 10)
|
|
if (isInputRange!R
|
|
&& isSomeChar!(ElementType!R)
|
|
&& isIntegral!T
|
|
&& isUnsigned!T)
|
|
in
|
|
{
|
|
assert(base >= 2);
|
|
assert(base <= 36);
|
|
}
|
|
do
|
|
{
|
|
T boundary = cast(T) (T.max / base);
|
|
if (range.empty)
|
|
{
|
|
return T.init;
|
|
}
|
|
|
|
T n;
|
|
int digit;
|
|
do
|
|
{
|
|
if (range.front >= 'a')
|
|
{
|
|
digit = range.front - 'W';
|
|
}
|
|
else if (range.front >= 'A' && range.front <= 'Z')
|
|
{
|
|
digit = range.front - '7';
|
|
}
|
|
else if (range.front >= '0' && range.front <= '9')
|
|
{
|
|
digit = range.front - '0';
|
|
}
|
|
else
|
|
{
|
|
return n;
|
|
}
|
|
if (digit >= base)
|
|
{
|
|
return n;
|
|
}
|
|
n = cast(T) (n * base + digit);
|
|
range.popFront();
|
|
|
|
if (range.empty)
|
|
{
|
|
return n;
|
|
}
|
|
}
|
|
while (n < boundary);
|
|
|
|
if (range.front >= 'a')
|
|
{
|
|
digit = range.front - 'W';
|
|
}
|
|
else if (range.front >= 'A')
|
|
{
|
|
digit = range.front - '7';
|
|
}
|
|
else if (range.front >= '0')
|
|
{
|
|
digit = range.front - '0';
|
|
}
|
|
else
|
|
{
|
|
return n;
|
|
}
|
|
if (n > cast(T) ((T.max - digit) / base))
|
|
{
|
|
return n;
|
|
}
|
|
n = cast(T) (n * base + digit);
|
|
range.popFront();
|
|
|
|
return n;
|
|
}
|
|
|
|
// ':' is not a hex value
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
string colon = ":";
|
|
auto actual = readIntegral!ubyte(colon, 16);
|
|
assert(actual == 0);
|
|
assert(colon.length == 1);
|
|
}
|
|
|
|
// reads ubyte.max
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
string number = "255";
|
|
assert(readIntegral!ubyte(number) == 255);
|
|
assert(number.empty);
|
|
}
|
|
|
|
// detects integer overflow
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
string number = "500";
|
|
readIntegral!ubyte(number);
|
|
assert(number.front == '0');
|
|
assert(number.length == 1);
|
|
}
|
|
|
|
// stops on a non-digit
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
string number = "10-";
|
|
readIntegral!ubyte(number);
|
|
assert(number.front == '-');
|
|
}
|
|
|
|
// returns false if the number string is empty
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
string number = "";
|
|
readIntegral!ubyte(number);
|
|
assert(number.empty);
|
|
}
|
|
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
string number = "29";
|
|
assert(readIntegral!ubyte(number) == 29);
|
|
assert(number.empty);
|
|
}
|
|
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
string number = "25467";
|
|
readIntegral!ubyte(number);
|
|
assert(number.front == '6');
|
|
}
|
|
|
|
// Converts lower case hexadecimals
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
string number = "a";
|
|
assert(readIntegral!ubyte(number, 16) == 10);
|
|
assert(number.empty);
|
|
}
|
|
|
|
// Converts upper case hexadecimals
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
string number = "FF";
|
|
assert(readIntegral!ubyte(number, 16) == 255);
|
|
assert(number.empty);
|
|
}
|
|
|
|
// Handles small overflows
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
string number = "256";
|
|
assert(readIntegral!ubyte(number, 10) == 25);
|
|
assert(number.front == '6');
|
|
}
|
|
|
|
/**
|
|
* If the source type $(D_PARAM From) and the target type $(D_PARAM To) are
|
|
* equal, does nothing. If $(D_PARAM From) can be implicitly converted to
|
|
* $(D_PARAM To), just returns $(D_PARAM from).
|
|
*
|
|
* Params:
|
|
* To = Target type.
|
|
*
|
|
* Returns: $(D_PARAM from).
|
|
*/
|
|
template to(To)
|
|
{
|
|
/**
|
|
* Params:
|
|
* From = Source type.
|
|
* from = Source value.
|
|
*/
|
|
ref To to(From)(ref From from)
|
|
if (is(To == From))
|
|
{
|
|
return from;
|
|
}
|
|
|
|
/// ditto
|
|
To to(From)(From from)
|
|
if (is(Unqual!To == Unqual!From) || (isNumeric!From && isFloatingPoint!To))
|
|
{
|
|
return from;
|
|
}
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
auto val = 5.to!int();
|
|
assert(val == 5);
|
|
static assert(is(typeof(val) == int));
|
|
}
|
|
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
int val = 5;
|
|
assert(val.to!int() == 5);
|
|
}
|
|
|
|
/**
|
|
* Performs checked conversion from an integral type $(D_PARAM From) to an
|
|
* integral type $(D_PARAM To).
|
|
*
|
|
* Params:
|
|
* From = Source type.
|
|
* To = Target type.
|
|
* from = Source value.
|
|
*
|
|
* Returns: $(D_PARAM from) converted to $(D_PARAM To).
|
|
*
|
|
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) is too small or too
|
|
* large to be represented by $(D_PARAM To).
|
|
*/
|
|
To to(To, From)(From from)
|
|
if (isIntegral!From
|
|
&& isIntegral!To
|
|
&& !is(Unqual!To == Unqual!From)
|
|
&& !is(To == enum))
|
|
{
|
|
static if ((isUnsigned!From && isSigned!To && From.sizeof == To.sizeof)
|
|
|| From.sizeof > To.sizeof)
|
|
{
|
|
if (from > To.max)
|
|
{
|
|
throw make!ConvException(defaultAllocator,
|
|
"Positive integer overflow");
|
|
}
|
|
}
|
|
static if (isSigned!From)
|
|
{
|
|
static if (isUnsigned!To)
|
|
{
|
|
if (from < 0)
|
|
{
|
|
throw make!ConvException(defaultAllocator,
|
|
"Negative integer overflow");
|
|
}
|
|
}
|
|
else static if (From.sizeof > To.sizeof)
|
|
{
|
|
if (from < To.min)
|
|
{
|
|
throw make!ConvException(defaultAllocator,
|
|
"Negative integer overflow");
|
|
}
|
|
}
|
|
}
|
|
static if (From.sizeof <= To.sizeof)
|
|
{
|
|
return from;
|
|
}
|
|
else static if (isSigned!To)
|
|
{
|
|
return cast(To) from;
|
|
}
|
|
else
|
|
{
|
|
return from & To.max;
|
|
}
|
|
}
|
|
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
// ubyte -> ushort
|
|
assert((cast(ubyte) 0).to!ushort == 0);
|
|
assert((cast(ubyte) 1).to!ushort == 1);
|
|
assert((cast(ubyte) (ubyte.max - 1)).to!ushort == ubyte.max - 1);
|
|
assert((cast(ubyte) ubyte.max).to!ushort == ubyte.max);
|
|
|
|
// ubyte -> short
|
|
assert((cast(ubyte) 0).to!short == 0);
|
|
assert((cast(ubyte) 1).to!short == 1);
|
|
assert((cast(ubyte) (ubyte.max - 1)).to!short == ubyte.max - 1);
|
|
assert((cast(ubyte) ubyte.max).to!short == ubyte.max);
|
|
}
|
|
|
|
@nogc pure @safe unittest
|
|
{
|
|
// ubyte <- ushort
|
|
assert((cast(ushort) 0).to!ubyte == 0);
|
|
assert((cast(ushort) 1).to!ubyte == 1);
|
|
assert((cast(ushort) (ubyte.max - 1)).to!ubyte == ubyte.max - 1);
|
|
assert((cast(ushort) ubyte.max).to!ubyte == ubyte.max);
|
|
|
|
// ubyte <- short
|
|
assert((cast(short) 0).to!ubyte == 0);
|
|
assert((cast(short) 1).to!ubyte == 1);
|
|
assert((cast(short) (ubyte.max - 1)).to!ubyte == ubyte.max - 1);
|
|
assert((cast(short) ubyte.max).to!ubyte == ubyte.max);
|
|
|
|
// short <-> int
|
|
assert(short.min.to!int == short.min);
|
|
assert((short.min + 1).to!int == short.min + 1);
|
|
assert((cast(short) -1).to!int == -1);
|
|
assert((cast(short) 0).to!int == 0);
|
|
assert((cast(short) 1).to!int == 1);
|
|
assert((short.max - 1).to!int == short.max - 1);
|
|
assert(short.max.to!int == short.max);
|
|
|
|
assert((cast(int) short.min).to!short == short.min);
|
|
assert((cast(int) short.min + 1).to!short == short.min + 1);
|
|
assert((cast(int) -1).to!short == -1);
|
|
assert((cast(int) 0).to!short == 0);
|
|
assert((cast(int) 1).to!short == 1);
|
|
assert((cast(int) short.max - 1).to!short == short.max - 1);
|
|
assert((cast(int) short.max).to!short == short.max);
|
|
|
|
// uint <-> int
|
|
assert((cast(uint) 0).to!int == 0);
|
|
assert((cast(uint) 1).to!int == 1);
|
|
assert((cast(uint) (int.max - 1)).to!int == int.max - 1);
|
|
assert((cast(uint) int.max).to!int == int.max);
|
|
|
|
assert((cast(int) 0).to!uint == 0);
|
|
assert((cast(int) 1).to!uint == 1);
|
|
assert((cast(int) (int.max - 1)).to!uint == int.max - 1);
|
|
assert((cast(int) int.max).to!uint == int.max);
|
|
}
|
|
|
|
@nogc pure @safe unittest
|
|
{
|
|
assertThrown!ConvException(&to!(short, int), int.min);
|
|
assertThrown!ConvException(&to!(short, int), int.max);
|
|
assertThrown!ConvException(&to!(ushort, uint), uint.max);
|
|
assertThrown!ConvException(&to!(uint, int), -1);
|
|
}
|
|
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
enum Test : int
|
|
{
|
|
one,
|
|
two,
|
|
}
|
|
assert(Test.one.to!int == 0);
|
|
assert(Test.two.to!int == 1);
|
|
}
|
|
|
|
/**
|
|
* Converts a floating point number to an integral type.
|
|
*
|
|
* Params:
|
|
* From = Source type.
|
|
* To = Target type.
|
|
* from = Source value.
|
|
*
|
|
* Returns: Truncated $(D_PARAM from) (everything after the decimal point is
|
|
* dropped).
|
|
*
|
|
* Throws: $(D_PSYMBOL ConvException) if
|
|
* $(D_INLINECODE from < To.min || from > To.max).
|
|
*/
|
|
To to(To, From)(From from)
|
|
if (isFloatingPoint!From
|
|
&& isIntegral!To
|
|
&& !is(Unqual!To == Unqual!From)
|
|
&& !is(To == enum))
|
|
{
|
|
if (from > To.max)
|
|
{
|
|
throw make!ConvException(defaultAllocator,
|
|
"Positive number overflow");
|
|
}
|
|
else if (from < To.min)
|
|
{
|
|
throw make!ConvException(defaultAllocator,
|
|
"Negative number overflow");
|
|
}
|
|
return cast(To) from;
|
|
}
|
|
|
|
///
|
|
@nogc pure @safe unittest
|
|
{
|
|
assert(1.5.to!int == 1);
|
|
assert(2147483646.5.to!int == 2147483646);
|
|
assert((-2147483647.5).to!int == -2147483647);
|
|
assert(2147483646.5.to!uint == 2147483646);
|
|
}
|
|
|
|
@nogc pure @safe unittest
|
|
{
|
|
assertThrown!ConvException(&to!(int, double), 2147483647.5);
|
|
assertThrown!ConvException(&to!(int, double), -2147483648.5);
|
|
assertThrown!ConvException(&to!(uint, double), -21474.5);
|
|
}
|
|
|
|
/**
|
|
* Performs checked conversion from an integral type $(D_PARAM From) to an
|
|
* $(D_KEYWORD enum).
|
|
*
|
|
* Params:
|
|
* From = Source type.
|
|
* To = Target type.
|
|
* from = Source value.
|
|
*
|
|
* Returns: $(D_KEYWORD enum) value.
|
|
*
|
|
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) is not a member of
|
|
* $(D_PSYMBOL To).
|
|
*/
|
|
To to(To, From)(From from)
|
|
if (isIntegral!From && is(To == enum))
|
|
{
|
|
foreach (m; EnumMembers!To)
|
|
{
|
|
if (from == m)
|
|
{
|
|
return m;
|
|
}
|
|
}
|
|
throw make!ConvException(defaultAllocator,
|
|
"Value not found in enum '" ~ To.stringof ~ "'");
|
|
}
|
|
|
|
///
|
|
@nogc pure @safe unittest
|
|
{
|
|
enum Test : int
|
|
{
|
|
one,
|
|
two,
|
|
}
|
|
static assert(is(typeof(1.to!Test) == Test));
|
|
assert(0.to!Test == Test.one);
|
|
assert(1.to!Test == Test.two);
|
|
}
|
|
|
|
@nogc pure @safe unittest
|
|
{
|
|
enum Test : uint
|
|
{
|
|
one,
|
|
two,
|
|
}
|
|
assertThrown!ConvException(&to!(Test, int), 5);
|
|
}
|
|
|
|
/**
|
|
* Converts $(D_PARAM from) to a boolean.
|
|
*
|
|
* If $(D_PARAM From) is a numeric type, then `1` becomes $(D_KEYWORD true),
|
|
* `0` $(D_KEYWORD false). Otherwise $(D_PSYMBOL ConvException) is thrown.
|
|
*
|
|
* If $(D_PARAM To) is a string (built-in string or $(D_PSYMBOL String)),
|
|
* then `"true"` or `"false"` are converted to the appropriate boolean value.
|
|
* Otherwise $(D_PSYMBOL ConvException) is thrown.
|
|
*
|
|
* Params:
|
|
* From = Source type.
|
|
* To = Target type.
|
|
* from = Source value.
|
|
*
|
|
* Returns: $(D_KEYWORD from) converted to a boolean.
|
|
*
|
|
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) isn't convertible.
|
|
*/
|
|
To to(To, From)(From from)
|
|
if (isNumeric!From && is(Unqual!To == bool) && !is(Unqual!To == Unqual!From))
|
|
{
|
|
if (from == 0)
|
|
{
|
|
return false;
|
|
}
|
|
else if (from < 0)
|
|
{
|
|
throw make!ConvException(defaultAllocator,
|
|
"Negative number overflow");
|
|
}
|
|
else if (from <= 1)
|
|
{
|
|
return true;
|
|
}
|
|
throw make!ConvException(defaultAllocator,
|
|
"Positive number overflow");
|
|
}
|
|
|
|
///
|
|
@nogc pure @safe unittest
|
|
{
|
|
assert(!0.0.to!bool);
|
|
assert(0.2.to!bool);
|
|
assert(0.5.to!bool);
|
|
assert(1.0.to!bool);
|
|
|
|
assert(!0.to!bool);
|
|
assert(1.to!bool);
|
|
}
|
|
|
|
@nogc pure @safe unittest
|
|
{
|
|
assertThrown!ConvException(&to!(bool, int), -1);
|
|
assertThrown!ConvException(&to!(bool, int), 2);
|
|
}
|
|
|
|
/// ditto
|
|
To to(To, From)(auto ref const From from)
|
|
if ((is(From == String) || isSomeString!From) && is(Unqual!To == bool))
|
|
{
|
|
if (from == "true")
|
|
{
|
|
return true;
|
|
}
|
|
else if (from == "false")
|
|
{
|
|
return false;
|
|
}
|
|
throw make!ConvException(defaultAllocator,
|
|
"String doesn't contain a boolean value");
|
|
}
|
|
|
|
///
|
|
@nogc pure @safe unittest
|
|
{
|
|
assert("true".to!bool);
|
|
assert(!"false".to!bool);
|
|
assert(String("true").to!bool);
|
|
assert(!String("false").to!bool);
|
|
|
|
}
|
|
|
|
@nogc pure @safe unittest
|
|
{
|
|
assertThrown!ConvException(() => "1".to!bool);
|
|
}
|
|
|
|
/**
|
|
* Converts a boolean to $(D_PARAM To).
|
|
*
|
|
* If $(D_PARAM To) is a numeric type, then $(D_KEYWORD true) becomes `1`,
|
|
* $(D_KEYWORD false) `0`.
|
|
*
|
|
* If $(D_PARAM To) is a $(D_PSYMBOL String), then `"true"` or `"false"`
|
|
* is returned.
|
|
*
|
|
* Params:
|
|
* From = Source type.
|
|
* To = Target type.
|
|
* from = Source value.
|
|
*
|
|
* Returns: $(D_PARAM from) converted to $(D_PARAM To).
|
|
*/
|
|
To to(To, From)(From from)
|
|
if (is(Unqual!From == bool) && isNumeric!To && !is(Unqual!To == Unqual!From))
|
|
{
|
|
return from;
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
assert(true.to!float == 1.0);
|
|
assert(true.to!double == 1.0);
|
|
assert(true.to!ubyte == 1);
|
|
assert(true.to!byte == 1);
|
|
assert(true.to!ushort == 1);
|
|
assert(true.to!short == 1);
|
|
assert(true.to!uint == 1);
|
|
assert(true.to!int == 1);
|
|
|
|
assert(false.to!float == 0);
|
|
assert(false.to!double == 0);
|
|
assert(false.to!ubyte == 0);
|
|
assert(false.to!byte == 0);
|
|
assert(false.to!ushort == 0);
|
|
assert(false.to!short == 0);
|
|
assert(false.to!uint == 0);
|
|
assert(false.to!int == 0);
|
|
}
|
|
|
|
deprecated("Use tanya.format.format instead")
|
|
To to(To, From)(auto ref From from)
|
|
if (is(Unqual!To == String))
|
|
{
|
|
return format!"{}"(from);
|
|
}
|
|
|
|
/**
|
|
* Converts a stringish range to an integral value.
|
|
*
|
|
* Params:
|
|
* From = Source type.
|
|
* To = Target type.
|
|
* from = Source value.
|
|
*
|
|
* Returns: $(D_PARAM from) converted to $(D_PARAM To).
|
|
*
|
|
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) doesn't contain an
|
|
* integral value.
|
|
*/
|
|
To to(To, From)(auto ref From from)
|
|
if (isInputRange!From && isSomeChar!(ElementType!From) && isIntegral!To)
|
|
{
|
|
if (from.empty)
|
|
{
|
|
throw make!ConvException(defaultAllocator, "Input range is empty");
|
|
}
|
|
|
|
static if (isSigned!To)
|
|
{
|
|
bool negative;
|
|
}
|
|
if (from.front == '-')
|
|
{
|
|
static if (isUnsigned!To)
|
|
{
|
|
throw make!ConvException(defaultAllocator,
|
|
"Negative integer overflow");
|
|
}
|
|
else
|
|
{
|
|
negative = true;
|
|
from.popFront();
|
|
}
|
|
}
|
|
|
|
if (from.empty)
|
|
{
|
|
throw make!ConvException(defaultAllocator, "Input range is empty");
|
|
}
|
|
|
|
ubyte base = 10;
|
|
if (from.front == '0')
|
|
{
|
|
from.popFront();
|
|
if (from.empty)
|
|
{
|
|
return To.init;
|
|
}
|
|
else if (from.front == 'x' || from.front == 'X')
|
|
{
|
|
base = 16;
|
|
from.popFront();
|
|
}
|
|
else if (from.front == 'b' || from.front == 'B')
|
|
{
|
|
base = 2;
|
|
from.popFront();
|
|
}
|
|
else
|
|
{
|
|
base = 8;
|
|
}
|
|
}
|
|
|
|
auto unsigned = readIntegral!(Unsigned!To, From)(from, base);
|
|
if (!from.empty)
|
|
{
|
|
throw make!ConvException(defaultAllocator, "Integer overflow");
|
|
}
|
|
|
|
static if (isSigned!To)
|
|
{
|
|
if (negative)
|
|
{
|
|
auto predecessor = cast(Unsigned!To) (unsigned - 1);
|
|
if (predecessor > cast(Unsigned!To) To.max)
|
|
{
|
|
throw make!ConvException(defaultAllocator,
|
|
"Negative integer overflow");
|
|
}
|
|
return cast(To) (-(cast(Largest!(To, ptrdiff_t)) predecessor) - 1);
|
|
}
|
|
else if (unsigned > cast(Unsigned!To) To.max)
|
|
{
|
|
throw make!ConvException(defaultAllocator, "Integer overflow");
|
|
}
|
|
else
|
|
{
|
|
return unsigned;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return unsigned;
|
|
}
|
|
}
|
|
|
|
///
|
|
@nogc pure @safe unittest
|
|
{
|
|
assert("1234".to!uint() == 1234);
|
|
assert("1234".to!int() == 1234);
|
|
assert("1234".to!int() == 1234);
|
|
|
|
assert("0".to!int() == 0);
|
|
assert("-0".to!int() == 0);
|
|
|
|
assert("0x10".to!int() == 16);
|
|
assert("0X10".to!int() == 16);
|
|
assert("-0x10".to!int() == -16);
|
|
|
|
assert("0b10".to!int() == 2);
|
|
assert("0B10".to!int() == 2);
|
|
assert("-0b10".to!int() == -2);
|
|
|
|
assert("010".to!int() == 8);
|
|
assert("-010".to!int() == -8);
|
|
|
|
|
|
assert("-128".to!byte == cast(byte) -128);
|
|
|
|
assertThrown!ConvException(() => "".to!int);
|
|
assertThrown!ConvException(() => "-".to!int);
|
|
assertThrown!ConvException(() => "-5".to!uint);
|
|
assertThrown!ConvException(() => "-129".to!byte);
|
|
assertThrown!ConvException(() => "256".to!ubyte);
|
|
}
|