1 /******************************************************************************
2 
3     Template for a union that knows its active field and ensures the active
4     field is read.
5 
6     See_Also:
7         ocean.text.formatter.SmartUnion -- for helper functions to format a
8         SmartUnion to a string
9 
10     Copyright:
11         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
12         All rights reserved.
13 
14     License:
15         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
16         Alternatively, this file may be distributed under the terms of the Tango
17         3-Clause BSD License (see LICENSE_BSD.txt for details).
18 
19  ******************************************************************************/
20 
21 module ocean.core.SmartUnion;
22 
23 import ocean.transition;
24 import ocean.core.ExceptionDefinitions;
25 import ocean.core.Test;
26 import ocean.core.Verify;
27 import ocean.meta.codegen.Identifier;
28 import ocean.meta.types.Templates /* : TemplateInstanceArgs */;
29 
30 
31 /******************************************************************************
32 
33     Provides a getter and setter method for each member of U. Additionally an
34     "Active" enumerator and an "active" getter method is provided. The "Active"
35     enumerator members copy the U member names, the values of that members start
36     with 1. The "Active" enumerator has an additional "none" member with the
37     value 0. The "active" getter method returns the "Active" enumerator value of
38     the type currently set in the union -- this may be "none" if the union is in
39     its initial state.
40 
41  ******************************************************************************/
42 
43 struct SmartUnion ( U )
44 {
45     static assert (is (U == union), "SmartUnion: need a union, not \"" ~ U.stringof ~ '"');
46 
47     /**************************************************************************
48 
49         Holds the actual union U instance and Active enumerator value. To reduce
50         the risk of a name collision, this member is named "_".
51 
52      **************************************************************************/
53 
54     private SmartUnionIntern!(U) _;
55 
56     /**************************************************************************
57 
58         Active enumerator type alias
59 
60         Note: There is a member named "_".
61 
62      **************************************************************************/
63 
64     alias _.Active Active;
65 
66     /**************************************************************************
67 
68         Returns:
69             Active enumerator value of the currently active member or 0
70             (Active.none) if no member has yet been set.
71 
72      **************************************************************************/
73 
74     Active active ( ) { return (&this)._.active; }
75 
76     /***************************************************************************
77 
78         Returns:
79             name of the currently active member or "none" if no member has yet
80             been set.
81 
82     ***************************************************************************/
83 
84     public istring active_name ( )
85     {
86         return (&this)._.active_names[(&this)._.active];
87     }
88 
89     /**************************************************************************
90 
91         Member getter/setter method definitions string mixin
92 
93      **************************************************************************/
94 
95     mixin (AllMethods!(U, "", 0));
96 
97     private alias typeof(this) Type;
98 }
99 
100 ///
101 unittest
102 {
103     union MyUnion
104     {
105         int x;
106         mstring y;
107     }
108 
109     void main ( )
110     {
111         SmartUnion!(MyUnion) u;
112         istring name;
113         u.Active a;             // u.Active is defined as
114                                 // `enum u.Active {none, x, y}`
115 
116         a = u.active;           // a is now a.none
117         name = u.active_name;   // name is now "none"
118         int b = u.x;            // error, u.x has not yet been set
119         u.x   = 35;
120         a = u.active;           // a is now a.x
121         name = u.active_name;   // name is now "x"
122         mstring c = u.y;        // error, u.y is not the active member
123     }
124 }
125 
126 unittest
127 {
128     SmartUnion!(U1) u1;
129     SmartUnion!(U2) u2;
130     SmartUnion!(U3) u3;
131 
132     test!("==")(u1.active, u1.Active.none);
133     test!("==")(u2.active, u2.Active.none);
134     test!("==")(u3.active, u3.Active.none);
135 
136     test!("==")(u1.active, 0);
137     test!("==")(u2.active, 0);
138     test!("==")(u3.active, 0);
139 
140     test!("==")(u1.active_name, "none");
141     test!("==")(u2.active_name, "none");
142     test!("==")(u3.active_name, "none");
143 
144     testThrown!(Exception)(u1.a(), false);
145     testThrown!(Exception)(u1.b(), false);
146     testThrown!(Exception)(u2.a(), false);
147     testThrown!(Exception)(u2.b(), false);
148     testThrown!(Exception)(u3.a(), false);
149     testThrown!(Exception)(u3.b(), false);
150 
151     u1.a(42);
152     test!("==")(u1.a, 42);
153     test!("==")(u1.active, u1.Active.a);
154     test!("==")(u1.active_name, "a");
155     testThrown!(Exception)(u1.b(), false);
156 
157     u2.a(new C1());
158     test!("==")(u2.a.v, uint.init);
159     test!("==")(u2.active, u2.Active.a);
160     test!("==")(u2.active_name, "a");
161     testThrown!(Exception)(u2.b(), false);
162 
163     u3.a(S1(42));
164     test!("==")(u3.a, S1(42));
165     test!("==")(u3.active, u3.Active.a);
166     test!("==")(u3.active_name, "a");
167     testThrown!(Exception)(u3.b(), false);
168 
169     u1.b("Hello world".dup);
170     test!("==")(u1.b, "Hello world"[]);
171     test!("==")(u1.active, u1.Active.b);
172     test!("==")(u1.active_name, "b");
173     testThrown!(Exception)(u1.a(), false);
174 
175     u2.b(S1.init);
176     test!("==")(u2.b, S1.init);
177     test!("==")(u2.active, u2.Active.b);
178     test!("==")(u2.active_name, "b");
179     testThrown!(Exception)(u2.a(), false);
180 
181     u3.b(21);
182     test!("==")(u3.b, 21);
183     test!("==")(u3.active, u3.Active.b);
184     test!("==")(u3.active_name, "b");
185     testThrown!(Exception)(u3.a(), false);
186 
187 }
188 
189 version (UnitTest)
190 {
191     class C1
192     {
193         uint v;
194     }
195 
196     struct S1
197     {
198         uint v;
199     }
200 
201     union U1
202     {
203         uint a;
204         char[] b;
205     }
206 
207     union U2
208     {
209         C1 a;
210         S1 b;
211     }
212 
213     union U3
214     {
215         S1 a;
216         uint b;
217     }
218 }
219 
220 
221 /*******************************************************************************
222 
223     Calls the specified callable with the active field of the provided
224     smart-union. If no field is active, does nothing.
225 
226     Note: declared at module-scope (rather than nested inside the SmartUnion
227     template) to work around limitations of template alias parameters. (Doing it
228     like this allows it to be called with a local name.)
229 
230     Params:
231         Callable = alias for the thing to be called with the active member of
232             the provided smart-union
233         SU = type of smart-union to operate on
234         smart_union = smart-union instance whose active field should be passed
235             to Callable
236 
237 *******************************************************************************/
238 
239 public void callWithActive ( alias Callable, SU ) ( SU smart_union )
240 {
241     static assert(is(TemplateInstanceArgs!(SmartUnion, SU)));
242     alias typeof(smart_union._.u) U;
243 
244     if ( !smart_union._.active )
245         return;
246 
247     auto active_i = smart_union._.active - 1;
248     verify(active_i < U.tupleof.length);
249 
250     // "static foreach", unrolls into the equivalent of a switch
251     foreach ( i, ref field; smart_union._.u.tupleof )
252     {
253         if ( i == active_i )
254         {
255             Callable(field);
256             break;
257         }
258     }
259 }
260 
261 ///
262 unittest
263 {
264     // This example is D2 only because it requires a function template,
265     // `print`, and D1 doesn't allow defining a function template at the scope
266     // of a function, including a `unittest`. In D1 this example works if
267     // `print`, is defined outside of function scope.
268 
269     union TestUnion
270     {
271         int a;
272         float b;
273     }
274     alias SmartUnion!(TestUnion) TestSmartUnion;
275 
276     static struct ActiveUnionFieldPrinter
277     {
278         static void print ( T ) ( T t )
279         {
280             Stdout.formatln("{}", t);
281         }
282 
283         void printActiveUnionField ( )
284         {
285             TestSmartUnion su;
286             su.a = 23;
287             callWithActive!(print)(su);
288         }
289     }
290 }
291 
292 version ( UnitTest )
293 {
294     import ocean.io.Stdout;
295 }
296 
297 ///
298 unittest
299 {
300     union TestUnion
301     {
302         int a;
303         float b;
304     }
305     alias SmartUnion!(TestUnion) TestSmartUnion;
306 
307     TestSmartUnion u;
308     u.a = 1;
309 
310     with (TestSmartUnion.Active) final switch (u.active)
311     {
312         case a:
313         case b:
314             break;
315 
316         case none:
317             assert(false);
318     }
319 }
320 
321 /******************************************************************************
322 
323     Holds the actual union U instance and Active enumerator value and provides
324     templates to generate the code defining the member getter/setter methods and
325     the Active enumerator.
326 
327  ******************************************************************************/
328 
329 private struct SmartUnionIntern ( U )
330 {
331     /**************************************************************************
332 
333         U instance
334 
335      **************************************************************************/
336 
337     U u;
338 
339     /**************************************************************************
340 
341         Number of members in U
342 
343      **************************************************************************/
344 
345     enum N = U.tupleof.length;
346 
347     /**************************************************************************
348 
349         Active enumerator definition string mixin
350 
351      **************************************************************************/
352 
353     mixin("enum Active{none" ~ MemberList!(0, N, U) ~ "}");
354 
355     /**************************************************************************
356 
357         Memorizes which member is currently active (initially none which is 0)
358 
359      **************************************************************************/
360 
361     Active active;
362 
363     /***************************************************************************
364 
365         List of active state names
366 
367     ***************************************************************************/
368 
369     enum istring[] active_names = member_string_list();
370 
371     /***************************************************************************
372 
373         CTFE function to generate the list of active state names for union U.
374 
375         Returns:
376             a list containing the names of each of the active states of the
377             smart-union (i.e. the names of the fields of U)
378 
379     ***************************************************************************/
380 
381     static private istring[] member_string_list ( )
382     {
383         istring[] names = ["none"[]];
384         foreach ( i, F; typeof(U.init.tupleof) )
385         {
386             names ~= identifier!(U.tupleof[i]);
387         }
388         return names;
389     }
390 }
391 
392 /*******************************************************************************
393 
394     Evaluates to a ',' separated list of the names of the members of U.
395 
396     Params:
397         i   = U member start index
398         len = number of members in U
399         U = aggregate to iterate over
400 
401     Evaluates to:
402         a ',' separated list of the names of the members of U
403 
404 *******************************************************************************/
405 
406 private template MemberList ( uint i, size_t len, U )
407 {
408     static if ( i == len )
409     {
410         static immutable MemberList = "";
411     }
412     else
413     {
414         static immutable MemberList = "," ~ identifier!(U.tupleof[i]) ~ MemberList!(i + 1, len, U);
415     }
416 }
417 
418 /*******************************************************************************
419 
420     Evaluates to code defining a getter, a setter and a static opCall()
421     initializer method, where the name of the getter/setter method is
422     pre ~ ".u." ~ the name of the i-th member of U.
423 
424     The getter/setter methods use pre ~ ".active" which must be the Active
425     enumerator:
426         - the getter uses an 'in' contract to make sure the active member is
427           accessed,
428         - the setter method sets pre ~ ".active" to the active member.
429 
430     Example: For
431     ---
432         union U {int x; char y;}
433     ---
434 
435     ---
436         mixin (Methods!("my_smart_union", 1).both);
437     ---
438     evaluates to
439     ---
440         // Getter for my_smart_union.u.y. Returns:
441         //     my_smart_union.u.y
442 
443         char[] y()
444         {
445             return my_smart_union.u.y;
446         }
447 
448         // Setter for my_smart_union.u.y. Params:
449         //     y = new value for y
450         // Returns:
451         //     y
452 
453         char[] y(char[] y)
454         {
455            my_smart_union.active = my_smart_union.active.y;
456            return my_smart_union.u.y = y;
457         }
458     ---
459 
460     Methods.get and Methods.set evaluate to only the getter or setter
461     method, respectively.
462 
463     Params:
464         pre = prefix for U instance "u"
465         i   = index of U instance "u" member
466 
467     Evaluates to:
468         get  = getter method for the U member
469         set  = setter method for the U member
470         opCall = static SmartUnion initialiser with the value set to the U
471             member
472 
473 *******************************************************************************/
474 
475 private template Methods ( U, uint i )
476 {
477     static immutable member = identifier!(U.tupleof[i]);
478 
479     static immutable member_access = "_.u." ~ member;
480 
481     static immutable type = "typeof(" ~ member_access ~ ")";
482 
483     static immutable get = type ~ ' ' ~  member ~ "() "
484         ~ "{ verify(_.active == _.active." ~ member ~ ", "
485         ~ `"SmartUnion: '` ~ member ~ `' not active"); `
486         ~ "return " ~ member_access ~ "; }";
487 
488     static immutable set = type ~ ' ' ~  member ~ '(' ~ type ~ ' ' ~ member ~ ")"
489         ~ "{ _.active = _.active." ~ member ~ ";"
490         ~ "return " ~ member_access ~ '=' ~ member ~ "; }";
491 
492     static immutable ini = "static Type opCall(" ~ type ~ ' ' ~ member ~ ")"
493         ~ "{ Type su; su." ~ member ~ '=' ~ member ~ "; return su; }";
494 
495     static immutable local_import = "import ocean.core.Enforce;\n";
496 
497     static immutable all = local_import ~ get ~ '\n' ~ set ~ '\n' ~ ini;
498 }
499 
500 /*******************************************************************************
501 
502     Evaluates to code defining a getter and setter method for each U member.
503 
504     Params:
505         u_pre = prefix for U instance "u"
506         pre   = method definition code prefix, code will be appended to pre
507         i     = U instance "u" member start index
508 
509     Evaluates to:
510         code defining a getter and setter method for each U member
511 
512 *******************************************************************************/
513 
514 private template AllMethods ( U, istring pre, uint i)
515 {
516     static if (i < U.tupleof.length)
517     {
518         static immutable AllMethods =
519             AllMethods!(U, pre ~ '\n' ~ Methods!(U, i).all, i + 1);
520     }
521     else
522     {
523         static immutable AllMethods = pre;
524     }
525 }