diff options
author | Dan Goodliffe <dan@randomdan.homeip.net> | 2022-10-22 17:57:47 +0100 |
---|---|---|
committer | Dan Goodliffe <dan@randomdan.homeip.net> | 2022-10-22 17:57:47 +0100 |
commit | 38d7045685d4904d013ea4990a3aa7f5e78309cf (patch) | |
tree | 60915147de5e37dc4a3fab39408b6abbfc49ce09 | |
parent | Add free extend builder with placeholder network support (diff) | |
download | ilt-38d7045685d4904d013ea4990a3aa7f5e78309cf.tar.bz2 ilt-38d7045685d4904d013ea4990a3aa7f5e78309cf.tar.xz ilt-38d7045685d4904d013ea4990a3aa7f5e78309cf.zip |
Add magic support to printing/parsing/validating enumerations
-rw-r--r-- | lib/enumDetails.hpp | 133 | ||||
-rw-r--r-- | lib/stream_support.hpp | 11 | ||||
-rw-r--r-- | test/Jamfile.jam | 2 | ||||
-rw-r--r-- | test/enumDetailsData.hpp | 20 | ||||
-rw-r--r-- | test/test-enumDetails.cpp | 50 | ||||
-rw-r--r-- | test/test-static-enumDetails.cpp | 78 |
6 files changed, 294 insertions, 0 deletions
diff --git a/lib/enumDetails.hpp b/lib/enumDetails.hpp new file mode 100644 index 0000000..ace6252 --- /dev/null +++ b/lib/enumDetails.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include <algorithm> +#include <array> +#include <limits> +#include <optional> +#include <string_view> + +/// EnumTypeDetails +// Extracts the fully qualified name of the enumeration +template<typename E> struct EnumTypeDetails { +#ifndef ENUM_PROBE +protected: +#endif + constexpr static std::string_view SEARCH_TYPE {"E = "}; + constexpr static auto + typeraw() + { + return std::string_view {__PRETTY_FUNCTION__}; + }; + constexpr static auto typeNameStart {typeraw().find(SEARCH_TYPE) + SEARCH_TYPE.length()}; + constexpr static auto typeNameEnd {typeraw().find_first_of("];", typeNameStart)}; + constexpr static auto typeNameLen {typeNameEnd - typeNameStart}; + constexpr static auto typeNameArr {[]() { + std::array<char, typeNameLen> out; + typeraw().copy(out.begin(), typeNameEnd - typeNameStart, typeNameStart); + return out; + }()}; + +public: + constexpr static std::string_view typeName {typeNameArr.data(), typeNameArr.size()}; +}; + +/// EnumValueDetails +// Extracts the value name and constructs string_views of the parts +template<auto value> struct EnumValueDetails : public EnumTypeDetails<decltype(value)> { +#ifndef ENUM_PROBE +private: +#endif + using T = EnumTypeDetails<decltype(value)>; + constexpr static auto + raw() + { + return std::string_view {__PRETTY_FUNCTION__}; + }; + constexpr static auto nameStart {raw().find_last_of(": ") + 1}; + constexpr static auto nameEnd {raw().find_first_of("];", nameStart)}; + constexpr static auto nameLen {nameEnd - nameStart}; + constexpr static auto nameArr {[]() { + std::array<char, nameLen> out; + raw().copy(out.begin(), nameLen, nameStart); + return out; + }()}; + +public: + constexpr static std::string_view valueName {nameArr.data(), nameArr.size()}; + constexpr static auto valid {valueName.back() < '0' || valueName.back() > '9'}; + constexpr static auto raw_value {value}; +}; + +template<typename E> struct EnumValueCollection { + using Vs = std::make_integer_sequence<int, 256>; +}; + +template<typename E> struct EnumDetails { +public: + using EVC = EnumValueCollection<E>; + +#ifndef ENUM_PROBE +private: +#endif + template<auto... n> + constexpr static auto + get_valids(std::integer_sequence<int, n...>) + { + return std::array {EnumValueDetails<static_cast<E>(n)>::valid...}; + } + template<auto... n> + constexpr static auto + get_values(std::integer_sequence<int, n...>) + { + return std::array {EnumValueDetails<static_cast<E>(n)>::raw_value...}; + } + template<auto... n> + constexpr static auto + get_valueNames(std::integer_sequence<int, n...>) + { + return std::array {EnumValueDetails<values[n]>::valueName...}; + } + + constexpr static auto valid_flags {get_valids(typename EVC::Vs {})}; + constexpr static auto valid_count {std::count_if(valid_flags.begin(), valid_flags.end(), std::identity {})}; + +public: + constexpr static auto values {[]() { + constexpr auto values {get_values(typename EVC::Vs {})}; + static_assert(std::is_sorted(values.begin(), values.end()), "Candidate values must be sorted"); + std::array<E, valid_count> out; + auto write = out.begin(); + for (auto v = values.begin(); const bool & valid : valid_flags) { + if (valid) { + *write++ = static_cast<E>(*v); + } + ++v; + } + return out; + }()}; + constexpr static auto names {get_valueNames(std::make_integer_sequence<int, valid_count> {})}; + + constexpr static bool + is_valid(E value) noexcept + { + return std::binary_search(values.begin(), values.end(), value); + } + + constexpr static std::optional<E> + parse(std::string_view name) noexcept + { + if (const auto itr = std::find(names.begin(), names.end(), name); itr != names.end()) { + return values[std::distance(names.begin(), itr)]; + } + return std::nullopt; + } + + constexpr static std::optional<std::string_view> + to_string(E value) noexcept + { + if (const auto itr = std::find(values.begin(), values.end(), value); itr != values.end()) { + return names[std::distance(values.begin(), itr)]; + } + return std::nullopt; + } +}; diff --git a/lib/stream_support.hpp b/lib/stream_support.hpp index 5238234..2ab2f9d 100644 --- a/lib/stream_support.hpp +++ b/lib/stream_support.hpp @@ -1,5 +1,6 @@ #pragma once +#include "enumDetails.hpp" #include <glm/glm.hpp> #include <iostream> #include <maths.h> @@ -51,6 +52,16 @@ namespace std { { return s << arc.first << " ↺ " << arc.second; } + + template<typename E> + concept IsEnum = std::is_enum_v<E>; + + template<IsEnum E> + inline std::ostream & + operator<<(std::ostream & s, const E & e) + { + return s << EnumTypeDetails<E>::typeName << "::" << EnumDetails<E>::to_string(e).value(); + } } template<typename T> diff --git a/test/Jamfile.jam b/test/Jamfile.jam index afe58c8..7606ae4 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -29,3 +29,5 @@ run test-geo.cpp ; run test-network.cpp ; run test-persistence.cpp : -- : [ sequence.insertion-sort [ glob fixtures/json/*.json fixtures/json/bad/*.json ] ] ; run test-text.cpp ; +run test-enumDetails.cpp ; +compile test-static-enumDetails.cpp ; diff --git a/test/enumDetailsData.hpp b/test/enumDetailsData.hpp new file mode 100644 index 0000000..0e98af5 --- /dev/null +++ b/test/enumDetailsData.hpp @@ -0,0 +1,20 @@ +#pragma once +#include <enumDetails.hpp> + +enum GlobalUnscoped { aa, b, c }; +enum class GlobalScoped { aa, b, c }; +namespace ns { + enum Unscoped { aa, b, c }; + enum class Scoped { aa, b, c }; +} +namespace test1 { + enum class DefaultDense { a, bee, ci, de }; +} +namespace test2 { + enum class NumberedSparse { a = 0, bee = 3, ci = -20, de = 100 }; +} + +template<> struct EnumValueCollection<test2::NumberedSparse> { + // Any ordered integer_sequence which includes all enumeration values + using Vs = std::integer_sequence<int, -100, -20, 0, 3, 10, 100, 1000>; +}; diff --git a/test/test-enumDetails.cpp b/test/test-enumDetails.cpp new file mode 100644 index 0000000..c803cf8 --- /dev/null +++ b/test/test-enumDetails.cpp @@ -0,0 +1,50 @@ +#define BOOST_TEST_MODULE test_enumDetails + +#include "enumDetailsData.hpp" +#include <boost/test/data/test_case.hpp> +#include <boost/test/unit_test.hpp> +#include <enumDetails.hpp> +#include <stream_support.hpp> + +constexpr std::array INVALID_NAMES {"", "missing", "GlobalScoped::aa", "GlobalScoped", "ns::aa", "a", "bb"}; +constexpr std::array VALID_NAMES {"aa", "b", "c"}; +template<typename E> constexpr std::array VALID_VALUES {E::aa, E::b, E::c}; +// Not a template, else Boost test framework throws printing the context +constexpr std::array INVALID_VALUES {-1, 3, 20}; + +#define TESTS_FOR_TYPE(TYPE) \ + BOOST_DATA_TEST_CASE(invalid_check_##TYPE, INVALID_VALUES, in) \ + { \ + BOOST_CHECK(!EnumDetails<TYPE>::is_valid(static_cast<TYPE>(in))); \ + } \ + BOOST_DATA_TEST_CASE(invalid_parse_##TYPE, INVALID_NAMES, in) \ + { \ + BOOST_CHECK(!EnumDetails<TYPE>::parse(in).has_value()); \ + } \ + BOOST_DATA_TEST_CASE(invalid_to_string_##TYPE, INVALID_VALUES, in) \ + { \ + BOOST_CHECK(!EnumDetails<TYPE>::to_string(static_cast<TYPE>(in)).has_value()); \ + } \ + BOOST_DATA_TEST_CASE(valid_check_##TYPE, VALID_VALUES<TYPE>, in) \ + { \ + BOOST_CHECK(EnumDetails<TYPE>::is_valid(in)); \ + } \ + BOOST_DATA_TEST_CASE(valid_parse_##TYPE, VALID_NAMES ^ VALID_VALUES<TYPE>, in, out) \ + { \ + const auto v = EnumDetails<TYPE>::parse(in); \ + BOOST_REQUIRE(v.has_value()); \ + BOOST_CHECK_EQUAL(v.value(), out); \ + } \ + BOOST_DATA_TEST_CASE(valid_to_string_##TYPE, VALID_VALUES<TYPE> ^ VALID_NAMES, in, out) \ + { \ + const auto v = EnumDetails<TYPE>::to_string(in); \ + BOOST_CHECK(v.has_value()); \ + BOOST_CHECK_EQUAL(v.value(), out); \ + } + +TESTS_FOR_TYPE(GlobalScoped) +TESTS_FOR_TYPE(GlobalUnscoped) +using ns_unscoped = ns::Unscoped; +using ns_scoped = ns::Scoped; +TESTS_FOR_TYPE(ns_unscoped) +TESTS_FOR_TYPE(ns_scoped) diff --git a/test/test-static-enumDetails.cpp b/test/test-static-enumDetails.cpp new file mode 100644 index 0000000..03c2203 --- /dev/null +++ b/test/test-static-enumDetails.cpp @@ -0,0 +1,78 @@ +#define ENUM_PROBE +#include "enumDetailsData.hpp" +#include <enumDetails.hpp> + +// Test type name +static_assert(EnumTypeDetails<GlobalUnscoped>::typeName == "GlobalUnscoped"); +static_assert(EnumTypeDetails<GlobalScoped>::typeName == "GlobalScoped"); +static_assert(EnumTypeDetails<ns::Unscoped>::typeName == "ns::Unscoped"); +static_assert(EnumTypeDetails<ns::Scoped>::typeName == "ns::Scoped"); + +static_assert(EnumValueDetails<GlobalUnscoped::aa>::valueName == "aa"); +static_assert(EnumValueDetails<GlobalScoped::aa>::valueName == "aa"); +static_assert(EnumValueDetails<ns::Unscoped::aa>::valueName == "aa"); +static_assert(EnumValueDetails<ns::Scoped::aa>::valueName == "aa"); + +namespace test1 { + static_assert(EnumValueDetails<DefaultDense::a>::valid); + static_assert(EnumValueDetails<DefaultDense::de>::valid); + static_assert(EnumValueDetails<static_cast<DefaultDense>(0)>::valid); + static_assert(EnumValueDetails<static_cast<DefaultDense>(3)>::valid); + static_assert(!EnumValueDetails<static_cast<DefaultDense>(-1)>::valid); + static_assert(!EnumValueDetails<static_cast<DefaultDense>(4)>::valid); + static_assert(EnumValueDetails<DefaultDense::a>::valueName == "a"); + static_assert(EnumValueDetails<DefaultDense::de>::valueName == "de"); + using ED_DD = EnumDetails<DefaultDense>; + static_assert(EnumValueCollection<DefaultDense>::Vs::size() == 256); + static_assert(ED_DD::valid_flags.size() == 256); + static_assert(ED_DD::values.size() == 4); + static_assert(std::is_sorted(ED_DD::values.begin(), ED_DD::values.end())); + static_assert(ED_DD::values.at(0) == DefaultDense::a); + static_assert(ED_DD::values.at(3) == DefaultDense::de); + static_assert(ED_DD::names.at(0) == "a"); + static_assert(ED_DD::names.at(3) == "de"); + + static_assert(ED_DD::is_valid(DefaultDense::a)); + static_assert(ED_DD::is_valid(DefaultDense::de)); + static_assert(!ED_DD::is_valid(DefaultDense(-1))); + static_assert(!ED_DD::parse("").has_value()); + static_assert(!ED_DD::parse("nonsense").has_value()); + static_assert(ED_DD::parse("bee").value() == DefaultDense::bee); + static_assert(ED_DD::parse("ci").value() == DefaultDense::ci); + static_assert(ED_DD::to_string(DefaultDense::de).value() == "de"); + static_assert(!ED_DD::to_string(static_cast<DefaultDense>(10)).has_value()); +} + +namespace test2 { + static_assert(EnumValueDetails<NumberedSparse::bee>::valid); + static_assert(EnumValueDetails<static_cast<NumberedSparse>(0)>::valid); + static_assert(EnumValueDetails<static_cast<NumberedSparse>(3)>::valid); + static_assert(EnumValueDetails<static_cast<NumberedSparse>(-20)>::valid); + static_assert(EnumValueDetails<static_cast<NumberedSparse>(100)>::valid); + static_assert(!EnumValueDetails<static_cast<NumberedSparse>(2)>::valid); + static_assert(EnumValueDetails<NumberedSparse::a>::valueName == "a"); + static_assert(EnumValueDetails<NumberedSparse::de>::valueName == "de"); + using ED_NS = EnumDetails<NumberedSparse>; + static_assert(EnumValueCollection<NumberedSparse>::Vs::size() == 7); + static_assert(ED_NS::values.size() == 4); + static_assert(ED_NS::valid_flags.size() == 7); + static_assert(std::is_sorted(ED_NS::values.begin(), ED_NS::values.end())); + static_assert(ED_NS::values.at(0) == NumberedSparse::ci); + static_assert(ED_NS::values.at(1) == NumberedSparse::a); + static_assert(ED_NS::values.at(2) == NumberedSparse::bee); + static_assert(ED_NS::values.at(3) == NumberedSparse::de); + static_assert(ED_NS::names.at(0) == "ci"); + static_assert(ED_NS::names.at(1) == "a"); + static_assert(ED_NS::names.at(2) == "bee"); + static_assert(ED_NS::names.at(3) == "de"); + + static_assert(ED_NS::is_valid(NumberedSparse::a)); + static_assert(ED_NS::is_valid(NumberedSparse::de)); + static_assert(!ED_NS::is_valid(NumberedSparse(-1))); + static_assert(!ED_NS::parse("").has_value()); + static_assert(!ED_NS::parse("nonsense").has_value()); + static_assert(ED_NS::parse("bee").value() == NumberedSparse::bee); + static_assert(ED_NS::parse("ci").value() == NumberedSparse::ci); + static_assert(ED_NS::to_string(NumberedSparse::ci).value() == "ci"); + static_assert(!ED_NS::to_string(static_cast<NumberedSparse>(10)).has_value()); +} |