From 6653bd71994d7866ce79de61b12a2261e6451ea0 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Wed, 10 Feb 2021 12:46:32 -0800 Subject: [PATCH] Create EnumFormatter --- Source/Core/Common/CMakeLists.txt | 1 + Source/Core/Common/EnumFormatter.h | 91 +++++++++++++++++++ Source/Core/DolphinLib.props | 1 + Source/UnitTests/Common/CMakeLists.txt | 1 + Source/UnitTests/Common/EnumFormatterTest.cpp | 67 ++++++++++++++ Source/UnitTests/UnitTests.vcxproj | 1 + 6 files changed, 162 insertions(+) create mode 100644 Source/Core/Common/EnumFormatter.h create mode 100644 Source/UnitTests/Common/EnumFormatterTest.cpp diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 4a3623d4e8..9f7486a571 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -42,6 +42,7 @@ add_library(common DynamicLibrary.h ENetUtil.cpp ENetUtil.h + EnumFormatter.h Event.h FileSearch.cpp FileSearch.h diff --git a/Source/Core/Common/EnumFormatter.h b/Source/Core/Common/EnumFormatter.h new file mode 100644 index 0000000000..2ca1bb114e --- /dev/null +++ b/Source/Core/Common/EnumFormatter.h @@ -0,0 +1,91 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +/* + * Helper for using enums with fmt. + * + * Usage example: + * + * enum class Foo + * { + * A = 0, + * B = 1, + * C = 2, + * }; + * + * template <> + * struct fmt::formatter : EnumFormatter + * { + * formatter() : EnumFormatter({"A", "B", "C"}) {} + * }; + * + * enum class Bar + * { + * D = 0, + * E = 1, + * F = 3, + * }; + * + * template <> + * struct fmt::formatter : EnumFormatter + * { + * // using std::array here fails due to nullptr not being const char*, at least in MSVC + * // (but only when a field is used; directly in the constructor is OK) + * static constexpr array_type names = {"D", "E", nullptr, "F"}; + * formatter() : EnumFormatter(names) {} + * }; + */ +template (last_member) + 1, + std::enable_if_t, bool> = true> +class EnumFormatter +{ +public: + constexpr auto parse(fmt::format_parse_context& ctx) + { + auto it = ctx.begin(), end = ctx.end(); + // 'u' for user display, 's' for shader generation + if (it != end && (*it == 'u' || *it == 's')) + formatting_for_shader = (*it++ == 's'); + return it; + } + + template + auto format(const T& e, FormatContext& ctx) + { + const auto value = static_cast>(e); + + if (!formatting_for_shader) + { + if (value >= 0 && value < size && m_names[value] != nullptr) + return fmt::format_to(ctx.out(), "{} ({})", m_names[value], value); + else + return fmt::format_to(ctx.out(), "Invalid ({})", value); + } + else + { + if (value >= 0 && value < size && m_names[value] != nullptr) + return fmt::format_to(ctx.out(), "{:#x}u /* {} */", value, m_names[value]); + else + return fmt::format_to(ctx.out(), "{:#x}u /* Invalid */", + static_cast>(value)); + } + } + +protected: + // This is needed because std::array deduces incorrectly if nullptr is included in the list + using array_type = std::array; + + constexpr explicit EnumFormatter(const array_type names) : m_names(std::move(names)) {} + +private: + const array_type m_names; + bool formatting_for_shader = false; +}; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index d78534993a..638e6734e2 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -45,6 +45,7 @@ + diff --git a/Source/UnitTests/Common/CMakeLists.txt b/Source/UnitTests/Common/CMakeLists.txt index 120fee6c33..86d9dedfcf 100644 --- a/Source/UnitTests/Common/CMakeLists.txt +++ b/Source/UnitTests/Common/CMakeLists.txt @@ -5,6 +5,7 @@ add_dolphin_test(BlockingLoopTest BlockingLoopTest.cpp) add_dolphin_test(BusyLoopTest BusyLoopTest.cpp) add_dolphin_test(CommonFuncsTest CommonFuncsTest.cpp) add_dolphin_test(CryptoEcTest Crypto/EcTest.cpp) +add_dolphin_test(EnumFormatterTest EnumFormatterTest.cpp) add_dolphin_test(EventTest EventTest.cpp) add_dolphin_test(FixedSizeQueueTest FixedSizeQueueTest.cpp) add_dolphin_test(FlagTest FlagTest.cpp) diff --git a/Source/UnitTests/Common/EnumFormatterTest.cpp b/Source/UnitTests/Common/EnumFormatterTest.cpp new file mode 100644 index 0000000000..fb713db2f2 --- /dev/null +++ b/Source/UnitTests/Common/EnumFormatterTest.cpp @@ -0,0 +1,67 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include "Common/CommonTypes.h" +#include "Common/EnumFormatter.h" + +enum class Enum1 : u32 +{ + A = 0, + B = 1, + C = 2, +}; + +template <> +struct fmt::formatter : EnumFormatter +{ + formatter() : EnumFormatter({"A", "B", "C"}) {} +}; + +enum class Enum2 : s32 +{ + D = 0, + E = 1, + F = 3, +}; + +template <> +struct fmt::formatter : EnumFormatter +{ + static constexpr array_type names = {"D", "E", nullptr, "F"}; + formatter() : EnumFormatter(names) {} +}; + +TEST(EnumUtil, Enum1) +{ + EXPECT_EQ(fmt::to_string(Enum1::A), "A (0)"); + EXPECT_EQ(fmt::to_string(Enum1::B), "B (1)"); + EXPECT_EQ(fmt::to_string(Enum1::C), "C (2)"); + EXPECT_EQ(fmt::to_string(static_cast(3)), "Invalid (3)"); + EXPECT_EQ(fmt::to_string(static_cast(4)), "Invalid (4)"); + + EXPECT_EQ(fmt::format("{:s}", Enum1::A), "0x0u /* A */"); + EXPECT_EQ(fmt::format("{:s}", Enum1::B), "0x1u /* B */"); + EXPECT_EQ(fmt::format("{:s}", Enum1::C), "0x2u /* C */"); + EXPECT_EQ(fmt::format("{:s}", static_cast(3)), "0x3u /* Invalid */"); + EXPECT_EQ(fmt::format("{:s}", static_cast(4)), "0x4u /* Invalid */"); +} + +TEST(EnumUtil, Enum2) +{ + EXPECT_EQ(fmt::to_string(Enum2::D), "D (0)"); + EXPECT_EQ(fmt::to_string(Enum2::E), "E (1)"); + EXPECT_EQ(fmt::to_string(static_cast(2)), "Invalid (2)"); + EXPECT_EQ(fmt::to_string(Enum2::F), "F (3)"); + EXPECT_EQ(fmt::to_string(static_cast(4)), "Invalid (4)"); + EXPECT_EQ(fmt::to_string(static_cast(-1)), "Invalid (-1)"); + + EXPECT_EQ(fmt::format("{:s}", Enum2::D), "0x0u /* D */"); + EXPECT_EQ(fmt::format("{:s}", Enum2::E), "0x1u /* E */"); + EXPECT_EQ(fmt::format("{:s}", static_cast(2)), "0x2u /* Invalid */"); + EXPECT_EQ(fmt::format("{:s}", Enum2::F), "0x3u /* F */"); + EXPECT_EQ(fmt::format("{:s}", static_cast(4)), "0x4u /* Invalid */"); + EXPECT_EQ(fmt::format("{:s}", static_cast(-1)), "0xffffffffu /* Invalid */"); +} diff --git a/Source/UnitTests/UnitTests.vcxproj b/Source/UnitTests/UnitTests.vcxproj index 230ac50412..d0b0c27fb6 100644 --- a/Source/UnitTests/UnitTests.vcxproj +++ b/Source/UnitTests/UnitTests.vcxproj @@ -49,6 +49,7 @@ +