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