libcruft-util/introspection/enum_simple.hpp

187 lines
6.6 KiB
C++

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2022, Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include <string_view>
#include <string>
#include <stdexcept>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wenum-constexpr-conversion"
namespace cruft::introspection {
namespace detail {
/// Return the full pretty signature of the function for parsing.
///
/// It must include an enum literal.
template <typename EnumT, EnumT Value>
static consteval
std::string_view
enum_pretty_function (void)
{
return __PRETTY_FUNCTION__;
}
}
/// Convert an enum to a string representation.
///
/// If there is no valid name for the value then return the empty string.
///
/// This parses the result of `enum_pretty_function` and so must be
/// customised for each compiler.
///
/// NOTE: The output of `enum_pretty_function` may change if it's in a different
/// namespace. Testing is _essential_.
template <typename EnumT, EnumT Value>
requires (std::is_enum_v<EnumT>)
consteval std::string_view
to_string [[maybe_unused]] (void)
{
#if defined(__clang__)
// std::string_view cruft::introspection::detail::enum_pretty_function() [EnumT = enum_t, Value = main()::value0]
//
// std::string_view enum_pretty_function() [EnumT = enum_t, Value = (enum_t)42]
//
// "std::string_view cruft::introspection::detail::enum_pretty_function() [EnumT = enum_t, Value = test_local(cruft::TAP::logger &)::value0]"
auto const signature = detail::enum_pretty_function<EnumT, Value> ();
auto const comma = signature.find (',');
auto const close = signature.find (']', comma + 1);
auto const coloncolon = signature.rfind ("::", close);
auto const equals = signature.find ('=', comma + 1);
auto const first = coloncolon == std::string_view::npos ? equals + 2 : coloncolon + 2;
if (signature[first] == '(')
return "";
return signature.substr (first, close - first);
#elif defined(__GNUC__)
// "constexpr std::string_view cruft::introspection::detail::enum_pretty_function() [with EnumT = main()::enum_t; EnumT Value = main::value1; std::string_view = std::basic_string_view<char>]"
//
// consteval std::string_view enum_pretty_function() [with EnumT = enum_t; EnumT Value = (enum_t)42; std::string_view = std::basic_string_view<char>]
//
// "constexpr std::string_view cruft::introspection::detail::enum_pretty_function() [with EnumT = test_local(cruft::TAP::logger&)::enum_t; EnumT Value = test_local::value0; std::string_view = std::basic_string_view<char>]"
auto const signature = detail::enum_pretty_function<EnumT, Value> ();
auto const semicolon0 = signature.find (';');
auto const semicolon1 = signature.find (';', semicolon0 + 1);
auto const coloncolon = signature.rfind ("::", semicolon1);
auto const equals = signature.find ('=', semicolon0);
// static_assert (equals > semicolon0);
// static_assert (equals < semicolon1);
auto const first = coloncolon == std::string_view::npos ? equals + 2 : coloncolon + 2;
if (signature[first] == '(')
return "";
return signature.substr (first, semicolon1 - first);
#else
#error Unknown platform
#endif
}
namespace detail {
template <typename EnumT>
struct helper {
static_assert (std::is_enum_v<EnumT>);
using underlying_type = std::underlying_type_t<EnumT>;
template <EnumT cursor, EnumT last>
static constexpr
EnumT
_from (std::string_view name)
{
static_assert (underlying_type (cursor) <= underlying_type (last));
// The terminating case. We haven't found the value.
if constexpr (cursor == last) {
throw std::invalid_argument (std::string (name));
// Compare to the string version of the cursor (if there is one),
// recursing to the next candidate if it's not found.
} else {
auto constexpr cursor_name = to_string<EnumT, cursor> ();
if constexpr (!cursor_name.empty ())
if (cursor_name == name)
return cursor;
constexpr auto next = static_cast<EnumT> (
static_cast<underlying_type> (cursor) + 1
);
return _from<next, last> (name);
}
}
// Convert a string to an enum by iterating over every possible value in a
// given range and comparing the strings against the provided string.
//
// If the bounds here are incorrect then the result will never be found.
// It should be possible to change this search range fairly easily, though
// for larger values you might run into compile time recursion limits.
static constexpr
EnumT
from (std::string_view name)
{
return _from<EnumT (0), EnumT (31)> (name);
}
template <EnumT cursor, EnumT last>
static constexpr
std::string_view
_to (EnumT val)
{
if constexpr (cursor == last)
throw std::invalid_argument ("Unknown enum value");
else if (val != cursor) {
constexpr auto next = static_cast<EnumT> (
static_cast<underlying_type> (cursor) + 1
);
return _to<next, last> (val);
} else {
return to_string<EnumT, cursor> ();
}
}
static constexpr
std::string_view
to (EnumT val)
{
return _to<EnumT (0), EnumT (128)> (val);
}
};
}
template <typename EnumT>
requires (std::is_enum_v<EnumT>)
constexpr
EnumT
from_string (std::string_view str)
{
return detail::helper<EnumT>::from (str);
}
template <typename EnumT>
requires (std::is_enum_v<EnumT>)
constexpr
std::string_view
to_string (EnumT val)
{
return detail::helper<EnumT>::to (val);
}
}
#pragma GCC diagnostic pop