1 module nxt.typecons_ex; 2 3 // TODO: Move to Phobos and refer to http://forum.dlang.org/thread/lzyqywovlmdseqgqfvun@forum.dlang.org#post-ibvkvjwexdafpgtsamut:40forum.dlang.org 4 // TODO: Better with?: 5 /* inout(Nullable!T) nullable(T)(inout T a) */ 6 /* { */ 7 /* return typeof(return)(a); */ 8 /* } */ 9 /* inout(Nullable!(T, nullValue)) nullable(alias nullValue, T)(inout T value) */ 10 /* if (is (typeof(nullValue) == T)) */ 11 /* { */ 12 /* return typeof(return)(value); */ 13 /* } */ 14 15 import std.typecons : Nullable, NullableRef; 16 17 /** Instantiator for `Nullable`. 18 */ 19 auto nullable(T)(T a) 20 { 21 return Nullable!T(a); 22 } 23 24 /// 25 @safe pure nothrow @nogc unittest 26 { 27 auto x = 42.5.nullable; 28 assert(is(typeof(x) == Nullable!double)); 29 } 30 31 /** Instantiator for `Nullable`. 32 */ 33 auto nullable(alias nullValue, T)(T value) 34 if (is (typeof(nullValue) == T)) 35 { 36 return Nullable!(T, nullValue)(value); 37 } 38 39 /// 40 @safe pure nothrow @nogc unittest 41 { 42 auto x = 3.nullable!(int.max); 43 assert(is (typeof(x) == Nullable!(int, int.max))); 44 } 45 46 /** Instantiator for `NullableRef`. 47 */ 48 auto nullableRef(T)(T* a) @safe pure nothrow 49 { 50 return NullableRef!T(a); 51 } 52 53 /// 54 /*TODO: @safe*/ pure nothrow @nogc unittest 55 { 56 auto x = 42.5; 57 auto xr = nullableRef(&x); 58 assert(!xr.isNull); 59 xr.nullify; 60 assert(xr.isNull); 61 } 62 63 /** See_Also: http://forum.dlang.org/thread/jwdbjlobbilowlnpdzzo@forum.dlang.org 64 */ 65 template New(T) 66 if (is(T == class)) 67 { 68 T New(Args...) (Args args) 69 { 70 return new T(args); 71 } 72 } 73 74 import std.traits : isArray, isUnsigned, isInstanceOf; 75 import std.range.primitives : hasSlicing; 76 77 /** Check if `T` is castable to `U`. 78 */ 79 enum isCastableTo(T, U) = __traits(compiles, { cast(U)(T.init); }); 80 81 enum isIndex(I) = (is(I == enum) || 82 isUnsigned!I || // TODO: should we allow isUnsigned here? 83 isCastableTo!(I, size_t)); 84 85 /** Check if `T` can be indexed by an instance of `I`. 86 * 87 * See_Also: http://forum.dlang.org/post/ajxtksnsxqmeulsedmae@forum.dlang.org 88 * 89 * TODO: move to traits_ex.d 90 * TODO: Move to Phobos 91 */ 92 enum hasIndexing(T, I = size_t) = is(typeof(T.init[I.init]) == typeof(T.init[0])); 93 94 /// 95 @safe pure nothrow @nogc unittest 96 { 97 static assert(!hasIndexing!(int)); 98 static assert(hasIndexing!(int[3])); 99 static assert(hasIndexing!(byte[])); 100 static assert(hasIndexing!(byte[], uint)); 101 static assert(hasIndexing!(string)); 102 } 103 104 /** Check if `R` is indexable by `I`. */ 105 enum isIndexableBy(R, I) = (hasIndexing!R && isIndex!I); 106 107 /// 108 @safe pure nothrow @nogc unittest 109 { 110 static assert(isIndexableBy!(int[3], ubyte)); 111 } 112 113 /** Check if `R` is indexable by a automatically `R`-local defined integer type named `I`. 114 */ 115 enum isIndexableBy(R, alias I) = (hasIndexing!R && is(string == typeof(I))); // TODO: extend to isSomeString? 116 117 /// 118 @safe pure nothrow @nogc unittest 119 { 120 static assert(isIndexableBy!(int[], "I")); 121 } 122 123 /** Generate bounds-checked `opIndex` and `opSlice`. 124 */ 125 static private 126 mixin template _genIndexAndSliceOps(I) 127 { 128 import nxt.conv_ex : toDefaulted; 129 130 // indexing 131 132 /// Get element at compile-time index `i`. 133 auto ref at(size_t i)() inout 134 { 135 assert(cast(size_t)i < _r.length, "Index " ~ i ~ " must be smaller than array length" ~ _r.length.stringof); 136 return _r[i]; 137 } 138 139 /// Get element at index `i`. 140 auto ref opIndex(I i) inout 141 { 142 assert(cast(size_t)i < _r.length, "Range violation with index of type " ~ I.stringof); 143 return _r[cast(size_t)i]; 144 } 145 146 /// Set element at index `i` to `value`. 147 auto ref opIndexAssign(V)(V value, I i) 148 { 149 assert(cast(size_t)i < _r.length, "Range violation with index of type " ~ I.stringof); 150 151 import core.lifetime : move; 152 153 move(value, _r[cast(size_t)i]); 154 return _r[cast(size_t)i]; 155 } 156 157 // slicing 158 static if (hasSlicing!R) 159 { 160 auto ref opSlice(I i, I j) inout 161 { 162 return _r[cast(size_t)i .. cast(size_t)j]; 163 } 164 auto ref opSliceAssign(V)(V value, I i, I j) 165 { 166 return _r[cast(size_t)i .. cast(size_t)j] = value; // TODO: use `move()` 167 } 168 } 169 } 170 171 /** Generate @trusted non-bounds-checked `opIndex` and `opSlice`. 172 */ 173 static private 174 mixin template _genIndexAndSliceOps_unchecked(I) 175 { 176 @trusted: 177 178 // indexing 179 180 /// Get element at compile-time index `i`. 181 auto ref at(size_t i)() inout 182 { 183 static assert(i < _r.length, "Index " ~ i.stringof ~ " must be smaller than array length " ~ _r.length.stringof); 184 return _r.ptr[i]; 185 } 186 187 /// Get element at index `i`. 188 auto ref opIndex(I i) inout 189 { 190 return _r.ptr[cast(size_t)i]; // safe to avoid range checking 191 } 192 193 /// Set element at index `i` to `value`. 194 auto ref opIndexAssign(V)(V value, I i) 195 { 196 return _r.ptr[cast(size_t)i] = value; 197 } 198 199 // slicing 200 static if (hasSlicing!R) 201 { 202 auto ref opSlice(I i, I j) inout { return _r.ptr[cast(size_t)i .. 203 cast(size_t)j]; } 204 auto ref opSliceAssign(V)(V value, I i, I j) { return _r.ptr[cast(size_t)i .. 205 cast(size_t)j] = value; } 206 } 207 } 208 209 /** Wrapper for `R` with Type-Safe `I`-Indexing. 210 See_Also: http://forum.dlang.org/thread/gayfjaslyairnzrygbvh@forum.dlang.org#post-gayfjaslyairnzrygbvh:40forum.dlang.org 211 212 TODO: Merge with https://github.com/rcorre/enumap 213 214 TODO: Use std.range.indexed when I is an enum with non-contigious 215 enumerators. Perhaps use among aswell. 216 217 TODO: Rename to something more concise such as [Bb]y. 218 219 TODO: Allow `I` to be a string and if so derive `Index` to be that string. 220 */ 221 struct IndexedBy(R, I) 222 if (isIndexableBy!(R, I)) 223 { 224 alias Index = I; /// indexing type 225 mixin _genIndexAndSliceOps!I; 226 R _r; 227 alias _r this; // TODO: Use opDispatch instead; to override only opSlice and opIndex 228 } 229 230 /** Statically-Sized Array of ElementType `E` indexed by `I`. 231 TODO: assert that `I` is continuous if it is a `enum`. 232 */ 233 struct IndexedArray(E, I) 234 if (isIndex!I) 235 { 236 static assert(I.min == 0, "Index type I is currently limited to start at 0 and be continuous"); 237 alias Index = I; /// indexing type 238 mixin _genIndexAndSliceOps!I; 239 alias R = E[I.max + 1]; // needed by mixins 240 R _r; // static array 241 alias _r this; // TODO: Use opDispatch instead; to override only opSlice and opIndex 242 } 243 244 /// 245 @safe pure nothrow unittest 246 { 247 enum N = 7; 248 enum I { x = 0, y = 1, z = 2} 249 alias E = int; 250 alias A = IndexedArray!(E, I); 251 static assert(A.sizeof == 3*int.sizeof); 252 A x; 253 x[I.x] = 1; 254 x[I.y] = 2; 255 x[I.z] = 3; 256 static assert(!__traits(compiles, { x[1] = 3; })); // no integer indexing 257 } 258 259 /** Instantiator for `IndexedBy`. 260 */ 261 auto indexedBy(I, R)(R range) 262 if (isIndexableBy!(R, I)) 263 { 264 return IndexedBy!(R, I)(range); 265 } 266 267 struct IndexedBy(R, string IndexTypeName) 268 if (hasIndexing!R && 269 IndexTypeName != "IndexTypeName") // prevent name lookup failure 270 { 271 static if (__traits(isStaticArray, R)) // if length is known at compile-time 272 { 273 import nxt.modulo : Mod; 274 mixin(`alias ` ~ IndexTypeName ~ ` = Mod!(R.length);`); // TODO: relax integer precision argument of `Mod` 275 276 // dummy variable needed for symbol argument to `_genIndexAndSliceOps_unchecked` 277 mixin(`private static alias I__ = ` ~ IndexTypeName ~ `;`); 278 279 mixin _genIndexAndSliceOps_unchecked!(I__); // no range checking needed because I is always < R.length 280 281 /** Get index of element `E` wrapped in a `bool`-convertable struct. */ 282 auto findIndex(E)(E e) @safe pure nothrow @nogc 283 { 284 static struct Result 285 { 286 Index index; // index if exists is `true', 0 otherwise 287 bool exists; // `true` iff `index` is defined, `false` otherwise 288 bool opCast(T : bool)() const @safe pure nothrow @nogc { return exists; } 289 } 290 import std.algorithm : countUntil; 291 const ix = _r[].countUntil(e); // is safe 292 if (ix >= 0) 293 { 294 return Result(Index(ix), true); 295 } 296 return Result(Index(0), false); 297 } 298 } 299 else 300 { 301 mixin(q{ struct } ~ IndexTypeName ~ 302 q{ { 303 alias T = size_t; 304 this(T ix) { this._ix = ix; } 305 T opCast(U : T)() const { return _ix; } 306 private T _ix = 0; 307 } 308 }); 309 mixin _genIndexAndSliceOps!(mixin(IndexTypeName)); 310 } 311 R _r; 312 alias _r this; // TODO: Use opDispatch instead; to override only opSlice and opIndex 313 } 314 315 /* Wrapper type for `R' indexable/sliceable only with type `R.Index`. */ 316 template TypesafelyIndexed(R) 317 if (hasIndexing!R) // prevent name lookup failure 318 { 319 alias TypesafelyIndexed = IndexedBy!(R, "Index"); 320 } 321 322 /** Instantiator for `IndexedBy`. 323 */ 324 auto indexedBy(string I, R)(R range) 325 if (isArray!R && 326 I != "IndexTypeName") // prevent name lookup failure 327 { 328 return IndexedBy!(R, I)(range); 329 } 330 331 /** Instantiator for `TypesafelyIndexed`. 332 */ 333 auto strictlyIndexed(R)(R range) 334 if (hasIndexing!(R)) 335 { 336 return TypesafelyIndexed!(R)(range); 337 } 338 339 /// 340 @safe pure nothrow unittest 341 { 342 enum m = 3; 343 int[m] x = [11, 22, 33]; 344 auto y = x.strictlyIndexed; 345 346 alias Y = typeof(y); 347 348 static assert(is(typeof(y.findIndex(11).index) == Y.Index)); 349 350 assert(y.findIndex(11).exists); 351 assert(y.findIndex(22).exists); 352 assert(y.findIndex(33).exists); 353 354 if (auto hit = y.findIndex(11)) { assert(hit.index == 0); } else { assert(false); } 355 if (auto hit = y.findIndex(22)) { assert(hit.index == 1); } else { assert(false); } 356 if (auto hit = y.findIndex(33)) { assert(hit.index == 2); } else { assert(false); } 357 358 assert(!y.findIndex(44)); 359 assert(!y.findIndex(55)); 360 361 assert(!y.findIndex(44).exists); 362 assert(!y.findIndex(55).exists); 363 } 364 365 /// 366 @safe pure nothrow unittest 367 { 368 enum N = 7; 369 alias T = TypesafelyIndexed!(size_t[N]); // static array 370 static assert(T.sizeof == N*size_t.sizeof); 371 import nxt.modulo : Mod, mod; 372 373 T x; 374 375 x[Mod!N(1)] = 11; 376 x[1.mod!N] = 11; 377 assert(x[1.mod!N] == 11); 378 379 x.at!1 = 12; 380 static assert(!__traits(compiles, { x.at!N; })); 381 assert(x.at!1 == 12); 382 } 383 384 /// 385 @safe pure nothrow unittest 386 { 387 int[3] x = [1, 2, 3]; 388 389 // sample index 390 struct Index(T = size_t) 391 if (isUnsigned!T) 392 { 393 this(T i) { this._i = i; } 394 T opCast(U : T)() const { return _i; } 395 private T _i = 0; 396 } 397 alias J = Index!size_t; 398 399 enum E { e0, e1, e2 } 400 401 with (E) 402 { 403 auto xb = x.indexedBy!ubyte; 404 auto xi = x.indexedBy!uint; 405 auto xj = x.indexedBy!J; 406 auto xe = x.indexedBy!E; 407 auto xf = x.strictlyIndexed; 408 409 auto xs = x.indexedBy!"I"; 410 alias XS = typeof(xs); 411 XS xs_; 412 413 // indexing with correct type 414 xb[ 0 ] = 11; assert(xb[ 0 ] == 11); 415 xi[ 0 ] = 11; assert(xi[ 0 ] == 11); 416 xj[J(0)] = 11; assert(xj[J(0)] == 11); 417 xe[ e0 ] = 11; assert(xe[ e0 ] == 11); 418 419 // indexing with wrong type 420 static assert(!__traits(compiles, { xb[J(0)] = 11; })); 421 static assert(!__traits(compiles, { xi[J(0)] = 11; })); 422 static assert(!__traits(compiles, { xj[ 0 ] = 11; })); 423 static assert(!__traits(compiles, { xe[ 0 ] = 11; })); 424 static assert(!__traits(compiles, { xs[ 0 ] = 11; })); 425 static assert(!__traits(compiles, { xs_[ 0 ] = 11; })); 426 427 import std.algorithm.comparison : equal; 428 import std.algorithm.iteration : filter; 429 430 assert(equal(xb[].filter!(a => a < 11), [2, 3])); 431 assert(equal(xi[].filter!(a => a < 11), [2, 3])); 432 assert(equal(xj[].filter!(a => a < 11), [2, 3])); 433 assert(equal(xe[].filter!(a => a < 11), [2, 3])); 434 // assert(equal(xs[].filter!(a => a < 11), [2, 3])); 435 } 436 } 437 438 /// 439 @safe pure nothrow unittest 440 { 441 auto x = [1, 2, 3]; 442 443 // sample index 444 struct Index(T = size_t) 445 if (isUnsigned!T) 446 { 447 this(T ix) { this._ix = ix; } 448 T opCast(U : T)() const { return _ix; } 449 private T _ix = 0; 450 } 451 alias J = Index!size_t; 452 453 enum E { e0, e1, e2 } 454 455 with (E) 456 { 457 auto xb = x.indexedBy!ubyte; 458 auto xi = x.indexedBy!uint; 459 auto xj = x.indexedBy!J; 460 auto xe = x.indexedBy!E; 461 462 // indexing with correct type 463 xb[ 0 ] = 11; assert(xb[ 0 ] == 11); 464 xi[ 0 ] = 11; assert(xi[ 0 ] == 11); 465 xj[J(0)] = 11; assert(xj[J(0)] == 11); 466 xe[ e0 ] = 11; assert(xe[ e0 ] == 11); 467 468 // slicing with correct type 469 xb[ 0 .. 1 ] = 12; assert(xb[ 0 .. 1 ] == [12]); 470 xi[ 0 .. 1 ] = 12; assert(xi[ 0 .. 1 ] == [12]); 471 xj[J(0) .. J(1)] = 12; assert(xj[J(0) .. J(1)] == [12]); 472 xe[ e0 .. e1 ] = 12; assert(xe[ e0 .. e1 ] == [12]); 473 474 // indexing with wrong type 475 static assert(!__traits(compiles, { xb[J(0)] = 11; })); 476 static assert(!__traits(compiles, { xi[J(0)] = 11; })); 477 static assert(!__traits(compiles, { xj[ 0 ] = 11; })); 478 static assert(!__traits(compiles, { xe[ 0 ] = 11; })); 479 480 // slicing with wrong type 481 static assert(!__traits(compiles, { xb[J(0) .. J(0)] = 11; })); 482 static assert(!__traits(compiles, { xi[J(0) .. J(0)] = 11; })); 483 static assert(!__traits(compiles, { xj[ 0 .. 0 ] = 11; })); 484 static assert(!__traits(compiles, { xe[ 0 .. 0 ] = 11; })); 485 486 import std.algorithm.comparison : equal; 487 import std.algorithm.iteration : filter; 488 489 assert(equal(xb.filter!(a => a < 11), [2, 3])); 490 assert(equal(xi.filter!(a => a < 11), [2, 3])); 491 assert(equal(xj.filter!(a => a < 11), [2, 3])); 492 assert(equal(xe.filter!(a => a < 11), [2, 3])); 493 } 494 } 495 496 /// 497 @safe pure nothrow unittest 498 { 499 auto x = [1, 2, 3]; 500 struct I(T = size_t) 501 { 502 this(T ix) { this._ix = ix; } 503 T opCast(U : T)() const { return _ix; } 504 private T _ix = 0; 505 } 506 alias J = I!size_t; 507 auto xj = x.indexedBy!J; 508 } 509 510 /// 511 @safe pure nothrow unittest 512 { 513 auto x = [1, 2, 3]; 514 struct I(T = size_t) 515 { 516 private T _ix = 0; 517 } 518 alias J = I!size_t; 519 static assert(!__traits(compiles, { auto xj = x.indexedBy!J; })); 520 } 521 522 /// 523 version(none) 524 @safe pure nothrow unittest 525 { 526 auto x = [1, 2, 3]; 527 import nxt.bound : Bound; 528 alias B = Bound!(ubyte, 0, 2); 529 B b; 530 auto c = cast(size_t)b; 531 auto y = x.indexedBy!B; 532 } 533 534 /// 535 @safe pure nothrow unittest 536 { 537 import nxt.container.dynamic_array : Array = DynamicArray; 538 539 enum Lang { en, sv, fr } 540 541 alias Ixs = Array!int; 542 543 struct S 544 { 545 Lang lang; 546 string data; 547 Ixs ixs; 548 } 549 550 alias A = Array!S; 551 552 struct I 553 { 554 size_t opCast(U : size_t)() const @safe pure nothrow @nogc { return _ix; } 555 uint _ix; 556 alias _ix this; 557 } 558 559 I i; 560 static assert(isCastableTo!(I, size_t)); 561 static assert(isIndexableBy!(A, I)); 562 563 alias IA = IndexedBy!(A, I); 564 IA ia; 565 ia ~= S.init; 566 assert(ia.length == 1); 567 auto s = S(Lang.en, "alpha", Ixs.withLength(42)); 568 569 import core.lifetime : move; 570 571 ia ~= move(s); 572 assert(ia.length == 2); 573 } 574 575 /** Returns: a `string` containing the definition of an `enum` named `name` and 576 with enumerator names given by `Es`, optionally prepended with `prefix` and 577 appended with `suffix`. 578 579 TODO: Move to Phobos std.typecons 580 */ 581 template makeEnumFromSymbolNames(string prefix = `__`, 582 string suffix = ``, 583 bool firstUndefined = true, 584 bool useMangleOf = false, 585 Es...) 586 if (Es.length != 0) 587 { 588 enum members = 589 { 590 string s = firstUndefined ? `undefined, ` : ``; 591 foreach (E; Es) 592 { 593 static if (useMangleOf) 594 { 595 enum E_ = E.mangleof; 596 } 597 else 598 { 599 import std.traits : isPointer; 600 static if (isPointer!E) 601 { 602 import std.traits : TemplateOf; 603 enum isTemplateInstance = is(typeof(TemplateOf!(typeof(*E.init)))); 604 static if (isTemplateInstance) // strip template params for now 605 { 606 enum E_ = __traits(identifier, TemplateOf!(typeof(*E))) ~ `Ptr`; 607 } 608 else 609 { 610 enum E_ = typeof(*E.init).stringof ~ `Ptr`; 611 } 612 } 613 else 614 { 615 enum E_ = E.stringof; 616 } 617 618 } 619 s ~= prefix ~ E_ ~ suffix ~ `, `; 620 } 621 return s; 622 }(); 623 mixin("enum makeEnumFromSymbolNames : ubyte {" ~ members ~ "}"); 624 } 625 626 /// 627 @safe pure nothrow @nogc unittest 628 { 629 import std.meta : AliasSeq; 630 struct E(T) { T x; } 631 alias Types = AliasSeq!(byte, short, int*, E!int*); 632 alias Type = makeEnumFromSymbolNames!(`_`, `_`, true, false, Types); 633 static assert(is(Type == enum)); 634 static assert(Type.undefined.stringof == `undefined`); 635 static assert(Type._byte_.stringof == `_byte_`); 636 static assert(Type._short_.stringof == `_short_`); 637 static assert(Type._intPtr_.stringof == `_intPtr_`); 638 static assert(Type._EPtr_.stringof == `_EPtr_`); 639 } 640 641 /// 642 @safe pure nothrow @nogc unittest 643 { 644 import std.meta : AliasSeq; 645 646 struct E(T) { T x; } 647 648 alias Types = AliasSeq!(byte, short, int*, E!int*); 649 alias Type = makeEnumFromSymbolNames!(`_`, `_`, true, true, Types); 650 651 static assert(is(Type == enum)); 652 653 static assert(Type.undefined.stringof == `undefined`); 654 static assert(Type._g_.stringof == `_g_`); 655 static assert(Type._s_.stringof == `_s_`); 656 static assert(Type._Pi_.stringof == `_Pi_`); 657 } 658 659 /** 660 See_Also: https://p0nce.github.io/d-idioms/#Rvalue-references:-Understanding-auto-ref-and-then-not-using-it 661 */ 662 mixin template RvalueRef() 663 { 664 alias T = typeof(this); // typeof(this) get us the type we're in 665 static assert (is(T == struct)); 666 667 @nogc @safe 668 ref inout(T) asRef() inout pure nothrow return 669 { 670 return this; 671 } 672 } 673 674 @safe @nogc pure nothrow unittest 675 { 676 static struct Vec 677 { 678 @safe @nogc pure nothrow: 679 float x, y; 680 this(float x, float y) pure nothrow 681 { 682 this.x = x; 683 this.y = y; 684 } 685 mixin RvalueRef; 686 } 687 688 static void foo(ref const Vec pos) 689 { 690 } 691 692 Vec v = Vec(42, 23); 693 foo(v); // works 694 foo(Vec(42, 23).asRef); // works as well, and use the same function 695 } 696 697 /** Convert enum value `v` to `string`. 698 See also http://forum.dlang.org/post/aqqhlbaepoimpopvouwv@forum.dlang.org 699 */ 700 string enumToString(E)(E v) 701 { 702 static assert(is(E == enum), "emumToString is only meant for enums"); 703 switch(v) 704 { 705 foreach (m; __traits(allMembers, E)) 706 { 707 case mixin("E." ~ m) : return m; 708 } 709 default: 710 { 711 string result = "cast(" ~ E.stringof ~ ")"; 712 uint val = v; 713 714 enum headLength = E.stringof.length + "cast()".length; 715 uint log10Val = (val < 10) ? 0 : (val < 100) ? 1 : (val < 1_000) ? 2 : 716 (val < 10_000) ? 3 : (val < 100_000) ? 4 : (val < 1_000_000) ? 5 : 717 (val < 10_000_000) ? 6 : (val < 100_000_000) ? 7 : (val < 1000_000_000) ? 8 : 9; 718 719 result.length += log10Val + 1; 720 721 foreach (uint i; 0 .. log10Val + 1) 722 { 723 cast(char)result[headLength + log10Val - i] = cast(char) ('0' + (val % 10)); 724 val /= 10; 725 } 726 return cast(string) result; 727 } 728 } 729 } 730 731 @safe pure nothrow unittest 732 { 733 enum ET { one, two } 734 // static assert(to!string(ET.one) == "one"); 735 static assert (enumToString(ET.one) == "one"); 736 assert (enumToString(ET.one) == "one"); 737 }