diff --git a/debug.hpp b/debug.hpp index 90d84854..240a9e90 100644 --- a/debug.hpp +++ b/debug.hpp @@ -23,6 +23,7 @@ #include #include + /////////////////////////////////////////////////////////////////////////////// #ifdef ENABLE_DEBUGGING #define DEBUG_ONLY(X) do { \ @@ -57,43 +58,34 @@ /////////////////////////////////////////////////////////////////////////////// -#define _CHECK_META(C, SUCCESS, FAILURE) do { \ - const auto __DEBUG_value = (C); \ - if (!__DEBUG_value) { \ - std::cerr << PACKAGE << ": " \ - << __FILE__ << ":" \ - << __LINE__ << ": " \ - << __FUNCTION__ \ - << ". Assertion '" << #C \ - << "' failed: " << __DEBUG_value << std::endl; \ - \ - { FAILURE } \ - } else { \ - { SUCCESS } \ - } \ -} while (0) +#define _CHECK_PANIC(FMT,...) do { \ + panic ("%s:%s:%i:%s\n" FMT, \ + PACKAGE, __FILE__, __LINE__, __FUNCTION__, \ + __VA_ARGS__); \ +} while(0) /////////////////////////////////////////////////////////////////////////////// -#define CHECK(C) do { DEBUG_ONLY(_CHECK_META((C), { ; }, { panic (); });); } while (0) +#define CHECK(C) do { \ + DEBUG_ONLY( \ + if (!(C)) \ + panic (); \ + ); \ +} while (0) /////////////////////////////////////////////////////////////////////////////// #define CHECK_EQ(A,B) do { \ DEBUG_ONLY( \ const auto __a = (A); \ const auto __b = (B); \ - _CHECK_META (util::almost_equal (__a, __b), \ - { ; }, \ - { \ - std::ostringstream __debug_os; \ - __debug_os.precision (15); \ - __debug_os \ - << "expected equality.\n" \ - << "__a: " << #A << " is " << __a << ")" \ - << "\n != \n" \ - << "__b: " << #B << " is " << __b << ")"; \ - panic (__debug_os.str ()); \ - }); \ + \ + if (!util::almost_equal (__a, __b)) { \ + _CHECK_PANIC("expected equality\n" \ + "__a: %s is %!\n" \ + "__b: %s is %!\n", \ + #A, __a, \ + #B, __b); \ + } \ ); \ } while (0) @@ -103,17 +95,14 @@ DEBUG_ONLY( \ const auto __a = (A); \ const auto __b = (B); \ - _CHECK_META (__a < __b, \ - { ; }, \ - { \ - std::ostringstream __debug_check_lt_os; \ - __debug_check_lt_os \ - << "expected less than.\n" \ - << "__a: " << #A << " is " << __a << ")" \ - << "\n >= \n" \ - << "__b: " << #B << " is " << __b << ")"; \ - panic (__debug_check_lt_os.str ()); \ - }); \ + \ + if (__a >= __b) { \ + _CHECK_PANIC("expected less than\n" \ + "__a: %s is %!\n" \ + "__b: %s is %!\n", \ + #A, __a, \ + #B, __b); \ + }; \ ); \ } while (0) @@ -123,17 +112,14 @@ DEBUG_ONLY( \ const auto __a = (A); \ const auto __b = (B); \ - _CHECK_META (__a <= __b, \ - { ; }, \ - { \ - std::ostringstream __debug_check_lt_os; \ - __debug_check_lt_os \ - << "expected less or equal to\n" \ - << "__a: " << #A << " is " << __a << ")" \ - << "\n > \n" \ - << "__b: " << #B << " is " << __b << ")"; \ - panic (__debug_check_lt_os.str ()); \ - }); \ + \ + if (__a > __b) { \ + _CHECK_PANIC("expected less than or equal\n" \ + "__a: %s is %!\n" \ + "__b: %s is %!\n", \ + #A, __a, \ + #B, __b); \ + } \ ); \ } while (0) @@ -143,17 +129,14 @@ DEBUG_ONLY( \ const auto __a = (A); \ const auto __b = (B); \ - _CHECK_META (__a > __b, \ - { ; }, \ - { \ - std::ostringstream __debug_check_gt_os; \ - __debug_check_gt_os \ - << "expected greater than.\n" \ - << "__a: " << #A << " is " << __a << ")" \ - << "\n <= \n" \ - << "__b: " << #B << " is " << __b << ")"; \ - panic (__debug_check_gt_os.str ()); \ - }); \ + \ + if (__a <= __b) { \ + _CHECK_PANIC ("expected greater than\n" \ + "__a: %s is %!\n" \ + "__b: %s is %!\n", \ + #A, __a, \ + #B, __b); \ + } \ ); \ } while (0) @@ -163,39 +146,35 @@ DEBUG_ONLY( \ const auto __a = (A); \ const auto __b = (B); \ - _CHECK_META (__a >= __b, \ - { ; }, \ - { \ - std::ostringstream __debug_check_gt_os; \ - __debug_check_gt_os \ - << "expected greater or equal to.\n" \ - << "__a: " << #A << " is " << __a << ")" \ - << "\n < \n" \ - << "__b: " << #B << " is " << __b << ")"; \ - panic (__debug_check_gt_os.str ()); \ - }); \ + \ + if (__a < __b) { \ + _CHECK_PANIC ("expected greater or equal\n" \ + "__a: %s is %!\n" \ + "__b: %s is %!\n", \ + #A, __a, \ + #B, __b); \ + }; \ ); \ } while (0) /////////////////////////////////////////////////////////////////////////////// -#define CHECK_LIMIT(VAL,LO,HI) do { \ - DEBUG_ONLY( \ - const auto __val = (VAL); \ - const auto __hi = (HI); \ - const auto __lo = (LO); \ - \ - _CHECK_META (__val >= __lo && __val <= __hi, \ - { ; }, \ - { \ - std::ostringstream __os; \ - __os << "expected satisifies limit\n" \ - << "__val: " << #VAL << " = " << __val << '\n' \ - << "__lo: " << #LO << " = " << __lo << '\n' \ - << "__hi: " << #HI << " = " << __hi << '\n'; \ - panic (__os.str ()); \ - }); \ - ); \ +#define CHECK_LIMIT(V,L,H) do { \ + DEBUG_ONLY( \ + const auto __v = (V); \ + const auto __l = (L); \ + const auto __h = (H); \ + \ + if (__v < __l || __v > __h) { \ + _CHECK_PANIC ("expected limit\n" \ + "__l: %s is %!\n" \ + "__h: %s is %!\n" \ + "__v: %s is %!\n", \ + #H, __h, \ + #L, __l, \ + #V, __v); \ + }; \ + ); \ } while (0) /////////////////////////////////////////////////////////////////////////////// @@ -203,16 +182,14 @@ DEBUG_ONLY( \ const auto __a = (A); \ const auto __b = (B); \ - _CHECK_META (!util::almost_equal (__a, __b), \ - { ; }, \ - { \ - std::ostringstream __debug_neq_os; \ - __debug_neq_os << "unexpected equality.\n" \ - << "__a: " << #A << " is " << __a << ")" \ - << "\n == \n" \ - << "__b: " << #B << " is " << __b << ")"; \ - panic (__debug_neq_os.str ()); \ - }); \ + \ + if (util::almost_equal (__a, __b)) { \ + _CHECK_PANIC ("expected inequality\n" \ + "__a: %s is %s\n" \ + "__b: %s is %s\n", \ + #A, __a, \ + #B, __b); \ + }; \ ); \ } while (0) @@ -221,14 +198,12 @@ #define CHECK_ZERO(A) do { \ DEBUG_ONLY( \ const auto __a = (A); \ - _CHECK_META (util::almost_zero (__a), \ - { ; }, \ - { \ - std::ostringstream __debug_nez_os; \ - __debug_nez_os << "expected zero.\n" \ - << "__a: " << #A << " is " << __a << ")"; \ - panic (__debug_nez_os.str ()); \ - }); \ + \ + if (!util::almost_zero (__a)) { \ + _CHECK_PANIC ("expected zero\n" \ + "__a: %s is %s\n" \ + #A, __a); \ + }; \ ); \ } while (0) @@ -237,63 +212,53 @@ #define CHECK_NEZ(A) do { \ DEBUG_ONLY( \ const auto __a = (A); \ - _CHECK_META (!util::almost_zero (__a), \ - { ; }, \ - { \ - std::ostringstream __debug_nez_os; \ - __debug_nez_os << "unexpected zero.\n" \ - << "__a: " << #A << " is " << __a << ")"; \ - panic (__debug_nez_os.str ()); \ - }); \ + if (util::exactly_zero (__a)) \ + _CHECK_PANIC ("expected zero\n" \ + "__a: %s is %!", \ + #A, __a); \ ); \ } while (0) /////////////////////////////////////////////////////////////////////////////// -#define CHECK_THROWS(E,C) do { \ - DEBUG_ONLY( \ - bool caught = false; \ - \ - try \ - { C; } \ - catch (E) \ - { caught = true; } \ - \ - if (!caught) \ - panic ("expected exception: " #E); \ - ); \ +#define CHECK_THROWS(E,C) do { \ + DEBUG_ONLY( \ + bool caught = false; \ + \ + try \ + { C; } \ + catch (E) \ + { caught = true; } \ + \ + if (!caught) \ + _CHECK_PANIC ("expected exception: %s", #E); \ + ); \ } while (0) /////////////////////////////////////////////////////////////////////////////// -#define CHECK_NOTHROW(C) do { \ - DEBUG_ONLY( \ - try { \ - C; \ - } catch (...) { \ - panic ("unexpected exception"); \ - } \ - ); \ +#define CHECK_NOTHROW(C) do { \ + DEBUG_ONLY( \ + try { \ + C; \ + } catch (const std::exception &e) { \ + _CHECK_PANIC ("unexpected exception: %s", \ + e.what ()); \ + } catch (...) { \ + _CHECK_PANIC ("unexpected exception: %s", \ + "unknown"); \ + } \ + ); \ } while (0) /////////////////////////////////////////////////////////////////////////////// -class panic_error { - protected: - std::string m_what; - - public: - explicit panic_error (const std::string &_what): - m_what (_what) - { ; } -}; - - -/////////////////////////////////////////////////////////////////////////////// -void panic [[noreturn]] (const std::string&); constexpr void panic [[noreturn]] (const char*); constexpr void panic [[noreturn]] (void); +template +constexpr void panic [[noreturn]] (const char *fmt, const Args&...); + /////////////////////////////////////////////////////////////////////////////// constexpr void not_implemented [[noreturn]] (void); @@ -368,12 +333,13 @@ namespace util { namespace debug { } } +#include "./debug.ipp" + /////////////////////////////////////////////////////////////////////////////// // XXX: maths needs to be included so that CHECK_EQ/NEQ can call almost_equal, // but maths.hpp might be using CHECK_ macros so we must include maths.hpp // after we define the CHECK_ macros so the preprocessor can resolve them. #include "./maths.hpp" -#include "./debug.ipp" #endif // __DEBUG_HPP diff --git a/debug.ipp b/debug.ipp index f54475e5..0843f6de 100644 --- a/debug.ipp +++ b/debug.ipp @@ -20,11 +20,18 @@ #define __UTIL_DEBUG_IPP +#include "./format.hpp" + #include /////////////////////////////////////////////////////////////////////////////// namespace util { namespace debug { namespace detail { void panic [[noreturn]] (const char *msg); + + template + void panic [[noreturn]] (const char *msg, const Args& ...args) + { panic (util::format::render (msg, args...).c_str ()); } + void not_implemented [[noreturn]] (const char *msg); void unreachable [[noreturn]] (const char *msg); } } } @@ -76,13 +83,6 @@ constexpr void unreachable [[noreturn]] (const char *msg) /////////////////////////////////////////////////////////////////////////////// -inline void panic [[noreturn]] (const std::string &msg) -{ - util::debug::detail::panic (msg.c_str ()); -} - - -//----------------------------------------------------------------------------- constexpr void panic [[noreturn]] (void) { panic ("nfi"); @@ -96,3 +96,15 @@ constexpr void panic [[noreturn]] (const char *msg) ? panic (msg) : util::debug::detail::panic (msg); } + + +//----------------------------------------------------------------------------- +template +constexpr +void +panic [[noreturn]] (const char *fmt, const Args& ...args) +{ + ! fmt + ? panic () + : util::debug::detail::panic (fmt, args...); +} diff --git a/maths.hpp b/maths.hpp index 38205e33..5db3d158 100644 --- a/maths.hpp +++ b/maths.hpp @@ -38,6 +38,135 @@ /////////////////////////////////////////////////////////////////////////////// namespace util { + /////////////////////////////////////////////////////////////////////////// + // Comparisons + inline bool + almost_equal (const float &a, const float &b) + { + return ieee_single::almost_equal (a, b); + } + + + //----------------------------------------------------------------------------- + inline bool + almost_equal (const double &a, const double &b) + { + return ieee_double::almost_equal (a, b); + } + + + //----------------------------------------------------------------------------- + template + typename std::enable_if_t< + std::is_floating_point::value && + std::is_floating_point::value, + bool + > + almost_equal (const A &a, const B &b) + { + using common_t = std::common_type_t; + return almost_equal (static_cast (a), + static_cast (b)); + } + + + //----------------------------------------------------------------------------- + template + typename std::enable_if_t< + std::is_integral::value && + std::is_integral::value && + std::is_signed::value == std::is_signed::value, + bool + > + almost_equal (const A &a, const B &b) { + using common_t = std::common_type_t; + return static_cast (a) == static_cast (b); + } + + + //----------------------------------------------------------------------------- + template + typename std::enable_if< + !std::is_arithmetic::value || + !std::is_arithmetic::value, + bool + >::type + almost_equal (const Ta &a, const Tb &b) + { return a == b; } + + + //----------------------------------------------------------------------------- + // Useful for explictly ignore equality warnings +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" + template + constexpr + typename std::enable_if_t< + std::is_arithmetic::value && + std::is_arithmetic::value, + bool + > + exactly_equal (const Ta a, const Tb b) + { + return a == b; + } + + //------------------------------------------------------------------------- + template + typename std::enable_if_t< + !std::is_arithmetic::value || + !std::is_arithmetic::value, + bool + > + exactly_equal (const Ta &a, const Tb &b) + { + return a == b; + } +#pragma GCC diagnostic pop + + + //----------------------------------------------------------------------------- + template + constexpr + std::enable_if_t< + std::is_integral::value, bool + > + almost_zero (T t) + { + return t == 0; + } + + template + std::enable_if_t< + !std::is_integral::value, bool + > + almost_zero (T a) + { return almost_equal (a, T{0}); } + + + //------------------------------------------------------------------------- + template + constexpr + typename std::enable_if_t< + std::is_integral::value, bool + > + exactly_zero (T t) + { + return exactly_equal (t, T{0}); + } + + + template + typename std::enable_if_t< + !std::is_integral::value, bool + > + exactly_zero (T t) + { + return exactly_equal (t, T{0}); + } + + + /////////////////////////////////////////////////////////////////////////// template T abs [[gnu::const]] (T t) @@ -170,97 +299,20 @@ namespace util { constexpr T gcd (T a, T b) { - if (a == b) return a; + CHECK_NEZ (a); + CHECK_NEZ (b); - if (a > b) return gcd (a - b, b); - if (b > a) return gcd (a, b - a); + while (a != b) { + if (a > b) + a -= b; + else if (b > a) + b -= a; + } - unreachable (); + return a; } - /////////////////////////////////////////////////////////////////////////////// - // Comparisons - inline bool - almost_equal (const float &a, const float &b) - { - return ieee_single::almost_equal (a, b); - } - - - //----------------------------------------------------------------------------- - inline bool - almost_equal (const double &a, const double &b) - { - return ieee_double::almost_equal (a, b); - } - - - //----------------------------------------------------------------------------- - template - typename std::enable_if_t< - std::is_floating_point::value && - std::is_floating_point::value, - bool - > - almost_equal (const A &a, const B &b) - { - using common_t = std::common_type_t; - return almost_equal (static_cast (a), - static_cast (b)); - } - - - //----------------------------------------------------------------------------- - template - typename std::enable_if_t< - std::is_integral::value && - std::is_integral::value && - std::is_signed::value == std::is_signed::value, - bool - > - almost_equal (const A &a, const B &b) { - using common_t = std::common_type_t; - return static_cast (a) == static_cast (b); - } - - - //----------------------------------------------------------------------------- - template - typename std::enable_if< - !std::is_arithmetic::value || - !std::is_arithmetic::value, - bool - >::type - almost_equal (const Ta &a, const Tb &b) - { return a == b; } - - - //----------------------------------------------------------------------------- - // Useful for explictly ignore equality warnings -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wfloat-equal" - template - bool - exactly_equal (const T &a, const U &b) - { return a == b; } -#pragma GCC diagnostic pop - - - //----------------------------------------------------------------------------- - template - bool - almost_zero (T a) - { return almost_equal (a, T{0}); } - - - //----------------------------------------------------------------------------- - template - bool - exactly_zero (T a) - { return exactly_equal (a, T{0}); } - - //----------------------------------------------------------------------------- template const T&