diff --git a/projectfiles/CodeBlocks-SCons/wesnoth.cbp b/projectfiles/CodeBlocks-SCons/wesnoth.cbp index a1bc60e1137..25eef962f3c 100644 --- a/projectfiles/CodeBlocks-SCons/wesnoth.cbp +++ b/projectfiles/CodeBlocks-SCons/wesnoth.cbp @@ -163,8 +163,6 @@ - - @@ -753,6 +751,7 @@ + @@ -948,6 +947,8 @@ + + diff --git a/projectfiles/CodeBlocks/wesnoth.cbp b/projectfiles/CodeBlocks/wesnoth.cbp index 9c0204fbe0a..1607da4b60f 100644 --- a/projectfiles/CodeBlocks/wesnoth.cbp +++ b/projectfiles/CodeBlocks/wesnoth.cbp @@ -788,6 +788,7 @@ + diff --git a/projectfiles/CodeLite/wesnoth.project b/projectfiles/CodeLite/wesnoth.project index 25fc6eb8eeb..69487f8250a 100644 --- a/projectfiles/CodeLite/wesnoth.project +++ b/projectfiles/CodeLite/wesnoth.project @@ -3384,7 +3384,8 @@ - + + @@ -4424,6 +4425,7 @@ + diff --git a/projectfiles/VC9/wesnoth.vcproj b/projectfiles/VC9/wesnoth.vcproj index b27518da9a0..2382e405415 100644 --- a/projectfiles/VC9/wesnoth.vcproj +++ b/projectfiles/VC9/wesnoth.vcproj @@ -16555,6 +16555,62 @@ /> + + + + + + + + + + + + + + + + + + + + @@ -20182,6 +20238,10 @@ RelativePath="..\..\src\lua_jailbreak_exception.hpp" > + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 94feccae60e..59258a429b5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1257,6 +1257,7 @@ if(ENABLE_TESTS) tests/test_image_modifications.cpp tests/test_lexical_cast.cpp tests/test_map_location.cpp + tests/test_make_enum.cpp tests/test_mp_connect.cpp tests/test_network_worker.cpp tests/test_sdl_utils.cpp diff --git a/src/SConscript b/src/SConscript index 23b6d142cb1..51edf252c57 100644 --- a/src/SConscript +++ b/src/SConscript @@ -663,6 +663,7 @@ test_sources = Split(""" tests/test_image_modifications.cpp tests/test_lexical_cast.cpp tests/test_map_location.cpp + tests/test_make_enum.cpp tests/test_mp_connect.cpp tests/test_network_worker.cpp tests/test_sdl_utils.cpp diff --git a/src/make_enum.hpp b/src/make_enum.hpp new file mode 100644 index 00000000000..4b49d351ddb --- /dev/null +++ b/src/make_enum.hpp @@ -0,0 +1,212 @@ +/* + Copyright (C) 2014 by Chris Beck + Part of the Battle for Wesnoth Project http://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ + +// Defines the MAKE_ENUM macro, +// and companion macro MAKE_ENUM_STREAM_OPS, which also enables lexical_cast +// Currently this has 1 argument and 2 argument variety. + +/** + * + * Example Usage: + * + */ + +/* + +namespace foo { + + MAKE_ENUM(enumname, + (con1, name1) + (con2, name2) + (con3, name3) + ) + + MAKE_ENUM_STREAM_OPS1(enumname) +} + + + + + +class bar { + public: + + MAKE_ENUM(another, + (val1, name1) + (val2, name2) + (val3, name3) + ) + +}; + +MAKE_ENUM_STREAM_OPS2(bar , another) + +*/ + +/** + * + * What it does: + * + * generates an enum foo::enumname, with functions to convert to and from string + * + * + * foo::string_to_enumname(std::string); //throws bad_enum_cast + * foo::string_to_enumname(std::string, foo::enumname default); //no throw + * foo::enumname_to_string(foo::enumname); //no throw + * + * The stream ops define + * std::ostream & operator<< (std::ostream &, foo::enumname) + * std::istream & operator>> (std::istream &, foo::enumname &) + * + * In case of a bad string -> enum conversion from istream output, + * the istream will have its fail bit set. + * This means that lexical_casts will throw a bad_lexical_cast, + * in the similar scenario. + * + * + * + * + * For example code, see src/tests/test_make_enum.cpp + * + **/ + +#ifndef MAKE_ENUM_HPP +#define MAKE_ENUM_HPP + +#include +#include + +class bad_enum_cast : public std::exception +{ +public: + bad_enum_cast(const std::string& enumname, const std::string str) + : message("Failed to convert String \"" + str + "\" to type " + enumname) + {} + + virtual ~bad_enum_cast() throw() {} + + const char * what() const throw() + { + return message.c_str(); + } + +private: + const std::string message; +}; + + +#define CAT2( A, B ) A ## B +#define CAT3( A, B, C ) A ## B ## C + +//Clang apparently doesn't support __VA_ARGS__ with --C98 (???) +//Can try this again when we upgrade +/* +#define GET_COUNT(_1, _2, _3, _4, _5, COUNT, ...) COUNT +#define VA_SIZE(...) GET_COUNT(__VA_ARGS__, 5, 4, 3, 2, 1) + +#define VA_SELECT(MNAME, ...) CAT2(MNAME, VA_SIZE(__VA_ARGS__) )(__VA_ARGS__) +*/ + + +#define EXPANDENUMVALUE( a, b ) a , +#define EXPANDENUMTYPE( r, data, elem ) EXPANDENUMVALUE elem + +#define EXPANDENUMFUNCTIONRETURN( a, b ) if ( str == b ) return a; +#define EXPANDENUMFUNCTION( r, data, elem ) EXPANDENUMFUNCTIONRETURN elem + +#define EXPANDENUMFUNCTIONREVRETURN( a, b ) if ( val == a ) return b; +#define EXPANDENUMFUNCTIONREV( r, data, elem ) EXPANDENUMFUNCTIONREVRETURN elem + +#define ADD_PAREN_1( A, B ) ((A, B)) ADD_PAREN_2 +#define ADD_PAREN_2( A, B ) ((A, B)) ADD_PAREN_1 +#define ADD_PAREN_1_END +#define ADD_PAREN_2_END +#define MAKEPAIRS( INPUT ) BOOST_PP_CAT(ADD_PAREN_1 INPUT,_END) + +#define MAKEENUMTYPE( NAME, CONTENT ) \ +enum NAME { \ +BOOST_PP_SEQ_FOR_EACH(EXPANDENUMTYPE, , MAKEPAIRS(CONTENT)) \ +}; \ + + +#define MAKEENUMCAST( NAME, PREFIX, CONTENT ) \ +PREFIX NAME CAT3(string_to_, NAME, _default) (const std::string& str, NAME def) \ +{ \ + BOOST_PP_SEQ_FOR_EACH(EXPANDENUMFUNCTION, , MAKEPAIRS(CONTENT)) \ + return def; \ +} \ +PREFIX NAME CAT2(string_to_,NAME) (const std::string& str) \ +{ \ + BOOST_PP_SEQ_FOR_EACH(EXPANDENUMFUNCTION, , MAKEPAIRS(CONTENT)) \ + throw bad_enum_cast( #NAME , str); \ +} \ +PREFIX std::string CAT2(NAME,_to_string) (NAME val) \ +{ \ + BOOST_PP_SEQ_FOR_EACH(EXPANDENUMFUNCTIONREV, , MAKEPAIRS(CONTENT)) \ + assert(false); \ +} + + + +#define MAKE_ENUM( NAME, CONTENT ) \ +MAKEENUMTYPE( NAME, CONTENT ) \ +MAKEENUMCAST( NAME, static , CONTENT ) + +#define MAKE_ENUM_STREAM_OPS1( NAME ) \ +inline std::ostream& operator<< (std::ostream & os, NAME val) \ +{ \ + os << CAT2(NAME,_to_string) ( val ); \ + return os; \ +} \ +inline std::istream& operator>> (std::istream & is, NAME & val) \ +{ \ + std::istream::sentry s(is, true); \ + if(!s) return is; \ + std::string temp; \ + is >> temp; \ + try { \ + val = CAT2(string_to_, NAME) ( temp ); \ + } catch (bad_enum_cast & e) { \ + is.setstate(std::ios::failbit); \ + } \ + return is; \ +} \ + + +#define MAKE_ENUM_STREAM_OPS2( NAMESPACE, NAME ) \ +inline std::ostream& operator<< (std::ostream & os, NAMESPACE::NAME val) \ +{ \ + os << CAT2(NAMESPACE::NAME,_to_string) ( val ); \ + return os; \ +} \ +inline std::istream& operator>> (std::istream & is, NAMESPACE::NAME & val) \ +{ \ + std::istream::sentry s(is, true); \ + if(!s) return is; \ + std::string temp; \ + is >> temp; \ + try { \ + val = CAT2(NAMESPACE::string_to_,NAME) ( temp ); \ + } catch (bad_enum_cast & e) {\ + is.setstate(std::ios::failbit); \ + } \ + return is; \ +} \ + +//Clang apparently doesn't support __VA_ARGS__ with --C98 (???) +//Can try this again when we upgrade +//#define MAKE_ENUM_STREAM_OPS VA_SELECT(MAKE_ENUM_STREAM_OPS, __VA_ARGS__) + + +#endif diff --git a/src/tests/test_make_enum.cpp b/src/tests/test_make_enum.cpp new file mode 100644 index 00000000000..2c919e9825a --- /dev/null +++ b/src/tests/test_make_enum.cpp @@ -0,0 +1,146 @@ +/* + Copyright (C) 2014 by Chris Beck + Part of the Battle for Wesnoth Project http://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ + +#define GETTEXT_DOMAIN "wesnoth-test" + +#include + +#include "make_enum.hpp" +#include "util.hpp" + +namespace foo { + + MAKE_ENUM(enumname, + (con1, "name1") + (con2, "name2") + (con3, "name3") + ) + + MAKE_ENUM_STREAM_OPS1(enumname) +} + + +//generates an enum foo::enumname with lexical casts +/* +namespace foo { +enum enumname {con1, con2 ,con3} +} + +foo::enumname lexical_cast ( std::string str ) throws bad_lexical_cast +{ + ... +} +*/ + +class bar { + public: + + MAKE_ENUM(another, + (val1, "name1") + (val2, "name2") + (val3, "name3") + ) + +}; + +MAKE_ENUM_STREAM_OPS2(bar,another) + +/** Tests begin **/ + +BOOST_AUTO_TEST_SUITE ( test_make_enum ) + +BOOST_AUTO_TEST_CASE ( test_make_enum_namespace ) +{ + foo::enumname e = foo::string_to_enumname_default("name2", foo::con1); //returns con2 + + BOOST_CHECK_EQUAL ( e, foo::con2 ); + + std::string str = foo::enumname_to_string(foo::con1); //returns "name1" + + BOOST_CHECK_EQUAL ( str, "name1" ); + + std::string str2 = lexical_cast (e); //returns "name2" since e is con2 + + BOOST_CHECK_EQUAL ( str2, "name2" ); + + bool threw_exception_when_it_wasnt_supposed_to = false; + + try { + e = lexical_cast ("name3"); //returns con3 + } catch (bad_lexical_cast & e) { + //std::cerr << "enum lexical cast didn't work!" << std::endl; + threw_exception_when_it_wasnt_supposed_to = true; + } + + BOOST_CHECK( !threw_exception_when_it_wasnt_supposed_to ); + + bool threw_exception_when_it_was_supposed_to = false; + + try { + e = lexical_cast ("name4"); //throw bad_lexical_cast + } catch (bad_lexical_cast & e) { + //std::cerr << "enum lexical cast worked!" << std::endl; + threw_exception_when_it_was_supposed_to = true; + } + + BOOST_CHECK( threw_exception_when_it_was_supposed_to ); + + std::stringstream ss; + ss << e; + BOOST_CHECK_EQUAL (ss.str(), "name3"); +} + +BOOST_AUTO_TEST_CASE ( test_make_enum_class ) +{ + bar::another e = bar::string_to_another_default("name2", bar::val1); //returns val2 + + BOOST_CHECK_EQUAL ( e, bar::val2 ); + + std::string str = bar::another_to_string(bar::val1); //returns "name1" + + BOOST_CHECK_EQUAL ( str, "name1" ); + + std::string str2 = lexical_cast (e); //returns "name2" since e is val2 + + BOOST_CHECK_EQUAL ( str2, "name2" ); + + bool threw_exception_when_it_wasnt_supposed_to = false; + + try { + e = lexical_cast ("name3"); //returns val3 + } catch (bad_lexical_cast & e) { + //std::cerr << "enum lexical cast didn't work!" << std::endl; + threw_exception_when_it_wasnt_supposed_to = true; + } + + BOOST_CHECK( !threw_exception_when_it_wasnt_supposed_to ); + + bool threw_exception_when_it_was_supposed_to = false; + + try { + e = lexical_cast ("name4"); //throw bad_lexical_cast + } catch (bad_lexical_cast & e) { + //std::cerr << "enum lexical cast worked!" << std::endl; + threw_exception_when_it_was_supposed_to = true; + } + + BOOST_CHECK( threw_exception_when_it_was_supposed_to ); + + std::stringstream ss; + ss << e; + BOOST_CHECK_EQUAL (ss.str(), "name3"); +} + + +BOOST_AUTO_TEST_SUITE_END()