aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.flagset
diff options
context:
space:
mode:
Diffstat (limited to 'nihil.flagset')
-rw-r--r--nihil.flagset/CMakeLists.txt23
-rw-r--r--nihil.flagset/flagset.ccm228
-rw-r--r--nihil.flagset/test.cc149
3 files changed, 400 insertions, 0 deletions
diff --git a/nihil.flagset/CMakeLists.txt b/nihil.flagset/CMakeLists.txt
new file mode 100644
index 0000000..be9b99b
--- /dev/null
+++ b/nihil.flagset/CMakeLists.txt
@@ -0,0 +1,23 @@
+# This source code is released into the public domain.
+
+add_library(nihil.flagset STATIC)
+target_sources(nihil.flagset
+ PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
+ flagset.ccm
+)
+
+if(NIHIL_TESTS)
+ enable_testing()
+
+ add_executable(nihil.flagset.test test.cc)
+ target_link_libraries(nihil.flagset.test PRIVATE
+ nihil.flagset
+ Catch2::Catch2WithMain
+ )
+
+ find_package(Catch2 REQUIRED)
+
+ include(CTest)
+ include(Catch)
+ catch_discover_tests(nihil.flagset.test)
+endif()
diff --git a/nihil.flagset/flagset.ccm b/nihil.flagset/flagset.ccm
new file mode 100644
index 0000000..8369b75
--- /dev/null
+++ b/nihil.flagset/flagset.ccm
@@ -0,0 +1,228 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+/*
+ * flagset: a type-type flags type.
+ */
+
+#include <concepts>
+#include <cstdint>
+#include <iostream>
+#include <limits>
+#include <print>
+
+export module nihil.flagset;
+
+namespace nihil {
+
+export template<std::integral base_type, typename Tag>
+struct flagset final {
+ using underlying_type = base_type;
+
+ /*
+ * Create an empty flags.
+ */
+ flagset() noexcept = default;
+
+ /*
+ * Copyable.
+ */
+ flagset(flagset const &other) noexcept
+ : m_value(other.m_value)
+ {}
+
+ /*
+ * Create flags from an integer mask.
+ */
+ template<base_type flag>
+ [[nodiscard]] static constexpr auto mask() noexcept -> flagset
+ {
+ return flagset(flag);
+ }
+
+ /*
+ * Create flags for a specific bit.
+ */
+ template<unsigned bitnr>
+ [[nodiscard]] static constexpr auto bit() noexcept -> flagset
+ {
+ static_assert(bitnr < std::numeric_limits<base_type>::digits);
+ return flagset(static_cast<base_type>(1) << bitnr);
+ }
+
+ /*
+ * Create flags from a runtime value.
+ */
+ [[nodiscard]] static auto from_int(base_type value) noexcept
+ -> flagset
+ {
+ return flagset(value);
+ }
+
+ /*
+ * Assign this flagset.
+ */
+ auto operator=(this flagset &lhs, flagset rhs) noexcept
+ -> flagset &
+ {
+ if (&lhs != &rhs)
+ lhs.m_value = rhs.m_value;
+ return lhs;
+ }
+
+ /*
+ * The integer value of this flagset.
+ */
+ [[nodiscard]] constexpr auto value(this flagset self) noexcept
+ -> base_type
+ {
+ return self.m_value;
+ }
+
+ /*
+ * True if this flagset has any bits set.
+ */
+ [[nodiscard]] explicit constexpr operator bool(this flagset self)
+ noexcept
+ {
+ return self.m_value != 0;
+ }
+
+ /*
+ * Set bits.
+ */
+ constexpr auto operator|= (this flagset &lhs, flagset rhs) noexcept
+ -> flagset &
+ {
+ lhs.m_value |= rhs.value();
+ return lhs;
+ }
+
+ /*
+ * Mask bits.
+ */
+ constexpr auto operator&= (this flagset &lhs, flagset rhs) noexcept
+ -> flagset &
+ {
+ lhs.m_value &= rhs.value();
+ return lhs;
+ }
+
+ /*
+ * Invert bits.
+ */
+ [[nodiscard]] constexpr auto operator~ (this flagset self) noexcept
+ -> flagset
+ {
+ return flagset(~self.m_value);
+ }
+
+ /*
+ * xor bits.
+ */
+ constexpr auto operator^= (this flagset &lhs, flagset rhs)
+ noexcept -> flagset
+ {
+ lhs.m_value ^= rhs.value();
+ return lhs;
+ }
+
+private:
+ base_type m_value = 0;
+
+ explicit constexpr flagset(base_type mask) noexcept
+ : m_value(mask)
+ {}
+};
+
+export template<std::integral base_type, typename Tag>
+[[nodiscard]] auto operator| (flagset<base_type, Tag> lhs,
+ flagset<base_type, Tag> rhs) noexcept
+ -> flagset<base_type, Tag>
+{
+ return (lhs |= rhs);
+}
+
+export template<std::integral base_type, typename Tag>
+[[nodiscard]] auto operator& (flagset<base_type, Tag> lhs,
+ flagset<base_type, Tag> rhs) noexcept
+ -> flagset<base_type, Tag>
+{
+ return (lhs &= rhs);
+}
+
+export template<std::integral base_type, typename Tag>
+[[nodiscard]] auto operator^ (flagset<base_type, Tag> lhs,
+ flagset<base_type, Tag> rhs) noexcept
+ -> flagset<base_type, Tag>
+{
+ return (lhs ^= rhs);
+}
+
+export template<std::integral base_type, typename Tag>
+[[nodiscard]] auto operator== (flagset<base_type, Tag> lhs,
+ flagset<base_type, Tag> rhs) noexcept
+ -> bool
+{
+ return lhs.value() == rhs.value();
+}
+
+export template<std::integral base_type, typename Tag>
+auto operator<<(std::ostream &strm, flagset<base_type, Tag> flags)
+ -> std::ostream &
+{
+ std::print(strm, "{}", flags);
+ return strm;
+}
+
+} // namespace nihil
+
+/*
+ * Formatting for flagset.
+ */
+export template<std::integral base_type, typename Tag, typename Char>
+struct std::formatter<nihil::flagset<base_type, Tag>, Char>
+{
+ using flags_t = nihil::flagset<base_type, Tag>;
+
+ template<typename ParseContext>
+ constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator
+ {
+ return ctx.begin();
+ }
+
+ template<typename FmtContext>
+ auto format(flags_t flags, FmtContext& ctx) const
+ -> FmtContext::iterator
+ {
+ auto constexpr digits = std::numeric_limits<base_type>::digits;
+ auto value = flags.value();
+ auto it = ctx.out();
+ *it++ = Char{'<'};
+
+ auto printed_any = false;
+
+ for (unsigned i = 0; i < digits; ++i) {
+ unsigned bit = (digits - 1) - i;
+
+ auto this_bit = static_cast<base_type>(1) << bit;
+ if ((value & this_bit) == 0)
+ continue;
+
+ if (printed_any)
+ *it++ = Char{','};
+
+ if (bit > 10)
+ *it++ = Char{'0'} + (bit / 10);
+ *it++ = Char{'0'} + (bit % 10);
+
+ printed_any = true;
+ }
+
+ *it++ = Char{'>'};
+ return it;
+ }
+};
diff --git a/nihil.flagset/test.cc b/nihil.flagset/test.cc
new file mode 100644
index 0000000..c3ebd35
--- /dev/null
+++ b/nihil.flagset/test.cc
@@ -0,0 +1,149 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <concepts>
+#include <format>
+#include <sstream>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.flagset;
+
+namespace {
+
+struct test_tag{};
+using testflags = nihil::flagset<unsigned, test_tag>;
+
+constexpr auto zero = testflags::bit<0>();
+constexpr auto one = testflags::bit<1>();
+constexpr auto two = testflags::bit<2>();
+constexpr auto twelve = testflags::bit<12>();
+
+}
+
+TEST_CASE("flagset: invariant", "[nihil]")
+{
+ static_assert(std::regular<testflags>);
+ static_assert(sizeof(testflags) == sizeof(testflags::underlying_type));
+}
+
+TEST_CASE("flagset: bit<>", "[nihil]")
+{
+ REQUIRE(zero.value() == 0x1);
+ REQUIRE(one.value() == 0x2);
+}
+
+TEST_CASE("flagset: mask<>", "[nihil]")
+{
+ auto zero_ = testflags::mask<0x1>();
+ auto one_ = testflags::mask<0x2>();
+
+ REQUIRE(zero_ == zero);
+ REQUIRE(one_ == one);
+}
+
+TEST_CASE("flagset: constructor", "[nihil]")
+{
+ SECTION("default construct") {
+ auto flags = testflags();
+ REQUIRE(flags.value() == 0);
+ }
+
+ SECTION("construct from int") {
+ auto flags = testflags::from_int(one.value() | zero.value());
+ REQUIRE(flags == (one | zero));
+ }
+
+ SECTION("copy construct") {
+ auto flags = one;
+ auto flags2(flags);
+ REQUIRE(flags == flags2);
+ }
+}
+
+TEST_CASE("flagset: operators", "[nihil]")
+{
+ SECTION("operator|") {
+ REQUIRE((zero | one).value() == 0x3);
+ }
+
+ SECTION("operator|=") {
+ auto flags = zero;
+ flags |= one;
+ REQUIRE(flags.value() == 0x3);
+ }
+
+ SECTION("operator&") {
+ auto flags = zero | one;
+
+ REQUIRE((flags & zero) == zero);
+ }
+
+ SECTION("operator&=") {
+ auto flags = zero | one | two;
+ REQUIRE(flags.value() == 0x7);
+ flags &= (zero | one);
+ REQUIRE(flags.value() == 0x3);
+ }
+
+ SECTION("operator^") {
+ auto flags = zero | one;
+ REQUIRE((flags ^ (one | two)) == (zero | two));
+ }
+
+ SECTION("operator^=") {
+ auto flags = zero | one;
+ flags ^= (one | two);
+ REQUIRE(flags == (zero | two));
+ }
+
+ SECTION("operator~") {
+ auto flags = ~zero;
+ REQUIRE(flags.value() == ~static_cast<unsigned>(1));
+ }
+
+ SECTION("operator==") {
+ auto flags = zero;
+ REQUIRE(flags == zero);
+ REQUIRE(flags != one);
+ }
+}
+
+TEST_CASE("flagset: assignment", "[nihil]")
+{
+ auto flags = zero;
+ REQUIRE(flags == zero);
+
+ flags = one;
+ REQUIRE(flags == one);
+ REQUIRE(flags != zero);
+}
+
+TEST_CASE("flagset: format", "[nihil]")
+{
+ REQUIRE(std::format("{}", testflags()) == "<>");
+ REQUIRE(std::format("{}", zero) == "<0>");
+ REQUIRE(std::format("{}", one) == "<1>");
+ REQUIRE(std::format("{}", zero | one) == "<1,0>");
+
+ REQUIRE(std::format("{}", twelve) == "<12>");
+ REQUIRE(std::format("{}", twelve | one) == "<12,1>");
+}
+
+TEST_CASE("flagset: ostream operator<<", "[nihil]")
+{
+ auto write = [] (testflags flags) -> std::string {
+ auto strm = std::ostringstream();
+ strm << flags;
+ return strm.str();
+ };
+
+ REQUIRE(write(testflags()) == "<>");
+ REQUIRE(write(zero) == "<0>");
+ REQUIRE(write(one) == "<1>");
+ REQUIRE(write(zero | one) == "<1,0>");
+
+ REQUIRE(write(twelve) == "<12>");
+ REQUIRE(write(twelve | one) == "<12,1>");
+}