module flags; import std.typetuple; import std.array; pure: nothrow: @safe: private: template isValidFlagEnumBody( string s ) { enum isValidFlagEnumBody = __traits( compiles, { mixin( "enum foo { " ~ s ~ " }" ); } ); } unittest { assert( isValidFlagEnumBody!q{a} ); assert( isValidFlagEnumBody!q{a,b} ); assert( !isValidFlagEnumBody!q{a.} ); } template MakeEnum( string s ) { mixin( "enum MakeEnum { " ~ s ~ " }" ); } template parsedString( string s ) { alias TypeTuple!( __traits( allMembers, MakeEnum!s ) ) parsedString; } unittest { assert( parsedString!"a"[0] == "a" ); assert( parsedString!"a".length == 1 ); assert( parsedString!"a,b"[0] == "a" ); assert( parsedString!"a,b"[1] == "b" ); assert( parsedString!"a,b".length == 2 ); } /** Represents a set of flags. These can be manipulated via regular bitwise operators - |, &, and ^. Example: ---- alias Flags!q{ A, B } myFlags; myFlags a = myFlags.A | myFlags.B; assert( a & myFlags.A ); assert( a & myFlags.B ); ---- **/ public struct Flags( string s ) if ( isValidFlagEnumBody!s && parsedString!s.length <= 64 ) { private alias parsedString!s memberNames; static if ( memberNames.length <= 8 ) { private alias ubyte Representation; } else static if ( memberNames.length <= 16 ) { private alias ushort Representation; } else static if ( memberNames.length <= 32 ) { private alias uint Representation; } else static if ( memberNames.length <= 64 ) { private alias ulong Representation; } private Representation value; mixin template flagsMembers( int N ) { } mixin template flagsMembers( int N, string T, U... ) { mixin( "enum " ~ T ~ " = Flags( 1L << N );" ); mixin flagsMembers!( N + 1, U ); } mixin flagsMembers!( 0, memberNames ); private this( Representation value ) { this.value = value; } Flags opBinary( string op )( Flags other ) const if ( op == "|" || op == "^" || op == "&" ) { mixin( "return Flags( value " ~ op ~ " other.value );" ); } ref Flags opOpAssign( string op )( Flags other ) if ( op == "|" || op == "^" || op == "&" ) { mixin( "value " ~ op ~ "= other.value;" ); return this; } Representation opCast( T )( ) const if ( is( Representation : T ) ) { return value; } bool opCast( T : bool )( ) const { return value != 0; } @trusted string toString( ) const { string[] result; foreach (i, e; memberNames) { if (value & ( 1L << i ) ) { result ~= e; } } return "<" ~ result.join(", ") ~ ">"; } } unittest { // Test sizes of created types. alias Flags!"a" myFlags1; alias Flags!"a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p" myFlags2; alias Flags!"a1,b1,c1,d1,e1,f1,g1,h1,i1,j1,k1,l1,m1,n1,o1,p1,s1,a2,b2,c2,d2,e2,f2,g2,h2,i2,j2,k2,l2,m2,n2,o2" myFlags3; alias Flags!"a1,b1,c1,d1,e1,f1,g1,h1,i1,j1,k1,l1,m1,n1,o1,p1,s1,a2,b2,c2,d2,e2,f2,g2,h2,i2,j2,k2,l2,m2,n2,o2,p2,s2,a3,b3,c3,d3,e3,f3,g3,h3,i3,j3,k3,l3,m3,n3,o3,p3,s3,a4,b4,c4,d4,e4,f4,g4,h4,i4,j4,k4,l4,m4" myFlags4; assert( myFlags1.sizeof == 1 ); assert( myFlags2.sizeof == 2 ); assert( myFlags3.sizeof == 4 ); assert( myFlags4.sizeof == 8 ); // Test that valid types do compile, and invalid types not. assert( __traits( compiles, Flags!"a" ) ); assert( __traits( compiles, Flags!"a, b" ) ); assert( !__traits( compiles, Flags!"" ) ); assert( !__traits( compiles, Flags!"a." ) ); assert( !__traits( compiles, Flags!"a1,b1,c1,d1,e1,f1,g1,h1,i1,j1,k1,l1,m1,n1,o1,p1,s1,a2,b2,c2,d2,e2,f2,g2,h2,i2,j2,k2,l2,m2,n2,o2,p2,s2,a3,b3,c3,d3,e3,f3,g3,h3,i3,j3,k3,l3,m3,n3,o3,p3,s3,a4,b4,c4,d4,e4,f4,g4,h4,i4,j4,k4,l4,m4,n4" ) ); alias Flags!q{ a, b } testFlags; // Test that operators work. auto o = testFlags.a | testFlags.b; assert( o != testFlags.a ); assert( o != testFlags.b ); assert( o.value != 0 ); auto p = o ^ testFlags.a; assert( p == testFlags.b ); assert( p != testFlags.a ); auto q = o & testFlags.a; assert( q == testFlags.a ); assert( o != testFlags.b ); o &= testFlags.a; assert( o == testFlags.a ); p |= testFlags.a; assert( p == ( testFlags.a | testFlags.b ) ); q ^= testFlags.a; assert( q.value == 0 ); // Test toString. assert( testFlags.a.toString() == "" ); assert( testFlags.b.toString() == "" ); assert( ( testFlags.a | testFlags.b ).toString() == "" ); assert( testFlags().toString() == "<>" ); // Test casting. assert( !testFlags() ); assert( testFlags.a ); assert( testFlags.b ); assert( testFlags.a | testFlags.b ); assert( cast(bool)testFlags() == false ); assert( cast(bool)testFlags.a == true ); assert( cast(int)testFlags() == 0 ); assert( cast(int)testFlags.a != 0 ); // Test invalid comparisons. assert( !__traits( compiles, { testFlags.a == 1; } ) ); assert( !__traits( compiles, { testFlags.a == true; } ) ); // Test invalid conversions. assert( !__traits( compiles, { int i = testFlags.a; } ) ); assert( !__traits( compiles, { bool b = testFlags.a; } ) ); // Test operators with invalid types. assert( !__traits( compiles, { auto tmp = testFlags.b | myFlags1.a; } ) ); assert( !__traits( compiles, { auto tmp = testFlags.b | 1; } ) ); } void main( ) {}