summaryrefslogtreecommitdiff
path: root/Source/Core/Common/EnumFormatter.h
blob: 77c25989ad8d1d67ea3b004d4f53714c2e5d54e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "Common/EnumMap.h"

#include <fmt/format.h>
#include <type_traits>

/*
 * Helper for using enums with fmt.
 *
 * Usage example:
 *
 * enum class Foo
 * {
 *   A = 0,
 *   B = 1,
 *   C = 2,
 * };
 *
 * template <>
 * struct fmt::formatter<Foo> : EnumFormatter<Foo::C>
 * {
 *   constexpr formatter() : EnumFormatter({"A", "B", "C"}) {}
 * };
 *
 * enum class Bar
 * {
 *   D = 0,
 *   E = 1,
 *   F = 3,
 * };
 *
 * template <>
 * struct fmt::formatter<Bar> : EnumFormatter<Bar::F>
 * {
 *   // 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"};
 *   constexpr formatter() : EnumFormatter(names) {}
 * };
 */
template <Common::Enum auto last_member>
class EnumFormatter
{
  using T = decltype(last_member);

public:
  constexpr auto parse(fmt::format_parse_context& ctx)
  {
    auto it = ctx.begin(), end = ctx.end();
    // 'u' for user display, 's' for shader generation, 'n' for name only
    if (it != end && (*it == 'u' || *it == 's' || *it == 'n'))
      format_type = *it++;
    return it;
  }

  template <typename FormatContext>
  auto format(const T& e, FormatContext& ctx) const
  {
    const auto value_s = static_cast<std::underlying_type_t<T>>(e);      // Possibly signed
    const auto value_u = static_cast<std::make_unsigned_t<T>>(value_s);  // Always unsigned
    const bool has_name = m_names.InBounds(e) && m_names[e] != nullptr;

    switch (format_type)
    {
    default:
    case 'u':
      if (has_name)
        return fmt::format_to(ctx.out(), "{} ({})", m_names[e], value_s);
      else
        return fmt::format_to(ctx.out(), "Invalid ({})", value_s);
    case 's':
      if (has_name)
        return fmt::format_to(ctx.out(), "{:#x}u /* {} */", value_u, m_names[e]);
      else
        return fmt::format_to(ctx.out(), "{:#x}u /* Invalid */", value_u);
    case 'n':
      if (has_name)
        return fmt::format_to(ctx.out(), "{}", m_names[e]);
      else
        return fmt::format_to(ctx.out(), "Invalid ({})", value_s);
    }
  }

protected:
  // This is needed because std::array deduces incorrectly if nullptr is included in the list
  using array_type = Common::EnumMap<const char*, last_member>;

  constexpr explicit EnumFormatter(const array_type names) : m_names(std::move(names)) {}

  const array_type m_names;
  char format_type = 'u';
};