introspection: add simple enum mode that parses debugging macros
This commit is contained in:
parent
4b0900e299
commit
5dd58a93b3
@ -429,6 +429,8 @@ list (
|
||||
init.hpp
|
||||
introspection/enum_manual.cpp
|
||||
introspection/enum_manual.hpp
|
||||
introspection/enum_simple.cpp
|
||||
introspection/enum_simple.hpp
|
||||
introspection/name.cpp
|
||||
introspection/name.hpp
|
||||
introspection/type.cpp
|
||||
@ -747,6 +749,7 @@ if (TESTS)
|
||||
hton
|
||||
io
|
||||
introspection
|
||||
introspection/enum_simple
|
||||
iterator
|
||||
job/dispatch
|
||||
job/queue
|
||||
|
1
introspection/enum_simple.cpp
Normal file
1
introspection/enum_simple.cpp
Normal file
@ -0,0 +1 @@
|
||||
#include "./enum_simple.hpp"
|
175
introspection/enum_simple.hpp
Normal file
175
introspection/enum_simple.hpp
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
|
||||
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 constexpr
|
||||
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>)
|
||||
constexpr 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]
|
||||
|
||||
auto const signature = detail::enum_pretty_function<EnumT, Value> ();
|
||||
|
||||
auto const comma = signature.find (',');
|
||||
auto const coloncolon = signature.find ("::", comma + 1);
|
||||
auto const equals = signature.find ('=', comma + 1);
|
||||
auto const close = 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>]
|
||||
|
||||
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.find ("::", semicolon0 + 1);
|
||||
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
|
||||
EnumT
|
||||
from (std::string_view name)
|
||||
{
|
||||
return _from<EnumT (0), EnumT (128)> (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 std::string_view
|
||||
to (EnumT val)
|
||||
{
|
||||
return _to<EnumT (0), EnumT (128)> (val);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
template <typename EnumT>
|
||||
requires (std::is_enum_v<EnumT>)
|
||||
EnumT
|
||||
from_string (std::string_view str)
|
||||
{
|
||||
return detail::helper<EnumT>::from (str);
|
||||
}
|
||||
|
||||
|
||||
template <typename EnumT>
|
||||
requires (std::is_enum_v<EnumT>)
|
||||
std::string_view
|
||||
to_string (EnumT val)
|
||||
{
|
||||
return detail::helper<EnumT>::to (val);
|
||||
}
|
||||
}
|
21
test/introspection/enum_simple.cpp
Normal file
21
test/introspection/enum_simple.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include <cruft/util/introspection/enum_simple.hpp>
|
||||
#include <cruft/util/tap.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
||||
int
|
||||
main ()
|
||||
{
|
||||
cruft::TAP::logger tap;
|
||||
|
||||
enum enum_t { value0 = 0, value1 = 1, value3 = 3, };
|
||||
|
||||
std::cout << cruft::introspection::detail::enum_pretty_function<enum_t, value0> () << '\n';
|
||||
|
||||
tap.expect_eq (cruft::introspection::from_string<enum_t> ("value1"), value1, "from_string, dynamic");
|
||||
tap.expect_eq (cruft::introspection::to_string<enum_t, value1> (), "value1", "to_string, static");
|
||||
tap.expect_eq (cruft::introspection::to_string (value1), "value1", "to_string, dynamic");
|
||||
|
||||
return tap.status ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user