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 }