draconisplusplus/include/rfl/internal/enums/StringConverter.hpp

133 lines
3.8 KiB
C++
Raw Normal View History

2024-05-31 22:59:00 -04:00
#ifndef RFL_INTERNAL_ENUMS_STRINGCONVERTER_HPP_
#define RFL_INTERNAL_ENUMS_STRINGCONVERTER_HPP_
#include <algorithm>
#include <array>
#include <string>
#include <string_view>
#include <type_traits>
#include "../../Result.hpp"
#include "../../internal/strings/join.hpp"
#include "../../internal/strings/split.hpp"
#include "../../type_name_t.hpp"
#include "get_enum_names.hpp"
#include "is_flag_enum.hpp"
namespace rfl {
namespace internal {
namespace enums {
template <class EnumType>
class StringConverter {
public:
static constexpr bool is_flag_enum_ = is_flag_enum<EnumType>;
static constexpr auto names_ = get_enum_names<EnumType, is_flag_enum_>();
using NamesLiteral = typename decltype(names_)::Literal;
public:
/// Transform an enum to a matching string.
static std::string enum_to_string(const EnumType _enum) {
if constexpr (is_flag_enum_) {
return flag_enum_to_string(_enum);
} else {
return enum_to_single_string(_enum);
}
}
/// Transforms a string to the matching enum.
static Result<EnumType> string_to_enum(const std::string& _str) {
static_assert(names_.size != 0,
"No enum could be identified. Please choose enum values "
"between 0 to 127 or for flag enums choose 1,2,4,8,16,...");
if constexpr (is_flag_enum_) {
return string_to_flag_enum(_str);
} else {
return single_string_to_enum(_str);
}
}
private:
/// Iterates through the enum bit by bit and matches it against the flags.
static std::string flag_enum_to_string(const EnumType _e) {
using T = std::underlying_type_t<EnumType>;
auto val = static_cast<T>(_e);
int i = 0;
std::vector<std::string> flags;
while (val != 0) {
const auto bit = val & static_cast<T>(1);
if (bit == 1) {
auto str = enum_to_single_string(
static_cast<EnumType>(static_cast<T>(1) << i));
flags.emplace_back(std::move(str));
}
++i;
val >>= 1;
}
return strings::join("|", flags);
}
/// This assumes that _enum can be exactly matched to one of the names and
/// does not have to be combined using |.
static std::string enum_to_single_string(const EnumType _enum) {
const auto to_str = [](const auto _l) { return _l.str(); };
for (size_t i = 0; i < names_.size; ++i) {
if (names_.enums_[i] == _enum) {
return NamesLiteral::from_value(
static_cast<typename NamesLiteral::ValueType>(i))
.transform(to_str)
.value();
}
}
return std::to_string(static_cast<std::underlying_type_t<EnumType>>(_enum));
}
/// Finds the enum matching the literal.
static EnumType literal_to_enum(const NamesLiteral _lit) noexcept {
return names_.enums_[_lit.value()];
}
/// This assumes that _enum can be exactly matched to one of the names and
/// does not have to be combined using |.
static Result<EnumType> single_string_to_enum(const std::string& _str) {
const auto r = NamesLiteral::from_string(_str).transform(literal_to_enum);
if (r) {
return r;
} else {
try {
return static_cast<EnumType>(std::stoi(_str));
} catch (std::exception& exp) {
return Error(exp.what());
}
}
}
/// Only relevant if this is a flag enum - combines the different matches
/// using |.
static Result<EnumType> string_to_flag_enum(
const std::string& _str) noexcept {
using T = std::underlying_type_t<EnumType>;
const auto split = strings::split(_str, "|");
auto res = static_cast<T>(0);
for (const auto& s : split) {
const auto r = single_string_to_enum(s);
if (r) {
res |= static_cast<T>(*r);
} else {
return r;
}
}
return static_cast<EnumType>(res);
}
};
} // namespace enums
} // namespace internal
} // namespace rfl
#endif