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 core.exception;
10 import std.array : appender;
11 import std.format : formattedWrite;
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] != (cast(const)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( this.tupleof[i] != (cast(const)rhs).tupleof[i] ) {
134 auto a = (cast(SC_Unqual!(typeof(this)))this).tupleof[i];
135 auto b = rhs.tupleof[i];
136 return a < b ? -1 : +1;
137 }
138 // not work well for structures???????
139 // if(auto c = typeid(_).compare(&this.tupleof[i],&rhs.tupleof[i]))
140 // return c;
141 return 0;
142 }
143 assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_)));
144 }
145 }
146
147 alias std.traits.Unqual SC_Unqual;
148
149 unittest
150 {
151 class Temp
152 {
153 int x;
154 string y;
155 mixin SimpleConstructor;
156 mixin SimpleCompare;
157 }
158 assert_eq( new Temp(1,"foo"), new Temp(1,"foo") );
159 assert_eq( (new Temp(1,"foo")).toHash, (new Temp(1,"foo")).toHash );
160 assert_ne( new Temp(1,"foo"), new Temp(2,"foo") );
161 assert_ne( new Temp(1,"foo"), new Temp(1,"bar") );
162 assert_gt( new Temp(1,"foo"), new Temp(1,"bar") );
163 assert_lt( new Temp(1,"foo"), new Temp(2,"bar") );
164 assert_ge( new Temp(1,"foo"), new Temp(1,"foo") );
165
166 class TempDummy
167 {
168 int x;
169 string y;
170 mixin SimpleConstructor;
171 mixin SimpleCompare;
172 }
173 assert_throw!AssertError( new Temp(1,"foo") == new TempDummy(1,"foo") );
174 assert_throw!AssertError( new Temp(1,"foo") <= new TempDummy(1,"foo") );
175 }
176
177 /// Mixing-in a simple toString method
178
179 /*mixin*/
180 template SimpleToString()
181 {
182 /// member-by-member toString
183 override string toString()
184 {
185 string str = sprintf!"%s("(typeof(this).stringof);
186 foreach(i,mem; this.tupleof)
187 {
188 if(i) str ~= ",";
189 static if( is(typeof(mem) == std.bigint.BigInt) )
190 str ~= std.bigint.toDecimalString(mem);
191 else
192 str ~= sprintf!"%s"(mem);
193 }
194 return str ~ ")";
195 }
196 }
197
198 version(unittest) import std.bigint;
199 unittest
200 {
201 class Temp
202 {
203 int x;
204 string y;
205 BigInt z;
206 mixin SimpleConstructor;
207 mixin SimpleToString;
208 }
209 assert_eq( (new Temp(1,"foo",BigInt(42))).toString(), "Temp(1,foo,42)" );
210 }
211
212 /// Everything is in
213
214 /*mixin*/
215 template SimpleClass()
216 {
217 mixin SimpleConstructor;
218 mixin SimpleCompare;
219 mixin SimpleToString;
220 }
221
222 /// Simple PatternMatcher
223
224 /*mixin*/
225 template SimplePatternMatch()
226 {
227 SPM_Return!(PP) match(string fn=__FILE__, size_t ln=__LINE__, PP...)(PP pts)
228 {
229 foreach(i,_; pts)
230 {
231 alias pts[i] pt; // bug? pts[i]-->pt do not work
232 static if(__traits(compiles, SPM_isMatchTag(pt)))
233 {
234 if( auto v = cast(pt.dynamicType)this )
235 return pt(v.tupleof);
236 }
237 else
238 static if(__traits(compiles, SPM_isMatchAny(pt)))
239 {
240 return pt();
241 }
242 else
243 {
244 if( auto v = cast(SPM_PTT!(pt)[0])this )
245 return pt(v);
246 }
247 }
248 SPM_throwAssertError(fn, ln, "pattern matching failure");
249 assert(false);
250 }
251 }
252
253 /// Pattern case clause
254
255 SPM_MatchTag!(T, fn) when(T, alias fn)()
256 {
257 SPM_MatchTag!(T, fn) m;
258 return m;
259 }
260
261 /// Pattern case clause
262
263 SPM_MatchAny!(fn) otherwise(alias fn)()
264 {
265 SPM_MatchAny!(fn) m;
266 return m;
267 }
268
269 // implementation detail of SimplePatternMatch
270
271 void SPM_throwAssertError(T...)(T t) { core.exception.onAssertErrorMsg(t); }
272
273 struct SPM_MatchTag(T, alias fn)
274 {
275 alias T dynamicType;
276 auto opCall(typeof(T.tupleof) s) { return fn(s); }
277 }
278
279 struct SPM_MatchAny(alias fn)
280 {
281 auto opCall() { return fn(); }
282 }
283
284 template SPM_PTT(alias p)
285 {
286 alias ParameterTypeTuple!(p) SPM_PTT;
287 }
288
289 template SPM_Each(P)
290 {
291 static if(__traits(compiles, SPM_isMatchTag(P.init)))
292 alias typeof(P(P.dynamicType.tupleof)) SPM_Each;
293 else
294 static if(__traits(compiles, SPM_isMatchAny(P.init)))
295 alias typeof(P()) SPM_Each;
296 else
297 alias ReturnType!(P) SPM_Each;
298 }
299
300 template SPM_aVoid(T:void, TS...) { alias SPM_aVoid!(TS) SPM_aVoid; }
301 template SPM_aVoid(T, TS...) { alias TypeTuple!(T,SPM_aVoid!(TS)) SPM_aVoid; }
302 template SPM_aVoid() { alias TypeTuple!() SPM_aVoid; }
303
304 template SPM_Return(PP...)
305 {
306 alias CommonType!(SPM_aVoid!(staticMap!(SPM_Each, PP))) SPM_Return;
307 }
308
309 void SPM_isMatchTag(T,alias fn)(SPM_MatchTag!(T,fn)){}
310 void SPM_isMatchAny(alias fn)(SPM_MatchAny!(fn)){}
311
312 unittest
313 {
314 static abstract class Base {
315 mixin SimplePatternMatch;
316 }
317 class D1 : Base {
318 int x;
319 real y;
320 mixin SimpleConstructor;
321 }
322 class D2 : Base {
323 string s;
324 mixin SimpleConstructor;
325 }
326 class D3 : Base {
327 int[int] m;
328 mixin SimpleConstructor;
329 }
330
331 Base d1 = new D1(1, 2.3);
332 Base d2 = new D2("foobar");
333 Base d3 = new D3(null); (cast(D3)d3).m[1]=10;
334
335 // normal dispatch
336 assert_eq( d1.match(
337 (D1 x){return 1;},
338 (D2 x){return 2;}
339 ), 1);
340 assert_eq( d2.match(
341 (D1 x){return 1;},
342 (D2 x){return 2;}
343 ), 2);
344 assert_throw!AssertError( d3.match(
345 (D1 x){return 1;},
346 (D2 x){return 2;}
347 ));
348 assert_eq( d3.match(
349 (D1 x){return 1;},
350 (D2 x){return 2;},
351 (Base x){return 3;}
352 ), 3);
353 assert_eq( d2.match(
354 (D1 x){return 1;},
355 (D2 x){return 2;},
356 (Base x){return 3;}
357 ), 2);
358 assert_eq( d2.match(
359 (D1 x){return 1;},
360 (Base x){return 3;},
361 (D2 x){return 2;}
362 ), 3);
363
364 // member decomposing match
365 assert_eq( d1.match(
366 when!(D1, (x, y){return x + cast(int)y;}),
367 when!(D2, (x){return x.length;}),
368 when!(D3, (x){return x[1];})
369 ), 3);
370 assert_eq( d2.match(
371 when!(D1, (x, y){return x + cast(int)y;}),
372 when!(D2, (x){return x.length;}),
373 when!(D3, (x){return x[1];})
374 ), 6);
375 assert_eq( d3.match(
376 when!(D1, (x, y){return x + cast(int)y;}),
377 when!(D2, (x){return x.length;}),
378 when!(D3, (x){return x[1];})
379 ), 10);
380 assert_throw!AssertError( d3.match(
381 when!(D1, (x, y){return x + cast(int)y;}),
382 when!(D2, (x){return x.length;})
383 ));
384 assert_eq( d2.match(
385 when!(D1, (x, y){return x + cast(int)y;}),
386 when!(D2, (x){return x.length;}),
387 otherwise!({return 999;})
388 ), 6);
389 assert_eq( d2.match(
390 when!(D1, (x, y){return x + cast(int)y;}),
391 otherwise!({return 999;}),
392 when!(D2, (x){return x.length;})
393 ), 999);
394 }