summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Goodliffe <dan@randomdan.homeip.net>2022-10-22 17:57:47 +0100
committerDan Goodliffe <dan@randomdan.homeip.net>2022-10-22 17:57:47 +0100
commit38d7045685d4904d013ea4990a3aa7f5e78309cf (patch)
tree60915147de5e37dc4a3fab39408b6abbfc49ce09
parentAdd free extend builder with placeholder network support (diff)
downloadilt-38d7045685d4904d013ea4990a3aa7f5e78309cf.tar.bz2
ilt-38d7045685d4904d013ea4990a3aa7f5e78309cf.tar.xz
ilt-38d7045685d4904d013ea4990a3aa7f5e78309cf.zip
Add magic support to printing/parsing/validating enumerations
-rw-r--r--lib/enumDetails.hpp133
-rw-r--r--lib/stream_support.hpp11
-rw-r--r--test/Jamfile.jam2
-rw-r--r--test/enumDetailsData.hpp20
-rw-r--r--test/test-enumDetails.cpp50
-rw-r--r--test/test-static-enumDetails.cpp78
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());
+}