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