diff options
| -rw-r--r-- | CMakeLists.txt | 1 | ||||
| -rw-r--r-- | nihil.flagset/CMakeLists.txt | 23 | ||||
| -rw-r--r-- | nihil.flagset/flagset.ccm | 228 | ||||
| -rw-r--r-- | nihil.flagset/test.cc | 149 |
4 files changed, 401 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 1efeb51..435f7ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ add_compile_options(-Wextra) add_compile_options(-Werror) add_compile_options(-Wpedantic) +add_subdirectory(nihil.flagset) add_subdirectory(nihil) if(NIHIL_UCL) 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>"); +} |
