1 /** Additions to $(STDMODULE _typecons).
2 
3 Copyright: Denis Shelomovskij 2012
4 
5 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
6 
7 Authors: Denis Shelomovskij
8 */
9 module unstd.typecons;
10 
11 
12 public import std.typecons;
13 
14 import std..string: strip, format;
15 import std.array: appender;
16 import std.traits: isIntegral, IntegralTypeOf;
17 import unstd.generictuple: MapTuple;
18 
19 
20 @safe pure nothrow @nogc:
21 
22 private struct EnumMember(Base)
23 {
24 	string str, name, valStr;
25 	Base val;
26 }
27 
28 private template enumMembers(Base, members...)
29 {
30 	template enumMember(string str)
31 	{
32 		enum pos = ()
33 		{
34 			// Because std.string.indexOf isn't CTFE-able
35 			foreach(size_t i, c; str) if(c == '=')
36 				return i;
37 			return cast(size_t) -1;
38 		}();
39 		static if(pos == -1)
40 			enum enumMember = EnumMember!Base(str, str.strip(), null);
41 		else
42 		{
43 			enum valStr = str[pos + 1 .. $];
44 			enum enumMember = EnumMember!Base(str, str[0 .. pos].strip(), valStr, mixin(valStr));
45 		}
46 	}
47 
48 	alias enumMembers = MapTuple!(enumMember, members);
49 }
50 
51 struct FlagEnumImpl(string name, Args...)
52 {
53 	private static if(is(Args[0]))
54 	{
55 		alias Base = IntegralTypeOf!(Args[0]);
56 		alias members = Args[1 .. $];
57 	}
58 	else
59 	{
60 		alias Base = int;
61 		alias members = Args;
62 	}
63 
64 	private Base val;
65 
66 	invariant()
67 	{
68 		assert(!(val & m_unused));
69 	}
70 
71 	@property Base value() const { return val; }
72 
73 	T opCast(T : bool)() const
74 	{ return !!val; }
75 
76 	T opCast(T)() const
77 		if(isIntegral!T && T.sizeof >= Base.sizeof)
78 	{ return val; }
79 
80 	FlagEnumImpl opOpAssign(string op)(in FlagEnumImpl y) if (op == "&" || op == "|")
81 	{
82 		mixin(format("val %s= y.val;", op));
83 		return this;
84 	}
85 
86 	FlagEnumImpl opBinary(string op)(in FlagEnumImpl y) const if (op == "&" || op == "|")
87 	{
88 		FlagEnumImpl t = this;
89 		return mixin(format("t %s= y", op));
90 	}
91 
92 	string toString() const
93 	{
94 		auto app = appender!string();
95 		app.put(name);
96 
97 		immutable bool isOneSet = val && !((val - 1) & val);
98 		app.put(isOneSet ? "." : ".{");
99 
100 		bool first = true;
101 		foreach(i, m; enumMembers)
102 		{
103 			if(val & mixin(m.name).value)
104 			{
105 				if(!first)
106 					app.put("|");
107 				app.put(m.name);
108 				first = false;
109 			}
110 		}
111 		if(!isOneSet)
112 			app.put("}");
113 		return app.data;
114 	}
115 
116 	private static string genMembers(EnumMember!Base[] members)
117 	{
118 		string s;
119 		Base defVal = 1, used = 0;
120 		foreach(i, m; members)
121 		{
122 			immutable Base val = m.valStr ? m.val : defVal;
123 			if(m.valStr)
124 			{
125 				assert(val, "Flag value can't be zero: " ~ m.str);
126 				assert(!((val - 1) & val), "Flag value must contain only one bit set: " ~ m.str);
127 				assert(!(used & val), "Flag value already used: " ~ m.str);
128 			}
129 			else
130 			{
131 				assert(!(used & val), "Next bit value already used: " ~ format("%s = %s", m.name, val));
132 				assert(val, "Can't set next bit, integer overflow: " ~ m.name);
133 			}
134 
135 			used |= val;
136 
137 			defVal = cast(Base) (val << 1);
138 
139 			// With enum: `enum FlagEnumImpl %s = FlagEnumImpl(%s);`
140 			// Can't use enum because it is an lvalue, see Issue @@@8915@@@
141 			s ~= format("static @property FlagEnumImpl %s() pure nothrow { return FlagEnumImpl(%s); }", m.name, val);
142 		}
143 		s ~= format("private enum Base m_unused = %s;", ~used);
144 		return s;
145 	};
146 
147 	private alias enumMembers = .enumMembers!(Base, members);
148 	mixin(genMembers([enumMembers]));
149 }
150 
151 /**
152 Creates a set of flags.
153 
154 Example:
155 ----
156 mixin flagEnum!("Access", byte, "read = 2", "write", "execute"); // write == 4, execute == 8
157 
158 assert(cast(uint) Access.init == 0);
159 assert(cast(uint) Access.write == 4);
160 assert(cast(uint) Access.execute == 8);
161 
162 auto folderAccess = Access.read | Access.execute;
163 auto fileAccess   = Access.read | Access.write | Access.execute;
164 
165 fileAccess &= folderAccess;
166 assert(fileAccess == (Access.read | Access.execute));
167 
168 if(fileAccess)
169 {
170 	// have some access
171 }
172 else
173 	assert(0);
174 
175 
176 import std.stdio: writeln;
177 
178 writeln(fileAccess & Access.read);   // Writes "Access.read"
179 writeln(fileAccess);                 // Writes "Access.{read|execute}"
180 writeln(fileAccess & Access.write);  // Writes "Access.{}"
181 ----
182 
183 Bugs:
184 As it is implemented as a $(D struct), $(D Enum.init) behaves like lvalue (except it can't be assigned to), see  $(DBUGZILLA 8915).
185 I.e. $(D (ref Enum e){ } (Enum.init)) compiles.
186 
187 As it is a $(D struct), op assignment operators are allowed for rvalues, see  $(DBUGZILLA 8916).
188 I.e. things like $(D Enum.init |= Enum.a) and $(D Enum.a |= Enum.a) compiles.
189 */
190 mixin template flagEnum(string name, Args...)
191 {
192 	mixin("alias " ~ name ~ " = FlagEnumImpl!(name, Args);");
193 }
194 
195 // Note: unittest can't be used as an example here as there is no way to place it before `Bugs` section.
196 
197 unittest
198 {
199 	mixin flagEnum!("AB", "a", "b", "cc");
200 	
201 	// Types and identifiers
202 	static assert(is(typeof(AB.a) == AB));
203 	static assert(is(typeof(AB.a.value) == int));
204 	static assert(__traits(identifier, AB.a) == "a");
205 	
206 	// Values
207 	static assert(AB.init.value == 0);
208 	static assert(AB.a.value == 1);
209 	static assert(AB.cc.value == 4);
210 
211 	// Conversions to integral types
212 	static assert(!__traits(compiles, cast(short) AB.b));
213 	static assert(cast(int) AB.b == 2);
214 	static assert(cast(uint) AB.b == 2);
215 	static assert(cast(long) AB.b == 2);
216 
217 	// Conversions to bool
218 	static assert(!AB.init);
219 	static assert(!!AB.a);
220 	if(AB.init) assert(0); else { }
221 	if(AB.a) { } else assert(0);
222 
223 	// Bitwise operations
224 	static assert(cast(int) (AB.a | AB.b) == (1 | 2));
225 	static assert((AB.a & AB.b).value == 0);
226 	static assert((AB.a | AB.b | AB.cc).value == (1 | 2 | 4));
227 	static assert((AB.a | AB.b & AB.cc) == AB.a);
228 	static assert(cast(AB) (1 | 2) == (AB.a | AB.b));
229 
230 	// Not an lvalue
231 	static void refAB(ref AB) { }
232 	/+ Tests disabled because of [implementation] @@@BUG@@@
233 	static assert(!__traits(compiles, refAB(AB.init)));
234 	+/
235 	static assert(!__traits(compiles, refAB(AB.a)));
236 	
237 	// Immutability
238 	/+ Tests disabled because of implementation @@@BUG@@@
239 	static assert(!__traits(compiles, AB.init |= AB.a));
240 	static assert(!__traits(compiles, AB.a |= AB.a));
241 	static assert(!__traits(compiles, AB.a &= AB.a));
242 	+/
243 	
244 	// Mutability
245 	auto var = AB.a;
246 	var |= AB.b;
247 	assert(var == (AB.a | AB.b));
248 	var &= AB.b;
249 	assert(var == AB.b);
250 	immutable ivar = var;
251 	static assert(!__traits(compiles, ivar |= AB.b));
252 	var |= ivar;
253 
254 	// Restricted operations
255 	static assert(!__traits(compiles, AB.a == 1));
256 	static assert(!__traits(compiles, AB.a | 1));
257 	static assert(!__traits(compiles, AB.a + AB.b));
258 
259 	mixin flagEnum!("EF", "e", "f");
260 
261 	static assert(!__traits(compiles, AB.a == EF.e));
262 	static assert(!__traits(compiles, AB.a | EF.e));
263 	static assert(!__traits(compiles, AB.a & EF.e));
264 }
265 
266 unittest
267 {
268 	mixin flagEnum!("ubAB", ubyte, "a", "b", "cc");
269 	static assert(ubAB.a.value == 1);
270 	static assert(!__traits(compiles, cast(char) ubAB.b));
271 	static assert(cast(ubyte) ubAB.b == 2);
272 	static assert(cast(byte) ubAB.b == 2);
273 	static assert(ubAB.cc.value == 4);
274 	static assert(cast(ubyte) (ubAB.a | ubAB.b) == (1 | 2));
275 	static assert((ubAB.a | ubAB.b & ubAB.cc) == ubAB.a);
276 	static assert(cast(ubAB) (1 | 2) == (ubAB.a | ubAB.b));
277 }
278 
279 unittest
280 {
281 	// Explicit values
282 	mixin flagEnum!("AB", "a", "b = 4", "c", "d", "e = 63 + 1", "f = 0b10", "g = 0x8 << 4", "h");
283 
284 	static assert(AB.a.value == 1);
285 	static assert(AB.b.value == 4);
286 	static assert(AB.c.value == 8);
287 	static assert(AB.d.value == 16);
288 	static assert(AB.e.value == 64);
289 	static assert(AB.f.value == 2);
290 	static assert(AB.g.value == 0x80);
291 
292 	// Invalid explicit/implicit values
293 	static assert(!__traits(compiles, FlagEnumImpl!("A", "a = 0"))); // zero
294 	static assert(!__traits(compiles, FlagEnumImpl!("A", "a", "b = 1"))); // explicit value used
295 	static assert(!__traits(compiles, FlagEnumImpl!("A", "a", "b = 4", "c", "d = 8"))); // ditto
296 	static assert(!__traits(compiles, FlagEnumImpl!("A", "a", "b = 4", "c", "d = 2", "e"))); // implicit value used
297 }
298 
299 unittest
300 {
301 	// Almost overflow
302 	mixin flagEnum!("ubAB", ubyte, "a = 0x40", "b");
303 	static assert(ubAB.a.value == 0x40);
304 	static assert(ubAB.b.value == 0x80);
305 
306 	// Overflow
307 	static assert(!__traits(compiles, FlagEnumImpl!("ubAB2", ubyte, "a = 0x40", "b", "c")));
308 }
309 
310 unittest
311 {
312 	// Conversion to string
313 	mixin flagEnum!("AB", "a", "b ", "\n \rcc \v");
314 	static assert(AB.init.toString() == "AB.{}");
315 	static assert(AB.a.toString() == "AB.a");
316 	static assert(AB.cc.toString() == "AB.cc");
317 	static assert((AB.a | AB.b).toString() == "AB.{a|b}");
318 	static assert((AB.a & AB.b).toString() == "AB.{}");
319 }