239 lines
10 KiB
C++
239 lines
10 KiB
C++
/*****************************************************************************
|
|
*
|
|
* 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 <mapnik/config.hpp>
|
|
#include <mapnik/debug.hpp>
|
|
|
|
// stl
|
|
#include <iostream>
|
|
#include <cstdlib>
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <tuple>
|
|
#include <stdexcept>
|
|
#include <map>
|
|
#include <string_view>
|
|
|
|
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<class EnumT>
|
|
using EnumStringT = std::tuple<EnumT, std::string_view>;
|
|
|
|
template<class EnumT, std::size_t N>
|
|
using EnumMapT = std::array<EnumStringT<EnumT>, N>;
|
|
|
|
template<class EnumT, std::size_t N>
|
|
constexpr char const* EnumGetValue(EnumMapT<EnumT, N> 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<class EnumT, std::size_t N>
|
|
constexpr EnumT EnumGetKey(EnumMapT<EnumT, N> 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<typename ENUM,
|
|
char const* (*F_TO_STRING)(ENUM),
|
|
ENUM (*F_FROM_STRING)(const char*),
|
|
std::map<ENUM, std::string> (*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<ENUM, std::string> 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<enum_class, std::string> 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<enum_class, std::string> fnc_name##_lookup() \
|
|
{ \
|
|
std::map<enum_class, std::string> val_map; \
|
|
std::transform(fnc_name##_map.begin(), \
|
|
fnc_name##_map.end(), \
|
|
std::inserter(val_map, val_map.end()), \
|
|
[](const mapnik::detail::EnumStringT<enum_class>& val) { \
|
|
return std::pair<enum_class, std::string>{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<enum_class, type_alias##_to_string, type_alias##_from_string, type_alias##_lookup>; \
|
|
extern template struct MAPNIK_DECL \
|
|
enumeration<enum_class, type_alias##_to_string, type_alias##_from_string, type_alias##_lookup>;
|
|
|
|
#define IMPLEMENT_ENUM(type_alias, enum_class) \
|
|
IMPLEMENT_ENUM_FNCS(type_alias, enum_class) \
|
|
template struct MAPNIK_DECL \
|
|
enumeration<enum_class, type_alias##_to_string, type_alias##_from_string, type_alias##_lookup>;
|
|
|
|
/** 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 (<i>a-z, A-Z, 0-9</i>),
|
|
* underscores (<i>_</i>) and dashes (<i>-</i>).
|
|
*
|
|
*
|
|
* @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
|