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 tricks.tricks;
8 import tricks.test;
9 import std.array : appender;
10 import std.format : formattedWrite;
11 import core.exception;
12 import std.traits;
13 import std.typetuple;
14
15 /// Simple Wrapper for std.format.doFormat
16
17 string sprintf(string fmt, T...)(T params)
18 {
19 auto writer = appender!string();
20 formattedWrite(writer, fmt, params);
21 return writer.data;
22 }
23
24 unittest
25 {
26 assert_eq( sprintf!"%s == %04d"("1+2", 3), "1+2 == 0003" );
27 assert_eq( sprintf!"%2$s == %1$s"("1+2", 5, 8), "5 == 1+2" );
28 assert_throw!Error( sprintf!"%s%s"(1) );
29 }
30
31 /// Create an exception with automatically completed filename and lineno information
32
33 ExceptionType genex(ExceptionType, string fn=__FILE__, int ln=__LINE__, T...)(T params)
34 {
35 static if( T.length > 0 && is(T[$-1] : Throwable) )
36 return new ExceptionType(params[0..$-1], fn, ln, params[$-1]);
37 else
38 return new ExceptionType(params, fn, ln);
39 }
40
41 unittest
42 {
43 assert_ne( genex!Exception("msg").file, "" );
44 assert_ne( genex!Exception("msg").line, 0 );
45 assert_ne( genex!Exception("msg",new Exception("bar")).next, Exception.init );
46 }
47
48 /// Mixing-in the bean constructor for a class
49
50 /*mixin*/
51 template SimpleConstructor()
52 {
53 /// member-by-member constructor
54 static if( is(typeof(super) == Object) || super.tupleof.length==0 )
55 this( typeof(this.tupleof) params )
56 {
57 static if(this.tupleof.length>0)
58 this.tupleof = params;
59 }
60 else
61 this( typeof(super.tupleof) ps, typeof(this.tupleof) params )
62 {
63 // including (only) the direct super class members
64 // may not always be a desirable choice, but should work for many cases
65 super(ps);
66 static if(this.tupleof.length>0)
67 this.tupleof = params;
68 }
69 }
70
71 unittest
72 {
73 class Temp
74 {
75 int x;
76 string y;
77 mixin SimpleConstructor;
78 }
79 assert_eq( (new Temp(1,"foo")).x, 1 );
80 assert_eq( (new Temp(1,"foo")).y, "foo" );
81 assert( !__traits(compiles, new Temp) );
82 assert( !__traits(compiles, new Temp(1)) );
83 assert( !__traits(compiles, new Temp("foo",1)) );
84
85 class Tomp : Temp
86 {
87 real z;
88 mixin SimpleConstructor;
89 }
90 assert_eq( (new Tomp(1,"foo",2.5)).x, 1 );
91 assert_eq( (new Tomp(1,"foo",2.5)).y, "foo" );
92 assert_eq( (new Tomp(1,"foo",2.5)).z, 2.5 );
93 assert( !__traits(compiles, new Tomp(3.14)) );
94
95 // shiyo- desu. Don't use in this way.
96 // Tamp tries to call new Tomp(real) (because it only sees Tomp's members),
97 // but it fails because Tomp takes (int,string,real).
98 assert( !__traits(compiles, {
99 class Tamp : Tomp { mixin SimpleConstructor; }
100 }) );
101 }
102
103 /// Mixing-in the MOST-DERIVED-member-wise comparator for a class
104
105 /*mixin*/
106 template SimpleCompare()
107 {
108 override bool opEquals(Object rhs_) const /// member-by-member equality
109 {
110 if( auto rhs = cast(typeof(this))rhs_ )
111 {
112 foreach(i,_; this.tupleof)
113 if( this.tupleof[i] != rhs.tupleof[i] )
114 return false;
115 return true;
116 }
117 assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_)));
118 }
119
120 override hash_t toHash() const /// member-by-member hash
121 {
122 hash_t h = 0;
123 foreach(mem; this.tupleof)
124 h += typeid(mem).getHash(&mem);
125 return h;
126 }
127
128 override int opCmp(Object rhs_) const /// member-by-member compare
129 {
130 if( auto rhs = cast(typeof(this))rhs_ )
131 {
132 foreach(i,_; this.tupleof)
133 if(auto c = typeid(_).compare(&this.tupleof[i],&rhs.tupleof[i]))
134 return c;
135 return 0;
136 }
137 assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_)));
138 }
139 }
140
141 unittest
142 {
143 class Temp
144 {
145 int x;
146 string y;
147 mixin SimpleConstructor;
148 mixin SimpleCompare;
149 }
150 assert_eq( new Temp(1,"foo"), new Temp(1,"foo") );
151 assert_eq( (new Temp(1,"foo")).toHash, (new Temp(1,"foo")).toHash );
152 assert_ne( new Temp(1,"foo"), new Temp(2,"foo") );
153 assert_ne( new Temp(1,"foo"), new Temp(1,"bar") );
154 assert_gt( new Temp(1,"foo"), new Temp(1,"bar") );
155 assert_lt( new Temp(1,"foo"), new Temp(2,"bar") );
156 assert_ge( new Temp(1,"foo"), new Temp(1,"foo") );
157
158 class TempDummy
159 {
160 int x;
161 string y;
162 mixin SimpleConstructor;
163 mixin SimpleCompare;
164 }
165 assert_throw!AssertError( new Temp(1,"foo") == new TempDummy(1,"foo") );
166 assert_throw!AssertError( new Temp(1,"foo") <= new TempDummy(1,"foo") );
167 }
168
169 /// Mixing-in a simple toString method
170
171 /*mixin*/
172 template SimpleToString()
173 {
174 /// member-by-member toString
175 override string toString()
176 {
177 string str = sprintf!"%s("(typeof(this).stringof);
178 foreach(i,mem; this.tupleof)
179 {
180 if(i) str ~= ",";
181 static if( is(typeof(mem) == std.bigint.BigInt) )
182 str ~= std.bigint.toDecimalString(mem);
183 else
184 str ~= sprintf!"%s"(mem);
185 }
186 return str ~ ")";
187 }
188 }
189
190 version(unittest) import std.bigint;
191 unittest
192 {
193 class Temp
194 {
195 int x;
196 string y;
197 BigInt z;
198 mixin SimpleConstructor;
199 mixin SimpleToString;
200 }
201 assert_eq( (new Temp(1,"foo",BigInt(42))).toString(), "Temp(1,foo,42)" );
202 }
203
204 /// Everything is in
205
206 /*mixin*/
207 template SimpleClass()
208 {
209 mixin SimpleConstructor;
210 mixin SimpleCompare;
211 mixin SimpleToString;
212 }
213
214 /// Simple PatternMatcher
215
216 /*mixin*/
217 template SimplePatternMatch()
218 {
219 SPM_Return!(PP) match(string fn=__FILE__, size_t ln=__LINE__, PP...)(PP pts)
220 {
221 foreach(i,_; pts)
222 {
223 alias pts[i] pt; // bug? pts[i]-->pt do not work
224 static if(__traits(compiles, SPM_isMatchTag(pt)))
225 {
226 if( auto v = cast(pt.dynamicType)this )
227 return pt(v.tupleof);
228 }
229 else
230 static if(__traits(compiles, SPM_isMatchAny(pt)))
231 {
232 return pt();
233 }
234 else
235 {
236 if( auto v = cast(SPM_PTT!(pt)[0])this )
237 return pt(v);
238 }
239 }
240 SPM_throwAssertError(fn, ln, "pattern matching failure");
241 assert(false);
242 }
243 }
244
245 /// Pattern case clause
246
247 SPM_MatchTag!(T, fn) when(T, alias fn)()
248 {
249 SPM_MatchTag!(T, fn) m;
250 return m;
251 }
252
253 /// Pattern case clause
254
255 SPM_MatchAny!(fn) otherwise(alias fn)()
256 {
257 SPM_MatchAny!(fn) m;
258 return m;
259 }
260
261 // implementation detail of SimplePatternMatch
262
263 void SPM_throwAssertError(T...)(T t) { core.exception.onAssertErrorMsg(t); }
264
265 struct SPM_MatchTag(T, alias fn)
266 {
267 alias T dynamicType;
268 auto opCall(typeof(T.tupleof) s) { return fn(s); }
269 }
270
271 struct SPM_MatchAny(alias fn)
272 {
273 auto opCall() { return fn(); }
274 }
275
276 template SPM_PTT(alias p)
277 {
278 alias ParameterTypeTuple!(p) SPM_PTT;
279 }
280
281 template SPM_Each(P)
282 {
283 static if(__traits(compiles, SPM_isMatchTag(P.init)))
284 alias typeof(P(P.dynamicType.tupleof)) SPM_Each;
285 else
286 static if(__traits(compiles, SPM_isMatchAny(P.init)))
287 alias typeof(P()) SPM_Each;
288 else
289 alias ReturnType!(P) SPM_Each;
290 }
291
292 template SPM_aVoid(T:void, TS...) { alias SPM_aVoid!(TS) SPM_aVoid; }
293 template SPM_aVoid(T, TS...) { alias TypeTuple!(T,SPM_aVoid!(TS)) SPM_aVoid; }
294 template SPM_aVoid() { alias TypeTuple!() SPM_aVoid; }
295
296 template SPM_Return(PP...)
297 {
298 alias CommonType!(SPM_aVoid!(staticMap!(SPM_Each, PP))) SPM_Return;
299 }
300
301 void SPM_isMatchTag(T,alias fn)(SPM_MatchTag!(T,fn)){}
302 void SPM_isMatchAny(alias fn)(SPM_MatchAny!(fn)){}
303
304 unittest
305 {
306 static abstract class Base {
307 mixin SimplePatternMatch;
308 }
309 class D1 : Base {
310 int x;
311 real y;
312 mixin SimpleConstructor;
313 }
314 class D2 : Base {
315 string s;
316 mixin SimpleConstructor;
317 }
318 class D3 : Base {
319 int[int]m;
320 mixin SimpleConstructor;
321 }
322
323 Base d1 = new D1(1, 2.3);
324 Base d2 = new D2("foobar");
325 Base d3 = new D3(null); (cast(D3)d3).m[1]=10;
326
327 // normal dispatch
328 assert_eq( d1.match(
329 (D1 x){return 1;},
330 (D2 x){return 2;},
331 ), 1);
332 assert_eq( d2.match(
333 (D1 x){return 1;},
334 (D2 x){return 2;},
335 ), 2);
336 assert_throw!AssertError( d3.match(
337 (D1 x){return 1;},
338 (D2 x){return 2;},
339 ));
340 assert_eq( d3.match(
341 (D1 x){return 1;},
342 (D2 x){return 2;},
343 (Base x){return 3;},
344 ), 3);
345 assert_eq( d2.match(
346 (D1 x){return 1;},
347 (D2 x){return 2;},
348 (Base x){return 3;},
349 ), 2);
350 assert_eq( d2.match(
351 (D1 x){return 1;},
352 (Base x){return 3;},
353 (D2 x){return 2;},
354 ), 3);
355
356 // member decomposing match
357 assert_eq( d1.match(
358 when!(D1, (x, y){return x + cast(int)y;}),
359 when!(D2, (x){return x.length;}),
360 when!(D3, (x){return x[1];}),
361 ), 3);
362 assert_eq( d2.match(
363 when!(D1, (x, y){return x + cast(int)y;}),
364 when!(D2, (x){return x.length;}),
365 when!(D3, (x){return x[1];}),
366 ), 6);
367 assert_eq( d3.match(
368 when!(D1, (x, y){return x + cast(int)y;}),
369 when!(D2, (x){return x.length;}),
370 when!(D3, (x){return x[1];}),
371 ), 10);
372 assert_throw!AssertError( d3.match(
373 when!(D1, (x, y){return x + cast(int)y;}),
374 when!(D2, (x){return x.length;}),
375 ));
376 assert_eq( d2.match(
377 when!(D1, (x, y){return x + cast(int)y;}),
378 when!(D2, (x){return x.length;}),
379 otherwise!({return 999;}),
380 ), 6);
381 assert_eq( d2.match(
382 when!(D1, (x, y){return x + cast(int)y;}),
383 otherwise!({return 999;}),
384 when!(D2, (x){return x.length;}),
385 ), 999);
386 }