Created
June 24, 2016 21:08
-
-
Save andralex/a0c0ad32704e6ba66e458ac48add4a99 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import std.traits : isIntegral, isUnsigned, Unqual; | |
/** | |
*/ | |
pure @safe nothrow @nogc | |
{ | |
int pow(int lhs, uint rhs, ref bool overflow) | |
{ return powImpl(lhs, rhs, overflow); } | |
/// ditto | |
long pow(long lhs, uint rhs, ref bool overflow) | |
{ return powImpl(lhs, rhs, overflow); } | |
/// ditto | |
uint pow(uint lhs, uint rhs, ref bool overflow) | |
{ return powImpl(lhs, rhs, overflow); } | |
/// ditto | |
ulong pow(ulong lhs, uint rhs, ref bool overflow) | |
{ return powImpl(lhs, rhs, overflow); } | |
} | |
// Inspiration: http://www.stepanovpapers.com/PAM.pdf | |
private T powImpl(T)(T b, uint e, ref bool overflow) | |
if (isIntegral!T && T.sizeof >= 4) | |
{ | |
if (e <= 1) return e == 1 ? b : 1; | |
import core.checkedint : muls, mulu; | |
static if (isUnsigned!T) alias mul = mulu; | |
else alias mul = muls; | |
T r = b; | |
--e; | |
// Loop invariant: r * (b ^^ e) is the actual result | |
for (;; e /= 2) | |
{ | |
if (e % 2) | |
{ | |
r = mul(r, b, overflow); | |
if (e == 1) break; | |
} | |
b = mul(b, b, overflow); | |
} | |
return r; | |
} | |
unittest | |
{ | |
static void testPow(T)(T x, uint e) | |
{ | |
bool overflow; | |
foreach (uint i; 0 .. e) | |
{ | |
assert(pow(x, i, overflow) == x ^^ i); | |
assert(!overflow); | |
} | |
assert(pow(x, e, overflow) == x ^^ e); | |
assert(overflow); | |
} | |
testPow!int(3, 20); | |
testPow!uint(3, 21); | |
testPow!long(3, 40); | |
testPow!ulong(3, 41); | |
} | |
private enum valueConvertible(T1, T2) = isIntegral!T1 && isIntegral!T2 && | |
is(T1 : T2) && ( | |
isUnsigned!T1 == isUnsigned!T2 || // same signedness | |
!isUnsigned!T2 && T2.sizeof > T1.sizeof // safely convertible | |
); | |
/** | |
*/ | |
auto add(L, R)(const L lhs, const R rhs, ref bool overflow) | |
if (isIntegral!L && isIntegral!R && L.sizeof >= 4 && R.sizeof >= 4) | |
{ | |
import core.checkedint; | |
alias Result = typeof(lhs + rhs); | |
static if (valueConvertible!(L, Result) && valueConvertible!(R, Result)) | |
{ | |
static if (isUnsigned!Result) alias impl = addu; | |
else alias impl = adds; | |
return impl(Result(lhs), Result(rhs), overflow); | |
} | |
else | |
{ | |
static assert(isUnsigned!Result); | |
static assert(isUnsigned!L != isUnsigned!R); | |
// Mixed signs | |
static if (!isUnsigned!L) | |
{ | |
if (lhs < 0) | |
{ | |
return subu(Result(rhs), Result(negs(lhs, overflow)), | |
overflow); | |
} | |
} | |
else static if (!isUnsigned!R) | |
{ | |
if (rhs < 0) | |
{ | |
return subu(Result(lhs), Result(negs(rhs, overflow)), | |
overflow); | |
} | |
} | |
return addu(Result(lhs), Result(rhs), overflow); | |
} | |
} | |
unittest | |
{ | |
bool overflow; | |
assert(add(1, 1, overflow) == 2 && !overflow); | |
assert(add(1, 1u, overflow) == 2 && !overflow); | |
assert(add(-1, 1u, overflow) == 0 && !overflow); | |
assert(add(1u, -1, overflow) == 0 && !overflow); | |
} | |
auto sub(L, R)(const L lhs, const R rhs, ref bool overflow) | |
if (isIntegral!L && isIntegral!R && L.sizeof >= 4 && R.sizeof >= 4) | |
{ | |
import core.checkedint; | |
alias Result = typeof(lhs - rhs); | |
static if (valueConvertible!(L, Result) && valueConvertible!(R, Result)) | |
{ | |
static if (isUnsigned!Result) alias impl = subu; | |
else alias impl = subs; | |
return impl(Result(lhs), Result(rhs), overflow); | |
} | |
else | |
{ | |
static assert(isUnsigned!Result); | |
static assert(isUnsigned!L != isUnsigned!R); | |
// Mixed signs | |
static if (!isUnsigned!L) | |
{ | |
if (lhs < 0) | |
{ | |
overflow = true; | |
return Result(0); //lhs - rhs; | |
} | |
} | |
else static if (!isUnsigned!R) | |
{ | |
if (rhs < 0) | |
{ | |
return addu(Result(lhs), Result(negs(rhs, overflow)), overflow); | |
} | |
} | |
return subu(Result(lhs), Result(rhs), overflow); | |
} | |
} | |
unittest | |
{ | |
bool overflow; | |
assert(sub(1, 1, overflow) == 0 && !overflow); | |
assert(sub(1, 1u, overflow) == 0 && !overflow); | |
assert(sub(1u, -1, overflow) == 2 && !overflow); | |
assert(sub(-1, 1u, overflow) == 0 && overflow); | |
} | |
auto mul(L, R)(const L lhs, const R rhs, ref bool overflow) | |
if (isIntegral!L && isIntegral!R && L.sizeof >= 4 && R.sizeof >= 4) | |
{ | |
import core.checkedint; | |
alias Result = typeof(lhs * rhs); | |
static if (valueConvertible!(L, Result) && valueConvertible!(R, Result)) | |
{ | |
static if (isUnsigned!Result) alias impl = mulu; | |
else alias impl = muls; | |
return impl(Result(lhs), Result(rhs), overflow); | |
} | |
else | |
{ | |
static assert(isUnsigned!Result); | |
static assert(isUnsigned!L != isUnsigned!R); | |
// Mixed signs | |
static if (!isUnsigned!L) | |
{ | |
if (lhs < 0) | |
{ | |
overflow = true; | |
return Result(0); //lhs * rhs; | |
} | |
} | |
else static if (!isUnsigned!R) | |
{ | |
if (rhs < 0) | |
{ | |
overflow = true; | |
return Result(0);// lhs * rhs; | |
} | |
} | |
return mulu(Result(lhs), Result(rhs), overflow); | |
} | |
} | |
unittest | |
{ | |
bool overflow; | |
assert(mul(2, 3, overflow) == 6 && !overflow); | |
assert(mul(2, 3u, overflow) == 6 && !overflow); | |
assert(mul(1u, -1, overflow) == 0 && overflow); | |
//assert(mul(-1, 1u, overflow) == uint.max - 1 && overflow); | |
} | |
template divrem(string op) | |
{ | |
typeof(L() + R()) divrem(L, R)(const L lhs, const R rhs, ref bool overflow) | |
if (isIntegral!L && isIntegral!R && L.sizeof >= 4 && R.sizeof >= 4) | |
{ | |
import core.checkedint; | |
alias Result = typeof(lhs / rhs); | |
static if (valueConvertible!(L, Result) && | |
valueConvertible!(R, Result)) | |
{ | |
if (rhs != 0) return mixin("lhs"~op~"rhs"); | |
overflow = true; | |
return Result(0); | |
} | |
else | |
{ | |
static assert(isUnsigned!Result); | |
static assert(isUnsigned!L != isUnsigned!R); | |
// Mixed signs | |
static if (!isUnsigned!L) | |
{ | |
if (lhs < 0 || rhs == 0) | |
{ | |
overflow = true; | |
return Result(0); // mixin("lhs"~op~"rhs"); | |
} | |
} | |
else static if (!isUnsigned!R) | |
{ | |
if (rhs <= 0) | |
{ | |
overflow = true; | |
return Result(0); // mixin("lhs"~op~"rhs"); | |
} | |
} | |
return mixin("Result(lhs)"~op~"Result(rhs)"); | |
} | |
} | |
} | |
unittest | |
{ | |
bool overflow; | |
assert(divrem!"/"(6, 3, overflow) == 2 && !overflow); | |
assert(divrem!"/"(6, 3, overflow) == 2 && !overflow); | |
assert(divrem!"/"(6u, 3, overflow) == 2 && !overflow); | |
assert(divrem!"/"(6, 3u, overflow) == 2 && !overflow); | |
assert(divrem!"/"(11, 0, overflow) == 0 && overflow); | |
overflow = false; | |
assert(divrem!"/"(6u, 0, overflow) == 0 && overflow); | |
overflow = false; | |
assert(divrem!"/"(-6, 2u, overflow) == 0 && overflow); | |
overflow = false; | |
assert(divrem!"/"(-6, 0u, overflow) == 0 && overflow); | |
} | |
/** | |
Promotes an integral to `int`, `uint`, `long`, or `ulong` following the value | |
conversion rules: everything smaller than `int` is converted to `int`, and | |
all other types remain unchanged. | |
*/ | |
pure @safe nothrow @nogc | |
{ | |
int promote(byte x) { return x; } | |
int promote(ubyte x) { return x; } | |
int promote(short x) { return x; } | |
int promote(ushort x) { return x; } | |
int promote(int x) { return x; } | |
uint promote(uint x) { return x; } | |
long promote(long x) { return x; } | |
ulong promote(ulong x) { return x; } | |
} | |
/** | |
*/ | |
struct Checked(T, Hook) | |
if (isIntegral!T && is(T == Unqual!T)) | |
{ | |
import std.algorithm : among; | |
import std.traits : hasMember, isFloatingPoint; | |
import std.experimental.allocator.common : stateSize; | |
/** | |
*/ | |
alias Payload = T; | |
// state { | |
private T payload; | |
static if (stateSize!Hook > 0) Hook hook; | |
else alias hook = Hook; | |
// } | |
/** | |
*/ | |
T representation() const { return payload; } | |
/** | |
*/ | |
enum min = Checked(T.min); | |
/// ditto | |
enum max = Checked(T.max); | |
/** | |
*/ | |
this(T1)(const T1 rhs) if (valueConvertible!(T1, T)) | |
{ | |
payload = rhs; | |
} | |
/** | |
*/ | |
this(T1, Hook1)(const Checked!(T1, Hook1) rhs) if (valueConvertible!(T1, T)) | |
{ | |
payload = rhs.payload; | |
} | |
/** | |
*/ | |
void opAssign(U)(const U rhs) if (is(typeof(Checked(rhs)))) | |
{ | |
// Just "reuse" the ctor | |
payload = Checked(rhs).payload; | |
} | |
/** | |
*/ | |
T1 opCast(T1)() | |
if (isIntegral!T1 || isFloatingPoint!T1 || is(T1 == bool)) | |
{ | |
static if (is(T1 == bool)) | |
{ | |
return payload != 0; | |
} | |
else static if (isIntegral!T1 && valueConvertible!(T, T1)) | |
{ | |
return payload; | |
} | |
else // may lose bits or precision | |
{ | |
static if (hasMember!(Hook, "hookOpCast")) | |
{ | |
return hook.hookOpCast!T1(payload); | |
} | |
else static if (hasMember!(Hook, "onBadCast")) | |
{ | |
static if (isUnsigned!T == isUnsigned!T1) // Same signedness | |
{ | |
auto result = cast(T1) payload; | |
if (result == payload) return result; | |
} | |
else | |
{ | |
// TODO: make faster | |
auto result = cast(T1) payload; | |
if (result == payload && (result < 0) == (payload < 0)) | |
return result; | |
} | |
return hook.onBadCast!T1(payload); | |
} | |
else | |
{ | |
return cast(T1) payload; | |
} | |
} | |
} | |
/** | |
*/ | |
bool opEquals(T1)(const T1 rhs) //const pure @safe nothrow @nogc | |
if (isIntegral!T1 || isFloatingPoint!T1 || is(T1 == bool)) | |
{ | |
static if (valueConvertible!(T, T1) || valueConvertible!(T1, T)) | |
{ | |
return payload == rhs; | |
} | |
else static if (hasMember!(Hook, "hookOpEquals")) | |
{ | |
return hook.hookOpEquals(payload, rhs); | |
} | |
else static if (hasMember!(Hook, "onBadOpEquals")) | |
{ | |
if (payload != rhs) return false; | |
static if (isUnsigned!T) | |
{ | |
if (rhs >= 0) return true; | |
} | |
else | |
{ | |
if (payload >= 0) return true; | |
} | |
return hook.onBadOpEquals(payload, rhs); | |
} | |
else | |
{ | |
return payload == rhs; | |
} | |
} | |
/// ditto | |
bool opEquals(T1, Hook1)(const Checked!(T1, Hook1) rhs) | |
if (is(typeof(this == rhs.payload))) | |
{ | |
alias R = typeof(payload + rhs.payload); | |
static if (valueConvertible!(T, R) && valueConvertible!(T1, R)) | |
{ | |
return payload == rhs.payload; | |
} | |
else static if (is(Hook == Hook1)) | |
{ | |
// Use the lhs hook | |
return this == rhs.payload; | |
} | |
else static if (hasMember!(Hook, "hookOpEquals")) | |
{ | |
return hook.hookOpEquals(payload, rhs); | |
} | |
else static if (hasMember!(Hook1, "hookOpEquals")) | |
{ | |
return rhs.hook.hookOpEquals(rhs.payload, this); | |
} | |
else static if (hasMember!(Hook, "onBadOpEquals") && | |
!hasMember!(Hook1, "onBadOpEquals")) | |
{ | |
// Use the lhs hook | |
return this == rhs.payload; | |
} | |
else static if (hasMember!(Hook1, "onBadOpEquals") && | |
!hasMember!(Hook, "onBadOpEquals")) | |
{ | |
// Use the rhs hook | |
return this.payload == rhs; | |
} | |
else | |
{ | |
static assert(0, "Conflict between lhs and rhs hooks," | |
" use .representation on one side to disambiguate."); | |
} | |
} | |
/** | |
*/ | |
auto opCmp(T1)(const T1 rhs) //const pure @safe nothrow @nogc | |
if (isIntegral!T1 || isFloatingPoint!T1 || is(T1 == bool)) | |
{ | |
static if (valueConvertible!(T, T1) || valueConvertible!(T1, T)) | |
{ | |
return payload < rhs ? -1 : payload > rhs; | |
} | |
else static if (isFloatingPoint!T1) | |
{ | |
return payload < rhs ? -1 | |
: payload > rhs ? 1 | |
: payload == rhs ? 0 : float.nan; | |
} | |
else static if (hasMember!(Hook, "hookOpCmp")) | |
{ | |
return hook.hookOpCmp(payload, rhs); | |
} | |
else static if (hasMember!(Hook, "onBadOpCmp")) | |
{ | |
static assert(isUnsigned!T != isUnsigned!T1); | |
static if (isUnsigned!T) | |
{ | |
if (rhs >= 0) | |
return payload < rhs ? -1 : payload > rhs; | |
} | |
else | |
{ | |
if (payload >= 0) | |
return payload < rhs ? -1 : payload > rhs; | |
} | |
return hook.onBadOpCmp(payload, rhs); | |
} | |
else | |
{ | |
// Mimic nasty built-in behavior | |
return payload < rhs ? -1 : payload > rhs; | |
} | |
} | |
/// ditto | |
auto opCmp(T1, Hook1)(const Checked!(T1, Hook1) rhs) | |
{ | |
alias R = typeof(payload + rhs.payload); | |
static if (valueConvertible!(T, R) && valueConvertible!(T, R)) | |
{ | |
return payload < rhs.payload ? -1 : payload > rhs.payload; | |
} | |
else static if (is(Hook == Hook1)) | |
{ | |
// Use the lhs hook | |
return this.opCmp(rhs.payload); | |
} | |
else static if (hasMember!(Hook, "hookOpCmp")) | |
{ | |
return hook.hookOpCmp(payload, rhs); | |
} | |
else static if (hasMember!(Hook1, "hookOpCmp")) | |
{ | |
return rhs.hook.hookOpCmp(rhs.payload, this); | |
} | |
else static if (hasMember!(Hook, "onBadOpCmp") && | |
!hasMember!(Hook1, "onBadOpCmp")) | |
{ | |
// Use the lhs hook | |
return this == rhs.payload; | |
} | |
else static if (hasMember!(Hook1, "onBadOpCmp") && | |
!hasMember!(Hook, "onBadOpCmp")) | |
{ | |
// Use the rhs hook | |
return this.payload == rhs; | |
} | |
else | |
{ | |
static assert(0, "Conflict between lhs and rhs hooks," | |
" use .representation on one side to disambiguate."); | |
} | |
} | |
/** | |
*/ | |
auto opUnary(string op)() | |
if (op == "+" || op == "-" || op == "~") | |
{ | |
static if (op == "+") | |
return Checked(this); // "+" is not hookable | |
else static if (hasMember!(Hook, "hookOpUnary")) | |
{ | |
auto r = hook.hookOpUnary!op(payload); | |
return Checked!(typeof(r), Hook)(r); | |
} | |
else static if (op == "-" && hasMember!(Hook, "onOverflow")) | |
{ | |
alias R = typeof(-payload); | |
bool overflow; | |
auto r = negs(R(payload), overflow); | |
if (overflow) r = hook.onOverflow!op(payload); | |
return Checked(r); | |
} | |
else | |
return Checked(mixin(op ~ "payload")); | |
} | |
/// ditto | |
ref Checked opUnary(string op)() return | |
if (op == "++" || op == "--") | |
{ | |
static if (hasMember!(Hook, "hookOpUnary")) | |
hook.hookOpUnary!op(payload); | |
else static if (hasMember!(Hook, "onOverflow")) | |
{ | |
static if (op == "++") | |
{ | |
if (payload == payload.max) | |
payload = hook.onOverflow!"++"(payload); | |
else | |
++payload; | |
} | |
else | |
{ | |
if (payload == payload.min) | |
payload = hook.onOverflow!"--"(payload); | |
else | |
--payload; | |
} | |
} | |
else | |
mixin(op ~ "payload;"); | |
return this; | |
} | |
/** | |
*/ | |
auto opBinary(string op, Rhs)(const Rhs rhs) | |
if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool)) | |
{ | |
alias R = typeof(payload + rhs); | |
static assert(is(typeof(mixin("payload"~op~"rhs")) == R)); | |
static if (isIntegral!R) alias Result = Checked!(R, Hook); | |
else alias Result = R; | |
static if (hasMember!(Hook, "hookOpBinary")) | |
{ | |
auto r = hook.hookOpBinary!op(payload, rhs); | |
return Result(r); | |
} | |
else static if (is(Rhs == bool)) | |
{ | |
return mixin("this"~op~"typeof(payload + rhs)(rhs)"); | |
} | |
else static if (isFloatingPoint!Rhs) | |
{ | |
return mixin("payload"~op~"rhs"); | |
} | |
else static if (op.among("+", "-", "*", "/", "%") && | |
hasMember!(Hook, "onOverflow")) | |
{ | |
bool overflow; | |
static if (op == "+") alias impl = add; | |
else static if (op == "-") alias impl = sub; | |
else static if (op == "*") alias impl = mul; | |
else static if (op == "/") alias impl = divrem!"/"; | |
else static if (op == "%") alias impl = divrem!"%"; | |
auto r = impl(promote(payload), promote(rhs), overflow); | |
if (overflow) r = hook.onOverflow!op(payload, rhs); | |
return Result(r); | |
} | |
else static if (op == "^^" && hasMember!(Hook, "onOverflow")) | |
{ | |
static if (!isUnsigned!Rhs) | |
{ | |
if (rhs < 0) return Result(R(0)); | |
} | |
static if (isUnsigned!R && !isUnsigned!T) | |
{ | |
if (payload < 0 && (rhs & 1)) | |
return Result(hook.onOverflow!op(payload, rhs)); | |
} | |
bool overflow; | |
import std.conv : unsigned; | |
auto r = pow(R(payload), unsigned(rhs), overflow); | |
if (overflow) r = hook.onOverflow!op(payload, rhs); | |
return Result(r); | |
} | |
else static if (op.among("<<", ">>", ">>>") && | |
hasMember!(Hook, "onOverflow")) | |
{ | |
if (!isUnsigned!Rhs && rhs < 0 || rhs > R.sizeof) | |
return Result(hook.onOverflow!op(payload, rhs)); | |
} | |
// Default is built-in behavior | |
return Result(mixin("payload"~op~"rhs")); | |
} | |
/// ditto | |
auto opBinary(string op, T1, Hook1)(const Checked!(T1, Hook1) rhs) | |
{ | |
alias R = typeof(payload + rhs.payload); | |
static if (valueConvertible!(T, R) && valueConvertible!(T, R) || | |
is(Hook == Hook1)) | |
{ | |
// Delegate to lhs | |
return mixin("this"~op~"rhs.payload"); | |
} | |
else static if (hasMember!(Hook, "hookOpBinary")) | |
{ | |
return hook.hookOpBinary!op(payload, rhs); | |
} | |
else static if (hasMember!(Hook1, "hookOpBinary")) | |
{ | |
// Delegate to rhs | |
return mixin("this.payload"~op~"rhs"); | |
} | |
else static if (hasMember!(Hook, "onOverflow") && | |
!hasMember!(Hook1, "onOverflow")) | |
{ | |
// Delegate to lhs | |
return mixin("this"~op~"rhs.payload"); | |
} | |
else static if (hasMember!(Hook1, "onOverflow") && | |
!hasMember!(Hook, "onOverflow")) | |
{ | |
// Delegate to rhs | |
return mixin("this.payload"~op~"rhs"); | |
} | |
else | |
{ | |
static assert(0, "Conflict between lhs and rhs hooks," | |
" use .representation on one side to disambiguate."); | |
} | |
} | |
auto opBinaryRight(string op, Lhs)(const Lhs lhs) const | |
if (isIntegral!Lhs || isFloatingPoint!Lhs || is(Lhs == bool)) | |
{ | |
// Swap enforcement to the left-hand signedness | |
static if (is(Lhs == bool)) | |
{ | |
alias R = typeof(mixin("lhs"~op~"payload")); | |
return mixin("Checked!(R, Hook)(R(lhs))"~op~"payload"); | |
} | |
else static if (isFloatingPoint!Lhs) | |
return mixin("lhs"~op~"payload"); | |
else | |
{ | |
// Create hooked type on the lhs, let it handle things | |
return mixin("Checked!(Lhs, Hook)(lhs)"~op~"payload"); | |
} | |
} | |
} | |
// representation | |
unittest | |
{ | |
assert(Checked!(ubyte, void)(ubyte(22)).representation == 22); | |
} | |
/** | |
Force all overflows to fail with `assert(0)`. | |
*/ | |
struct Croak | |
{ | |
Dst onBadCast(Dst, Src)(Src src) | |
{ | |
assert(0, "Bad cast"); | |
} | |
bool onBadOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) | |
{ | |
assert(0, "Bad comparison for equality"); | |
} | |
bool onBadOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) | |
{ | |
assert(0, "Bad comparison for ordering"); | |
} | |
typeof(~lhs) onOverflow(string op, Lhs)(Lhs lhs) | |
{ | |
assert(0); | |
} | |
typeof(lhs + rhs) onOverflow(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs) | |
{ | |
assert(0); | |
} | |
} | |
version(unittest) private struct CountOverflows | |
{ | |
uint calls; | |
auto onOverflow(string op, Lhs)(Lhs lhs) | |
{ | |
++calls; | |
return mixin(op~"lhs"); | |
} | |
auto onOverflow(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs) | |
{ | |
++calls; | |
return mixin("lhs"~op~"rhs"); | |
} | |
} | |
version(unittest) private struct CountOpBinary | |
{ | |
uint calls; | |
auto hookOpBinary(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs) | |
{ | |
++calls; | |
return mixin("lhs"~op~"rhs"); | |
} | |
} | |
// opBinary | |
@nogc nothrow pure @safe unittest | |
{ | |
auto x = Checked!(int, void)(42), y = Checked!(int, void)(142); | |
assert(x + y == 184); | |
assert(x + 100 == 142); | |
assert(y - x == 100); | |
assert(200 - x == 158); | |
assert(y * x == 142 * 42); | |
assert(x / 1 == 42); | |
auto x1 = Checked!(int, CountOverflows)(42); | |
assert(x1 + 0 == 42); | |
assert(x1 + false == 42); | |
assert(is(typeof(x1 + 0.5) == double)); | |
assert(x1 + 0.5 == 42.5); | |
assert(x1.hook.calls == 0); | |
assert(x1 + int.max == int.max + 42); | |
assert(x1.hook.calls == 1); | |
assert(x1 * 2 == 84); | |
assert(x1.hook.calls == 1); | |
assert(x1 / 2 == 21); | |
assert(x1.hook.calls == 1); | |
auto x2 = Checked!(int, CountOpBinary)(42); | |
assert(x2 + 1 == 43); | |
assert(x2.hook.calls == 1); | |
auto x3 = Checked!(uint, CountOverflows)(42u); | |
assert(x3 + 1 == 43); | |
assert(x3.hook.calls == 0); | |
assert(x3 - 1 == 41); | |
assert(x3.hook.calls == 0); | |
assert(x3 + (-42) == 0); | |
assert(x3.hook.calls == 0); | |
assert(x3 - (-42) == 84); | |
assert(x3.hook.calls == 0); | |
assert(x3 * 2 == 84); | |
assert(x3.hook.calls == 0); | |
assert(x3 * -2 == -84); | |
assert(x3.hook.calls == 1); | |
assert(x3 / 2 == 21); | |
assert(x3.hook.calls == 1); | |
assert(x3 / -2 == 0); | |
assert(x3.hook.calls == 2); | |
auto x4 = Checked!(int, CountOverflows)(42); | |
assert(x4 + 1 == 43); | |
assert(x4.hook.calls == 0); | |
assert(x4 + 1u == 43); | |
assert(x4.hook.calls == 0); | |
assert(x4 - 1 == 41); | |
assert(x4.hook.calls == 0); | |
assert(x4 * 2 == 84); | |
assert(x4.hook.calls == 0); | |
x4 = -2; | |
assert(x4 + 2u == 0); | |
assert(x4.hook.calls == 0); | |
assert(x4 * 2u == -4); | |
assert(x4.hook.calls == 1); | |
auto x5 = Checked!(int, CountOverflows)(3); | |
assert(x5 ^^ 0 == 1); | |
assert(x5 ^^ 1 == 3); | |
assert(x5 ^^ 2 == 9); | |
assert(x5 ^^ 3 == 27); | |
assert(x5 ^^ 4 == 81); | |
assert(x5 ^^ 5 == 81 * 3); | |
assert(x5 ^^ 6 == 81 * 9); | |
} | |
// opBinaryRight | |
@nogc nothrow pure @safe unittest | |
{ | |
auto x1 = Checked!(int, CountOverflows)(42); | |
assert(1 + x1 == 43); | |
assert(true + x1 == 43); | |
assert(0.5 + x1 == 42.5); | |
auto x2 = Checked!(int, void)(42); | |
assert(x1 + x2 == 84); | |
assert(x2 + x1 == 84); | |
} | |
unittest | |
{ | |
Checked!(int, void) x; | |
x = 42; | |
assert(x.payload == 42); | |
x = x; | |
assert(x.payload == 42); | |
x = short(43); | |
assert(x.payload == 43); | |
x = ushort(44); | |
assert(x.payload == 44); | |
} | |
unittest | |
{ | |
static assert(!is(typeof(Checked!(short, void)(ushort(42))))); | |
static assert(!is(typeof(Checked!(int, void)(long(42))))); | |
static assert(!is(typeof(Checked!(int, void)(ulong(42))))); | |
assert(Checked!(short, void)(short(42)).payload == 42); | |
assert(Checked!(int, void)(ushort(42)).payload == 42); | |
} | |
// opCast | |
@nogc nothrow pure @safe unittest | |
{ | |
static assert(is(typeof(cast(float) Checked!(int, void)(42)) == float)); | |
assert(cast(float) Checked!(int, void)(42) == 42); | |
assert(is(typeof(cast(long) Checked!(int, void)(42)) == long)); | |
assert(cast(long) Checked!(int, void)(42) == 42); | |
static assert(is(typeof(cast(long) Checked!(uint, void)(42u)) == long)); | |
assert(cast(long) Checked!(uint, void)(42u) == 42); | |
auto x = Checked!(int, void)(42); | |
if (x) {} else assert(0); | |
x = 0; | |
if (x) assert(0); | |
static struct Hook1 | |
{ | |
uint calls; | |
Dst hookOpCast(Dst, Src)(Src value) | |
{ | |
++calls; | |
return 42; | |
} | |
} | |
auto y = Checked!(long, Hook1)(long.max); | |
assert(cast(int) y == 42); | |
assert(y.hook.calls == 1); | |
static struct Hook2 | |
{ | |
uint calls; | |
Dst onBadCast(Dst, Src)(Src value) | |
{ | |
++calls; | |
return 42; | |
} | |
} | |
auto x1 = Checked!(uint, Hook2)(100u); | |
assert(cast(ushort) x1 == 100); | |
assert(x1.hook.calls == 0); | |
assert(cast(int) x1 == 100); | |
assert(x1.hook.calls == 0); | |
x1 = uint.max; | |
assert(cast(int) x1 == 42); | |
assert(x1.hook.calls == 1); | |
} | |
// opEquals | |
@nogc nothrow pure @safe unittest | |
{ | |
assert(Checked!(int, void)(42) == 42L); | |
assert(42UL == Checked!(int, void)(42)); | |
static struct Hook1 | |
{ | |
uint calls; | |
bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) | |
{ | |
++calls; | |
return lhs != rhs; | |
} | |
} | |
auto x1 = Checked!(int, Hook1)(100); | |
assert(x1 == Checked!(long, Hook1)(100)); | |
assert(x1.hook.calls == 0); | |
assert(x1 != 100u); | |
assert(x1.hook.calls == 1); | |
static struct Hook2 | |
{ | |
uint calls; | |
bool onBadOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) | |
{ | |
++calls; | |
return true; | |
} | |
} | |
auto x2 = Checked!(int, Hook2)(-100); | |
assert(x2 == -100); | |
assert(x2.hook.calls == 0); | |
assert(x2 == cast(uint) -100); | |
assert(x2.hook.calls == 1); | |
auto x3 = Checked!(uint, Hook2)(100u); | |
assert(x3 == 100); | |
assert(x2 != x3); | |
} | |
// opCmp | |
@nogc nothrow pure @safe unittest | |
{ | |
Checked!(int, void) x; | |
assert(x <= x); | |
assert(x < 45); | |
assert(x < 45u); | |
assert(x > -45); | |
assert(x < 44.2); | |
assert(x > -44.2); | |
assert(!(x < double.init)); | |
assert(!(x > double.init)); | |
assert(!(x <= double.init)); | |
assert(!(x >= double.init)); | |
static struct Hook1 | |
{ | |
uint calls; | |
int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) | |
{ | |
++calls; | |
return 0; | |
} | |
} | |
auto x1 = Checked!(int, Hook1)(42); | |
assert(!(x1 < 43u)); | |
assert(!(43u < x1)); | |
assert(x1.hook.calls == 2); | |
static struct Hook2 | |
{ | |
uint calls; | |
int onBadOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) | |
{ | |
++calls; | |
return 0; | |
} | |
} | |
auto x2 = Checked!(int, Hook2)(-42); | |
assert(!(x2 < 43u)); | |
assert(!(43u > x2)); | |
assert(x2.hook.calls == 2); | |
x2 = 42; | |
assert(x2 > 41u); | |
auto x3 = Checked!(uint, Hook2)(42u); | |
assert(x3 > 41); | |
assert(!(x3 > -41)); | |
} | |
// opUnary | |
@nogc nothrow pure @safe unittest | |
{ | |
auto x = Checked!(int, void)(42); | |
assert(x == +x); | |
static assert(is(typeof(-x) == typeof(x))); | |
assert(-x == Checked!(int, void)(-42)); | |
static assert(is(typeof(~x) == typeof(x))); | |
assert(~x == Checked!(int, void)(~42)); | |
static struct Hook1 | |
{ | |
uint calls; | |
auto hookOpUnary(string op, T)(T value) if (op == "-") | |
{ | |
++calls; | |
return T(42); | |
} | |
auto hookOpUnary(string op, T)(T value) if (op == "~") | |
{ | |
++calls; | |
return T(43); | |
} | |
} | |
auto x1 = Checked!(int, Hook1)(100); | |
assert(is(typeof(-x1) == typeof(x1))); | |
assert(-x1 == Checked!(int, Hook1)(42)); | |
assert(is(typeof(~x1) == typeof(x1))); | |
assert(~x1 == Checked!(int, Hook1)(43)); | |
assert(x1.hook.calls == 2); | |
static struct Hook2 | |
{ | |
uint calls; | |
auto hookOpUnary(string op, T)(ref T value) if (op == "++") | |
{ | |
++calls; | |
--value; | |
} | |
auto hookOpUnary(string op, T)(ref T value) if (op == "--") | |
{ | |
++calls; | |
++value; | |
} | |
} | |
auto x2 = Checked!(int, Hook2)(100); | |
assert(++x2 == 99); | |
assert(x2 == 99); | |
assert(--x2 == 100); | |
assert(x2 == 100); | |
auto x3 = Checked!(int, CountOverflows)(int.max - 1); | |
assert(++x3 == int.max); | |
assert(x3.hook.calls == 0); | |
assert(++x3 == int.min); | |
assert(x3.hook.calls == 1); | |
x3 = int.min + 1; | |
assert(--x3 == int.min); | |
assert(x3.hook.calls == 1); | |
assert(--x3 == int.max); | |
assert(x3.hook.calls == 2); | |
} | |
// | |
@nogc nothrow pure @safe unittest | |
{ | |
Checked!(int, void) x; | |
assert(x == x); | |
assert(x == +x); | |
assert(x == -x); | |
++x; | |
assert(x == 1); | |
x++; | |
assert(x == 2); | |
x = 42; | |
assert(x == 42); | |
short _short = 43; | |
x = _short; | |
assert(x == _short); | |
ushort _ushort = 44; | |
x = _ushort; | |
assert(x == _ushort); | |
assert(x == 44.0); | |
assert(x != 44.1); | |
assert(x < 45); | |
assert(x < 44.2); | |
assert(x > -45); | |
assert(x > -44.2); | |
assert(cast(long) x == 44); | |
assert(cast(short) x == 44); | |
Checked!(uint, void) y; | |
assert(y <= y); | |
assert(y == 0); | |
assert(y < x); | |
x = -1; | |
assert(x > y); | |
} | |
void main(){} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment