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 }