diff --git a/include/iris/enum_bitops.hpp b/include/iris/enum_bitops.hpp new file mode 100644 index 0000000..f2d2434 --- /dev/null +++ b/include/iris/enum_bitops.hpp @@ -0,0 +1,113 @@ +#ifndef IRIS_ENUM_BITOPS_HPP +#define IRIS_ENUM_BITOPS_HPP + +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#ifndef NDEBUG +#include +#endif + +#include + +namespace iris { + +template +struct bitops_enabled : std::false_type {}; + +template +constexpr bool bitops_enabled_v = bitops_enabled::value; + +namespace detail { + +template +concept bitops_enum_has_min_bit = std::same_as::min_bit)>, int>; + +template +concept bitops_enum_has_max_bit = std::same_as::max_bit)>, int>; + +} // detail + +template +concept BitopsEnabledEnum = std::is_enum_v && bitops_enabled_v; + +inline namespace bitops_operators { + +template +[[nodiscard]] constexpr T operator~(T a) noexcept +{ + return static_cast(~std::to_underlying(a)); +} + +template +[[nodiscard]] constexpr T operator|(T a, T b) noexcept +{ + static_assert(std::is_unsigned_v>); + return static_cast(std::to_underlying(a) | std::to_underlying(b)); +} + +template +constexpr T& operator|=(T& a, T b) noexcept +{ + static_assert(std::is_unsigned_v>); + return a = a | b; +} + +template +[[nodiscard]] constexpr T operator&(T a, T b) noexcept +{ + static_assert(std::is_unsigned_v>); + return static_cast(std::to_underlying(a) & std::to_underlying(b)); +} + +template +constexpr T& operator&=(T& a, T b) noexcept +{ + static_assert(std::is_unsigned_v>); + return a = a & b; +} + +template +[[nodiscard]] constexpr T operator^(T a, T b) noexcept +{ + static_assert(std::is_unsigned_v>); + return static_cast(std::to_underlying(a) ^ std::to_underlying(b)); +} + +template +constexpr T& operator^=(T& a, T b) noexcept +{ + static_assert(std::is_unsigned_v>); + return a = a ^ b; +} + +} // bitops_operators + +template +[[nodiscard]] constexpr bool contains(T a, T b) noexcept +{ + static_assert(std::is_unsigned_v>); + return (std::to_underlying(a) & std::to_underlying(b)) == std::to_underlying(b); +} + +template +[[nodiscard]] constexpr bool contains_single_bit(T a, T b) noexcept +{ + static_assert(std::is_unsigned_v>); + assert(std::has_single_bit(std::to_underlying(b))); + return std::to_underlying(a) & std::to_underlying(b); +} + +template +[[nodiscard]] constexpr bool contains_any_bit(T a, T b) noexcept +{ + static_assert(std::is_unsigned_v>); + return std::to_underlying(a) & std::to_underlying(b); +} + +} // iris + +#endif diff --git a/include/iris/enum_bitops_algorithm.hpp b/include/iris/enum_bitops_algorithm.hpp new file mode 100644 index 0000000..cf7f7fc --- /dev/null +++ b/include/iris/enum_bitops_algorithm.hpp @@ -0,0 +1,35 @@ +#ifndef IRIS_ENUM_BITOPS_ALGORITHM_HPP +#define IRIS_ENUM_BITOPS_ALGORITHM_HPP + +// SPDX-License-Identifier: MIT + +#include + +#include +#include +#include + +namespace iris { + +template +[[nodiscard]] constexpr auto each_bit(T flags) noexcept +{ + static_assert(std::is_unsigned_v>); + static_assert(detail::bitops_enum_has_max_bit); + static_assert(bitops_enabled::max_bit < std::numeric_limits>::digits); + + if constexpr (detail::bitops_enum_has_min_bit) { + static_assert(bitops_enabled::min_bit <= bitops_enabled::max_bit); + return std::views::iota(bitops_enabled::min_bit, bitops_enabled::max_bit + 1) + | std::views::filter([cat = std::to_underlying(flags)](int i) constexpr noexcept -> bool { return (cat >> i) & 1; }) + | std::views::transform([](int i) constexpr noexcept { return static_cast(static_cast>(1) << i); }); + } else { + return std::views::iota(0, bitops_enabled::max_bit + 1) + | std::views::filter([cat = std::to_underlying(flags)](int i) constexpr noexcept -> bool { return (cat >> i) & 1; }) + | std::views::transform([](int i) constexpr noexcept { return static_cast(static_cast>(1) << i); }); + } +} + +} // iris + +#endif diff --git a/include/iris/enum_bitops_io.hpp b/include/iris/enum_bitops_io.hpp new file mode 100644 index 0000000..a38257a --- /dev/null +++ b/include/iris/enum_bitops_io.hpp @@ -0,0 +1,97 @@ +#ifndef IRIS_ENUM_BITOPS_IO_HPP +#define IRIS_ENUM_BITOPS_IO_HPP + +// SPDX-License-Identifier: MIT + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace iris { + +inline namespace bitops_operators { + +template +std::ostream& operator<<(std::ostream& os, T const& val) +{ + using std::to_string; + + if constexpr (requires { + { to_string(val) } -> std::convertible_to; + }) { + return os << to_string(val); + } else { + return os << std::to_underlying(val); + } +} + +} // bitops_operators + +template +[[nodiscard]] constexpr T parse_flag(StringLike auto const& str) noexcept +{ + return bitops_enabled::parse(std::basic_string_view{str}); +} + +template +[[nodiscard]] constexpr T parse_flags(StringLike auto const& str, StringLike auto const& delim) noexcept +{ + using namespace iris::bitops_operators; + + std::basic_string_view str_sv{str}; + std::basic_string_view delim_sv{delim}; + + T res{}; + + for (auto const& r : str_sv | std::views::split(delim_sv)) { + std::basic_string_view const part_str{r.begin(), r.end()}; + + auto const part = iris::parse_flag(part_str); + if (part == T{}) { + return T{}; + } + res |= part; + } + + return res; +} + +} // iris + +namespace std { + +template<::iris::BitopsEnabledEnum T, class CharT> +struct formatter : formatter, CharT> +{ + using base_formatter = formatter, CharT>; + + template + constexpr auto parse(Context& ctx) + { + if (ctx.begin() == ctx.end()) return ctx.end(); + has_format_spec_ = true; + return base_formatter::parse(ctx); + } + + template + auto format(T const& val, Context& ctx) const + { + if (has_format_spec_) { + return base_formatter::format(std::to_underlying(val), ctx); + } + return format_to(ctx.out(), "{}", to_string(val)); + } + +private: + bool has_format_spec_ = false; +}; + +} // std + +#endif diff --git a/include/iris/string.hpp b/include/iris/string.hpp new file mode 100644 index 0000000..131432e --- /dev/null +++ b/include/iris/string.hpp @@ -0,0 +1,18 @@ +#ifndef IRIS_STRING_HPP +#define IRIS_STRING_HPP + +// SPDX-License-Identifier: MIT + +#include +#include + +namespace iris { + +template +concept StringLike = requires(T t) { + std::basic_string_view{t}; +}; + +} // iris + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8a34d20..67b72e9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -139,8 +139,9 @@ if(PROJECT_IS_TOP_LEVEL) set( IRIS_TEST_IRIS_TESTS - type_traits core + type_traits + enum indirect ) diff --git a/test/enum.cpp b/test/enum.cpp new file mode 100644 index 0000000..ac88b16 --- /dev/null +++ b/test/enum.cpp @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT + +#include "iris_test.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +enum class MyFlags : std::uint8_t +{ + FOO = 1 << 0, + BAR = 1 << 1, + BAZ = 1 << 2, +}; + +enum class SpellType : std::uint8_t +{ + TYPE_ATTACK = 1 << 0, + TYPE_DEFENSE = 1 << 1, + + ATTR_FIRE = 1 << 2, + ATTR_WATER = 1 << 3, + ATTR_THUNDER = 1 << 4, +}; + +template <> +struct iris::bitops_enabled : std::true_type +{ + static MyFlags parse(std::string_view sv) noexcept + { + using enum MyFlags; + if (sv == "foo") return FOO; + if (sv == "bar") return BAR; + if (sv == "baz") return BAZ; + return {}; + } +}; + +template <> +struct iris::bitops_enabled : std::true_type +{ + static constexpr int min_bit = 2; + static constexpr int max_bit = 4; +}; + +TEST_CASE("enum") +{ + using namespace iris::bitops_operators; + + using enum MyFlags; + + CHECK((~FOO == static_cast(~std::to_underlying(FOO)))); + + CHECK(((FOO & BAR) == static_cast(std::to_underlying(FOO) & std::to_underlying(BAR)))); + CHECK(((FOO ^ BAR) == static_cast(std::to_underlying(FOO) ^ std::to_underlying(BAR)))); + CHECK(((FOO | BAR) == static_cast(std::to_underlying(FOO) | std::to_underlying(BAR)))); + + CHECK( iris::contains(FOO, FOO)); + CHECK(!iris::contains(FOO, BAR)); + CHECK( iris::contains(FOO | BAR, FOO)); + CHECK(!iris::contains(FOO | BAR, BAZ)); + CHECK(!iris::contains(FOO | BAZ, FOO | BAR)); + CHECK(!iris::contains( BAR | BAZ, FOO | BAR)); + CHECK( iris::contains(FOO | BAR | BAZ, FOO | BAR)); + + CHECK( iris::contains_any_bit(FOO, FOO)); + CHECK(!iris::contains_any_bit(FOO, BAR)); + CHECK( iris::contains_any_bit(FOO | BAR, FOO)); + CHECK(!iris::contains_any_bit(FOO | BAR, BAZ)); + CHECK( iris::contains_any_bit(FOO | BAZ, FOO | BAR)); + CHECK( iris::contains_any_bit( BAR | BAZ, FOO | BAR)); + CHECK( iris::contains_any_bit(FOO | BAR | BAZ, FOO | BAR)); + + CHECK( iris::contains_single_bit(FOO, FOO)); + CHECK(!iris::contains_single_bit(FOO, BAR)); + CHECK( iris::contains_single_bit(FOO | BAR, FOO)); + CHECK(!iris::contains_single_bit(FOO | BAR, BAZ)); + + CHECK((iris::parse_flag("foo") == FOO)); + CHECK((iris::parse_flag("bar") == BAR)); + CHECK((iris::parse_flag("baz") == BAZ)); + CHECK((iris::parse_flag("yay") == MyFlags{})); + + CHECK((iris::parse_flags("foo", "|") == FOO)); + CHECK((iris::parse_flags("yay", "|") == MyFlags{})); + + CHECK((iris::parse_flags("foo|bar", "|") == (FOO | BAR))); + CHECK((iris::parse_flags("foo|yay", "|") == MyFlags{})); + CHECK((iris::parse_flags("foo,bar", "|") == MyFlags{})); + + CHECK(std::ranges::equal( + iris::each_bit(SpellType::TYPE_ATTACK | SpellType::ATTR_FIRE | SpellType::ATTR_THUNDER), + std::vector{SpellType::ATTR_FIRE, SpellType::ATTR_THUNDER} + )); + + CHECK(std::ranges::equal( + iris::each_bit(static_cast(static_cast>(-1))), + std::vector{SpellType::ATTR_FIRE, SpellType::ATTR_WATER, SpellType::ATTR_THUNDER} + )); +}