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 }