From 5dd58a93b3ec0ba96acc8f833ccd19fb389956be Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Fri, 1 Apr 2022 13:46:13 +1000 Subject: [PATCH] introspection: add simple enum mode that parses debugging macros --- CMakeLists.txt | 3 + introspection/enum_simple.cpp | 1 + introspection/enum_simple.hpp | 175 +++++++++++++++++++++++++++++ test/introspection/enum_simple.cpp | 21 ++++ 4 files changed, 200 insertions(+) create mode 100644 introspection/enum_simple.cpp create mode 100644 introspection/enum_simple.hpp create mode 100644 test/introspection/enum_simple.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 215ea5bc..02f94bf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/introspection/enum_simple.cpp b/introspection/enum_simple.cpp new file mode 100644 index 00000000..7b5ad536 --- /dev/null +++ b/introspection/enum_simple.cpp @@ -0,0 +1 @@ +#include "./enum_simple.hpp" \ No newline at end of file diff --git a/introspection/enum_simple.hpp b/introspection/enum_simple.hpp new file mode 100644 index 00000000..42b4680e --- /dev/null +++ b/introspection/enum_simple.hpp @@ -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 + */ + +#pragma once + +#include +#include +#include + + +namespace cruft::introspection { + namespace detail { + /// Return the full pretty signature of the function for parsing. + /// + /// It must include an enum literal. + template + 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 + requires (std::is_enum_v) + 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 (); + + 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]" + // + // consteval std::string_view enum_pretty_function() [with EnumT = enum_t; EnumT Value = (enum_t)42; 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.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 + 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 + 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 std::string_view + to (EnumT val) + { + return _to (val); + } + }; + } + + + template + requires (std::is_enum_v) + EnumT + from_string (std::string_view str) + { + return detail::helper::from (str); + } + + + template + requires (std::is_enum_v) + std::string_view + to_string (EnumT val) + { + return detail::helper::to (val); + } +} diff --git a/test/introspection/enum_simple.cpp b/test/introspection/enum_simple.cpp new file mode 100644 index 00000000..5804bd8b --- /dev/null +++ b/test/introspection/enum_simple.cpp @@ -0,0 +1,21 @@ +#include +#include + +#include + + +int +main () +{ + cruft::TAP::logger tap; + + enum enum_t { value0 = 0, value1 = 1, value3 = 3, }; + + std::cout << cruft::introspection::detail::enum_pretty_function () << '\n'; + + tap.expect_eq (cruft::introspection::from_string ("value1"), value1, "from_string, dynamic"); + tap.expect_eq (cruft::introspection::to_string (), "value1", "to_string, static"); + tap.expect_eq (cruft::introspection::to_string (value1), "value1", "to_string, dynamic"); + + return tap.status (); +} \ No newline at end of file