1 /** 2 Provides some type magic. 3 4 Authors: Tony J. Hudgins 5 Copyright: Copyright © 2019, Tony J. Hudgins 6 License: MIT 7 */ 8 module dext.typecons; 9 10 import std.traits : isSomeString; 11 12 private string ucfirst( S )( S s ) if( isSomeString!S ) 13 { 14 if( !s.length ) return s; 15 16 import std..string : capitalize; 17 return s[0 .. 1].capitalize() ~ s[1 .. $]; 18 } 19 20 @system unittest 21 { 22 auto none = ""; 23 assert( none.ucfirst() == "", "none.ucfirst()" ); 24 25 auto single = "a"; 26 27 assert( single.ucfirst() == "A", "single.ucfirst()" ); 28 29 auto multi = "abc"; 30 assert( multi.ucfirst() == "Abc", "multi.ucfirst()" ); 31 } 32 33 /++ 34 Represents a set of flags built from an enum intended to be used at compile-time for configuring the behaviour 35 of templates, mixins, or other compile-time features. 36 37 Credit to Ethan Watson and his wonderful DConf 2019 talk for this idea. 38 39 Examples: 40 ----------------- 41 enum MyConfig : string 42 { 43 someOption = "do something cool, like a barrel roll", 44 lazyProcessing = "be as lazy as possible" 45 } 46 47 alias MyParams = Params!MyConfig; 48 49 struct SomeStruct( MyParams params = MyParams.init ) 50 { 51 static if( params.someOption ) 52 { 53 // do something cool... 54 } 55 56 static if( params.lazyProcessing ) 57 { 58 // ... 59 } 60 } 61 ----------------- 62 63 See_Also: https://dconf.org/2019/talks/watson.html 64 Authors: Tony J. Hudgins 65 Copyright: Copyright © 2019, Tony J. Hudgins 66 License: MIT 67 +/ 68 struct Params( T ) if( is( T == enum ) ) 69 { 70 import std.format : format; 71 72 private { 73 alias Self = typeof( this ); 74 alias members = __traits( allMembers, T ); 75 76 bool[members.length] flagSet; 77 78 enum size_t[size_t] hashToIndex = { 79 import std.traits : EnumMembers; 80 81 size_t[size_t] map; 82 83 static foreach( i, value; EnumMembers!T ) 84 map[value.hashOf()] = i; 85 86 return map; 87 }(); 88 } 89 90 static foreach( i, name; members ) 91 { 92 mixin( "bool %s() const pure nothrow @property { return this.flagSet[%s]; }".format( name, i ) ); 93 mixin( "static Self of%s() pure nothrow @property { return Self( T.%s ); }".format( name.ucfirst(), name ) ); 94 } 95 96 /++ 97 Constructs a new parameter set from all possible values. 98 99 Authors: Tony J. Hudgins 100 Copyright: Copyright © 2019, Tony J. Hudgins 101 License: MIT 102 +/ 103 static Self all() pure nothrow @property 104 { 105 import std.traits : EnumMembers; 106 return Self( EnumMembers!T ); 107 } 108 109 /++ 110 Constructs a new parameter set from the specified overrides. 111 112 All flags not present in this constructor will default to [false]. 113 114 Params: 115 flags = A variadic list of flags to override. 116 117 Authors: Tony J. Hudgins 118 Copyright: Copyright © 2019, Tony J. Hudgins 119 License: MIT 120 +/ 121 this( inout( T )[] flags... ) 122 { 123 foreach( item; flags ) 124 { 125 auto idx = Self.hashToIndex[item.hashOf()]; 126 this.flagSet[idx] = true; 127 } 128 } 129 130 /++ 131 Copy constructor. 132 133 Params: 134 other = The object to copy from. 135 136 Authors: Tony J. Hudgins 137 Copyright: Copyright © 2019, Tony J. Hudgins 138 License: MIT 139 +/ 140 this( ref return scope inout Self other ) 141 { 142 this.flagSet[] = other.flagSet[]; 143 } 144 145 bool opBinary( string op )( inout T rhs ) inout if( op == "&" ) 146 { 147 auto idx = Self.hashToIndex[rhs.hashOf()]; 148 return this.flagSet[idx]; 149 } 150 151 bool opBinary( string op )( inout Self rhs ) inout if( op == "&" ) 152 { 153 return this.flagSet == rhs.flagSet; 154 } 155 156 Self opBinary( string op )( inout T rhs ) if( op == "|" || op == "^" ) 157 { 158 auto copy = this; 159 auto idx = Self.hashToIndex[rhs.hashOf()]; 160 161 static if( op == "|" ) copy.flagSet[idx] = true; 162 else static if( op == "^" ) copy.flagSet[idx] = false; 163 else static assert( false, op ~ " is unsupported" ); 164 165 return copy; 166 } 167 168 Self opBinary( string opt )( inout Self rhs ) if( op == "|" || op == "^" ) 169 { 170 auto copy = this; 171 172 foreach( i, x; rhs.flagSet ) 173 static if( op == "|" ) { if( x ) copy.flagSet[i] = true; } 174 else static if( op == "^" ) { if( x ) copy.flagSet[i] = false; } 175 else static assert( false, op ~ " is unsupported" ); 176 177 return copy; 178 } 179 180 bool opBinaryRight( string op )( inout T lhs ) if( op == "&" ) 181 { 182 return this.opBinary!( op )( lhs ); 183 } 184 185 Self opBinaryRight( string op )( inout T lhs ) if( op == "|" || op == "^" ) 186 { 187 return this.opBinary!( op )( lhs ); 188 } 189 190 bool opBinaryRight( string op )( inout T lhs ) if( op == "in" ) 191 { 192 return this.opBinary!( "&" )( lhs ); 193 } 194 } 195 196 version( unittest ) 197 { 198 enum Test 199 { 200 foo, 201 bar, 202 baz, 203 } 204 205 alias TestParams = Params!Test; 206 } 207 208 @system unittest 209 { 210 auto x = TestParams.ofFoo; 211 assert( x.foo, "x.foo" ); 212 213 x = x | Test.bar; 214 assert( x.bar, "x.bar" ); 215 216 x = x ^ Test.bar; 217 assert( !x.bar, "!x.bar" ); 218 219 assert( x & Test.foo, "x & foo" ); 220 assert( Test.foo in x, "foo in x" ); 221 }