/* * 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 */ #pragma once #include #include #include #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 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 requires (std::is_enum_v) 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 (); 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]" // // consteval std::string_view enum_pretty_function() [with EnumT = enum_t; EnumT Value = (enum_t)42; std::string_view = std::basic_string_view] // // "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]" auto const signature = detail::enum_pretty_function (); 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 struct helper { static_assert (std::is_enum_v); using underlying_type = std::underlying_type_t; template 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 (); if constexpr (!cursor_name.empty ()) if (cursor_name == name) return cursor; constexpr auto next = static_cast ( static_cast (cursor) + 1 ); return _from (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 (name); } template 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 ( static_cast (cursor) + 1 ); return _to (val); } else { return to_string (); } } static constexpr std::string_view to (EnumT val) { return _to (val); } }; } template requires (std::is_enum_v) constexpr EnumT from_string (std::string_view str) { return detail::helper::from (str); } template requires (std::is_enum_v) constexpr std::string_view to_string (EnumT val) { return detail::helper::to (val); } } #pragma GCC diagnostic pop