#ifndef PUGS_ASSERT_HPP #define PUGS_ASSERT_HPP #include <utils/PugsMacros.hpp> #include <utils/PugsTraits.hpp> #include <iostream> #include <rang.hpp> #include <string> #include <tuple> class AssertError : public std::runtime_error { private: const std::string m_file; const size_t m_line; const std::string m_function; const std::string m_test; const std::string m_message; public: friend std::ostream& operator<<(std::ostream& os, const AssertError& assert_error) { os << '\n' << "---------- Assertion error -----------\n" << " at " << assert_error.m_file << ':' << assert_error.m_line << '\n' << " in " << assert_error.m_function << '\n' << " assertion (" << assert_error.m_test << ") failed!\n"; if (not assert_error.m_message.empty()) { os << ' ' << assert_error.m_message << '\n'; } os << "--------------------------------------" << '\n'; return os; } template <typename... Args> AssertError(const std::string& filename, size_t line, const std::string& function, const std::tuple<Args...>& tuple_args, const std::string_view args_string) : std::runtime_error(""), m_file{filename}, m_line{line}, m_function{function}, m_test{[&] { if constexpr (sizeof...(Args) == 1) { return std::string(args_string); } else { std::string_view message = std::get<1>(tuple_args); std::string test_str{args_string, 0, args_string.size() - message.size() - 2}; while (test_str[test_str.size() - 1] == ' ' or test_str[test_str.size() - 1] == ',') { test_str.resize(test_str.size() - 1); } return test_str; } }()}, m_message{[&] { if constexpr (sizeof...(Args) == 1) { return std::string{}; } else { return std::get<1>(tuple_args); } }()} { if (m_message.empty()) { std::runtime_error::operator=(std::runtime_error(m_test.c_str())); } else { std::runtime_error::operator=(std::runtime_error(m_message.c_str())); } } }; template <typename... Args> struct AssertChecker; template <typename... Args> struct AssertChecker<std::tuple<Args...> > { void static check_args_type() { constexpr size_t size = sizeof...(Args); static_assert(size >= 1 and size <= 2, "Assert requires 1 or 2 parameters"); using ArgsTuple = std::tuple<Args...>; using assertion_t = std::tuple_element_t<0, ArgsTuple>; static_assert(std::is_integral_v<assertion_t> or std::is_pointer_v<assertion_t> or is_std_ptr_v<assertion_t>); if constexpr (size == 2) { using message_t = std::decay_t<std::remove_const_t<std::tuple_element_t<1, ArgsTuple> > >; static_assert(std::is_same_v<message_t, const char*>, "optional second argument must be a string literal"); } } }; #ifdef NDEBUG #define Assert(...) \ { \ using TupleArgs = decltype(std::make_tuple(__VA_ARGS__)); \ AssertChecker<TupleArgs>::check_args_type(); \ } #else // NDEBUG #define Assert(...) \ { \ using TupleArgs = decltype(std::make_tuple(__VA_ARGS__)); \ AssertChecker<TupleArgs>::check_args_type(); \ constexpr int tuple_size = std::tuple_size_v<TupleArgs>; \ static_assert(tuple_size >= 1 and tuple_size <= 2); \ if (not static_cast<bool>(std::get<0>(std::forward_as_tuple(__VA_ARGS__)))) { \ throw AssertError(__FILE__, __LINE__, __PRETTY_FUNCTION__, std::forward_as_tuple(__VA_ARGS__), #__VA_ARGS__); \ } \ } #endif // NDEBUG // store the current state of Assert macro. This is useful for // instance to noexcept management of Assert throws #ifdef NDEBUG #define NO_ASSERT true #else // NDEBUG #define NO_ASSERT false #endif // NDEBUG #endif // PUGS_ASSERT_HPP