From 09d0ac768a632bb299acf1fddb797716f38b3ae3 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Mon, 27 Sep 2021 14:27:29 +0100 Subject: [PATCH] Add an enum-compatible bitset class. (#10492) ## Summary of the Pull Request This introduces a new TIL class that is equivalent in functionality to a `std::bitset`, but where the positions in the bitset are enum values. It also has a few additional methods allowing for setting and testing multiple positions at the same time. The idea is that this class could be used in place of the `WI_SetFlag` and `WI_IsFlagSet` macros when working with sets of flags. ## PR Checklist * [x] Closes #10432 * [x] CLA signed. * [x] Tests added/passed * [ ] Documentation updated. * [ ] Schema updated. * [x] I've discussed this with core contributors already. Issue number where discussion took place: #10432 ## Validation Steps Performed I've added a few unit tests that verify the behaviour of all the new methods that aren't part of `std::bitset`. I've also tried it out as a replacement for the `GridLines` enum used in the renderer, and confirmed that it has all the functionality needed to replace that cleanly. --- .github/actions/spelling/allow/apis.txt | 1 + src/inc/til.h | 1 + src/inc/til/enumset.h | 132 ++++++++++++++ src/til/ut_til/EnumSetTests.cpp | 169 ++++++++++++++++++ src/til/ut_til/til.unit.tests.vcxproj | 1 + src/til/ut_til/til.unit.tests.vcxproj.filters | 1 + 6 files changed, 305 insertions(+) create mode 100644 src/inc/til/enumset.h create mode 100644 src/til/ut_til/EnumSetTests.cpp diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 4b6fbe4f5..19f5fb487 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -25,6 +25,7 @@ DERR dlldata DONTADDTORECENT DWORDLONG +enumset environstrings EXPCMDFLAGS EXPCMDSTATE diff --git a/src/inc/til.h b/src/inc/til.h index 3202a5959..59e07cb8e 100644 --- a/src/inc/til.h +++ b/src/inc/til.h @@ -21,6 +21,7 @@ #include "til/replace.h" #include "til/string.h" #include "til/pmr.h" +#include "til/enumset.h" // Use keywords on TraceLogging providers to specify the category // of event that we are emitting for filtering purposes. diff --git a/src/inc/til/enumset.h b/src/inc/til/enumset.h new file mode 100644 index 000000000..7ff45f368 --- /dev/null +++ b/src/inc/til/enumset.h @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include + +namespace til // Terminal Implementation Library. Also: "Today I Learned" +{ + // By design, this class hides several methods in the std::bitset class + // so they can be called with an enum parameter instead of a size_t, so + // we need to disable the "hides a non-virtual function" warning. +#pragma warning(push) +#pragma warning(disable : 26434) + + // til::enumset is a subclass of std::bitset, storing a fixed size array of + // boolean elements, the positions in the array being identified by values + // from a given enumerated type. By default it holds the same number of + // bits as a size_t value. + template::digits> + class enumset : public std::bitset + { + using _base = std::bitset; + + public: + using reference = typename _base::reference; + + enumset() = default; + + // Method Description: + // - Constructs a new bitset with the given list of positions set to true. + template...>>> + constexpr enumset(const Args... positions) noexcept : + _base((... | (1ULL << static_cast(positions)))) + { + } + + // Method Description: + // - Returns the value of the bit at the given position. + constexpr bool operator[](const Type pos) const + { + return _base::operator[](static_cast(pos)); + } + + // Method Description: + // - Returns a reference to the bit at the given position. + reference operator[](const Type pos) + { + return _base::operator[](static_cast(pos)); + } + + // Method Description: + // - Returns the value of the bit at the given position. + // Throws std::out_of_range if it is not a valid position + // in the bitset. + bool test(const Type pos) const + { + return _base::test(static_cast(pos)); + } + + // Method Description: + // - Returns true if any of the bits are set to true. + bool any() const noexcept + { + return _base::any(); + } + + // Method Description: + // - Returns true if any of the bits in the given positions are true. + template...>>> + bool any(const Args... positions) const noexcept + { + return (enumset{ positions... } & *this) != 0; + } + + // Method Description: + // - Returns true if all of the bits are set to true. + bool all() const noexcept + { + return _base::all(); + } + + // Method Description: + // - Returns true if all of the bits in the given positions are true. + template...>>> + bool all(const Args... positions) const noexcept + { + return (enumset{ positions... } & *this) == enumset{ positions... }; + } + + // Method Description: + // - Sets the bit in the given position to the specified value. + enumset& set(const Type pos, const bool val = true) + { + _base::set(static_cast(pos), val); + return *this; + } + + // Method Description: + // - Resets the bit in the given position to false. + enumset& reset(const Type pos) + { + _base::reset(static_cast(pos)); + return *this; + } + + // Method Description: + // - Flips the bit at the given position. + enumset& flip(const Type pos) + { + _base::flip(static_cast(pos)); + return *this; + } + + // Method Description: + // - Sets all of the bits in the given positions to true. + template + enumset& set_all(const Args... positions) + { + return (..., set(positions)); + } + + // Method Description: + // - Resets all of the bits in the given positions to false. + template + enumset& reset_all(const Args... positions) + { + return (..., reset(positions)); + } + }; +#pragma warning(pop) +} diff --git a/src/til/ut_til/EnumSetTests.cpp b/src/til/ut_til/EnumSetTests.cpp new file mode 100644 index 000000000..2a6e0449b --- /dev/null +++ b/src/til/ut_til/EnumSetTests.cpp @@ -0,0 +1,169 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" +#include "WexTestClass.h" + +using namespace WEX::Logging; + +class EnumSetTests +{ + TEST_CLASS(EnumSetTests); + + TEST_METHOD(Constructors) + { + enum class Flags + { + Zero, + One, + Two, + Three, + Four + }; + + { + Log::Comment(L"Default constructor with no bits set"); + til::enumset flags; + VERIFY_ARE_EQUAL(0b00000u, flags.to_ulong()); + } + + { + Log::Comment(L"Constructor with bit 3 set"); + til::enumset flags{ Flags::Three }; + VERIFY_ARE_EQUAL(0b01000u, flags.to_ulong()); + } + + { + Log::Comment(L"Constructor with bits 0, 2, and 4 set"); + til::enumset flags{ Flags::Zero, Flags::Two, Flags::Four }; + VERIFY_ARE_EQUAL(0b10101u, flags.to_ulong()); + } + } + + TEST_METHOD(SetResetFlipMethods) + { + enum class Flags + { + Zero, + One, + Two, + Three, + Four + }; + + Log::Comment(L"Start with no bits set"); + til::enumset flags; + VERIFY_ARE_EQUAL(0b00000u, flags.to_ulong()); + + Log::Comment(L"Set bit 2 to true"); + flags.set(Flags::Two); + VERIFY_ARE_EQUAL(0b00100u, flags.to_ulong()); + + Log::Comment(L"Flip bit 4 to true"); + flags.flip(Flags::Four); + VERIFY_ARE_EQUAL(0b10100u, flags.to_ulong()); + + Log::Comment(L"Set bit 0 to true"); + flags.set(Flags::Zero); + VERIFY_ARE_EQUAL(0b10101u, flags.to_ulong()); + + Log::Comment(L"Reset bit 2 to false, leaving 0 and 4 true"); + flags.reset(Flags::Two); + VERIFY_ARE_EQUAL(0b10001u, flags.to_ulong()); + + Log::Comment(L"Set bit 0 to false, leaving 4 true"); + flags.set(Flags::Zero, false); + VERIFY_ARE_EQUAL(0b10000u, flags.to_ulong()); + + Log::Comment(L"Flip bit 4, leaving all bits false "); + flags.flip(Flags::Four); + VERIFY_ARE_EQUAL(0b00000u, flags.to_ulong()); + + Log::Comment(L"Set bits 0, 3, and 2"); + flags.set_all(Flags::Zero, Flags::Three, Flags::Two); + VERIFY_ARE_EQUAL(0b01101u, flags.to_ulong()); + + Log::Comment(L"Reset bits 3, 4 (already reset), and 0, leaving 2 true"); + flags.reset_all(Flags::Three, Flags::Four, Flags::Zero); + VERIFY_ARE_EQUAL(0b00100u, flags.to_ulong()); + } + + TEST_METHOD(TestMethods) + { + enum class Flags + { + Zero, + One, + Two, + Three, + Four + }; + + Log::Comment(L"Start with bits 0, 2, and 4 set"); + til::enumset flags{ Flags::Zero, Flags::Two, Flags::Four }; + VERIFY_ARE_EQUAL(0b10101u, flags.to_ulong()); + + Log::Comment(L"Test bits 1 and 2 with the test method"); + VERIFY_IS_FALSE(flags.test(Flags::One)); + VERIFY_IS_TRUE(flags.test(Flags::Two)); + + Log::Comment(L"Test bit 3 and 4 with the array operator"); + VERIFY_IS_FALSE(flags[Flags::Three]); + VERIFY_IS_TRUE(flags[Flags::Four]); + + Log::Comment(L"Test if any bits are set"); + VERIFY_IS_TRUE(flags.any()); + Log::Comment(L"Test if either bit 1 or 3 are set"); + VERIFY_IS_FALSE(flags.any(Flags::One, Flags::Three)); + Log::Comment(L"Test if either bit 1 or 4 are set"); + VERIFY_IS_TRUE(flags.any(Flags::One, Flags::Four)); + + Log::Comment(L"Test if all bits are set"); + VERIFY_IS_FALSE(flags.all()); + Log::Comment(L"Test if both bits 0 and 4 are set"); + VERIFY_IS_TRUE(flags.all(Flags::Zero, Flags::Four)); + Log::Comment(L"Test if both bits 0 and 3 are set"); + VERIFY_IS_FALSE(flags.all(Flags::Zero, Flags::Three)); + } + + TEST_METHOD(ArrayReferenceOperator) + { + enum class Flags + { + Zero, + One, + Two, + Three, + Four + }; + + Log::Comment(L"Start with no bits set"); + til::enumset flags; + VERIFY_ARE_EQUAL(0b00000u, flags.to_ulong()); + + Log::Comment(L"Test bit 3 reference is false"); + auto reference = flags[Flags::Three]; + VERIFY_IS_FALSE(reference); + VERIFY_ARE_EQUAL(0b00000u, flags.to_ulong()); + + Log::Comment(L"Set bit 3 reference to true"); + flags.set(Flags::Three); + VERIFY_IS_TRUE(reference); + VERIFY_ARE_EQUAL(0b01000u, flags.to_ulong()); + + Log::Comment(L"Reset bit 3 reference to false"); + flags.reset(Flags::Three); + VERIFY_IS_FALSE(reference); + VERIFY_ARE_EQUAL(0b00000u, flags.to_ulong()); + + Log::Comment(L"Flip bit 3 reference to true"); + reference.flip(); + VERIFY_IS_TRUE(reference); + VERIFY_ARE_EQUAL(0b01000u, flags.to_ulong()); + + Log::Comment(L"Flip bit 3 reference back to false"); + reference.flip(); + VERIFY_IS_FALSE(reference); + VERIFY_ARE_EQUAL(0b00000u, flags.to_ulong()); + } +}; diff --git a/src/til/ut_til/til.unit.tests.vcxproj b/src/til/ut_til/til.unit.tests.vcxproj index 45e812076..48f9a8d3d 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj +++ b/src/til/ut_til/til.unit.tests.vcxproj @@ -17,6 +17,7 @@ + diff --git a/src/til/ut_til/til.unit.tests.vcxproj.filters b/src/til/ut_til/til.unit.tests.vcxproj.filters index 92003b075..2077c3d6c 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj.filters +++ b/src/til/ut_til/til.unit.tests.vcxproj.filters @@ -9,6 +9,7 @@ +