1 /**
2 * Authors: k.inaba
3 * License: NYSL 0.9982 http://www.kmonos.net/nysl/
4 *
5 * Common tricks and utilities for programming in D.
6 */
7 module polemy.tricks;
8 import std.array : appender;
9 import std.format : formattedWrite;
10 import core.exception : onAssertErrorMsg, AssertError;
11
12 /// Simple Wrapper for std.format.doFormat
13
14 string sprintf(string fmt, T...)(T params)
15 {
16 auto writer = appender!string();
17 formattedWrite(writer, fmt, params);
18 return writer.data;
19 }
20
21 unittest
22 {
23 assert( sprintf!"%s == %d"("1+2", 3) == "1+2 == 3" );
24 assert( sprintf!"%s == %04d"("1+2", 3) == "1+2 == 0003" );
25 }
26
27 /// Unittest helper that asserts an expression must throw something
28
29 void assert_throw(ExceptionType, T, string fn=__FILE__, int ln=__LINE__)(lazy T t, string msg="")
30 {
31 try {
32 t();
33 } catch(ExceptionType) {
34 return;
35 } catch(Throwable e) {
36 onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"exception [%s]"(e));
37 }
38 onAssertErrorMsg(fn, ln, msg.length ? msg : "no execption");
39 }
40
41 /// Unittest helper that asserts an expression must not throw anything
42
43 void assert_nothrow(T, string fn=__FILE__, int ln=__LINE__)(lazy T t, string msg="")
44 {
45 try {
46 t();
47 } catch(Throwable e) {
48 onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"exception [%s]"(e));
49 }
50 }
51
52 unittest
53 {
54 auto error = {throw new Error("hello");};
55 auto nothing = (){};
56 auto assertError = {assert(0);};
57
58 assert_nothrow ( assert_nothrow(nothing()) );
59 assert_throw!AssertError( assert_nothrow(error()) );
60 assert_throw!AssertError( assert_nothrow(assertError()) );
61
62 assert_nothrow ( assert_throw!Error(error()) );
63 assert_throw!AssertError( assert_throw!Error(nothing()) );
64 assert_nothrow ( assert_throw!Error(assertError()) );
65 assert_throw!AssertError( assert_throw!AssertError(error()) );
66 }
67
68 /// Unittest helpers asserting two values are in some relation ==, !=, <, <=, >, >=
69
70 template assertOp(string op)
71 {
72 void assertOp(A, B, string fn=__FILE__, int ln=__LINE__)(A a, B b, string msg="")
73 {
74 try {
75 if( mixin("a"~op~"b") ) return;
76 } catch(Throwable e) {
77 onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"exception [%s]"(e));
78 }
79 onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"%s !%s %s"(a,op,b));
80 }
81 }
82
83 alias assertOp!(`==`) assert_eq;
84 alias assertOp!(`!=`) assert_ne;
85 alias assertOp!(`<`) assert_lt;
86 alias assertOp!(`<=`) assert_le;
87 alias assertOp!(`>`) assert_gt;
88 alias assertOp!(`>=`) assert_ge;
89
90 unittest
91 {
92 assert_nothrow( assert_eq(1, 1) );
93 assert_nothrow( assert_ne(1, 0) );
94 assert_nothrow( assert_lt(0, 1) );
95 assert_nothrow( assert_le(0, 1) );
96 assert_nothrow( assert_le(0, 0) );
97 assert_nothrow( assert_gt(1, 0) );
98 assert_nothrow( assert_ge(1, 0) );
99 assert_nothrow( assert_ge(0, 0) );
100
101 assert_throw!AssertError( assert_eq(1, 0) );
102 assert_throw!AssertError( assert_ne(1, 1) );
103 assert_throw!AssertError( assert_lt(1, 1) );
104 assert_throw!AssertError( assert_lt(1, 0) );
105 assert_throw!AssertError( assert_le(1, 0) );
106 assert_throw!AssertError( assert_gt(0, 0) );
107 assert_throw!AssertError( assert_gt(0, 1) );
108 assert_throw!AssertError( assert_ge(0, 1) );
109
110 class Temp { bool opEquals(int x){return x/x==x;} }
111 assert_throw!AssertError( assert_eq(new Temp, 0) );
112 assert_nothrow ( assert_eq(new Temp, 1) );
113 assert_throw!AssertError( assert_eq(new Temp, 2) );
114 }
115
116 /* [Todo] is there any way to clearnly implement "assert_compiles" and "assert_not_compile"? */
117
118 /// Mixing-in the bean constructor for a class
119
120 /*mixin*/ template SimpleConstructor()
121 {
122 static if( is(typeof(super) == Object) || super.tupleof.length==0 )
123 this( typeof(this.tupleof) params )
124 {
125 static if(this.tupleof.length>0)
126 this.tupleof = params;
127 }
128 else
129 this( typeof(super.tupleof) ps, typeof(this.tupleof) params )
130 {
131 // including (only) the direct super class members
132 // may not always be a desirable choice, but should work for many cases
133 super(ps);
134 static if(this.tupleof.length>0)
135 this.tupleof = params;
136 }
137 }
138
139 unittest
140 {
141 class Temp
142 {
143 int x;
144 string y;
145 mixin SimpleConstructor;
146 }
147 assert_eq( (new Temp(1,"foo")).x, 1 );
148 assert_eq( (new Temp(1,"foo")).y, "foo" );
149 assert( !__traits(compiles, new Temp) );
150 assert( !__traits(compiles, new Temp(1)) );
151 assert( !__traits(compiles, new Temp("foo",1)) );
152
153 class Tomp : Temp
154 {
155 real z;
156 mixin SimpleConstructor;
157 }
158 assert_eq( (new Tomp(1,"foo",2.5)).x, 1 );
159 assert_eq( (new Tomp(1,"foo",2.5)).y, "foo" );
160 assert_eq( (new Tomp(1,"foo",2.5)).z, 2.5 );
161 assert( !__traits(compiles, new Tomp(3.14)) );
162
163 // shiyo- desu. Don't use in this way.
164 // Tamp tries to call new Tomp(real) (because it only sees Tomp's members),
165 // but it fails because Tomp takes (int,string,real).
166 assert( !__traits(compiles, {
167 class Tamp : Tomp
168 {
169 mixin SimpleConstructor;
170 }
171 }) );
172 }
173
174 /// Mixing-in the MOST-DERIVED-member-wise comparator for a class
175
176 /*mixin*/ template SimpleCompare()
177 {
178 override bool opEquals(Object rhs_) const
179 {
180 if( auto rhs = cast(typeof(this))rhs_ )
181 {
182 foreach(i,_; this.tupleof)
183 if( this.tupleof[i] != rhs.tupleof[i] )
184 return false;
185 return true;
186 }
187 assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_)));
188 }
189
190 override hash_t toHash() const
191 {
192 hash_t h = 0;
193 foreach(mem; this.tupleof)
194 h += typeid(mem).getHash(&mem);
195 return h;
196 }
197
198 override int opCmp(Object rhs_) const
199 {
200 if( auto rhs = cast(typeof(this))rhs_ )
201 {
202 foreach(i,_; this.tupleof)
203 if(auto c = typeid(_).compare(&this.tupleof[i],&rhs.tupleof[i]))
204 return c;
205 return 0;
206 }
207 assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_)));
208 }
209 }
210
211 unittest
212 {
213 class Temp
214 {
215 int x;
216 string y;
217 mixin SimpleConstructor;
218 mixin SimpleCompare;
219 }
220 assert_eq( new Temp(1,"foo"), new Temp(1,"foo") );
221 assert_eq( (new Temp(1,"foo")).toHash, (new Temp(1,"foo")).toHash );
222 assert_ne( new Temp(1,"foo"), new Temp(2,"foo") );
223 assert_ne( new Temp(1,"foo"), new Temp(1,"bar") );
224 assert_gt( new Temp(1,"foo"), new Temp(1,"bar") );
225 assert_lt( new Temp(1,"foo"), new Temp(2,"bar") );
226 assert_ge( new Temp(1,"foo"), new Temp(1,"foo") );
227
228 class TempDummy
229 {
230 int x;
231 string y;
232 mixin SimpleConstructor;
233 mixin SimpleCompare;
234 }
235 assert_throw!AssertError( new Temp(1,"foo") == new TempDummy(1,"foo") );
236 assert_throw!AssertError( new Temp(1,"foo") <= new TempDummy(1,"foo") );
237 }
238
239 /// Mixing-in a simple toString method
240
241 /*mixin*/ template SimpleToString()
242 {
243 override string toString()
244 {
245 string str = sprintf!"%s("(typeof(this).stringof);
246 foreach(i,mem; this.tupleof)
247 {
248 if(i) str ~= ",";
249 static if( is(typeof(mem) == std.bigint.BigInt) )
250 str ~= std.bigint.toDecimalString(mem);
251 else
252 str ~= sprintf!"%s"(mem);
253 }
254 return str ~ ")";
255 }
256 }
257
258 version(unittest) import std.bigint;
259 unittest
260 {
261 class Temp
262 {
263 int x;
264 string y;
265 BigInt z;
266 mixin SimpleConstructor;
267 mixin SimpleToString;
268 }
269 assert_eq( (new Temp(1,"foo",BigInt(42))).toString(), "Temp(1,foo,42)" );
270 }