/***************************************************************************** * * This file is part of Mapnik (c++ mapping toolkit) * * Copyright (C) 2024 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ #ifndef MAPNIK_ENUMERATION_HPP #define MAPNIK_ENUMERATION_HPP // mapnik #include #include // stl #include #include #include #include #include #include #include #include namespace mapnik { class illegal_enum_value : public std::exception { public: illegal_enum_value() : what_() {} illegal_enum_value(std::string const& _what) : what_(_what) {} virtual ~illegal_enum_value() {} virtual const char* what() const noexcept { return what_.c_str(); } protected: const std::string what_; }; namespace detail { template using EnumStringT = std::tuple; template using EnumMapT = std::array, N>; template constexpr char const* EnumGetValue(EnumMapT const& map, EnumT key, std::size_t i = 0) { if (i >= map.size()) { throw illegal_enum_value{"Enum value not present in map."}; } return (std::get<0>(map[i]) == key) ? std::get<1>(map[i]).data() : EnumGetValue(map, key, i + 1); } template constexpr EnumT EnumGetKey(EnumMapT const& map, char const* value, std::size_t i = 0) { if (i >= map.size()) { throw illegal_enum_value{"Enum key not present in map."}; } return (std::get<1>(map[i]) == value) ? std::get<0>(map[i]) : EnumGetKey(map, value, i + 1); } } // namespace detail template (*F_LOOKUP)()> struct MAPNIK_DECL enumeration { using native_type = ENUM; constexpr operator ENUM() const { return value_; } void operator=(ENUM v) { value_ = v; } enumeration() : value_() {} enumeration(ENUM v) : value_(v) {} void from_string(const std::string& str) { value_ = F_FROM_STRING(str.c_str()); } std::string as_string() const { return F_TO_STRING(value_); } static std::map lookupMap() { return F_LOOKUP(); } ENUM value_; }; #define DEFINE_ENUM_FNCS(fnc_name, enum_class) \ MAPNIK_DECL char const* fnc_name##_to_string(enum_class value); \ MAPNIK_DECL enum_class fnc_name##_from_string(const char* value); \ MAPNIK_DECL std::ostream& operator<<(std::ostream& stream, enum_class value); \ MAPNIK_DECL std::map fnc_name##_lookup(); #define IMPLEMENT_ENUM_FNCS(fnc_name, enum_class) \ char const* fnc_name##_to_string(enum_class value) \ { \ return mapnik::detail::EnumGetValue(fnc_name##_map, value); \ } \ enum_class fnc_name##_from_string(const char* value) \ { \ return mapnik::detail::EnumGetKey(fnc_name##_map, value); \ } \ std::ostream& operator<<(std::ostream& stream, enum_class value) \ { \ stream << fnc_name##_to_string(value); \ return stream; \ } \ std::map fnc_name##_lookup() \ { \ std::map val_map; \ std::transform(fnc_name##_map.begin(), \ fnc_name##_map.end(), \ std::inserter(val_map, val_map.end()), \ [](const mapnik::detail::EnumStringT& val) { \ return std::pair{std::get<0>(val), \ std::string{std::get<1>(val).data()}}; \ }); \ return val_map; \ } #define DEFINE_ENUM(type_alias, enum_class) \ DEFINE_ENUM_FNCS(type_alias, enum_class) \ using type_alias = enumeration; \ extern template struct MAPNIK_DECL \ enumeration; #define IMPLEMENT_ENUM(type_alias, enum_class) \ IMPLEMENT_ENUM_FNCS(type_alias, enum_class) \ template struct MAPNIK_DECL \ enumeration; /** Slim wrapper for enumerations. It creates a new type from a native enum and * a char pointer array. It almost exactly behaves like a native enumeration * type. It supports string conversion through stream operators. This is useful * for debugging, serialization/deserialization and also helps with implementing * language bindings. The two convenient macros DEFINE_ENUM() and IMPLEMENT_ENUM() * are provided to help with instanciation. * * @par Limitations: * - The enum must start at zero. * - The enum must be consecutive. * - The enum must be terminated with a special token consisting of the enum's * name plus "_MAX". * - The corresponding char pointer array must be terminated with an empty string. * - The names must only consist of characters and digits (a-z, A-Z, 0-9), * underscores (_) and dashes (-). * * * @warning At the moment the verify_mapnik_enum() method is called during static initialization. * It quits the application with exit code 1 if any error is detected. The other solution * i thought of is to do the checks at compile time (using boost::mpl). * * @par Example: * The following code goes into the header file: * @code * enum fruit_enum { * APPLE, * CHERRY, * BANANA, * PASSION_FRUIT, * fruit_enum_MAX * }; * * static const char * fruit_strings[] = { * "apple", * "cherry", * "banana", * "passion_fruit", * "" * }; * * DEFINE_ENUM( fruit, fruit_enum); * @endcode * In the corresponding cpp file do: * @code * IMPLEMENT_ENUM( fruit, fruit_strings ); * @endcode * And here is how to use the resulting type Fruit * @code * * int * main(int argc, char * argv[]) { * fruit f(APPLE); * switch ( f ) { * case BANANA: * case APPLE: * cerr << "No thanks. I hate " << f << "s" << endl; * break; * default: * cerr << "Hmmm ... yummy " << f << endl; * break; * } * * f = CHERRY; * * fruit_enum native_enum = f; * * f.from_string("passion_fruit"); * * for (unsigned i = 0; i < fruit::MAX; ++i) { * cerr << i << " = " << fruit::get_string(i) << endl; * } * * f.from_string("elephant"); // throws illegal_enum_value * * return 0; * } * @endcode */ } // namespace mapnik #endif // MAPNIK_ENUMERATION_HPP