1 /* 2 Copyright (C) 2014 - 2018 by Chris Beck <render787@gmail.com> 3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/ 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2 of the License, or 8 (at your option) any later version. 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY. 11 12 See the COPYING file for more details. 13 */ 14 15 /** 16 * @file 17 * Defines the MAKE_ENUM macro. 18 * 19 * Currently this comes in 1-argument and 2-argument versions. 20 * 21 * <b>Example usage:</b> 22 * 23 * @code 24 * #include "utils/make_enum.hpp" 25 * 26 * MAKE_ENUM(enumname, 27 * (val1, "name1") 28 * (val2, "name2") 29 * (val3, "name3") 30 * ) 31 * @endcode 32 * 33 * <b>What it does:</b> 34 * 35 * Generates a struct @a enumname which holds an enum and provides functions to 36 * convert to and from string, as well as a constant @a enumname::count, which 37 * is set to the number of possible enum values defined. 38 * 39 * @code 40 * // Throws bad_enum_cast if the string value is not recognized. 41 * enumname enumname::string_to_enum(const std::string&); 42 * 43 * // Returns the specified default instead of throwing if the string value is 44 * // not recognized. Never throws. 45 * enumname enumname::string_to_enum(const std::string&, enumname); 46 * 47 * // Never throws. 48 * std::string enumname::enum_to_string(enumname); 49 * 50 * // Count of defined enum values. 51 * const size_t enumname::count; 52 * @endcode 53 * 54 * It also defines the following stream operations: 55 * 56 * @code 57 * // Never throws. Fails an assertion check if the value passed is not defined 58 * // by the enumeration. 59 * std::ostream& operator<<(std::ostream&, enumname); 60 * 61 * // Never throws. Sets the stream's fail bit if the input is not recognized. 62 * std::istream& operator>>(std::istream&, enumname&); 63 * @endcode 64 * 65 * In case of a bad string -> enum conversion from istream input, the istream 66 * will have its fail bit set. This means that lexical_casts will throw a 67 * bad_lexical_cast in the similar scenario; however, note that 68 * bad_lexical_cast does not provide any details about the error. 69 * 70 * It is recommended to use MAKE_ENUM types with the built-in versions of 71 * lexical_cast or lexical_cast_default provided by Wesnoth (see lexical_cast.hpp). 72 * However, if you do <b>not</b> want wml_exception to be thrown under any 73 * circumstances, use the string_to_enumname functions instead. 74 * 75 * See src/tests/test_make_enum.cpp for example code. 76 */ 77 78 #pragma once 79 80 #include "global.hpp" 81 82 #include <cassert> 83 #include <exception> 84 #include <string> 85 86 #include <boost/preprocessor/cat.hpp> 87 #include <boost/preprocessor/seq/for_each.hpp> 88 #include <boost/preprocessor.hpp> 89 90 #include <istream> 91 #include <ostream> 92 93 class bad_enum_cast : public std::exception 94 { 95 public: bad_enum_cast(const std::string & enumname,const std::string & str)96 bad_enum_cast(const std::string& enumname, const std::string& str) 97 : message("Failed to convert string \"" + str + "\" to type " + enumname) 98 , name(enumname) 99 , bad_val(str) 100 {} 101 ~bad_enum_cast()102 virtual ~bad_enum_cast() NOEXCEPT {} 103 what() const104 const char * what() const NOEXCEPT 105 { 106 return message.c_str(); 107 } 108 type() const109 const char * type() const NOEXCEPT 110 { 111 return name.c_str(); 112 } 113 value() const114 const char * value() const NOEXCEPT 115 { 116 return bad_val.c_str(); 117 } 118 119 private: 120 std::string message, name, bad_val; 121 }; 122 123 namespace make_enum_detail 124 { 125 void debug_conversion_error(const std::string& tmp, const bad_enum_cast & e); 126 } 127 128 129 #define ADD_PAREN_1( A, B ) ((A, B)) ADD_PAREN_2 130 #define ADD_PAREN_2( A, B ) ((A, B)) ADD_PAREN_1 131 #define ADD_PAREN_1_END 132 #define ADD_PAREN_2_END 133 #define MAKEPAIRS( INPUT ) BOOST_PP_CAT(ADD_PAREN_1 INPUT,_END) 134 #define PP_SEQ_FOR_EACH_I_PAIR(macro, data, pairs) BOOST_PP_SEQ_FOR_EACH_I(macro, data, MAKEPAIRS(pairs)) 135 136 137 #define CAT2( A, B ) A ## B 138 #define CAT3( A, B, C ) A ## B ## C 139 140 141 #define EXPAND_ENUMVALUE_NORMAL(r, data, i, record) \ 142 BOOST_PP_TUPLE_ELEM(2, 0, record) = i, 143 144 145 #define EXPAND_ENUMFUNC_NORMAL(r, data, i, record) \ 146 if(data == BOOST_PP_TUPLE_ELEM(2, 1, record)) return BOOST_PP_TUPLE_ELEM(2, 0, record); 147 #define EXPAND_ENUMPARSE_NORMAL(r, data, i, record) \ 148 if(data == BOOST_PP_TUPLE_ELEM(2, 1, record)) { *this = BOOST_PP_TUPLE_ELEM(2, 0, record); return true; } 149 #define EXPAND_ENUMFUNCREV_NORMAL(r, data, i, record) \ 150 if(data == BOOST_PP_TUPLE_ELEM(2, 0, record)) return BOOST_PP_TUPLE_ELEM(2, 1, record); 151 152 #define EXPAND_ENUMFUNCTIONCOUNT(r, data, i, record) \ 153 + 1 154 155 class enum_tag 156 { 157 }; 158 #define MAKE_ENUM(NAME, CONTENT) \ 159 struct NAME : public enum_tag \ 160 { \ 161 enum type \ 162 { \ 163 PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMVALUE_NORMAL, ,CONTENT) \ 164 }; \ 165 type v; \ 166 NAME(type v) : v(v) {} \ 167 /* We don't want a default constructor but we need one in order to make lexical_cast working */ \ 168 NAME() : v() {} \ 169 /* operator type() const { return v; } */\ 170 static NAME string_to_enum (const std::string& str, NAME def) \ 171 { \ 172 PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNC_NORMAL, str , CONTENT) \ 173 return def; \ 174 } \ 175 static NAME string_to_enum (const std::string& str) \ 176 { \ 177 PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNC_NORMAL, str , CONTENT) \ 178 throw bad_enum_cast( #NAME , str); \ 179 } \ 180 /** Sets *this to the value given in str, does nothing if str was not valid enum value. */ \ 181 /** @returns true iff @a str was a valid enum value. */ \ 182 /** @param TStr a std::string or a string_span (from the wesnothd code). */ \ 183 template<typename TStr> \ 184 bool parse (const TStr& str) \ 185 { \ 186 PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMPARSE_NORMAL, str , CONTENT) \ 187 return false; \ 188 } \ 189 /* for const char* parameters we cannot use the template above because it would only compare the pointer. */ \ 190 bool parse (const char* str) \ 191 { \ 192 return parse(std::string(str)); \ 193 } \ 194 static std::string name() \ 195 { \ 196 return #NAME; \ 197 } \ 198 static std::string enum_to_string (NAME val) \ 199 { \ 200 PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNCREV_NORMAL, val.v , CONTENT) \ 201 assert(false && "Corrupted enum found with identifier NAME"); \ 202 throw "assertion ignored"; \ 203 } \ 204 static const char* enum_to_cstring (NAME val) \ 205 { \ 206 PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNCREV_NORMAL, val.v , CONTENT) \ 207 assert(false && "Corrupted enum found with identifier NAME"); \ 208 throw "assertion ignored"; \ 209 } \ 210 std::string to_string () const \ 211 { \ 212 return enum_to_string(*this); \ 213 } \ 214 const char* to_cstring () const \ 215 { \ 216 return enum_to_cstring(*this); \ 217 } \ 218 friend std::ostream& operator<< (std::ostream & os, NAME val) \ 219 { \ 220 os << enum_to_string(val); \ 221 return os; \ 222 } \ 223 friend std::ostream& operator<< (std::ostream & os, NAME::type val) \ 224 { \ 225 return (os << NAME(val)); \ 226 } \ 227 friend std::istream& operator>> (std::istream & is, NAME& val) \ 228 { \ 229 std::istream::sentry s(is, true); \ 230 if(!s) return is; \ 231 std::string temp; \ 232 is >> temp; \ 233 try { \ 234 val = string_to_enum(temp); \ 235 } catch (const bad_enum_cast & /*e*/) {\ 236 is.setstate(std::ios::failbit); \ 237 /*make_enum_detail::debug_conversion_error(temp, e); */\ 238 } \ 239 return is; \ 240 } \ 241 friend std::istream& operator>> (std::istream & os, NAME::type& val) \ 242 { \ 243 return (os >> reinterpret_cast< NAME &>(val)); \ 244 } \ 245 friend bool operator==(NAME v1, NAME v2) \ 246 { \ 247 return v1.v == v2.v; \ 248 } \ 249 friend bool operator==(NAME::type v1, NAME v2) \ 250 { \ 251 return v1 == v2.v; \ 252 } \ 253 friend bool operator==(NAME v1, NAME::type v2) \ 254 { \ 255 return v1.v == v2; \ 256 } \ 257 friend bool operator!=(NAME v1, NAME v2) \ 258 { \ 259 return v1.v != v2.v; \ 260 } \ 261 friend bool operator!=(NAME::type v1, NAME v2) \ 262 { \ 263 return v1 != v2.v; \ 264 } \ 265 friend bool operator!=(NAME v1, NAME::type v2) \ 266 { \ 267 return v1.v != v2; \ 268 } \ 269 /* operator< is used for by std::map and similar */ \ 270 friend bool operator<(NAME v1, NAME v2) \ 271 { \ 272 return v1.v < v2.v; \ 273 } \ 274 template<typename T> \ 275 T cast() \ 276 { \ 277 return static_cast<T>(v); \ 278 } \ 279 static NAME from_int(int i) \ 280 { \ 281 return static_cast<type>(i); \ 282 } \ 283 static const size_t count = 0 PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNCTIONCOUNT, , CONTENT);\ 284 bool valid() \ 285 { \ 286 return cast<size_t>() < count; \ 287 } \ 288 private: \ 289 /*prevent automatic conversion for any other built-in types such as bool, int, etc*/ \ 290 /*template<typename T> \ 291 operator T () const; */\ 292 /* For some unknown reason the following version doesn't compile: */ \ 293 /* template<typename T, typename = typename boost::enable_if<Cond>::type> \ 294 operator T(); */\ 295 }; 296