/**
* Authors: k.inaba
* License: NYSL 0.9982 http://www.kmonos.net/nysl/
*
* Common tricks and utilities for programming in D.
*/
module polemy.tricks;
import std.array : appender;
import std.format : formattedWrite;
import core.exception : onAssertErrorMsg, AssertError;
/// Simple Wrapper for std.format.doFormat
string sprintf(string fmt, T...)(T params)
{
auto writer = appender!string();
formattedWrite(writer, fmt, params);
return writer.data;
}
unittest
{
assert( sprintf!"%s == %d"("1+2", 3) == "1+2 == 3" );
assert( sprintf!"%s == %04d"("1+2", 3) == "1+2 == 0003" );
}
/// Unittest helper that asserts an expression must throw something
void assert_throw(ExceptionType, T, string fn=__FILE__, int ln=__LINE__)(lazy T t, string msg="")
{
try {
t();
} catch(ExceptionType) {
return;
} catch(Throwable e) {
onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"exception [%s]"(e));
}
onAssertErrorMsg(fn, ln, msg.length ? msg : "no execption");
}
/// Unittest helper that asserts an expression must not throw anything
void assert_nothrow(T, string fn=__FILE__, int ln=__LINE__)(lazy T t, string msg="")
{
try {
t();
} catch(Throwable e) {
onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"exception [%s]"(e));
}
}
unittest
{
auto error = {throw new Error("hello");};
auto nothing = (){};
auto assertError = {assert(0);};
assert_nothrow ( assert_nothrow(nothing()) );
assert_throw!AssertError( assert_nothrow(error()) );
assert_throw!AssertError( assert_nothrow(assertError()) );
assert_nothrow ( assert_throw!Error(error()) );
assert_throw!AssertError( assert_throw!Error(nothing()) );
assert_nothrow ( assert_throw!Error(assertError()) );
assert_throw!AssertError( assert_throw!AssertError(error()) );
}
/// Unittest helpers asserting two values are in some relation ==, !=, <, <=, >, >=
template assertOp(string op)
{
void assertOp(A, B, string fn=__FILE__, int ln=__LINE__)(A a, B b, string msg="")
{
try {
if( mixin("a"~op~"b") ) return;
} catch(Throwable e) {
onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"exception [%s]"(e));
}
onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"%s !%s %s"(a,op,b));
}
}
alias assertOp!(`==`) assert_eq;
alias assertOp!(`!=`) assert_ne;
alias assertOp!(`<`) assert_lt;
alias assertOp!(`<=`) assert_le;
alias assertOp!(`>`) assert_gt;
alias assertOp!(`>=`) assert_ge;
unittest
{
assert_nothrow( assert_eq("foo", "foo") );
assert_nothrow( assert_ne("foo", "bar") );
assert_nothrow( assert_lt("bar", "foo") );
assert_nothrow( assert_le("bar", "foo") );
assert_nothrow( assert_le("bar", "bar") );
assert_nothrow( assert_gt("foo", "bar") );
assert_nothrow( assert_ge("foo", "bar") );
assert_nothrow( assert_ge("bar", "bar") );
assert_throw!AssertError( assert_eq("foo", "bar") );
assert_throw!AssertError( assert_ne("foo", "foo") );
assert_throw!AssertError( assert_lt("foo", "foo") );
assert_throw!AssertError( assert_lt("foo", "bar") );
assert_throw!AssertError( assert_le("foo", "bar") );
assert_throw!AssertError( assert_gt("bar", "bar") );
assert_throw!AssertError( assert_gt("bar", "foo") );
assert_throw!AssertError( assert_ge("bar", "foo") );
class Temp { bool opEquals(int x){return x/x==x;} }
assert_throw!AssertError( assert_eq(new Temp, 0) );
assert_nothrow ( assert_eq(new Temp, 1) );
assert_throw!AssertError( assert_eq(new Temp, 2) );
}
/* [Todo] is there any way to clearnly implement "assert_compiles" and "assert_not_compile"? */
/// Mixing-in the bean constructor for a class
template SimpleConstructor()
{
static if( is(typeof(super) == Object) || super.tupleof.length==0 )
this( typeof(this.tupleof) params )
{
static if(this.tupleof.length>0)
this.tupleof = params;
}
else
this( typeof(super.tupleof) ps, typeof(this.tupleof) params )
{
// including (only) the direct super class members
// may not always be a desirable choice, but should work for many cases
super(ps);
static if(this.tupleof.length>0)
this.tupleof = params;
}
}
unittest
{
class Temp
{
int x;
string y;
mixin SimpleConstructor;
}
assert_eq( (new Temp(1,"foo")).x, 1 );
assert_eq( (new Temp(1,"foo")).y, "foo" );
assert( !__traits(compiles, new Temp) );
assert( !__traits(compiles, new Temp(1)) );
assert( !__traits(compiles, new Temp("foo",1)) );
class Tomp : Temp
{
real z;
mixin SimpleConstructor;
}
assert_eq( (new Tomp(1,"foo",2.5)).x, 1 );
assert_eq( (new Tomp(1,"foo",2.5)).y, "foo" );
assert_eq( (new Tomp(1,"foo",2.5)).z, 2.5 );
assert( !__traits(compiles, new Tomp(3.14)) );
// shiyo- desu. Don't use in this way.
// Tamp tries to call new Tomp(real) (because it only sees Tomp's members),
// but it fails because Tomp takes (int,string,real).
assert( !__traits(compiles, {
class Tamp : Tomp
{
mixin SimpleConstructor;
}
}) );
}
/// Mixing-in the MOST-DERIVED-member-wise comparator for a class
template SimpleCompare()
{
override bool opEquals(Object rhs_) const
{
if( auto rhs = cast(typeof(this))rhs_ )
{
foreach(i,_; this.tupleof)
if( this.tupleof[i] != rhs.tupleof[i] )
return false;
return true;
}
assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_)));
}
override hash_t toHash() const
{
hash_t h = 0;
foreach(mem; this.tupleof)
h += typeid(mem).getHash(&mem);
return h;
}
override int opCmp(Object rhs_) const
{
if( auto rhs = cast(typeof(this))rhs_ )
{
foreach(i,_; this.tupleof)
if(auto c = typeid(_).compare(&this.tupleof[i],&rhs.tupleof[i]))
return c;
return 0;
}
assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_)));
}
}
unittest
{
class Temp
{
int x;
string y;
mixin SimpleConstructor;
mixin SimpleCompare;
}
assert_eq( new Temp(1,"foo"), new Temp(1,"foo") );
assert_eq( (new Temp(1,"foo")).toHash, (new Temp(1,"foo")).toHash );
assert_ne( new Temp(1,"foo"), new Temp(2,"foo") );
assert_ne( new Temp(1,"foo"), new Temp(1,"bar") );
assert_gt( new Temp(1,"foo"), new Temp(1,"bar") );
assert_lt( new Temp(1,"foo"), new Temp(2,"bar") );
assert_ge( new Temp(1,"foo"), new Temp(1,"foo") );
class TempDummy
{
int x;
string y;
mixin SimpleConstructor;
mixin SimpleCompare;
}
assert_throw!AssertError( new Temp(1,"foo") == new TempDummy(1,"foo") );
assert_throw!AssertError( new Temp(1,"foo") <= new TempDummy(1,"foo") );
}