From b7277f69d3e69349f24358a0ba2b75d2efc9d9ab Mon Sep 17 00:00:00 2001 From: fusiled Date: Fri, 29 May 2026 16:30:54 -0500 Subject: [PATCH] Add logging feature --- .gitmodules | 0 CMakeLists.txt | 13 +- README.md | 24 +- include/Simo/core/Log.h | 105 + include/Simo/core/Time.h | 8 + include/Simo/module/Module.h | 48 +- src/core/Log.cc | 123 + src/module/Module.cc | 17 +- src/third-party/enchantum_single_header.hpp | 2442 +++++++++++++++++++ support/scripts/verify-coverage.sh | 2 +- tests/SimoSim/test_config.yaml | 2 + tests/collection/PingPongCollection.cc | 9 + tests/module/ModuleTest.cc | 99 + 13 files changed, 2875 insertions(+), 17 deletions(-) delete mode 100644 .gitmodules create mode 100644 include/Simo/core/Log.h create mode 100644 src/core/Log.cc create mode 100644 src/third-party/enchantum_single_header.hpp create mode 100644 tests/module/ModuleTest.cc diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29..0000000 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ebbba3..e15b34d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,8 +34,8 @@ if (ENABLE_COVERAGE) add_compile_options("--coverage" "-Og" "-g") add_link_options("--coverage") elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options("-Og" "-g" "--coverage" "-fprofile-instr-generate" "-fcoverage-mapping") - add_link_options("-fprofile-instr-generate" "--coverage" "-fcoverage-mapping") + add_compile_options("-Og" "-g" "-fprofile-instr-generate" "-fcoverage-mapping") + add_link_options("-fprofile-instr-generate" "-fcoverage-mapping") else() message(WARNING "Code coverage not supported for this compiler.") endif () @@ -63,6 +63,7 @@ add_library(Simo SHARED src/module/core/CoreModules.cc src/statistics/StatMapper.cc src/core/InitializationStatus.cc + src/core/Log.cc ) if ("${ENABLE_RELEASE_LTO}") @@ -105,7 +106,7 @@ target_include_directories(Simo PRIVATE src) add_executable(SimoSim src/SimoSim/SimoSim.cc) target_link_libraries(SimoSim PRIVATE Simo) -install(TARGETS Simo) +install(TARGETS Simo SimoSim) install(DIRECTORY include/ DESTINATION include ) @@ -161,6 +162,12 @@ target_sources(test_Statistics PRIVATE ) target_link_libraries(test_Statistics PRIVATE Simo) +create_unit_test_executable(test_Module) +target_sources(test_Module PRIVATE + tests/module/ModuleTest.cc +) +target_link_libraries(test_Module PRIVATE Simo) + add_library(SimoTestPingPongCollection SHARED tests/collection/PingPongCollection.cc ) diff --git a/README.md b/README.md index 86a0ad0..711c819 100644 --- a/README.md +++ b/README.md @@ -15,23 +15,29 @@ it is missing: a simple, easy to use library that does what you need. Guides (Markdown files inside `docs` folder) and Doxygen built from the main branch are stored in **[GitHub Pages](https://fusiled.github.io/Simo/)** . +## Get and initialize the repository + +```bash +git clone https://github.com/fusiled/Simo +cd Simo +git submodule update --init +``` + ## Build +Clone Simo and initialize the submodules: + Build dependencies: - CMake >= 3.31.0 -- Boost ([Boost.Test](https://www.boost.org/libs/test), -[Boost.TypeIndex](https://www.boost.org/libs/type_index)) +- Boost ([Boost.Test](https://www.boost.org/libs/test), [Boost.TypeIndex](https://www.boost.org/libs/type_index)) - [Glaze](https://github.com/stephenberry/glaze) - [Doxygen](https://www.doxygen.nl/index.html) (for documentation) -Library usage dependencies (headers-only): -- Boost ([Boost.TypeIndex](https://www.boost.org/libs/type_index)) -- [Glaze](https://github.com/stephenberry/glaze) - -The build will produce `libSimo`. +The build will produce `libSimo` (core library) and `SimoSim` (a general purpose +executable to run simulations). -The [nix](https://nixos.org/learn/) flake simplifies the set up of the dependencies. With nix installed you can: -- Build the package with `nix build` +The [nix](https://nixos.org/learn/) flake simplifies the setup of the dependencies. With nix installed you can: +- Build the package with `nix build`. The build will be accessible in the `result` folder. - Run `nix develop` to enter a virtual environment with all the required tools installed. The GitHub actions run entering the virtual environment of `nix develop`. diff --git a/include/Simo/core/Log.h b/include/Simo/core/Log.h new file mode 100644 index 0000000..cbd724c --- /dev/null +++ b/include/Simo/core/Log.h @@ -0,0 +1,105 @@ +/* + * Copyright 2026 Matteo Fusi and Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SIMO_LOG_HH +#define SIMO_LOG_HH +#include + +#include +#include +#include +#include + +#include "InitializationStatus.h" + +namespace Simo::Log { + +enum SIMO_PUBLIC LogLevel : std::uint8_t { + VERBOSE = 0, + DEBUG, + INFO, + WARNING, +}; + +class SIMO_PUBLIC Logger { + public: + /// Initialize the logger + InitializationStatus initialize(const std::filesystem::path& sink_path_v); + + /// Add a log level and give it a name + Logger& add_log_level(size_t level, std::string_view name); + + /// Setup default log levels and set default log level to the highest + Logger& populate_default_log_levels(); + + /// Setup log level explicitly + Logger& log_level(LogLevel level); + Logger& log_level(uint8_t level); + + /// Setup log level from string. Do nothing is string is not recognized + Logger& log_level(std::string_view level_name); + + size_t log_level() const; + + /// Enable/disable logger + Logger& enabled(bool new_enabled_value); + + bool enabled() const; + + template + Logger& log_callable(const size_t level, const bool print_level, + Callable&& c) { + if (!enabled() || level < log_level()) { + return *this; + } + if (print_level) { + *sink << '[' << level_map[level] << "] "; + } + *sink << std::format("{}", c()); + return *this; + } + + template + Logger& log_format(const size_t level, const bool print_level, Args... args) { + if (!enabled() || level < log_level()) { + return *this; + } + if (print_level) { + *sink << '[' << level_map[level] << "] "; + } + *sink << std::format(std::forward(args)...); + return *this; + } + + ~Logger(); + + /// Flush sink at path if this path is identified as a used sink + static void flush_sink(const std::filesystem::path& path); + + // Flush all the registered sinks + static void flush_all_sinks(); + + protected: + std::filesystem::path sink_path; + std::ofstream* sink = nullptr; + bool is_enabled = false; + size_t current_log_level = 0; + std::vector level_map; +}; + +} // namespace Simo::Log + +#endif // SIMO_LOG_HH \ No newline at end of file diff --git a/include/Simo/core/Time.h b/include/Simo/core/Time.h index 56a00a1..3f4b225 100644 --- a/include/Simo/core/Time.h +++ b/include/Simo/core/Time.h @@ -171,4 +171,12 @@ struct to { } // namespace glz +template <> +struct std::formatter : std::formatter { + auto format(Simo::Time t, format_context& ctx) const { + return formatter::format(std::format("{} ps", t.to_picoseconds()), + ctx); + } +}; + #endif // SIMO_TIME_HH diff --git a/include/Simo/module/Module.h b/include/Simo/module/Module.h index 7880d48..59ffab6 100644 --- a/include/Simo/module/Module.h +++ b/include/Simo/module/Module.h @@ -15,6 +15,9 @@ #ifndef SIMO_MODULE_HH #define SIMO_MODULE_HH +#include +#include + #include #include "Simo/core/InitializationStatus.h" @@ -33,7 +36,11 @@ class SIMO_PUBLIC Parameters { [[nodiscard]] std::string_view name() const; - void name(std::string_view name); + template + Self& name(this Self& self, const std::string_view name) { + self.name_ = name; + return self; + } // TODO make it to return a list of errors @@ -94,13 +101,15 @@ class SIMO_PUBLIC Module { [[nodiscard]] Context& sim_ctx() const; - virtual ~Module() = default; + virtual ~Module() {} /// Record a statistic in a StatMapper to dump statistics void record_statistics(Statistics::StatMapper& mapper); [[nodiscard]] Port* get_port(std::string_view); + Time current_time() const; + template Stat* get_statistic(const std::string_view name) { return statistics.get(name); @@ -111,6 +120,40 @@ class SIMO_PUBLIC Module { statistics.visit(f); } + /// Setup logging for the component + /// + /// Enable log by default when the operation succeeds. Calling this function + /// will re-initialize the log even if the same out_file is passed + virtual InitializationStatus log_setup( + const std::filesystem::path& out_file = "simo.log"); + + /// Enable/disable logging for the component + void log_enable(bool new_value); + + void log_level(size_t level); + void log_level(std::string_view level_name); + + /// Log a message with the given level. The message is generated by the + /// callable. New-line is added + /// + /// The callable is evaluated only when the log is enabled and the log level + /// is satisfied + template + void log(size_t level, Callable&& callable, bool print_level = false) { + auto f = [this, callable]() { + return std::format("[{}] [{}] {}\n", current_time(), name(), callable()); + }; + log_raw_callable(level, print_level, f); + } + + /// Log a message with the given level without adding timestamp + template + void log_raw_callable(size_t level, bool print_level, Callable&& callable) { + logger.log_callable(level, print_level, std::forward(callable)); + } + + Log::Logger& get_logger() { return logger; } + protected: /// Create a new statistic of type T template @@ -130,6 +173,7 @@ class SIMO_PUBLIC Module { Statistics::StatStorage statistics; std::unordered_map> ports; + Log::Logger logger; private: std::string name_; diff --git a/src/core/Log.cc b/src/core/Log.cc new file mode 100644 index 0000000..144a0f5 --- /dev/null +++ b/src/core/Log.cc @@ -0,0 +1,123 @@ +/* + * Copyright 2026 Matteo Fusi and Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include "third-party/enchantum_single_header.hpp" + +namespace Simo::Log { + +struct SinkTracker { + std::ofstream sink; + int ref_count = 0; +}; + +static std::unordered_map sink_tracker; + +InitializationStatus Logger::initialize( + const std::filesystem::path& sink_path_v) { + sink_path = sink_path_v; + if (sink_tracker.contains(sink_path)) { + // Sink file was already opened + sink_tracker[sink_path].ref_count++; + sink = &sink_tracker[sink_path].sink; + is_enabled = true; + return InitializationStatus::ok(nullptr); + } + std::ofstream new_sink; + new_sink.open(sink_path, std::ios::out | std::ios::trunc); + if (!new_sink.is_open()) { + return InitializationStatus( + nullptr, {"Failed to open log file: " + sink_path.string()}); + } + sink_tracker[sink_path] = {std::move(new_sink), 1}; + sink = &sink_tracker[sink_path].sink; + is_enabled = true; + return InitializationStatus::ok(nullptr); +} + +Logger& Logger::add_log_level(const size_t level, const std::string_view name) { + level_map.resize(std::max(level_map.size(), level + 1), ""); + level_map[level] = name; + return *this; +} + +Logger& Logger::populate_default_log_levels() { + level_map.clear(); + level_map.resize(enchantum::count); + for (const auto [idx, name] : enchantum::entries_generator) { + level_map[idx] = name; + } + log_level( + static_cast(level_map.size() - 1)); // Print only warnings + return *this; +} + +Logger& Logger::log_level(const LogLevel level) { + current_log_level = level; + return *this; +} + +Logger& Logger::log_level(const uint8_t level) { + current_log_level = level; + return *this; +} + +Logger& Logger::log_level(const std::string_view level_name) { + for (size_t i = 0; i < level_map.size(); i++) { + if (level_map[i] == level_name) { + current_log_level = i; + break; + } + } + return *this; +} + +size_t Logger::log_level() const { return current_log_level; } + +Logger& Logger::enabled(const bool new_enabled_value) { + is_enabled = new_enabled_value; + return *this; +} + +bool Logger::enabled() const { return is_enabled; } + +Logger::~Logger() { + int& ref_count = sink_tracker[sink_path].ref_count; + ref_count--; + if (ref_count <= 0) { + sink_tracker[sink_path].sink.close(); + sink_tracker.erase(sink_path); + } +} + +void Logger::flush_sink(const std::filesystem::path& path) { + if (!sink_tracker.contains(path)) { + return; + } + sink_tracker[path].sink << std::flush; +} + +void Logger::flush_all_sinks() { + for (auto& tracker : sink_tracker | std::views::values) { + tracker.sink << std::flush; + } +} + +} // namespace Simo::Log \ No newline at end of file diff --git a/src/module/Module.cc b/src/module/Module.cc index 1216200..a56b800 100644 --- a/src/module/Module.cc +++ b/src/module/Module.cc @@ -44,8 +44,6 @@ std::optional Parameters::get_subtree( std::string_view Parameters::name() const { return name_; } -void Parameters::name(const std::string_view name) { name_ = name; } - InitializationStatus Module::initialize(Context& sim_ctx_v, const Parameters& parameters) { sim_ctx_ = &sim_ctx_v; @@ -59,6 +57,8 @@ std::string_view Module::name() const { return name_; } Context& Module::sim_ctx() const { return *sim_ctx_; } +Time Module::current_time() const { return sim_ctx().current_time(); } + void Module::record_statistics(Statistics::StatMapper& mapper) { statistics.visit([&mapper](auto& s) { mapper.add(s); }); } @@ -68,4 +68,17 @@ Port* Module::get_port(const std::string_view name) { : nullptr; } +InitializationStatus Module::log_setup(const std::filesystem::path& out_file) { + logger = {}; + return logger.initialize(out_file); +} + +void Module::log_enable(const bool new_value) { logger.enabled(new_value); } + +void Module::log_level(const size_t level) { logger.log_level(level); } + +void Module::log_level(const std::string_view level_name) { + logger.log_level(level_name); +} + } // namespace Simo \ No newline at end of file diff --git a/src/third-party/enchantum_single_header.hpp b/src/third-party/enchantum_single_header.hpp new file mode 100644 index 0000000..620a020 --- /dev/null +++ b/src/third-party/enchantum_single_header.hpp @@ -0,0 +1,2442 @@ +#pragma once + +#ifdef ENCHANTUM_CONFIG_FILE + #include ENCHANTUM_CONFIG_FILE +#endif + +#ifndef ENCHANTUM_ALIAS_STRING_VIEW + #include +#endif + +namespace enchantum { +#ifdef ENCHANTUM_ALIAS_STRING_VIEW +ENCHANTUM_ALIAS_STRING_VIEW; +#else +using ::std::string_view; +#endif + +} // namespace enchantum +#include + +namespace enchantum { + +namespace details { +#define SZC(x) (sizeof(x) - 1) + constexpr string_view extract_name_from_type_name(const string_view type_name) noexcept + { + if (const auto n = type_name.rfind(':'); n != type_name.npos) + return type_name.substr(n + 1); + else + return type_name; + } + + template + constexpr auto raw_type_name_func() noexcept + { +#if defined(__NVCOMPILER) + constexpr std::size_t prefix = 0; + constexpr auto s = string_view(__PRETTY_FUNCTION__ + SZC("constexpr auto enchantum::details::raw_type_name_func() noexcept [with T = "), + SZC(__PRETTY_FUNCTION__) - SZC("constexpr auto enchantum::details::raw_type_name_func() noexcept [with T = ]")); +#elif defined(__clang__) + constexpr std::size_t prefix = 0; + constexpr auto s = string_view(__PRETTY_FUNCTION__ + SZC("auto enchantum::details::raw_type_name_func() [_ = "), + SZC(__PRETTY_FUNCTION__) - SZC("auto enchantum::details::raw_type_name_func() [_ = ]")); +#elif defined(_MSC_VER) + constexpr auto s = string_view(__FUNCSIG__ + SZC("auto __cdecl enchantum::details::raw_type_name_func<"), + SZC(__FUNCSIG__) - SZC("auto __cdecl enchantum::details::raw_type_name_func<") - + SZC(">(void) noexcept")); + + // clang-format off + constexpr auto prefix = std::is_enum_v ? SZC("enum ") : + std::is_class_v ? SZC("struct ") - (s[0] == 'c') : + 0; +// clang-format on +#elif defined(__GNUG__) + constexpr std::size_t prefix = 0; + constexpr auto s = string_view(__PRETTY_FUNCTION__ + + SZC("constexpr auto enchantum::details::raw_type_name_func() [with _ = "), + SZC(__PRETTY_FUNCTION__) - + SZC("constexpr auto enchantum::details::raw_type_name_func() [with _ = ]")); +#endif + std::array ret{}; + auto* const ret_data = ret.data(); + const auto* const s_data = s.data(); + + for (std::size_t i = 0; i < ret.size() - 1; ++i) + ret_data[i] = s_data[i + prefix]; + return ret; + } + + template + inline constexpr auto raw_type_name_func_var = raw_type_name_func(); + + template + constexpr auto type_name_func() noexcept + { + static_assert(!std::is_function_v> && !std::is_member_function_pointer_v, + "enchantum::type_name does not work well with function pointers or functions or member function\n" + "pointers"); + + constexpr auto& array = raw_type_name_func_var; + static_assert(array[array.size() - 2] != '>', "enchantum::type_name does not work well with a templated type"); + + constexpr auto s = details::extract_name_from_type_name(string_view(array.data(), array.size() - 1)); + std::array ret{}; + for (std::size_t i = 0; i < s.size(); ++i) + ret[i] = s[i]; + return ret; + } + + template + inline constexpr auto type_name_func_var = type_name_func(); + +#undef SZC + +} // namespace details + +template +inline constexpr auto type_name = string_view(details::type_name_func_var.data(), + details::type_name_func_var.size() - 1); + +template +inline constexpr auto raw_type_name = string_view(details::raw_type_name_func_var.data(), + details::raw_type_name_func_var.size() - 1); + +} // namespace enchantum + +#ifdef ENCHANTUM_CONFIG_FILE + #include ENCHANTUM_CONFIG_FILE +#endif + +#ifndef ENCHANTUM_ALIAS_OPTIONAL + #include +#endif + +namespace enchantum { +#ifdef ENCHANTUM_ALIAS_OPTIONAL +ENCHANTUM_ALIAS_OPTIONAL; +#else +using ::std::optional; +#endif + +} // namespace enchantum + +#ifdef ENCHANTUM_CONFIG_FILE + #include ENCHANTUM_CONFIG_FILE +#endif + +#ifndef ENCHANTUM_ALIAS_STRING + #include +#endif + +namespace enchantum { +#ifdef ENCHANTUM_ALIAS_STRING +ENCHANTUM_ALIAS_STRING; +#else +using ::std::string; +#endif + +} // namespace enchantum + +#ifdef __cpp_concepts + #include +#endif +#include +#include +#include + +#ifndef ENCHANTUM_ASSERT + #include +// clang-format off + #define ENCHANTUM_ASSERT(cond, msg, ...) assert(cond && msg) +// clang-format on +#endif + +#ifndef ENCHANTUM_THROW + // additional info such as local variables are here + #define ENCHANTUM_THROW(exception, ...) throw exception +#endif + +#ifndef ENCHANTUM_MAX_RANGE + #define ENCHANTUM_MAX_RANGE 256 +#endif +#ifndef ENCHANTUM_MIN_RANGE + #define ENCHANTUM_MIN_RANGE (-ENCHANTUM_MAX_RANGE) +#endif + +namespace enchantum { + +template> +inline constexpr bool is_scoped_enum = false; + +template +inline constexpr bool is_scoped_enum = !std::is_convertible_v>; + +template +inline constexpr bool is_unscoped_enum = std::is_enum_v && !is_scoped_enum; + +template +inline constexpr bool has_fixed_underlying_type = false; + +template +inline constexpr bool has_fixed_underlying_type = std::is_enum_v; + +#ifdef __cpp_concepts + +template +concept Enum = std::is_enum_v; + +template +inline constexpr bool is_bitflag = requires(E e) { + requires std::same_as || std::same_as; + { ~e } -> std::same_as; + { e | e } -> std::same_as; + { e &= e } -> std::same_as; + { e |= e } -> std::same_as; +}; + +template +concept SignedEnum = Enum && std::signed_integral>; + +template +concept UnsignedEnum = Enum && !SignedEnum; + +template +concept ScopedEnum = Enum && (!std::is_convertible_v>); + +template +concept UnscopedEnum = Enum && !ScopedEnum; + +template +concept EnumOfUnderlying = Enum && std::same_as, Underlying>; + +template +concept BitFlagEnum = Enum && is_bitflag; + +template +concept EnumFixedUnderlying = Enum && requires { T{0}; }; + +#else + +template +inline constexpr bool is_bitflag = false; + +// clang-format off +template +inline constexpr bool is_bitflag() &= E{}), + decltype(std::declval() |= E{}) + >> = std::is_enum_v + && (std::is_same_v || std::is_same_v) + && std::is_same_v + && std::is_same_v + && std::is_same_v() &= E{}), E&> + && std::is_same_v() |= E{}), E&> + ; +// clang-format on +#endif + +namespace details { + template + constexpr auto Max(T a, U b) + { + return a < b ? b : a; + } + template + constexpr auto Min(T a, U b) + { + return a > b ? b : a; + } +#if !defined(__NVCOMPILER) && defined(__clang__) && __clang_major__ >= 20 + template + inline constexpr bool is_valid_cast = false; + + template + inline constexpr bool is_valid_cast(V)>>> = true; + + template range, decltype(range) old_range> + constexpr auto valid_cast_range_recurse() noexcept + { + // this tests whether `static_cast`ing range is valid + // because C style enums stupidly is like a bit field + // `enum E { a,b,c,d = 3};` is like a bitfield `struct E { int val : 2;}` + // which means giving E.val a larger than 2 bit value is UB so is it for enums + // and gcc and msvc ignore this (for good) + // while clang makes it a subsituation failure which we can check for + // using std::inegral_constant makes sure this is a constant expression situation + // for SFINAE to occur + if constexpr (is_valid_cast) + return valid_cast_range_recurse(); + else + return old_range > 0 ? old_range * 2 - 1 : old_range; + } + template + constexpr auto valid_cast_range() noexcept + { + using T = std::underlying_type_t; + using L = std::numeric_limits; + + if constexpr (max_range == 0) + return T{0}; + else if constexpr (max_range > 0 && is_valid_cast) + return L::max(); + else if constexpr (max_range < 0 && is_valid_cast) + return L::min(); + else + return details::valid_cast_range_recurse(); + } + +#endif + + template + constexpr auto enum_range_of(const int max_range) + { + using T = std::underlying_type_t; + if constexpr (std::is_same_v) { + return max_range > 0; + } + else { + using L = std::numeric_limits; +#if !defined(__NVCOMPILER) && defined(__clang__) && __clang_major__ >= 20 + constexpr auto Max = has_fixed_underlying_type ? (L::max)() : details::valid_cast_range(); + constexpr auto Min = has_fixed_underlying_type + ? (L::min)() + : details::valid_cast_range ? -1 : 0>(); +#else + constexpr auto Max = (L::max)(); + constexpr auto Min = (L::min)(); +#endif + (void)Min; // Only used in signed branch + if constexpr (std::is_signed_v) { + return max_range > 0 ? details::Min(ENCHANTUM_MAX_RANGE, Max) : details::Max(ENCHANTUM_MIN_RANGE, Min); + } + else { + return max_range > 0 ? details::Min(static_cast(ENCHANTUM_MAX_RANGE), Max) : 0; + } + } + } +} // namespace details + +template +struct enum_traits { +private: + using T = std::underlying_type_t; +public: + using zxshady_enchantum_is_not_specialized_tag = void; + static constexpr auto max = details::enum_range_of(1); + static constexpr decltype(max) min = details::enum_range_of(-1); +}; + +namespace details { + template + inline constexpr bool has_specialized_traits = true; + template + inline constexpr bool has_specialized_traits::zxshady_enchantum_is_not_specialized_tag> = false; + +} // namespace details + +} // namespace enchantum + +#ifdef __cpp_concepts + #define ENCHANTUM_DETAILS_ENUM_CONCEPT(Name) Enum Name + #define ENCHANTUM_DETAILS_ENUM_BITFLAG_CONCEPT(Name) BitFlagEnum Name +#else + #define ENCHANTUM_DETAILS_ENUM_CONCEPT(Name) typename Name, std::enable_if_t, int> = 0 + #define ENCHANTUM_DETAILS_ENUM_BITFLAG_CONCEPT(Name) typename Name, std::enable_if_t, int> = 0 +#endif +#include +#include +#include +#include +#include + +namespace enchantum { +namespace details { + + template + inline constexpr std::size_t prefix_length_or_zero = 0; + + template + inline constexpr auto prefix_length_or_zero::prefix_length)> = std::size_t{ + enum_traits::prefix_length}; + + template + struct ReflectStringReturnValue { + Underlying values[ArraySize]{}; + std::uint8_t string_lengths[ArraySize]{}; + // the sum of all character names must be less than the size of this array + // no one will likely hit this unless you for some odd reason have extremely long names + char strings[1024 * 8]{}; + std::size_t total_string_length = 0; + std::size_t valid_count = 0; + }; + +} // namespace details +} // namespace enchantum + +#if defined(__NVCOMPILER) + + +#include +#include +#include +#include +#include +#include +namespace enchantum { + +namespace details { + constexpr std::size_t find_semicolon(const char* s) + { + for (std::size_t i = 0; true; ++i) + if (s[i] == ';') + return i; + } + constexpr std::size_t enum_in_array_name_size(const string_view raw_type_name, const bool is_scoped_enum) noexcept + { + if (is_scoped_enum) + return raw_type_name.size(); + + if (const auto pos = raw_type_name.rfind(':'); pos != string_view::npos) + return pos - 1; + return 0; + } + +#define SZC(x) (sizeof(x) - 1) + + template + constexpr auto var_name() noexcept + { + return __PRETTY_FUNCTION__ + SZC("constexpr auto enchantum::details::var_name() noexcept [with _ *V = (_ *)0; "); + } + + template + constexpr void parse_string( + const char* str, + const std::size_t least_length_when_casting, + const std::size_t least_length_when_value, + const IntType min, + const std::size_t array_size, + const bool null_terminated, + IntType* const values, + std::uint8_t* const string_lengths, + char* const strings, + std::size_t& total_string_length, + std::size_t& valid_count) + { + for (std::size_t index = 0; index < array_size; ++index) { + // check if cast (starts with '(') + str += SZC("_ *V = "); + if (str[0] == '(') { + str += least_length_when_casting; + while (*str++ != ';') + /*intentionally empty*/; + str += SZC(" "); + } + else { + str += least_length_when_value; + const auto commapos = details::find_semicolon(str); + if constexpr (IsBitFlag) + values[valid_count] = index == 0 ? IntType{} : static_cast(IntType{1} << (index - 1)); + else + values[valid_count] = static_cast(min + static_cast(index)); + string_lengths[valid_count++] = static_cast(commapos); + __builtin_memcpy(strings + total_string_length, str, commapos); + total_string_length += commapos + null_terminated; + str += commapos + SZC("; "); + } + } + } + + template + constexpr auto reflect(std::index_sequence) noexcept + { + using MinT = decltype(Min); + using T = std::underlying_type_t; + + constexpr auto elements_local = []() { + constexpr auto ArraySize = sizeof...(Is) + is_bitflag; +#pragma diag_suppress implicit_return_from_non_void_function + const auto str = [](auto dependant) { + constexpr bool always_true = sizeof(dependant) != 0; + // forces NVCC to shorten the string types + struct _ {}; + // using a pointer since C++17 only allows pointers to class types not the class types themselves + constexpr _* A{}; + using Underlying = std::make_unsigned_t, unsigned char, T>>; + // dummy 0 + if constexpr (always_true && is_bitflag) // sizeof... to make contest dependant + return details::var_name(!always_true), static_cast(Underlying(1) << Is)..., 0>(); + else + return details::var_name(static_cast(Is) + Min)..., int(!always_true)>(); + }(0); +#pragma diag_default implicit_return_from_non_void_function + + constexpr auto enum_in_array_len = details::enum_in_array_name_size(raw_type_name, is_scoped_enum); + // Ubuntu Clang 20 complains about using local constexpr variables in a local struct + ReflectStringReturnValue, ArraySize> ret; + + // ((anonymous namespace)::A)0 + // (anonymous namespace)::a + // this is needed to determine whether the above are cast expression if 2 braces are + // next to eachother then it is a cast but only for anonymoused namespaced enums + + details::parse_string>( + /*str = */ str, + /*least_length_when_casting=*/SZC("(") + enum_in_array_len + SZC(")0"), + /*least_length_when_value=*/details::prefix_length_or_zero + + (enum_in_array_len != 0 ? enum_in_array_len + SZC("::") : 0), + /*min = */ static_cast(Min), + /*array_size = */ ArraySize, + /*null_terminated= */ NullTerminated, + /*enum_values= */ ret.values, + /*string_lengths= */ ret.string_lengths, + /*strings= */ ret.strings, + /*total_string_length*/ ret.total_string_length, + /*valid_count*/ ret.valid_count); + + return ret; + }(); + + using Strings = std::array; + + struct { + decltype(elements_local) elements; + Strings strings{}; + } data = {elements_local}; + __builtin_memcpy(data.strings.data(), elements_local.strings, data.strings.size()); + return data; + } // namespace details + +} // namespace details +} // namespace enchantum +#elif defined(__clang__) + + +// Clang <= 12 outputs "NUMBER" if casting +// Clang > 12 outputs "(E)NUMBER". + +#if defined __has_warning + #if __has_warning("-Wenum-constexpr-conversion") + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wenum-constexpr-conversion" + #endif +#endif + +#include +#include +#include +#include +#include +#include + +namespace enchantum { + +namespace details { + constexpr auto enum_in_array_name(const string_view raw_type_name, const bool is_scoped_enum) noexcept + { + if (is_scoped_enum) + return raw_type_name; + + if (const auto pos = raw_type_name.rfind(':'); pos != string_view::npos) + return raw_type_name.substr(0, pos - 1); + return string_view(); + } + +#define SZC(x) (sizeof(x) - 1) + + template + constexpr auto var_name() noexcept + { + // "auto enchantum::details::var_name() [Vs = <(A)0, a, b, c, e, d, (A)6>]" + return __PRETTY_FUNCTION__ + SZC("auto enchantum::details::var_name() [Vs = <"); + } + + template + constexpr void parse_string( + std::size_t index_check, + const char* str, + const std::size_t least_length_when_casting, + const std::size_t least_length_when_value, + const IntType min, + const std::size_t array_size, + const bool null_terminated, + IntType* const values, + std::uint8_t* const string_lengths, + char* const strings, + std::size_t& total_string_length, + std::size_t& valid_count) + { + (void)index_check; + for (std::size_t index = 0; index < array_size; ++index) { +#if __clang_major__ > 12 + // check if cast (starts with '(') + if (str[index_check] == '(') +#else + // check if it is a number or negative sign + if (str[0] == '-' || (str[0] >= '0' && str[0] <= '9')) +#endif + { + str = __builtin_char_memchr(str + least_length_when_casting, ',', UINT8_MAX) + SZC(", "); + } + else { + str += least_length_when_value; + const auto commapos = static_cast(__builtin_char_memchr(str, ',', UINT8_MAX) - str); + if constexpr (IsBitFlag) + values[valid_count] = index == 0 ? IntType{} : static_cast(IntType{1} << (index - 1)); + else + values[valid_count] = static_cast(min + static_cast(index)); + string_lengths[valid_count++] = static_cast(commapos); + __builtin_memcpy(strings + total_string_length, str, commapos); + total_string_length += commapos + null_terminated; + str += commapos + SZC(", "); + } + } + } + + template + constexpr auto reflect(std::index_sequence) noexcept + { + using MinT = decltype(Min); + using T = std::underlying_type_t; + using Underlying = std::make_unsigned_t, unsigned char, T>>; + + constexpr auto elements_local = []() { + constexpr auto ArraySize = sizeof...(Is) + is_bitflag; + const auto str = [](auto dependant) { + constexpr bool always_true = sizeof(dependant) != 0; + // dummy 0 + if constexpr (always_true && is_bitflag) // sizeof... to make contest dependant + { + return details::var_name(!always_true), static_cast(Underlying(1) << Is)..., 0>(); + } + else { + return details::var_name(static_cast(Is) + Min)..., int(!always_true)>(); + } + }(0); + + constexpr auto enum_in_array_name = details::enum_in_array_name(raw_type_name, is_scoped_enum); + constexpr auto enum_in_array_len = enum_in_array_name.size(); + // Ubuntu Clang 20 complains about using local constexpr variables in a local struct + ReflectStringReturnValue, ArraySize> ret; + + // ((anonymous namespace)::A)0 + // (anonymous namespace)::a + // this is needed to determine whether the above are cast expression if 2 braces are + // next to eachother then it is a cast but only for anonymoused namespaced enums + constexpr std::size_t index_check = enum_in_array_name.size() != 0 && enum_in_array_name[0] == '(' ? 1 : 0; + + details::parse_string>( + /*index_check=*/index_check, + /*str = */ str, +#if __clang_major__ > 12 + /*least_length_when_casting=*/SZC("(") + enum_in_array_len + SZC(")0"), +#else + /*least_length_when_casting=*/1, +#endif + /*least_length_when_value=*/details::prefix_length_or_zero + + (enum_in_array_len != 0 ? enum_in_array_len + SZC("::") : 0), + /*min = */ static_cast(Min), + /*array_size = */ ArraySize, + /*null_terminated= */ NullTerminated, + /*enum_values= */ ret.values, + /*string_lengths= */ ret.string_lengths, + /*strings= */ ret.strings, + /*total_string_length*/ ret.total_string_length, + /*valid_count*/ ret.valid_count); + + return ret; + }(); + + using Strings = std::array; + + struct { + decltype(elements_local) elements; + Strings strings{}; + } data = {elements_local}; + __builtin_memcpy(data.strings.data(), elements_local.strings, data.strings.size()); + return data; + } // namespace details + +} // namespace details + +//template +//constexpr std::size_t enum_count = details::enum_count; + +} // namespace enchantum + +#if defined __has_warning + #if __has_warning("-Wenum-constexpr-conversion") + #pragma clang diagnostic pop + #endif +#endif +#undef SZC +#elif defined(__GNUC__) || defined(__GNUG__) + + +#include +#include +#include +#include +#include +#include + +#define ENCAHNTUM_DETAILS_GCC_MAJOR __GNUC__ +#if __GNUC__ <= 10 +// for out of bounds conversions for C style enums + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" +#endif + +namespace enchantum { +namespace details { +#define SZC(x) (sizeof(x) - 1) + + // this is needed since gcc transforms "{anonymous}" into "" for values + template + constexpr auto enum_in_array_name_size() noexcept + { + // constexpr auto f() [with auto _ = ( + //constexpr auto f() [with auto _ = (Scoped)0] + auto s = string_view(__PRETTY_FUNCTION__ + + SZC("constexpr auto enchantum::details::enum_in_array_name_size() [with auto Enum = "), + SZC(__PRETTY_FUNCTION__) - + SZC("constexpr auto enchantum::details::enum_in_array_name_size() [with auto Enum = ]")); + using E = decltype(Enum); + // if scoped + if constexpr (!std::is_convertible_v>) { + return s[0] == '(' ? s.size() - SZC("()0") : s.rfind(':') - 1; + } + else { + if (s[0] == '(') { + s.remove_prefix(SZC("(")); + s.remove_suffix(SZC(")0")); + } + if (const auto pos = s.rfind(':'); pos != s.npos) + return pos - 1; + return std::size_t{0}; + } + } + +#if __GNUC__ == 10 + template + constexpr auto gcc10_workaround() noexcept + { + using E = decltype(V); + using T = std::underlying_type_t; + constexpr auto prefix = SZC("constexpr auto enchantum::details::gcc10_workaround() [with auto V = "); + constexpr auto begin = __PRETTY_FUNCTION__ + prefix; + if constexpr (begin[0] == '(') { + std::size_t i = SZC(__PRETTY_FUNCTION__) - prefix - SZC("("); + const char* end = __PRETTY_FUNCTION__ + SZC(__PRETTY_FUNCTION__) - 1; + while (*end != ')') { + --end; + --i; + } + --i; + return i; + } + else if constexpr (static_cast(V) == (std::numeric_limits::max)()) { + constexpr auto s = details::enum_in_array_name_size(); + constexpr auto& tyname = raw_type_name; + if (constexpr auto pos = tyname.rfind("::"); pos != tyname.npos) { + return s + tyname.substr(pos).size(); + } + else { + return s + tyname.size(); + } + } + else { + return details::gcc10_workaround(static_cast(V) + 1)>(); + } + } +#endif + + template + constexpr auto length_of_enum_in_template_array_if_casting() noexcept + { + if constexpr (is_scoped_enum) { + return details::enum_in_array_name_size(); + } + else { +#if __GNUC__ == 10 + return details::gcc10_workaround((std::numeric_limits>::min)())>(); +#else + constexpr auto s = details::enum_in_array_name_size(); + constexpr auto& tyname = raw_type_name; + if (constexpr auto pos = tyname.rfind("::"); pos != tyname.npos) { + return s + tyname.substr(pos).size(); + } + else { + return s + tyname.size(); + } +#endif + } + } + + template + constexpr auto var_name() noexcept + { + return __PRETTY_FUNCTION__ + SZC("constexpr auto enchantum::details::var_name() [with auto ...Vs = {"); + } + + template + constexpr void parse_string( + const char* str, + const std::size_t least_length_when_casting, + const std::size_t least_length_when_value, + const IntType min, + const std::size_t array_size, + const bool null_terminated, + IntType* const values, + std::uint8_t* const string_lengths, + char* const strings, + std::size_t& total_string_length, + std::size_t& valid_count) + { + (void)min; // not always used + for (std::size_t index = 0; index < array_size; ++index) { + if (*str == '(') { + str = std::char_traits::find(str + least_length_when_casting, UINT8_MAX, ',') + SZC(", "); + } + else { + str += least_length_when_value; + // although gcc implementation of std::char_traits::find is using a for loop internally + // copying the code of the function makes it way slower to compile, this was surprising. + const auto commapos = static_cast(std::char_traits::find(str, UINT8_MAX, ',') - str); + if constexpr (IsBitFlag) + values[valid_count] = index == 0 ? IntType{} : static_cast(IntType{1} << (index - 1)); + else + values[valid_count] = static_cast(min + static_cast(index)); + string_lengths[valid_count++] = static_cast(commapos); + for (std::size_t i = 0; i < commapos; ++i) + strings[total_string_length++] = str[i]; + total_string_length += null_terminated; + str += commapos + SZC(", "); + } + } + } + + template + constexpr auto reflect(std::index_sequence) noexcept + { + + constexpr auto elements_local = []() { + constexpr auto ArraySize = sizeof...(Is) + is_bitflag; + using Under = std::underlying_type_t; + using Underlying = std::make_unsigned_t, unsigned char, Under>>; + + constexpr auto str = [](const auto dependant) { +#if __GNUC__ <= 10 + // GCC 10 does not have it + #define CAST(type, value) static_cast(value) +#else + // __builtin_bit_cast used to silence errors when casting out of unscoped enums range + #define CAST(type, value) __builtin_bit_cast(type, value) +#endif + // dummy 0 + if constexpr (sizeof(dependant) && is_bitflag) // sizeof... to make contest dependant + return details::var_name(Underlying{1} << Is))..., 0>(); + else + return details::var_name(static_cast(Is) + Min))..., 0>(); +#undef CAST + }(0); + + constexpr auto enum_in_array_len = details::enum_in_array_name_size(); + constexpr auto length_of_enum_in_template_array_casting = details::length_of_enum_in_template_array_if_casting(); + + ReflectStringReturnValue, ArraySize> ret; + details::parse_string>( + /*str = */ str, + /*least_length_when_casting=*/SZC("(") + length_of_enum_in_template_array_casting + SZC(")0"), + /*least_length_when_value=*/details::prefix_length_or_zero + + (enum_in_array_len != 0 ? enum_in_array_len + SZC("::") : 0), + /*min = */ static_cast>(Min), + /*array_size = */ ArraySize, + /*null_terminated= */ NullTerminated, + /*enum_values= */ ret.values, + /*string_lengths= */ ret.string_lengths, + /*strings= */ ret.strings, + /*total_string_length*/ ret.total_string_length, + /*valid_count*/ ret.valid_count); + return ret; + }(); + using Strings = std::array; + + struct { + decltype(elements_local) elements; + Strings strings{}; + } data = {elements_local}; + const auto size = data.strings.size(); + auto* const data_string = data.strings.data(); + for (std::size_t i = 0; i < size; ++i) + data_string[i] = elements_local.strings[i]; + return data; + } + +} // namespace details + +} // namespace enchantum + +#undef SZC + +#if __GNUC__ <= 10 + #pragma GCC diagnostic pop +#endif + +#elif defined(_MSC_VER) + + +#include +#include +#include +#include +#include +#include + +// This macro controls the compile time optimization of msvc +// This macro may break some enums with very large enum ranges selected. +// **may** as in I have not found a case where it does +// but it speeds up compilation massivly. +// from 20 secs to 14.6 secs +// from 119 secs to 85 +#ifndef ENCHANTUM_ENABLE_MSVC_SPEEDUP + #define ENCHANTUM_ENABLE_MSVC_SPEEDUP 1 +#endif +namespace enchantum { + +#define SZC(x) (sizeof(x) - 1) +namespace details { + + template + constexpr auto enum_in_array_name_size() noexcept + { + auto s = string_view{__FUNCSIG__ + SZC("auto __cdecl enchantum::details::enum_in_array_name_size<"), + SZC(__FUNCSIG__) - SZC("auto __cdecl enchantum::details::enum_in_array_name_size<>(void) noexcept")}; + + if constexpr (is_scoped_enum) { + if (s[0] == '(') { + s.remove_prefix(SZC("(enum ")); + s.remove_suffix(SZC(")0x0")); + return s.size(); + } + return s.substr(0, s.rfind(':') - 1).size(); + } + else { + if (s[0] == '(') { + s.remove_prefix(SZC("(enum ")); + s.remove_suffix(SZC(")0x0")); + } + if (const auto pos = s.rfind(':'); pos != s.npos) + return pos - 1; + return std::size_t(0); + } + } + + template + constexpr auto __cdecl var_name() noexcept + { + //auto __cdecl f{enum `anonymous-namespace'::UnscopedAnon + return __FUNCSIG__ + SZC("auto __cdecl enchantum::details::var_name<"); + } + + template + constexpr void parse_string( + const char* str, + const std::size_t least_length_when_casting, + const std::size_t least_length_when_value, + const IntType min, + const std::size_t array_size, + const bool null_terminated, + IntType* const values, + std::uint8_t* const string_lengths, + char* const strings, + std::size_t& total_string_length, + std::size_t& valid_count) + { + // clang-format off +#if ENCHANTUM_ENABLE_MSVC_SPEEDUP + constexpr auto skip_work_if_neg = IsBitFlag || std::is_unsigned_v || sizeof(IntType) <= 2 ? 0 : +// MSVC 19.31 and below don't cast int/unsigned int into `unsigned long long` (std::uint64_t) +// While higher versions do cast them +#if _MSC_VER <= 1931 + sizeof(IntType) == 4 +#else + std::is_same_v +#endif + ? sizeof(char32_t)*2-1 : sizeof(std::uint64_t)*2-1 - (sizeof(IntType)==8); // subtract 1 more from uint64_t since I am adding it in skip_if_cast_count +#endif + // clang-format on + for (std::size_t index = 0; index < array_size; ++index) { +#if _MSC_VER <= 1924 + // if it starts with the number 0 (because of 0x0) then it is a value + // and you cannot start an enum name with a digit so this is safe + if (*str == '0') { +#else + // if it starts with a '(' it is a cast! + if (*str == '(') { +#endif +#if ENCHANTUM_ENABLE_MSVC_SPEEDUP + if constexpr (skip_work_if_neg != 0) { + const auto i = min + static_cast(index); + str += least_length_when_casting + ((i < 0) * skip_work_if_neg); + } + else { + str += least_length_when_casting; + } +#else + str += least_length_when_casting; +#endif + while (*str++ != ',') + /*intentionally empty*/; + } + else { + str += least_length_when_value; + + // although gcc implementation of std::char_traits::find is using a for loop internally + // copying the code of the function makes it way slower to compile, this was surprising. + + if constexpr (IsBitFlag) + values[valid_count] = index == 0 ? IntType{} : static_cast(IntType{1} << (index - 1)); + else + values[valid_count] = static_cast(min + static_cast(index)); + + std::size_t i = 0; + while (str[i] != ',') + strings[total_string_length++] = str[i++]; + string_lengths[valid_count++] = static_cast(i); + + total_string_length += null_terminated; + str += i + SZC(","); + } + } + } + + template + constexpr auto reflect(std::index_sequence) noexcept + { + constexpr auto elements_local = []() { + constexpr auto ArraySize = sizeof...(Is) + is_bitflag; + using MinT = decltype(Min); + using Under = std::underlying_type_t; + using Underlying = std::make_unsigned_t, unsigned char, Under>>; + + constexpr auto str = [](const auto dependant) { + constexpr bool always_true = sizeof(dependant) != 0; + // dummy 0 + if constexpr (always_true && is_bitflag) // sizeof... to make contest dependant + return details::var_name(!always_true), static_cast(Underlying(1) << Is)..., 0>(); + else + return details::var_name(static_cast(Is) + Min)..., int(!always_true)>(); + }(0); + constexpr auto type_name_len = details::raw_type_name_func().size() - 1; + constexpr auto enum_in_array_len = details::enum_in_array_name_size(); + + ReflectStringReturnValue, ArraySize> ret; + details::parse_string>( + /*str = */ str, +#if _MSC_VER <= 1924 + /*least_length_when_casting=*/SZC("0x0"), +#else + /*least_length_when_casting=*/SZC("(enum ") + type_name_len + SZC(")0x0") + (sizeof(E) == 8), +#endif + /*least_length_when_value=*/details::prefix_length_or_zero + + (enum_in_array_len != 0 ? enum_in_array_len + SZC("::") : 0), + /*min = */ static_cast>(Min), + /*array_size = */ ArraySize, + /*null_terminated= */ NullTerminated, + /*enum_values= */ ret.values, + /*string_lengths= */ ret.string_lengths, + /*strings= */ ret.strings, + /*total_string_length*/ ret.total_string_length, + /*valid_count*/ ret.valid_count); + return ret; + }(); + + using Strings = std::array; + + struct { + decltype(elements_local) elements; + Strings strings{}; + } data = {elements_local}; + + const auto size = data.strings.size(); + auto* const data_string = data.strings.data(); + for (std::size_t i = 0; i < size; ++i) + data_string[i] = elements_local.strings[i]; + return data; + } +} // namespace details +} // namespace enchantum + +#undef SZC +#else + #error unsupported compiler please open an issue for enchantum +#endif + +#include +#include + +#ifndef ENCHANTUM_CHECK_OUT_OF_BOUNDS_BY + #define ENCHANTUM_CHECK_OUT_OF_BOUNDS_BY 2 +#endif +#if ENCHANTUM_CHECK_OUT_OF_BOUNDS_BY < 0 + #error ENCHANTUM_CHECK_OUT_OF_BOUNDS_BY must not be a negative number. +#endif +namespace enchantum { + +#ifdef __cpp_lib_to_underlying +using ::std::to_underlying; +#else +template +[[nodiscard]] constexpr auto to_underlying(const E e) noexcept +{ + return static_cast>(e); +} +#endif + +namespace details { + + template + constexpr std::size_t get_index_sequence_max( + const bool is_bitflag, + const bool has_fixed_underlying, + const std::size_t sizeof_enum, + const Int min, + const Int max, + const bool is_signed) + { + (void)has_fixed_underlying; + if (!is_bitflag) + return static_cast(max - min + 1); + +#if __clang_major__ >= 20 + if (!has_fixed_underlying) { + auto v = max; + std::size_t r = 1; + while (v >>= 1) + r++; + return r; + } +#endif + return (sizeof_enum * CHAR_BIT) - is_signed; + } + + template + struct FinalReflectionResult { + std::array values{}; + // +1 for easier iteration on on last string + std::array string_indices{}; + }; + + template::min, decltype(Min) Max = enum_traits::max> + inline constexpr auto reflection_data_impl = details::reflect( + std::make_index_sequence, + has_fixed_underlying_type, + sizeof(E), + Min, + Max, + std::is_signed_v>)>{}); + + // Thanks https://en.cppreference.com/w/cpp/utility/intcmp.html + template + constexpr bool cmp_less(const T t, const U u) noexcept + { + if constexpr (std::is_signed_v == std::is_signed_v) + return t < u; + else if constexpr (std::is_signed_v) + return t < 0 || std::make_unsigned_t(t) < u; + else + return u >= 0 && t < std::make_unsigned_t(u); + } + + template + constexpr bool cmp_less(const bool t, const U u) noexcept + { + return details::cmp_less(int(t), u); + } + + template + constexpr bool cmp_less(const T t, const bool u) noexcept + { + return details::cmp_less(t, int(u)); + } + + constexpr bool cmp_less(const bool t, const bool u) noexcept + { + return int(t) < int(u); + } + + template + constexpr T ClampToRange(U u) + { + using L = std::numeric_limits; + if (details::cmp_less((L::max)(), u)) + return (L::max)(); + if (details::cmp_less(u, (L::min)())) + return (L::min)(); + return T(u); + } + template + constexpr auto get_reflection_data() noexcept + { + constexpr auto elements = reflection_data_impl.elements; + using StringLengthType = std::conditional_t<(elements.total_string_length < UINT8_MAX), std::uint8_t, std::uint16_t>; + +#if ENCHANTUM_CHECK_OUT_OF_BOUNDS_BY >= 2 + if constexpr ( + #if __clang_major__ >= 20 + has_fixed_underlying_type && + #endif + !details::has_specialized_traits) { + static_assert(elements.valid_count == reflection_data_impl>(enum_traits::min * ENCHANTUM_CHECK_OUT_OF_BOUNDS_BY), + details::ClampToRange>(enum_traits::max * ENCHANTUM_CHECK_OUT_OF_BOUNDS_BY) + >.elements.valid_count, + "enchantum has detected that this enum is not fully reflected. Please look at https://github.com/ZXShady/enchantum/blob/main/docs/features.md#enchantum_check_out_of_bounds_by for more information"); + } +#endif + FinalReflectionResult ret; + std::size_t i = 0; + StringLengthType string_index = 0; + for (; i < elements.valid_count; ++i) { + ret.values[i] = static_cast(elements.values[i]); + // "aabc" + + ret.string_indices[i] = string_index; +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + // false positives from T += T + // it does not make sense. + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" +#endif + string_index += static_cast(elements.string_lengths[i] + NullTerminated); +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic pop +#endif + } + ret.string_indices[i] = string_index; + return ret; + } + + template + inline constexpr auto reflection_data_string_storage = details::reflection_data_impl.strings; + + template + inline constexpr auto reflection_data = details::get_reflection_data(); + + template + inline constexpr auto reflection_string_indices = reflection_data.string_indices; +} // namespace details + +#ifdef __cpp_concepts +template, bool NullTerminated = true> +#else +template, bool NullTerminated = true, std::enable_if_t, int> = 0> +#endif +inline constexpr auto entries = []() { + +#if defined(__NVCOMPILER) + // nvc++ had issues with that and did not allow it. it just did not work after testing in godbolt and I don't know why + const auto reflected = details::reflection_data; + const auto strings = details::reflection_data_string_storage.data(); +#else + const auto reflected = details::reflection_data, NullTerminated>; + const auto strings = details::reflection_data_string_storage, NullTerminated>.data(); +#endif + using Pairs = std::array; + Pairs ret{}; + constexpr auto size = ret.size(); + static_assert(size != 0, + "enchantum failed to reflect this enum.\n" + "Please read https://github.com/ZXShady/enchantum/blob/main/docs/limitations.md before opening an " + "issue\n" + "with your enum type with all its namespace/classes it is defined inside to help the creator debug the " + "issues."); + auto* const ret_data = ret.data(); + + for (std::size_t i = 0; i < size; ++i) { + auto& [e, s] = ret_data[i]; + e = reflected.values[i]; + using StringView = std::remove_cv_t>; + s = StringView(strings + reflected.string_indices[i], + reflected.string_indices[i + 1] - reflected.string_indices[i] - NullTerminated); + } + return ret; +}(); + +namespace details { + template + constexpr auto get_values() noexcept + { + constexpr auto enums = entries; + std::array ret{}; + const auto* const enums_data = enums.data(); + for (std::size_t i = 0; i < ret.size(); ++i) + ret[i] = enums_data[i].first; + return ret; + } + + template + constexpr auto get_names() noexcept + { + constexpr auto enums = entries, NullTerminated>; + std::array ret{}; + const auto* const enums_data = enums.data(); + for (std::size_t i = 0; i < ret.size(); ++i) + ret[i] = enums_data[i].second; + return ret; + } + +} // namespace details + +template +inline constexpr auto values = details::get_values(); + +#ifdef __cpp_concepts +template +#else +template, int> = 0> +#endif +inline constexpr auto names = details::get_names(); + +template +inline constexpr auto min = entries.front().first; + +template +inline constexpr auto max = entries.back().first; + +template +inline constexpr std::size_t count = entries.size(); + +template +inline constexpr bool has_zero_flag = [](const auto is_bitflag) { + if constexpr (is_bitflag.value) { + for (const auto v : values) + if (static_cast>(v) == 0) + return true; + } + return false; +}(std::bool_constant>{}); + +template +inline constexpr bool is_contiguous = static_cast( + enchantum::to_underlying(max) - enchantum::to_underlying(min)) + + 1 == + count; + +template +inline constexpr bool is_contiguous_bitflag = [](const auto is_bitflag) { + if constexpr (is_bitflag.value) { + constexpr auto& enums = entries; + using T = std::underlying_type_t; + for (auto i = std::size_t{has_zero_flag}; i < enums.size() - 1; ++i) + if (T(enums[i].first) << 1 != T(enums[i + 1].first)) + return false; + return true; + } + else { + return false; + } +}(std::bool_constant>{}); + +#ifdef __cpp_concepts +template +concept ContiguousEnum = Enum && is_contiguous; +template +concept ContiguousBitFlagEnum = BitFlagEnum && is_contiguous_bitflag; +#endif + +} // namespace enchantum + +#ifdef __cpp_impl_three_way_comparison + #include +#endif + +#if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && __has_include() +#include +namespace enchantum{ + namespace details + { + using ::std::countr_zero; + } +} +#else +namespace enchantum{ + namespace details + { + template + constexpr int countr_zero(T x) { + if (x == 0) + return sizeof(T) * 8; + + int count = 0; + while ((x & 1) == 0) { + x = static_cast(x >> 1); + ++count; + } + return count; + } + } +} +#endif +#include +#include +#include +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + // false positives from T += T + // it does not make sense. + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" +#endif + +namespace enchantum { +namespace details { + + struct senitiel {}; + + template + struct sized_iterator { + static_assert(Size < INT16_MAX, "Too many enum entries"); + private: + using IndexType = std::conditional_t<(Size <= INT8_MAX), std::int8_t, std::int16_t>; + public: + IndexType index{}; + constexpr CRTP& operator+=(const std::ptrdiff_t offset) & noexcept + { + index += static_cast(offset); + return static_cast(*this); + } + constexpr CRTP& operator-=(const std::ptrdiff_t offset) & noexcept + { + index -= static_cast(offset); + return static_cast(*this); + } + + constexpr CRTP& operator++() & noexcept + { + ++index; + return static_cast(*this); + } + constexpr CRTP& operator--() & noexcept + { + --index; + return static_cast(*this); + } + + [[nodiscard]] constexpr CRTP operator++(int) & noexcept + { + auto copy = static_cast(*this); + ++*this; + return copy; + } + [[nodiscard]] constexpr CRTP operator--(int) & noexcept + { + auto copy = static_cast(*this); + --*this; + return copy; + } + + [[nodiscard]] constexpr friend CRTP operator+(CRTP it, const std::ptrdiff_t offset) noexcept + { + it += offset; + return it; + } + + [[nodiscard]] constexpr friend CRTP operator+(const std::ptrdiff_t offset, CRTP it) noexcept + { + it += offset; + return it; + } + + [[nodiscard]] constexpr friend CRTP operator-(CRTP it, const std::ptrdiff_t offset) noexcept + { + it -= offset; + return it; + } + + [[nodiscard]] constexpr std::ptrdiff_t operator-(const sized_iterator that) const noexcept + { + return index - that.index; + } + + [[nodiscard]] constexpr std::ptrdiff_t operator-(senitiel) const noexcept { return index - Size; } + [[nodiscard]] friend constexpr std::ptrdiff_t operator-(senitiel, sized_iterator it) noexcept + { + return Size - it.index; + } + + [[nodiscard]] constexpr bool operator==(const sized_iterator that) const noexcept { return that.index == index; }; + [[nodiscard]] constexpr bool operator==(senitiel) const noexcept { return Size == index; } + +#ifdef __cpp_impl_three_way_comparison + [[nodiscard]] constexpr auto operator<=>(const sized_iterator that) const noexcept { return index <=> that.index; }; + [[nodiscard]] constexpr auto operator<=>(senitiel) const noexcept { return index <=> Size; } +#else + + [[nodiscard]] constexpr bool operator!=(const sized_iterator that) const noexcept { return that.index != index; }; + [[nodiscard]] constexpr bool operator!=(senitiel) const noexcept { return Size != index; } + + [[nodiscard]] friend constexpr bool operator==(senitiel, const sized_iterator it) noexcept + { + return Size == it.index; + } + + [[nodiscard]] friend constexpr bool operator!=(senitiel, const sized_iterator it) noexcept + { + return Size != it.index; + } + + [[nodiscard]] constexpr bool operator<(const sized_iterator that) const noexcept { return index < that.index; }; + [[nodiscard]] constexpr bool operator>(const sized_iterator that) const noexcept { return index > that.index; }; + [[nodiscard]] constexpr bool operator<=(const sized_iterator that) const noexcept { return index <= that.index; }; + [[nodiscard]] constexpr bool operator>=(const sized_iterator that) const noexcept { return index >= that.index; }; + + [[nodiscard]] constexpr bool operator<(senitiel) const noexcept { return index < Size; }; + [[nodiscard]] constexpr bool operator>(senitiel) const noexcept { return index > Size; }; + [[nodiscard]] constexpr bool operator<=(senitiel) const noexcept { return index <= Size; }; + [[nodiscard]] constexpr bool operator>=(senitiel) const noexcept { return index >= Size; }; + + [[nodiscard]] friend constexpr bool operator<(senitiel, const sized_iterator it) noexcept + { + return Size < it.index; + }; + [[nodiscard]] friend constexpr bool operator>(senitiel, const sized_iterator it) noexcept + { + return Size > it.index; + }; + [[nodiscard]] friend constexpr bool operator<=(senitiel, const sized_iterator it) noexcept + { + return Size <= it.index; + }; + [[nodiscard]] friend constexpr bool operator>=(senitiel, const sized_iterator it) noexcept + { + return Size >= it.index; + }; + +#endif + }; + + template + struct names_generator_t { + [[nodiscard]] static constexpr std::size_t size() noexcept { return count; } + + struct iterator : sized_iterator(size())> { + using value_type = String; + [[nodiscard]] constexpr String operator*() const noexcept + { + const auto* const p = details::reflection_string_indices.data(); + const auto* const strings = details::reflection_data_string_storage.data(); + return String(strings + p[this->index], p[this->index + 1] - p[this->index] - NullTerminated); + } + + [[nodiscard]] constexpr String operator[](const std::ptrdiff_t i) const noexcept { return *(*this + i); } + }; + + [[nodiscard]] static constexpr auto begin() { return iterator{}; } + [[nodiscard]] static constexpr auto end() { return senitiel{}; } + + [[nodiscard]] constexpr auto operator[](const std::size_t i) const noexcept + { + return *(begin() + static_cast(i)); + } + }; + + template + struct values_generator_t { + [[nodiscard]] static constexpr std::size_t size() noexcept { return count; } + + struct iterator : sized_iterator(size())> { + using value_type = E; + [[nodiscard]] constexpr E operator*() const noexcept + { + using T = std::underlying_type_t; + + if constexpr (is_contiguous) { + return static_cast(static_cast(min) + static_cast(this->index)); + } + else if constexpr (is_contiguous_bitflag) { + using UT = std::make_unsigned_t; + constexpr auto real_min_offset = details::countr_zero(static_cast(values[has_zero_flag])); + + if constexpr (has_zero_flag) + if (this->index == 0) + return E{}; + return static_cast(UT{1} << (real_min_offset + static_cast(this->index - has_zero_flag))); + } + else { + return values[static_cast(this->index)]; + } + } + [[nodiscard]] constexpr E operator[](const std::ptrdiff_t i) const noexcept { return *(*this + i); } + }; + + [[nodiscard]] static constexpr auto begin() { return iterator{}; } + [[nodiscard]] static constexpr auto end() { return senitiel{}; } + + [[nodiscard]] constexpr auto operator[](const std::size_t i) const noexcept + { + return *(begin() + static_cast(i)); + } + }; + + template, bool NullTerminated = true> + struct entries_generator_t { + [[nodiscard]] static constexpr std::size_t size() noexcept { return count; } + + struct iterator : sized_iterator(size())> { + using value_type = Pair; + [[nodiscard]] constexpr Pair operator*() const noexcept + { + return Pair{ + values_generator_t{}[static_cast(this->index)], + names_generator_t{}[static_cast(this->index)], + }; + } + [[nodiscard]] constexpr Pair operator[](const std::ptrdiff_t i) const noexcept { return *(*this + i); } + }; + + [[nodiscard]] static constexpr auto begin() { return iterator{}; } + [[nodiscard]] static constexpr auto end() { return senitiel{}; } + + [[nodiscard]] constexpr auto operator[](const std::size_t i) const noexcept + { + return *(begin() + static_cast(i)); + } + }; + +} // namespace details + +template +inline constexpr details::values_generator_t values_generator{}; + +#ifdef __cpp_concepts +template +inline constexpr details::names_generator_t names_generator{}; + +template, bool NullTerminated = true> +inline constexpr details::entries_generator_t entries_generator{}; + +#else +template, int> = 0> +inline constexpr details::names_generator_t names_generator{}; + +template, bool NullTerminated = true, std::enable_if_t, int> = 0> +inline constexpr details::entries_generator_t entries_generator{}; + +#endif + +} // namespace enchantum + +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic pop +#endif + +#include +#include + +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + +namespace enchantum { + +namespace details { + template + constexpr bool call_predicate(const BinaryPredicate binary_pred, const string_view a, const string_view b) + { + if constexpr (std::is_invocable_v) { + const auto a_size = a.size(); + if (a_size != b.size()) + return false; + const auto a_data = a.data(); + const auto b_data = b.data(); + + for (std::size_t i = 0; i < a_size; ++i) + if (!binary_pred(a_data[i],b_data[i])) + return false; + return true; + } + else { + static_assert(std::is_invocable_v, + "BinaryPredicate must be callable with atleast 2 char or 2 string_views"); + return binary_pred(a, b); + } + } + + constexpr std::pair minmax_string_size(const string_view* begin, const string_view* const end) + { + using T = std::size_t; + auto minmax = std::pair(std::numeric_limits::max(), 0); + + for (; begin != end; ++begin) { + const auto size = begin->size(); + minmax.first = minmax.first < size ? minmax.first : size; + minmax.second = minmax.second > size ? minmax.second : size; + } + return minmax; + } + +} // namespace details + +template +[[nodiscard]] constexpr bool contains(const std::underlying_type_t value) noexcept +{ + using T = std::underlying_type_t; + + if (value < T(min) || value > T(max)) + return false; + + if constexpr (is_contiguous_bitflag) { + if constexpr (has_zero_flag) + if (value == 0) + return true; + const auto u = static_cast>(value); + + // std::has_single_bit + return u != 0 && (u & (u - 1)) == 0; + } + else if constexpr (is_contiguous) { + return true; + } + else { + for (const auto v : values_generator) + if (static_cast(v) == value) + return true; + return false; + } +} + +template +[[nodiscard]] constexpr bool contains(const E value) noexcept +{ + return enchantum::contains(static_cast>(value)); +} + +template +[[nodiscard]] constexpr bool contains(const string_view name) noexcept +{ + constexpr auto minmax = details::minmax_string_size(names.data(), names.data() + names.size()); + if (const auto size = name.size(); size < minmax.first || size > minmax.second) + return false; + + for (const auto s : names_generator) + if (s == name) + return true; + return false; +} + +template +[[nodiscard]] constexpr bool contains(const string_view name, const BinaryPred binary_pred) noexcept +{ + for (const auto s : names_generator) + if (details::call_predicate(binary_pred, name, s)) + return true; + return false; +} + +namespace details { + template + struct index_to_enum_functor { + [[nodiscard]] constexpr optional operator()(const std::size_t index) const noexcept + { + if (index < count) + return optional(values_generator[index]); + return optional(); + } + }; + + struct enum_to_index_functor { + template + [[nodiscard]] constexpr optional operator()(const E e) const noexcept + { + using T = std::underlying_type_t; + + if constexpr (is_contiguous) { + if (enchantum::contains(e)) { + return optional(std::size_t(T(e) - T(min))); + } + } + else if constexpr (is_contiguous_bitflag) { + if (enchantum::contains(e)) { + constexpr bool has_zero = has_zero_flag; + if constexpr (has_zero) + if (static_cast(e) == 0) + return optional(0); // assumes 0 is the index of value `0` + + using U = std::make_unsigned_t; + return has_zero + details::countr_zero(static_cast(e)) - + details::countr_zero(static_cast(values_generator[has_zero])); + } + } + else { + for (std::size_t i = 0; i < count; ++i) { + if (values_generator[i] == e) + return optional(i); + } + } + return optional(); + } + }; + + template + struct cast_functor { + [[nodiscard]] constexpr optional operator()(const std::underlying_type_t value) const noexcept + { + if (!enchantum::contains(value)) + return optional(); + return optional(static_cast(value)); + } + + [[nodiscard]] constexpr optional operator()(const string_view name) const noexcept + { + constexpr auto minmax = details::minmax_string_size(names.data(), names.data() + names.size()); + if (const auto size = name.size(); size < minmax.first || size > minmax.second) + return optional(); // nullopt + + for (std::size_t i = 0; i < count; ++i) { + if (names_generator[i] == name) { + return optional(values_generator[i]); + } + } + return optional(); // nullopt + } + + template + [[nodiscard]] constexpr optional operator()(const string_view name, const BinaryPred binary_pred) const noexcept + { + + for (std::size_t i = 0; i < count; ++i) { + if (details::call_predicate(binary_pred, name, names_generator[i])) { + return optional(values_generator[i]); + } + } + return optional(); + } + }; + +} // namespace details + +template +inline constexpr details::index_to_enum_functor index_to_enum{}; + +inline constexpr details::enum_to_index_functor enum_to_index{}; + +template +inline constexpr details::cast_functor cast{}; + +namespace details { + struct to_string_functor { + template + [[nodiscard]] constexpr string_view operator()(const E value) const noexcept + { + if (const auto i = enchantum::enum_to_index(value)) + return names_generator[*i]; + return string_view(); + } + }; + +} // namespace details +inline constexpr details::to_string_functor to_string{}; + +} // namespace enchantum + +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic pop +#endif + +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + +namespace enchantum { + +template +inline constexpr E value_ors = [] { + static_assert(is_bitflag, ""); + using T = std::underlying_type_t; + T ret{}; + for (const auto val : values_generator) + ret |= static_cast(val); + return static_cast(ret); +}(); + +template +[[nodiscard]] constexpr bool contains_bitflag(const std::underlying_type_t value) noexcept +{ + if constexpr (!has_zero_flag) + if (value == 0) + return false; + + return value == (static_cast>(value_ors) & value); +} + +template +[[nodiscard]] constexpr bool contains_bitflag(const E value) noexcept +{ + return enchantum::contains_bitflag(static_cast>(value)); +} + +template +[[nodiscard]] constexpr bool contains_bitflag(const string_view s, const char sep, const BinaryPred binary_pred) noexcept +{ + std::size_t pos = 0; + for (std::size_t i = s.find(sep); i != s.npos; i = s.find(sep, pos)) { + if (!enchantum::contains(s.substr(pos, i - pos), binary_pred)) + return false; + pos = i + 1; + } + return enchantum::contains(s.substr(pos), binary_pred); +} + +template +[[nodiscard]] constexpr bool contains_bitflag(const string_view s, const char sep = '|') noexcept +{ + std::size_t pos = 0; + for (std::size_t i = s.find(sep); i != s.npos; i = s.find(sep, pos)) { + if (!enchantum::contains(s.substr(pos, i - pos))) + return false; + pos = i + 1; + } + return enchantum::contains(s.substr(pos)); +} + +template +[[nodiscard]] constexpr String to_string_bitflag(const E value, const char sep = '|') +{ + using T = std::underlying_type_t; + if constexpr (has_zero_flag) + if (static_cast(value) == 0) + return String(names_generator[0]); + + String name; + T check_value = 0; + for (auto i = std::size_t{has_zero_flag}; i < count; ++i) { + const auto v = static_cast(values_generator[i]); + if (v == (static_cast(value) & v)) { + const auto s = names_generator[i]; + if (!name.empty()) + name.append(1, sep); // append separator if not the first value + name.append(s.data(), s.size()); // not using operator += since this may not be std::string_view always + check_value |= v; + } + } + if (check_value == static_cast(value)) + return name; + return String(); +} + +template +[[nodiscard]] constexpr optional cast_bitflag(const string_view s, const char sep, const BinaryPred binary_pred) noexcept +{ + using T = std::underlying_type_t; + T check_value{}; + std::size_t pos = 0; + for (std::size_t i = s.find(sep); i != s.npos; i = s.find(sep, pos)) { + if (const auto v = enchantum::cast(s.substr(pos, i - pos), binary_pred)) + check_value |= static_cast(*v); + else + return optional(); + pos = i + 1; + } + + if (const auto v = enchantum::cast(s.substr(pos), binary_pred)) + return optional(static_cast(check_value | static_cast(*v))); + return optional(); +} + +template +[[nodiscard]] constexpr optional cast_bitflag(const string_view s, const char sep = '|') noexcept +{ + return enchantum::cast_bitflag(s, sep, [](const auto& a, const auto& b) { return a == b; }); +} + +template +[[nodiscard]] constexpr optional cast_bitflag(const std::underlying_type_t value) noexcept +{ + return enchantum::contains_bitflag(value) ? optional(static_cast(value)) : optional(); +} + +} // namespace enchantum + +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic pop +#endif + +#include + +namespace enchantum { +namespace details { + template + std::string format(E e) noexcept + { + if constexpr (is_bitflag) { + if (const auto name = enchantum::to_string_bitflag(e); !name.empty()) { + if constexpr (std::is_same_v) { + return name; + } + else { + return std::string(name.data(), name.size()); + } + } + } + else { + if (const auto name = enchantum::to_string(e); !name.empty()) + return std::string(name.data(), name.size()); + } + return std::to_string(+enchantum::to_underlying(e)); // promote using + to select int overload if to underlying returns char + } +} // namespace details +} // namespace enchantum + +#include + +namespace enchantum { + +#if 0 +namespace details { + + template + constexpr auto cartesian_product() + { + constexpr auto size = []() { + std::size_t x = range; + std::size_t n = sets; + while (--n != 0) + x *= range; + return x; + }(); + + std::array, size> products{}; + std::array counter{}; + + for (auto& product : products) { + product = counter; + + ++counter.back(); + for (std::size_t i = counter.size() - 1; i != 0; i--) { + if (counter[i] != range) + break; + + counter[i] = 0; + ++counter[i - 1]; + } + } + return products; + } + +} // namespace details +#endif + +#if 0 +template Func> +constexpr auto visit(Func func, E e) +noexcept(std::is_nothrow_invocable_v) +{ + using Ret = decltype(func(e)); + + + return [&](std::index_sequence) { + if ((values[Idx] == enums)) + (func(std::integral_constant[Idx]> {}), ...); + }(std::make_index_sequence>()); +} +template Func> +constexpr auto visit(Func func, Enums... enums) noexcept(std::is_nothrow_invocable_v) +{ + using Ret = decltype(func(enums...)); + return [&](std::index_sequence) { + if ((values[Idx] == enums) && ...) + (func(std::integral_constant[Idx]> {}), ...); + }(std::make_index_sequence>()...); +} +#endif +namespace details { + + template + constexpr auto for_each(Func& f, std::index_sequence) + { + // Clang 13 to 15 says ths syntax is invalid if I dont put more `()` + (void)((f(std::integral_constant[I]> {}), ...)); + } + +} // namespace details + +template +constexpr void for_each(Func f) // intentional not const +{ + details::for_each(f, std::make_index_sequence>{}); +} +} // namespace enchantum + +#include +#include + +namespace enchantum { + +template>> +class array : public Container { + static_assert(std::is_enum_v); +public: + using container_type = Container; + using index_type = E; + using typename Container::const_reference; + using typename Container::reference; + + using Container::at; + using Container::operator[]; + + [[nodiscard]] constexpr reference at(const E index) + { + if (const auto i = enchantum::enum_to_index(index)) + return operator[](*i); + ENCHANTUM_THROW(std::out_of_range("enchantum::array::at index out of range"), index); + } + + [[nodiscard]] constexpr const_reference at(const E index) const + { + if (const auto i = enchantum::enum_to_index(index)) + return operator[](*i); + ENCHANTUM_THROW(std::out_of_range("enchantum::array::at: index out of range"), index); + } + + [[nodiscard]] constexpr reference operator[](const E index) noexcept + { + return operator[](*enchantum::enum_to_index(index)); + } + + [[nodiscard]] constexpr const_reference operator[](const E index) const noexcept + { + return operator[](*enchantum::enum_to_index(index)); + } +}; + +} // namespace enchantum + +#ifndef ENCHANTUM_ALIAS_BITSET + #include +#endif + +#include + +namespace enchantum { + +namespace details { +#ifndef ENCHANTUM_ALIAS_BITSET + using ::std::bitset; +#else + ENCHANTUM_ALIAS_BITSET; +#endif +} // namespace details + +template>> +class bitset : public Container { + static_assert(std::is_enum_v); +public: + + using container_type = Container; + using typename Container::reference; + + using Container::operator[]; + using Container::flip; + using Container::reset; + using Container::set; + using Container::test; + + using Container::Container; + using Container::operator=; + + [[nodiscard]] string to_string(const char sep = '|') const + { + string name; + for (std::size_t i = 0; i < enchantum::count; ++i) { + if (test(i)) { + const auto s = enchantum::names_generator[i]; + if (!name.empty()) + name += sep; + name.append(s.data(), s.size()); // not using operator += since this may not be std::string_view always + } + } + return name; + } + + [[nodiscard]] constexpr auto to_string(const char zero, const char one) const + { + return Container::to_string(zero, one); + } + + constexpr bitset(const std::initializer_list values) noexcept + { + for (auto value : values) { + set(value, true); + } + } + + [[nodiscard]] constexpr reference operator[](const E index) noexcept + { + return operator[](*enchantum::enum_to_index(index)); + } + + [[nodiscard]] constexpr bool operator[](const E index) const noexcept + { + return operator[](*enchantum::enum_to_index(index)); + } + + constexpr bool test(const E pos) + { + + if (const auto i = enchantum::enum_to_index(pos)) + return test(*i); + ENCHANTUM_THROW(std::out_of_range("enchantum::bitset::test(E pos,bool value) out of range exception"), pos); + } + + constexpr bitset& set(const E pos, bool value = true) + { + + if (const auto i = enchantum::enum_to_index(pos)) + return static_cast(set(*i, value)); + ENCHANTUM_THROW(std::out_of_range("enchantum::bitset::set(E pos,bool value) out of range exception"), pos); + } + + constexpr bitset& reset(const E pos) + { + if (const auto i = enchantum::enum_to_index(pos)) + return static_cast(reset(*i)); + ENCHANTUM_THROW(std::out_of_range("enchantum::bitset::reset(E pos) out of range exception"), pos); + } + + constexpr bitset& flip(const E pos) + { + if (const auto i = enchantum::enum_to_index(pos)) + return static_cast(flip(*i)); + ENCHANTUM_THROW(std::out_of_range("enchantum::bitset::flip(E pos) out of range exception"), pos); + } +}; + +} // namespace enchantum + +template +struct std::hash> : std::hash>> { + using std::hash>>::operator(); +}; + +#include +/* +Note this header is an extremely easy way to cause ODR issues. + +class Flags { F1 = 1 << 0,F2 = 1<< 1}; +// **note I did not define any operators** + +enchantum::contains(Flags::F1); // considered a classical `Enum` concept + +using namespace enchantum::bitwise_operators; + +enchantum::contains(Flags::F1); // considered `BitFlagEnum` concept woops! ODR! + +*/ + +namespace enchantum { +namespace bitwise_operators { + + template + [[nodiscard]] constexpr E operator~(E e) noexcept + { + return static_cast(~static_cast>(e)); + } + + template + [[nodiscard]] constexpr E operator|(E a, E b) noexcept + { + using T = std::underlying_type_t; + return static_cast(static_cast(a) | static_cast(b)); + } + + template + [[nodiscard]] constexpr E operator&(E a, E b) noexcept + { + using T = std::underlying_type_t; + return static_cast(static_cast(a) & static_cast(b)); + } + + template + [[nodiscard]] constexpr E operator^(E a, E b) noexcept + { + using T = std::underlying_type_t; + return static_cast(static_cast(a) ^ static_cast(b)); + } + + template + constexpr E& operator|=(E& a, E b) noexcept + { + using T = std::underlying_type_t; + return a = static_cast(static_cast(a) | static_cast(b)); + } + + template + constexpr E& operator&=(E& a, E b) noexcept + { + using T = std::underlying_type_t; + return a = static_cast(static_cast(a) & static_cast(b)); + } + + template + constexpr E& operator^=(E& a, E b) noexcept + { + using T = std::underlying_type_t; + return a = static_cast(static_cast(a) ^ static_cast(b)); + } + +} // namespace bitwise_operators +} // namespace enchantum + +#define ENCHANTUM_DEFINE_BITWISE_FOR(Enum) \ + [[nodiscard]] constexpr Enum operator&(Enum a, Enum b) noexcept \ + { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) & static_cast(b)); \ + } \ + [[nodiscard]] constexpr Enum operator|(Enum a, Enum b) noexcept \ + { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) | static_cast(b)); \ + } \ + [[nodiscard]] constexpr Enum operator^(Enum a, Enum b) noexcept \ + { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) ^ static_cast(b)); \ + } \ + constexpr Enum& operator&=(Enum& a, Enum b) noexcept { return a = a & b; } \ + constexpr Enum& operator|=(Enum& a, Enum b) noexcept { return a = a | b; } \ + constexpr Enum& operator^=(Enum& a, Enum b) noexcept { return a = a ^ b; } \ + [[nodiscard]] constexpr Enum operator~(Enum a) noexcept \ + { \ + return static_cast(~static_cast>(a)); \ + } + +#include +#include + +namespace enchantum { +namespace iostream_operators { + template + std::basic_ostream& operator<<(std::basic_ostream& os, const E e) + { + return os << details::format(e); + } + + template + auto operator>>(std::basic_istream& is, E& value) -> decltype((value = E{}, is)) + // sfinae to check whether value is assignable + { + std::basic_string s; + is >> s; + if (!is) + return is; + + if constexpr (is_bitflag) { + if (const auto v = enchantum::cast_bitflag(s)) + value = *v; + else + is.setstate(std::ios_base::failbit); + } + else { + if (const auto v = enchantum::cast(s)) + value = *v; + else + is.setstate(std::ios_base::failbit); + } + return is; + } +} // namespace iostream_operators +} // namespace enchantum + +#include + +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + +namespace enchantum { +namespace details { + template + struct next_value_functor { + template + [[nodiscard]] constexpr optional operator()(const E value, const std::ptrdiff_t n = 1) const noexcept + { + if (!enchantum::contains(value)) + return optional{}; + + const auto index = static_cast(*enchantum::enum_to_index(value)) + (n * N); + if (index >= 0 && index < static_cast(count)) + return optional{values_generator[static_cast(index)]}; + return optional{}; + } + }; + + template + struct next_value_circular_functor { + template + [[nodiscard]] constexpr E operator()(const E value, const std::ptrdiff_t n = 1) const noexcept + { + ENCHANTUM_ASSERT(enchantum::contains(value), "next/prev_value_circular requires 'value' to be a valid enum member", value); + const auto i = static_cast(*enchantum::enum_to_index(value)); + constexpr auto count = static_cast(enchantum::count); + return values_generator[static_cast(((i + (n * N)) % count + count) % count)]; // handles wrap around and negative n + } + }; +} // namespace details + +inline constexpr details::next_value_functor<1> next_value{}; +inline constexpr details::next_value_functor<-1> prev_value{}; +inline constexpr details::next_value_circular_functor<1> next_value_circular{}; +inline constexpr details::next_value_circular_functor<-1> prev_value_circular{}; + +} // namespace enchantum + +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic pop +#endif + +#if __has_include() + + +#include + +#ifdef __cpp_concepts +template +struct fmt::formatter +#else +template +struct fmt::formatter>> +#endif +: fmt::formatter { + template + constexpr auto format(const E e, FmtContext& ctx) const + { + return fmt::formatter::format(enchantum::details::format(e), ctx); + } +}; +#elif (__cplusplus >= 202002 || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002)) && __has_include() + + +#include +#include + +template +struct std::formatter : std::formatter { + template + constexpr auto format(const E e, FmtContext& ctx) const + { + return std::formatter::format(enchantum::details::format(e), ctx); + } +}; +#endif \ No newline at end of file diff --git a/support/scripts/verify-coverage.sh b/support/scripts/verify-coverage.sh index 5eb072b..2c93731 100755 --- a/support/scripts/verify-coverage.sh +++ b/support/scripts/verify-coverage.sh @@ -19,7 +19,7 @@ BUILD_FOLDER=${1:-.} LINE_PERCENTAGE=${2:-70} -IGNORE_REGEX="Count.h|CLI11.hpp" +IGNORE_REGEX="Count.h|CLI11.hpp|enchantum_single_header.hpp" set -x diff --git a/tests/SimoSim/test_config.yaml b/tests/SimoSim/test_config.yaml index 8eca7ef..668aa5b 100644 --- a/tests/SimoSim/test_config.yaml +++ b/tests/SimoSim/test_config.yaml @@ -15,6 +15,8 @@ modules: value: time: 10 unit: PS + - name: log_level + value: INFO - name: collector type: collector parameters: diff --git a/tests/collection/PingPongCollection.cc b/tests/collection/PingPongCollection.cc index 1c80b87..529844e 100644 --- a/tests/collection/PingPongCollection.cc +++ b/tests/collection/PingPongCollection.cc @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -39,6 +40,8 @@ class PingPongParameters : public Simo::Parameters { // Declare parameters in constructor trie.add_unset("period").validator( [](const auto& t) { return t > Simo::Time::one; }); + trie.add("log_level", "WARNING"); + trie.add("log_file", "Simo.log"); } }; @@ -50,6 +53,8 @@ class PingModule : public Simo::Module { !status.success()) { return status; } + log_setup(parameters.get("log_file")->value()); + log_level(parameters.get("log_level")->value()); period = parameters.get("period")->value(); port = &create_port>("port"); @@ -73,6 +78,7 @@ class PingModule : public Simo::Module { void try_send() { sim_ctx().schedule_in(period, [this]() { try_send(); }); if (send_message) { + log(Simo::Log::LogLevel::INFO, []() { return "Send PING"; }); port->send_out(PingPongMessage::PING); ++(*num_msg_sent); start_send = false; @@ -96,6 +102,8 @@ class PongModule : public Simo::Module { return status; } period = parameters.get("period")->value(); + log_setup(parameters.get("log_file")->value()); + log_level(parameters.get("log_level")->value()); port = &create_port>("port"); num_msg_sent = &create_statistic("num_msg_sent"); @@ -118,6 +126,7 @@ class PongModule : public Simo::Module { void try_send() { sim_ctx().schedule_in(period, [this]() { try_send(); }); if (send_message) { + log(Simo::Log::LogLevel::INFO, []() { return "Send PONG"; }); port->send_out(PingPongMessage::PONG); ++(*num_msg_sent); } diff --git a/tests/module/ModuleTest.cc b/tests/module/ModuleTest.cc new file mode 100644 index 0000000..435c8d5 --- /dev/null +++ b/tests/module/ModuleTest.cc @@ -0,0 +1,99 @@ +// Copyright 2026 Matteo Fusi and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#define BOOST_TEST_MODULE Module +#include +#include + +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace { + +[[nodiscard]] std::string read_file_contents( + const std::filesystem::path& path) { + std::ifstream input(path); + std::string contents; + std::getline(input, contents, '\0'); + return contents; +} + +} // namespace + +struct Fixture { + Fixture() { + p.name("test_module"); + const auto status = m.initialize(ctx, p); + BOOST_CHECK(status.success()); + + const fs::path temp_dir = fs::temp_directory_path(); + temp_file = temp_dir / "simo.log"; + std::filesystem::remove(temp_file); + m.log_setup(temp_file); + logger = &m.get_logger(); + } + + void flush_log() const { Simo::Log::Logger::flush_sink(temp_file); } + + Simo::Context ctx; + Simo::Module m; + Simo::Parameters p; + fs::path temp_file; + Simo::Log::Logger* logger; +}; + +BOOST_FIXTURE_TEST_CASE(EnableLog, Fixture) { + logger->populate_default_log_levels(); + std::string message = "This is a log message"; + const std::string out_message = + std::format("[WARNING] [0 ps] [test_module] {}\n", message); + m.log(3, [&message]() { return message; }, true); + flush_log(); + const std::string log_content = read_file_contents(temp_file); + BOOST_CHECK_EQUAL(log_content, out_message); +} + +BOOST_FIXTURE_TEST_CASE(LogDisabled, Fixture) { + logger->populate_default_log_levels(); + m.log_enable(false); + std::string message = "This is a log message"; + m.log(3, [&message]() { return message; }, true); + flush_log(); + const std::string log_content = read_file_contents(temp_file); + BOOST_CHECK_EQUAL(log_content, ""); +} + +BOOST_FIXTURE_TEST_CASE(LogRaw, Fixture) { + logger->populate_default_log_levels(); + std::string message = "This is a log message"; + const std::string out_message = std::format("[WARNING] {}", message); + m.log_raw_callable(3, true, [&message]() { return message; }); + flush_log(); + const std::string log_content = read_file_contents(temp_file); + BOOST_CHECK_EQUAL(log_content, out_message); +} + +BOOST_FIXTURE_TEST_CASE(SkipLogDueToLevel, Fixture) { + logger->populate_default_log_levels(); + std::string message = "This is a log message"; + logger->log_level(2); + m.log(1, [&message]() { return message; }); + flush_log(); + const std::string log_content = read_file_contents(temp_file); + BOOST_CHECK_EQUAL(log_content, ""); +}