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*/
121 template SimpleConstructor()
122 {
123 static if( is(typeof(super) == Object) || super.tupleof.length==0 )
124 this( typeof(this.tupleof) params )
125 {
126 static if(this.tupleof.length>0)
127 this.tupleof = params;
128 }
129 else
130 this( typeof(super.tupleof) ps, typeof(this.tupleof) params )
131 {
132 // including (only) the direct super class members
133 // may not always be a desirable choice, but should work for many cases
134 super(ps);
135 static if(this.tupleof.length>0)
136 this.tupleof = params;
137 }
138 }
139
140 unittest
141 {
142 class Temp
143 {
144 int x;
145 string y;
146 mixin SimpleConstructor;
147 }
148 assert_eq( (new Temp(1,"foo")).x, 1 );
149 assert_eq( (new Temp(1,"foo")).y, "foo" );
150 assert( !__traits(compiles, new Temp) );
151 assert( !__traits(compiles, new Temp(1)) );
152 assert( !__traits(compiles, new Temp("foo",1)) );
153
154 class Tomp : Temp
155 {
156 real z;
157 mixin SimpleConstructor;
158 }
159 assert_eq( (new Tomp(1,"foo",2.5)).x, 1 );
160 assert_eq( (new Tomp(1,"foo",2.5)).y, "foo" );
161 assert_eq( (new Tomp(1,"foo",2.5)).z, 2.5 );
162 assert( !__traits(compiles, new Tomp(3.14)) );
163
164 // shiyo- desu. Don't use in this way.
165 // Tamp tries to call new Tomp(real) (because it only sees Tomp's members),
166 // but it fails because Tomp takes (int,string,real).
167 assert( !__traits(compiles, {
168 class Tamp : Tomp
169 {
170 mixin SimpleConstructor;
171 }
172 }) );
173 }
174
175 /// Mixing-in the MOST-DERIVED-member-wise comparator for a class
176
177 /*mixin*/
178 template SimpleCompare()
179 {
180 override bool opEquals(Object rhs_) const
181 {
182 if( auto rhs = cast(typeof(this))rhs_ )
183 {
184 foreach(i,_; this.tupleof)
185 if( this.tupleof[i] != rhs.tupleof[i] )
186 return false;
187 return true;
188 }
189 assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_)));
190 }
191
192 override hash_t toHash() const
193 {
194 hash_t h = 0;
195 foreach(mem; this.tupleof)
196 h += typeid(mem).getHash(&mem);
197 return h;
198 }
199
200 override int opCmp(Object rhs_) const
201 {
202 if( auto rhs = cast(typeof(this))rhs_ )
203 {
204 foreach(i,_; this.tupleof)
205 if(auto c = typeid(_).compare(&this.tupleof[i],&rhs.tupleof[i]))
206 return c;
207 return 0;
208 }
209 assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_)));
210 }
211 }
212
213 unittest
214 {
215 class Temp
216 {
217 int x;
218 string y;
219 mixin SimpleConstructor;
220 mixin SimpleCompare;
221 }
222 assert_eq( new Temp(1,"foo"), new Temp(1,"foo") );
223 assert_eq( (new Temp(1,"foo")).toHash, (new Temp(1,"foo")).toHash );
224 assert_ne( new Temp(1,"foo"), new Temp(2,"foo") );
225 assert_ne( new Temp(1,"foo"), new Temp(1,"bar") );
226 assert_gt( new Temp(1,"foo"), new Temp(1,"bar") );
227 assert_lt( new Temp(1,"foo"), new Temp(2,"bar") );
228 assert_ge( new Temp(1,"foo"), new Temp(1,"foo") );
229
230 class TempDummy
231 {
232 int x;
233 string y;
234 mixin SimpleConstructor;
235 mixin SimpleCompare;
236 }
237 assert_throw!AssertError( new Temp(1,"foo") == new TempDummy(1,"foo") );
238 assert_throw!AssertError( new Temp(1,"foo") <= new TempDummy(1,"foo") );
239 }
240
241 /// Mixing-in a simple toString method
242
243 /*mixin*/
244 template SimpleToString()
245 {
246 override string toString()
247 {
248 string str = sprintf!"%s("(typeof(this).stringof);
249 foreach(i,mem; this.tupleof)
250 {
251 if(i) str ~= ",";
252 static if( is(typeof(mem) == std.bigint.BigInt) )
253 str ~= std.bigint.toDecimalString(mem);
254 else
255 str ~= sprintf!"%s"(mem);
256 }
257 return str ~ ")";
258 }
259 }
260
261 version(unittest) import std.bigint;
262 unittest
263 {
264 class Temp
265 {
266 int x;
267 string y;
268 BigInt z;
269 mixin SimpleConstructor;
270 mixin SimpleToString;
271 }
272 assert_eq( (new Temp(1,"foo",BigInt(42))).toString(), "Temp(1,foo,42)" );
273 }
274
275 /// Everything is in
276
277 /*mixin*/
278 template SimpleClass()
279 {
280 mixin SimpleConstructor;
281 mixin SimpleCompare;
282 mixin SimpleToString;
283 }