/**
* Authors: k.inaba
* License: NYSL 0.9982 http://www.kmonos.net/nysl/
*
* Common tricks and utilities for programming in D.
*/
module tricks.tricks;
import tricks.test;
import std.array : appender;
import std.format : formattedWrite;
import core.exception;
import std.traits;
import std.typetuple;
/// 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_eq( sprintf!"%s == %04d"("1+2", 3), "1+2 == 0003" );
assert_eq( sprintf!"%2$s == %1$s"("1+2", 5, 8), "5 == 1+2" );
assert_throw!Error( sprintf!"%s%s"(1) );
}
/// Create an exception with automatically completed filename and lineno information
ExceptionType genex(ExceptionType, string fn=__FILE__, int ln=__LINE__, T...)(T params)
{
static if( T.length > 0 && is(T[$-1] : Throwable) )
return new ExceptionType(params[0..$-1], fn, ln, params[$-1]);
else
return new ExceptionType(params, fn, ln);
}
unittest
{
assert_ne( genex!Exception("msg").file, "" );
assert_ne( genex!Exception("msg").line, 0 );
assert_ne( genex!Exception("msg",new Exception("bar")).next, Exception.init );
}
/// Mixing-in the bean constructor for a class
/*mixin*/
template SimpleConstructor()
{
/// member-by-member constructor
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
/*mixin*/
template SimpleCompare()
{
override bool opEquals(Object rhs_) const /// member-by-member equality
{
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 /// member-by-member hash
{
hash_t h = 0;
foreach(mem; this.tupleof)
h += typeid(mem).getHash(&mem);
return h;
}
override int opCmp(Object rhs_) const /// member-by-member compare
{
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") );
}
/// Mixing-in a simple toString method
/*mixin*/
template SimpleToString()
{
/// member-by-member toString
override string toString()
{
string str = sprintf!"%s("(typeof(this).stringof);
foreach(i,mem; this.tupleof)
{
if(i) str ~= ",";
static if( is(typeof(mem) == std.bigint.BigInt) )
str ~= std.bigint.toDecimalString(mem);
else
str ~= sprintf!"%s"(mem);
}
return str ~ ")";
}
}
version(unittest) import std.bigint;
unittest
{
class Temp
{
int x;
string y;
BigInt z;
mixin SimpleConstructor;
mixin SimpleToString;
}
assert_eq( (new Temp(1,"foo",BigInt(42))).toString(), "Temp(1,foo,42)" );
}
/// Everything is in
/*mixin*/
template SimpleClass()
{
mixin SimpleConstructor;
mixin SimpleCompare;
mixin SimpleToString;
}
/// Simple PatternMatcher
/*mixin*/
template SimplePatternMatch()
{
SPM_Return!(PP) match(string fn=__FILE__, size_t ln=__LINE__, PP...)(PP pts)
{
foreach(i,_; pts)
{
alias pts[i] pt; // bug? pts[i]-->pt do not work
static if(__traits(compiles, SPM_isMatchTag(pt)))
{
if( auto v = cast(pt.dynamicType)this )
return pt(v.tupleof);
}
else
static if(__traits(compiles, SPM_isMatchAny(pt)))
{
return pt();
}
else
{
if( auto v = cast(SPM_PTT!(pt)[0])this )
return pt(v);
}
}
SPM_throwAssertError(fn, ln, "pattern matching failure");
assert(false);
}
}
/// Pattern case clause
SPM_MatchTag!(T, fn) when(T, alias fn)()
{
SPM_MatchTag!(T, fn) m;
return m;
}
/// Pattern case clause
SPM_MatchAny!(fn) otherwise(alias fn)()
{
SPM_MatchAny!(fn) m;
return m;
}
// implementation detail of SimplePatternMatch
void SPM_throwAssertError(T...)(T t) { core.exception.onAssertErrorMsg(t); }
struct SPM_MatchTag(T, alias fn)
{
alias T dynamicType;
auto opCall(typeof(T.tupleof) s) { return fn(s); }
}
struct SPM_MatchAny(alias fn)
{
auto opCall() { return fn(); }
}
template SPM_PTT(alias p)
{
alias ParameterTypeTuple!(p) SPM_PTT;
}
template SPM_Each(P)
{
static if(__traits(compiles, SPM_isMatchTag(P.init)))
alias typeof(P(P.dynamicType.tupleof)) SPM_Each;
else
static if(__traits(compiles, SPM_isMatchAny(P.init)))
alias typeof(P()) SPM_Each;
else
alias ReturnType!(P) SPM_Each;
}
template SPM_aVoid(T:void, TS...) { alias SPM_aVoid!(TS) SPM_aVoid; }
template SPM_aVoid(T, TS...) { alias TypeTuple!(T,SPM_aVoid!(TS)) SPM_aVoid; }
template SPM_aVoid() { alias TypeTuple!() SPM_aVoid; }
template SPM_Return(PP...)
{
alias CommonType!(SPM_aVoid!(staticMap!(SPM_Each, PP))) SPM_Return;
}
void SPM_isMatchTag(T,alias fn)(SPM_MatchTag!(T,fn)){}
void SPM_isMatchAny(alias fn)(SPM_MatchAny!(fn)){}
unittest
{
static abstract class Base {
mixin SimplePatternMatch;
}
class D1 : Base {
int x;
real y;
mixin SimpleConstructor;
}
class D2 : Base {
string s;
mixin SimpleConstructor;
}
class D3 : Base {
int[int]m;
mixin SimpleConstructor;
}
Base d1 = new D1(1, 2.3);
Base d2 = new D2("foobar");
Base d3 = new D3(null); (cast(D3)d3).m[1]=10;
// normal dispatch
assert_eq( d1.match(
(D1 x){return 1;},
(D2 x){return 2;},
), 1);
assert_eq( d2.match(
(D1 x){return 1;},
(D2 x){return 2;},
), 2);
assert_throw!AssertError( d3.match(
(D1 x){return 1;},
(D2 x){return 2;},
));
assert_eq( d3.match(
(D1 x){return 1;},
(D2 x){return 2;},
(Base x){return 3;},
), 3);
assert_eq( d2.match(
(D1 x){return 1;},
(D2 x){return 2;},
(Base x){return 3;},
), 2);
assert_eq( d2.match(
(D1 x){return 1;},
(Base x){return 3;},
(D2 x){return 2;},
), 3);
// member decomposing match
assert_eq( d1.match(
when!(D1, (x, y){return x + cast(int)y;}),
when!(D2, (x){return x.length;}),
when!(D3, (x){return x[1];}),
), 3);
assert_eq( d2.match(
when!(D1, (x, y){return x + cast(int)y;}),
when!(D2, (x){return x.length;}),
when!(D3, (x){return x[1];}),
), 6);
assert_eq( d3.match(
when!(D1, (x, y){return x + cast(int)y;}),
when!(D2, (x){return x.length;}),
when!(D3, (x){return x[1];}),
), 10);
assert_throw!AssertError( d3.match(
when!(D1, (x, y){return x + cast(int)y;}),
when!(D2, (x){return x.length;}),
));
assert_eq( d2.match(
when!(D1, (x, y){return x + cast(int)y;}),
when!(D2, (x){return x.length;}),
otherwise!({return 999;}),
), 6);
assert_eq( d2.match(
when!(D1, (x, y){return x + cast(int)y;}),
otherwise!({return 999;}),
when!(D2, (x){return x.length;}),
), 999);
}