1 module nxt.filters; 2 3 /// Growable flag. 4 enum Growable { no, yes } 5 6 /// Copyable flag. 7 enum Copyable { no, yes } 8 9 enum isDynamicDenseSetFilterable(E) = (__traits(isUnsigned, E) 10 /* && cast(size_t)E.max <= uint.max */ ); // and small enough 11 12 /** Store elements of type `E` in a set in the range `0 .. length`. 13 * 14 * Can be seen as a generalization of `std.typecons.BitFlags` to integer types. 15 * 16 * Typically used to implement very fast membership checking. For instance in 17 * graph traversal algorithms, this filter is typically used as a temporary set 18 * node (integer) ids that checks if a node has been previously visisted or not. 19 */ 20 struct DynamicDenseSetFilter(E, 21 /+ TODO: make these use the Flags template +/ 22 Growable growable = Growable.yes, 23 Copyable copyable = Copyable.no) 24 if (isDynamicDenseSetFilterable!E) 25 { 26 import core.memory : malloc = pureMalloc, calloc = pureCalloc, realloc = pureRealloc; 27 import core.bitop : bts, btr, btc, bt; 28 import nxt.qcmeman : free; 29 30 alias ElementType = E; 31 32 pure nothrow @safe @nogc: 33 34 static if (growable == Growable.no) 35 { 36 /// Maximum number of elements in filter. 37 enum elementMaxCount = cast(size_t)E.max + 1; 38 39 /// Construct from inferred capacity and length `elementMaxCount`. 40 static typeof(this) withInferredLength() 41 { 42 version (LDC) pragma(inline, true); 43 return typeof(return)(elementMaxCount); 44 } 45 } 46 47 /// Construct set to store E-values in the range `[0 .. length[`. 48 this(in size_t length) @trusted 49 { 50 _blocksPtr = null; 51 static if (growable == Growable.yes) 52 { 53 _length = length; 54 _capacity = 0; 55 assureCapacity(length); 56 } 57 else 58 { 59 _capacity = length; 60 _blocksPtr = cast(Block*)calloc(blockCount, Block.sizeof); 61 } 62 } 63 64 pragma(inline, true) 65 ~this() nothrow @nogc => release(); 66 67 /// Free storage. 68 pragma(inline, true) 69 private void release() @trusted @nogc => free(_blocksPtr); 70 71 /// Clear contents. 72 void clear()() @nogc 73 { 74 version (D_Coverage) {} else pragma(inline, true); 75 release(); 76 _blocksPtr = null; 77 static if (growable == Growable.yes) 78 _length = 0; 79 _capacity = 0; 80 } 81 82 static if (copyable) 83 { 84 this(this) @trusted 85 { 86 Block* srcBlocksPtr = _blocksPtr; 87 _blocksPtr = cast(Block*)malloc(blockCount * Block.sizeof); 88 _blocksPtr[0 .. blockCount] = srcBlocksPtr[0 .. blockCount]; 89 } 90 } 91 else 92 { 93 this(this) @disable; 94 95 /// Returns: shallow (and deep) duplicate of `this`. 96 typeof(this) dup()() @trusted /*tlm*/ 97 { 98 typeof(return) copy; 99 static if (growable == Growable.yes) 100 copy._length = this._length; 101 copy._capacity = this._capacity; 102 copy._blocksPtr = cast(Block*)malloc(blockCount * Block.sizeof); 103 copy._blocksPtr[0 .. blockCount] = this._blocksPtr[0 .. blockCount]; 104 return copy; 105 } 106 } 107 108 @property: 109 110 static if (growable == Growable.yes) 111 { 112 /// Assure capacity is at least `newLength`. 113 private void assureCapacity()(in size_t newCapacity) @trusted /*tlm*/ 114 { 115 if (_capacity < newCapacity) 116 { 117 const oldBlockCount = blockCount; 118 import std.math.algebraic : nextPow2; 119 _capacity = newCapacity.nextPow2; 120 _blocksPtr = cast(Block*)realloc(_blocksPtr, 121 blockCount * Block.sizeof); 122 _blocksPtr[oldBlockCount .. blockCount] = 0; 123 } 124 } 125 126 /// Assure length (and capacity) is at least `newLength`. 127 private void assureLength(size_t newLength) @trusted /*tlm*/ 128 { 129 assureCapacity(newLength); 130 if (_length < newLength) 131 _length = newLength; 132 } 133 } 134 135 /** Insert element `e`. 136 * 137 * Returns: precense status of element before insertion. 138 */ 139 bool insert()(in E e) @trusted /*tlm*/ 140 { 141 version (D_Coverage) {} else pragma(inline, true); 142 const ix = cast(size_t)e; 143 static if (growable == Growable.yes) 144 assureLength(ix + 1); 145 else 146 assert(ix < _capacity); 147 return bts(_blocksPtr, ix) != 0; 148 } 149 alias put = insert; // OutputRange compatibility 150 151 /** Remove element `e`. 152 * 153 * Returns: precense status of element before removal. 154 */ 155 bool remove()(in E e) @trusted /*tlm*/ 156 { 157 version (D_Coverage) {} else pragma(inline, true); 158 const ix = cast(size_t)e; 159 static if (growable == Growable.yes) 160 assureLength(ix + 1); 161 else 162 assert(ix < _capacity); 163 return btr(_blocksPtr, ix) != 0; 164 } 165 166 /** Insert element `e` if it's present otherwise remove it. 167 * 168 * Returns: `true` if elements was zeroed, `false` otherwise. 169 */ 170 bool complement()(in E e) @trusted // template-laze 171 { 172 version (D_Coverage) {} else pragma(inline, true); 173 const ix = cast(size_t)e; 174 static if (growable == Growable.yes) 175 assureLength(ix + 1); 176 else 177 assert(ix < _capacity); 178 return btc(_blocksPtr, ix) != 0; 179 } 180 181 /// Check if element `e` is stored/contained. 182 bool contains()(in E e) @trusted const /*tlm*/ 183 { 184 version (D_Coverage) {} else pragma(inline, true); 185 const ix = cast(size_t)e; 186 static if (growable == Growable.yes) 187 return ix < _length && bt(_blocksPtr, ix) != 0; 188 else 189 return ix < _capacity && bt(_blocksPtr, ix) != 0; 190 } 191 /// ditto 192 pragma(inline, true) 193 bool opBinaryRight(string op)(in E e) const if (op == "in") 194 => contains(e); 195 196 /// ditto 197 typeof(this) opBinary(string op)(in typeof(this) e) const 198 if (op == "|" || op == "&" || op == "^") 199 { 200 typeof(return) result; 201 mixin(`result._blocks[] = _blocks[] ` ~ op ~ ` e._blocks[];`); 202 return result; 203 } 204 205 /** Get current capacity in number of elements (bits). 206 If `growable` is `Growable.yes` then capacity is variable, otherwise it's constant. 207 */ 208 pragma(inline, true) 209 @property size_t capacity() const 210 => _capacity; 211 212 private: 213 pragma(inline, true) 214 @property size_t blockCount() const 215 => _capacity / Block.sizeof + (_capacity % Block.sizeof ? 1 : 0); 216 217 alias Block = size_t; /// Allocated block type. 218 Block* _blocksPtr; /// Pointer to blocks of bits. 219 static if (growable == Growable.yes) 220 size_t _length; /// Offset + 1 of highest set bit. 221 size_t _capacity; /// Number of bits allocated. 222 } 223 224 /// 225 pure nothrow @safe @nogc unittest { 226 alias E = uint; 227 228 import std.range.primitives : isOutputRange; 229 alias Set = DynamicDenseSetFilter!(E, Growable.no); 230 static assert(isOutputRange!(Set, E)); 231 232 const set0 = Set(); 233 assert(set0.capacity == 0); 234 235 const length = 2^^6; 236 auto set = DynamicDenseSetFilter!(E, Growable.no)(2*length); 237 const y = set.dup; 238 assert(y.capacity == 2*length); 239 240 foreach (const ix; 0 .. length) 241 { 242 assert(!set.contains(ix)); 243 assert(ix !in set); 244 245 assert(!set.insert(ix)); 246 assert(set.contains(ix)); 247 assert(ix in set); 248 249 assert(set.complement(ix)); 250 assert(!set.contains(ix)); 251 assert(ix !in set); 252 253 assert(!set.complement(ix)); 254 assert(set.contains(ix)); 255 assert(ix in set); 256 257 assert(!set.contains(ix + 1)); 258 } 259 260 auto z = set.dup; 261 foreach (const ix; 0 .. length) 262 { 263 assert(z.contains(ix)); 264 assert(ix in z); 265 } 266 267 foreach (const ix; 0 .. length) 268 { 269 assert(set.contains(ix)); 270 assert(ix in set); 271 } 272 273 foreach (const ix; 0 .. length) 274 { 275 assert(set.contains(ix)); 276 set.remove(ix); 277 assert(!set.contains(ix)); 278 } 279 } 280 281 /// 282 pure nothrow @safe @nogc unittest { 283 alias E = uint; 284 285 auto set = DynamicDenseSetFilter!(E, Growable.yes)(); 286 assert(set._length == 0); 287 288 const length = 2^^16; 289 foreach (const ix; 0 .. length) 290 { 291 assert(!set.contains(ix)); 292 assert(ix !in set); 293 294 assert(!set.insert(ix)); 295 assert(set.contains(ix)); 296 assert(ix in set); 297 298 assert(set.complement(ix)); 299 assert(!set.contains(ix)); 300 assert(ix !in set); 301 302 assert(!set.complement(ix)); 303 assert(set.contains(ix)); 304 assert(ix in set); 305 306 assert(!set.contains(ix + 1)); 307 } 308 } 309 310 /// test `RefCounted` storage 311 nothrow @nogc unittest /+ TODO: pure when https://github.com/dlang/phobos/pull/4692/files has been merged +/ 312 { 313 import std.typecons : RefCounted; 314 alias E = uint; 315 316 RefCounted!(DynamicDenseSetFilter!(E, Growable.yes)) set; 317 318 assert(set._length == 0); 319 assert(set.capacity == 0); 320 321 assert(!set.insert(0)); 322 assert(set._length == 1); 323 assert(set.capacity == 2); 324 325 const y = set; 326 327 foreach (const e; 1 .. 1000) 328 { 329 assert(!set.insert(e)); 330 assert(set._length == e + 1); 331 assert(y._length == e + 1); 332 } 333 334 const set1 = RefCounted!(DynamicDenseSetFilter!(E, Growable.yes))(42); 335 assert(set1._length == 42); 336 assert(set1.capacity == 64); 337 } 338 339 /// 340 pure nothrow @safe @nogc unittest { 341 enum E : ubyte { a, b, c, d, dAlias = d } 342 343 auto set = DynamicDenseSetFilter!(E, Growable.yes)(); 344 345 assert(set._length == 0); 346 347 import std.traits : EnumMembers; 348 foreach (const lang; [EnumMembers!E]) 349 assert(!set.contains(lang)); 350 foreach (const lang; [EnumMembers!E]) 351 { 352 set.insert(lang); 353 assert(set.contains(lang)); 354 } 355 356 } 357 358 /// 359 pure nothrow @safe @nogc unittest { 360 enum E : ubyte { a, b, c, d, dAlias = d } 361 362 auto set = DynamicDenseSetFilter!(E, Growable.no).withInferredLength(); /+ TODO: use instantiator function here +/ 363 assert(set.capacity == typeof(set).elementMaxCount); 364 365 static assert(!__traits(compiles, { assert(set.contains(0)); })); 366 static assert(!__traits(compiles, { assert(set.insert(0)); })); 367 static assert(!__traits(compiles, { assert(0 in set); })); 368 369 import std.traits : EnumMembers; 370 foreach (const lang; [EnumMembers!E]) 371 { 372 assert(!set.contains(lang)); 373 assert(lang !in set); 374 } 375 foreach (const lang; [EnumMembers!E]) 376 { 377 set.insert(lang); 378 assert(set.contains(lang)); 379 assert(lang in set); 380 } 381 382 } 383 384 /** Check if `E` is filterable in `StaticDenseSetFilter`, that is castable to 385 `uint` and castable from unsigned int zero. 386 */ 387 template isStaticDenseFilterableType(E) 388 { 389 static if (is(E == enum)) 390 enum isStaticDenseFilterableType = true; 391 else 392 { 393 import std.traits : hasIndirections, isUnsigned, isSomeChar; 394 static if (isUnsigned!E || 395 isSomeChar!E) 396 enum isStaticDenseFilterableType = true; 397 else static if (is(typeof(E.init.toUnsigned))) 398 { 399 alias UnsignedType = typeof(E.init.toUnsigned()); 400 enum isStaticDenseFilterableType = (isUnsigned!UnsignedType && 401 is(typeof(E.fromUnsigned(UnsignedType.init))) && 402 !hasIndirections!E); 403 } 404 else 405 enum isStaticDenseFilterableType = false; 406 } 407 } 408 409 pure nothrow @safe @nogc unittest { 410 static assert(isStaticDenseFilterableType!uint); 411 static assert(!isStaticDenseFilterableType!string); 412 static assert(isStaticDenseFilterableType!char); 413 static assert(!isStaticDenseFilterableType!(char*)); 414 415 enum E { a, b } 416 static assert(isStaticDenseFilterableType!E); 417 } 418 419 /** Store presence of elements of type `E` in a set in the range `0 .. length`. 420 * 421 * Can be seen as a generalization of `std.typecons.BitFlags` to integer types. 422 * 423 * Typically used to implement very fast membership checking in sets of 424 * enumerators. 425 * 426 * TODO: Add operators for bitwise `and` and `or` operations similar to 427 * https://dlang.org/library/std/typecons/bit_flags.html 428 */ 429 struct StaticDenseSetFilter(E, bool requestPacked = true) 430 if (isStaticDenseFilterableType!E) 431 { 432 import std.range.primitives : StdElementType = ElementType; 433 import std.traits : isIterable, isAssignable, isUnsigned; 434 import core.bitop : bts, btr, btc, bt; 435 436 alias ElementType = E; 437 alias This = typeof(this); 438 439 @safe pure: 440 441 string toString() const @property @trusted 442 { 443 import std.array : Appender; 444 import std.conv : to; 445 446 Appender!(typeof(return)) str = This.stringof ~ "(["; 447 bool other = false; 448 449 void putElement(in E e) 450 { 451 version (LDC) pragma(inline, true); 452 if (contains(e)) 453 { 454 if (other) 455 str.put(", "); 456 else 457 other = true; 458 str.put(e.to!string); 459 } 460 } 461 462 static if (is(E == enum)) 463 { 464 import std.traits : EnumMembers; 465 foreach (const e; [EnumMembers!E]) 466 putElement(e); 467 } 468 else 469 { 470 foreach (const i; E.unsignedMin .. E.unsignedMax + 1) 471 putElement(E.fromUnsigned(i)); 472 } 473 474 str.put("])"); 475 return str.data; 476 } 477 478 nothrow @nogc: 479 480 /** Construct from elements `r`. 481 */ 482 private this(R)(R r) 483 if (isIterable!R && 484 isAssignable!(E, StdElementType!R)) 485 { 486 foreach (const ref e; r) 487 insert(e); 488 } 489 490 /** Construct from `r` if `r` is non-empty, otherwise construct a full set. 491 */ 492 static typeof(this) withValuesOrFull(R)(R r) 493 if (isIterable!R && 494 isAssignable!(E, StdElementType!R)) 495 { 496 import std.range.primitives : empty; 497 if (r.empty) 498 return asFull(); 499 return typeof(return)(r); 500 } 501 502 pragma(inline, true): 503 504 /// Construct a full set . 505 static typeof(this) asFull()() /*tlm*/ 506 { 507 typeof(return) that = void; 508 that._blocks[] = Block.max; 509 return that; 510 } 511 512 /** Insert element `e`. 513 */ 514 void insert()(in E e) @trusted /*tlm*/ 515 { 516 import nxt.bitop_ex : setBit; 517 static if (isPackedInScalar) 518 _blocks[0].setBit(cast(size_t)e); 519 else 520 bts(_blocksPtr, cast(size_t)e); 521 } 522 alias put = insert; // OutputRange compatibility 523 524 /** Remove element `e`. 525 */ 526 void remove()(in E e) @trusted /*tlm*/ 527 { 528 import nxt.bitop_ex : resetBit; 529 static if (isPackedInScalar) 530 _blocks[0].resetBit(cast(size_t)e); 531 else 532 btr(_blocksPtr, cast(size_t)e); 533 } 534 535 @property: 536 537 /** Check if element `e` is present/stored/contained. 538 */ 539 bool contains()(in E e) @trusted const /*tlm*/ 540 { 541 import nxt.bitop_ex : testBit; 542 static if (isPackedInScalar) 543 return _blocks[0].testBit(cast(size_t)e); 544 else 545 return bt(_blocksPtr, cast(size_t)e) != 0; 546 } 547 548 /// ditto 549 bool opBinaryRight(string op)(in E e) const if (op == "in") 550 => contains(e); 551 552 /// ditto 553 typeof(this) opBinary(string op)(in typeof(this) e) const 554 if (op == "|" || op == "&" || op == "^") 555 { 556 typeof(return) result; 557 mixin(`result._blocks[] = _blocks[] ` ~ op ~ ` e._blocks[];`); 558 return result; 559 } 560 561 /// ditto 562 typeof(this) opOpAssign(string op)(in typeof(this) e) 563 if (op == "|" || op == "&" || op == "^") 564 { 565 version (DigitalMars) pragma(inline, false); 566 mixin(`_blocks[] ` ~ op ~ `= e._blocks[];`); 567 return this; 568 } 569 570 private: 571 static if (is(E == enum) || isUnsigned!E) // has normal 572 { 573 /// Maximum number of elements in filter. 574 enum elementMaxCount = E.max - E.min + 1; 575 } 576 else 577 { 578 /// Maximum number of elements in filter. 579 enum elementMaxCount = E.unsignedMax - E.unsignedMin + 1; 580 } 581 582 static if (requestPacked) 583 { 584 static if (elementMaxCount <= 8*ubyte.sizeof) 585 { 586 enum isPackedInScalar = true; 587 alias Block = ubyte; 588 } 589 else static if (elementMaxCount <= 8*ushort.sizeof) 590 { 591 enum isPackedInScalar = true; 592 alias Block = ushort; 593 } 594 else static if (elementMaxCount <= 8*uint.sizeof) 595 { 596 enum isPackedInScalar = true; 597 alias Block = uint; 598 } 599 else 600 { 601 enum isPackedInScalar = false; 602 alias Block = size_t; 603 } 604 } 605 else 606 { 607 enum isPackedInScalar = false; 608 alias Block = size_t; 609 } 610 611 /** Number of bits per `Block`. */ 612 enum bitsPerBlock = 8*Block.sizeof; 613 614 /** Number of `Block`s. */ 615 enum blockCount = (elementMaxCount + (bitsPerBlock-1)) / bitsPerBlock; 616 617 Block[blockCount] _blocks; /// Pointer to blocks of bits. 618 inout(Block)* _blocksPtr() @trusted inout 619 => _blocks.ptr; 620 } 621 622 version (unittest) 623 { 624 import std.traits : EnumMembers; 625 } 626 627 /// 628 pure nothrow @safe @nogc unittest { 629 enum E : ubyte { a, b, c, d, dAlias = d } 630 631 auto set = StaticDenseSetFilter!(E)(); 632 static assert(set.sizeof == 1); 633 634 static assert(!__traits(compiles, { assert(set.contains(0)); })); 635 static assert(!__traits(compiles, { assert(set.insert(0)); })); 636 static assert(!__traits(compiles, { assert(0 in set); })); 637 638 // initially empty 639 foreach (const lang; [EnumMembers!E]) 640 { 641 assert(!set.contains(lang)); 642 assert(lang !in set); 643 } 644 645 // insert 646 foreach (const lang; [EnumMembers!E]) 647 { 648 set.insert(lang); 649 assert(set.contains(lang)); 650 assert(lang in set); 651 } 652 653 // remove 654 foreach (const lang; [EnumMembers!E]) 655 { 656 set.remove(lang); 657 assert(!set.contains(lang)); 658 assert(lang !in set); 659 } 660 } 661 662 /// assignment from range 663 pure nothrow @safe @nogc unittest { 664 enum E : ubyte { a, b, c, d, dAlias = d } 665 666 const E[2] es = [E.a, E.c]; 667 auto set = StaticDenseSetFilter!(E)(es[]); 668 static assert(set.sizeof == 1); 669 670 foreach (const ref e; es) 671 { 672 assert(set.contains(e)); 673 } 674 } 675 676 /// assignment from range 677 pure nothrow @safe @nogc unittest { 678 enum E : ubyte { a, b, c, d } 679 680 const E[] es = []; 681 auto set = StaticDenseSetFilter!(E).withValuesOrFull(es[]); 682 static assert(set.sizeof == 1); 683 684 foreach (const e; [EnumMembers!E]) 685 { 686 assert(set.contains(e)); 687 assert(e in set); 688 set.remove(e); 689 assert(!set.contains(e)); 690 assert(e !in set); 691 } 692 } 693 694 /// assignment from range 695 pure nothrow @safe @nogc unittest { 696 enum E : ubyte { a, b, c, d } 697 698 const E[2] es = [E.a, E.c]; 699 auto set = StaticDenseSetFilter!(E).withValuesOrFull(es[]); 700 static assert(set.sizeof == 1); 701 702 foreach (const ref e; es) 703 { 704 assert(set.contains(e)); 705 set.remove(e); 706 assert(!set.contains(e)); 707 } 708 } 709 710 /// assignment from range 711 pure nothrow @safe @nogc unittest { 712 enum E : ubyte { a, b, c, d } 713 714 auto set = StaticDenseSetFilter!(E).asFull; 715 static assert(set.sizeof == 1); 716 717 foreach (const e; [EnumMembers!E]) 718 { 719 assert(set.contains(e)); 720 set.remove(e); 721 assert(!set.contains(e)); 722 } 723 } 724 725 /// assignment from range 726 pure nothrow @safe @nogc unittest { 727 enum E : ubyte 728 { 729 a, b, c, d, e, f, g, h, 730 } 731 732 auto set = StaticDenseSetFilter!(E).asFull; 733 static assert(set.sizeof == 1); 734 static assert(set.isPackedInScalar); 735 736 foreach (const e; [EnumMembers!E]) 737 { 738 assert(set.contains(e)); 739 set.remove(e); 740 assert(!set.contains(e)); 741 } 742 } 743 744 /// assignment from range 745 pure nothrow @safe @nogc unittest { 746 enum E : ubyte 747 { 748 a, b, c, d, e, f, g, h, 749 i, 750 } 751 752 auto set = StaticDenseSetFilter!(E).asFull; 753 static assert(set.sizeof == 2); 754 static assert(set.isPackedInScalar); 755 756 foreach (const e; [EnumMembers!E]) 757 { 758 assert(set.contains(e)); 759 set.remove(e); 760 assert(!set.contains(e)); 761 } 762 } 763 764 /// assignment from range 765 pure nothrow @safe @nogc unittest { 766 enum E : ubyte 767 { 768 a, b, c, d, e, f, g, h, 769 i, j, k, l, m, n, o, p, 770 } 771 772 auto set = StaticDenseSetFilter!(E).asFull; 773 static assert(set.sizeof == 2); 774 static assert(set.isPackedInScalar); 775 776 foreach (const e; [EnumMembers!E]) 777 { 778 assert(set.contains(e)); 779 set.remove(e); 780 assert(!set.contains(e)); 781 } 782 } 783 784 /// assignment from range 785 pure nothrow @safe @nogc unittest { 786 enum E : ubyte 787 { 788 a, b, c, d, e, f, g, h, 789 i, j, k, l, m, n, o, p, 790 r, 791 } 792 793 auto set = StaticDenseSetFilter!(E).asFull; 794 static assert(set.sizeof == 4); 795 static assert(set.isPackedInScalar); 796 797 foreach (const e; [EnumMembers!E]) 798 { 799 assert(set.contains(e)); 800 set.remove(e); 801 assert(!set.contains(e)); 802 } 803 } 804 805 /// assignment from range 806 pure nothrow @safe @nogc unittest { 807 enum E : ubyte 808 { 809 a, b, c, d, e, f, g, h, 810 i, j, k, l, m, n, o, p, 811 r, s, t, u, v, w, x, y, 812 z, a1, a2, a3, a4, a5, a6, a7, 813 a8 814 } 815 816 auto set = StaticDenseSetFilter!(E).asFull; 817 static assert(set.sizeof == 8); 818 static assert(!set.isPackedInScalar); 819 820 foreach (const e; [EnumMembers!E]) 821 { 822 assert(set.contains(e)); 823 set.remove(e); 824 assert(!set.contains(e)); 825 } 826 } 827 828 version (unittest) 829 { 830 enum Rel : ubyte { unknown, subClassOf, instanceOf, memberOf } 831 import nxt.bit_traits : packedBitSizeOf; 832 static assert(packedBitSizeOf!Rel == 2); 833 834 struct Role 835 { 836 Rel rel; 837 bool reversion; 838 839 enum unsignedMin = 0; 840 enum unsignedMax = 2^^(1 + // reversion 841 packedBitSizeOf!Rel // relation 842 ) - 1; 843 844 alias UnsignedType = ushort; 845 static assert(typeof(this).sizeof == UnsignedType.sizeof); 846 static assert(Role.sizeof == 2); 847 848 pragma(inline, true) 849 pure nothrow @safe @nogc: 850 851 /// Create from `UnsignedType` `u`. 852 static typeof(this) fromUnsigned(in UnsignedType u) @trusted 853 { 854 typeof(return) that; 855 that.reversion = (u >> 0) & 1; 856 that.rel = cast(Rel)(u >> 1); 857 return that; 858 } 859 860 /// Convert to `UnsignedType` `u`. 861 UnsignedType toUnsigned() const @trusted 862 => cast(UnsignedType)((cast(UnsignedType)reversion << 0) | 863 (cast(UnsignedType)rel << 1)); 864 865 UnsignedType opCast(UnsignedType)() const @nogc 866 => toUnsigned; 867 } 868 869 static assert(is(typeof(Role.init.toUnsigned))); 870 static assert(is(typeof(Role.init.toUnsigned()) == Role.UnsignedType)); 871 static assert(isStaticDenseFilterableType!Role); 872 } 873 874 /// assignment from range 875 pure @safe unittest { 876 auto set = StaticDenseSetFilter!(Role)(); 877 static assert(set.sizeof == 1); 878 879 assert(set.toString == "StaticDenseSetFilter!(Role, true)([])"); 880 881 // inserts 882 foreach (const rel; [EnumMembers!Rel]) 883 { 884 assert(!set.contains(Role(rel))); 885 assert(!set.contains(Role(rel, true))); 886 887 set.insert(Role(rel)); 888 assert(set.contains(Role(rel))); 889 890 assert(!set.contains(Role(rel, true))); 891 set.insert(Role(rel, true)); 892 893 assert(set.contains(Role(rel))); 894 assert(set.contains(Role(rel, true))); 895 } 896 897 assert(set.toString == "StaticDenseSetFilter!(Role, true)([const(Role)(unknown, false), const(Role)(unknown, true), const(Role)(subClassOf, false), const(Role)(subClassOf, true), const(Role)(instanceOf, false), const(Role)(instanceOf, true), const(Role)(memberOf, false), const(Role)(memberOf, true)])"); 898 899 // removes 900 foreach (const rel; [EnumMembers!Rel]) 901 { 902 assert(set.contains(Role(rel))); 903 assert(set.contains(Role(rel, true))); 904 905 set.remove(Role(rel)); 906 assert(!set.contains(Role(rel))); 907 908 assert(set.contains(Role(rel, true))); 909 set.remove(Role(rel, true)); 910 911 assert(!set.contains(Role(rel))); 912 assert(!set.contains(Role(rel, true))); 913 } 914 915 assert(set.toString == "StaticDenseSetFilter!(Role, true)([])"); 916 917 auto fullSet = StaticDenseSetFilter!(Role).asFull; 918 919 foreach (const rel; [EnumMembers!Rel]) 920 { 921 assert(fullSet.contains(Role(rel))); 922 assert(fullSet.contains(Role(rel, true))); 923 } 924 925 auto emptySet = StaticDenseSetFilter!(Role)(); 926 927 foreach (const rel; [EnumMembers!Rel]) 928 { 929 assert(!emptySet.contains(Role(rel))); 930 assert(!emptySet.contains(Role(rel, true))); 931 } 932 } 933 934 /// set operations 935 pure nothrow @safe @nogc unittest { 936 import nxt.array_help : s; 937 938 enum E : ubyte { a, b, c, d, dAlias = d } 939 940 auto a = StaticDenseSetFilter!(E)([E.a].s[]); 941 auto b = StaticDenseSetFilter!(E)([E.b].s[]); 942 auto c = StaticDenseSetFilter!(E)([E.b].s[]); 943 944 auto a_or_b = StaticDenseSetFilter!(E)([E.a, E.b].s[]); 945 auto a_and_b = StaticDenseSetFilter!(E)(); 946 947 assert((a | b | c) == a_or_b); 948 assert((a & b) == a_and_b); 949 950 a |= b; 951 assert(a == a_or_b); 952 }