diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 41ef327f..a0620019 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -14,7 +14,7 @@ add_library(RoughPy_Core INTERFACE include/roughpy/core/helpers.h include/roughpy/core/slice.h include/roughpy/core/smart_ptr.h - include/roughpy/core/strings.h + include/roughpy/core/string_utils.h ) add_library(RoughPy::Core ALIAS RoughPy_Core) @@ -41,3 +41,4 @@ set_target_properties(RoughPy_Core PROPERTIES ROUGHPY_COMPONENT Core) target_compile_definitions(RoughPy_Core INTERFACE NOMINMAX=1) +add_subdirectory(src) \ No newline at end of file diff --git a/core/include/roughpy/core/check.h b/core/include/roughpy/core/check.h index 9cc9d6ec..572304c0 100644 --- a/core/include/roughpy/core/check.h +++ b/core/include/roughpy/core/check.h @@ -11,7 +11,7 @@ #include "check_helpers.h" #include "macros.h" -#include "strings.h" +#include "string_utils.h" #if defined(RPY_GCC) # define RPY_FUNC_NAME __PRETTY_FUNCTION__ @@ -25,9 +25,8 @@ namespace rpy::errors { -template -RPY_NO_RETURN -void throw_exception( +template +RPY_NO_RETURN void throw_exception( std::string_view user_msg, const char* filename, int lineno, @@ -48,6 +47,26 @@ void throw_exception( // boost::stacktrace::stacktrace())); } +template +RPY_NO_RETURN void throw_exception( + const char* user_msg, + const char* filename, + int lineno, + const char* func +) +{ + throw E(string_cat( + "Error occurred in ", + filename, + " at line ", + lineno, + '(', + func, + "):\n", + user_msg + )); +} + }// namespace rpy::errors #define RPY_THROW_2(EXC_TYPE, MSG) \ @@ -104,17 +123,32 @@ void throw_exception( #define RPY_CHECK(...) \ RPY_INVOKE_VA(RPY_CHECK_SEL(RPY_COUNT_ARGS(__VA_ARGS__)), (__VA_ARGS__)) +// TODO: Ideally have customisable messages for the these checks + #define RPY_CHECK_EQ(a, b, ...) \ - RPY_CHECK(::rpy::compare_equal((a), (b)), __VA_ARGS__) + RPY_CHECK( \ + (::rpy::compare_equal((a), (b))), \ + "failed check \"" RPY_STRINGIFY((a) == (b)) "\"", __VA_ARGS__) #define RPY_CHECK_NE(a, b, ...) \ - RPY_CHECK(::rpy::compare_not_equal((a), (b)), __VA_ARGS__) + RPY_CHECK( \ + (::rpy::compare_not_equal((a), (b))), \ + "failed check \"" RPY_STRINGIFY((a) != (b)) "\"", __VA_ARGS__) #define RPY_CHECK_LT(a, b, ...) \ - RPY_CHECK(::rpy::compare_less((a), (b)), __VA_ARGS__) + RPY_CHECK( \ + (::rpy::compare_less((a), (b))), \ + "failed check \"" RPY_STRINGIFY((a) < (b)) "\"", __VA_ARGS__) #define RPY_CHECK_LE(a, b, ...) \ - RPY_CHECK(::rpy::compare_less_equal((a), (b)), __VA_ARGS__) + RPY_CHECK( \ + (::rpy::compare_less_equal((a), (b))), \ + "failed check \"" RPY_STRINGIFY((a) <= (b)) "\"", __VA_ARGS__) #define RPY_CHECK_GT(a, b, ...) \ - RPY_CHECK(::rpy::compare_greater((a), (b)), __VA_ARGS__) + RPY_CHECK( \ + (::rpy::compare_greater((a), (b))), \ + "failed check \"" RPY_STRINGIFY((a) > (b)) "\"", __VA_ARGS__ \ + ) #define RPY_CHECK_GE(a, b, ...) \ - RPY_CHECK(::rpy::compare_greater_equal((a), (b)), __VA_ARGS__) + RPY_CHECK( \ + (::rpy::compare_greater_equal((a), (b))), \ + "failed check \"" RPY_STRINGIFY((a) >= (b)) "\"", __VA_ARGS__) #endif// ROUGHPY_CORE_CHECK_H diff --git a/core/include/roughpy/core/debug_assertion.h b/core/include/roughpy/core/debug_assertion.h index 8d83a10a..3eda3815 100644 --- a/core/include/roughpy/core/debug_assertion.h +++ b/core/include/roughpy/core/debug_assertion.h @@ -14,12 +14,10 @@ # undef RPY_DEBUG #endif - #ifdef RPY_DEBUG # include "check_helpers.h" #endif - #ifdef RPY_DEBUG # if defined(RPY_GCC) || defined(RPY_CLANG) # define RPY_DBG_ASSERT(ARG) assert(ARG) @@ -30,18 +28,15 @@ # define RPY_DBG_ASSERT(ARG) (void) 0 #endif - -#define RPY_DBG_ASSERT_EQ(a, b) RPY_DBG_ASSERT(::rpy::compare_equal((a), (b))) -#define RPY_DBG_ASSERT_NE(a, b) RPY_DBG_ASSERT(::rpy::compare_not_equal((a), (b))) -#define RPY_DBG_ASSERT_LT(a, b) RPY_DBG_ASSERT(::rpy::compare_less((a), (b))) -#define RPY_DBG_ASSERT_GT(a, b) RPY_DBG_ASSERT(::rpy::compare_greater((a), (b))) -#define RPY_DBG_ASSERT_LE(a, b) RPY_DBG_ASSERT(::rpy::compare_less_equal((a), (b))) -#define RPY_DBG_ASSERT_GE(a, b) RPY_DBG_ASSERT(::rpy::compare_greater_equal((a), (b))) - - - - - - - -#endif //ROUGHPY_CORE_DEBUG_ASSERTION_H +#define RPY_DBG_ASSERT_EQ(a, b) RPY_DBG_ASSERT((::rpy::compare_equal((a), (b)))) +#define RPY_DBG_ASSERT_NE(a, b) \ + RPY_DBG_ASSERT((::rpy::compare_not_equal((a), (b)))) +#define RPY_DBG_ASSERT_LT(a, b) RPY_DBG_ASSERT((::rpy::compare_less((a), (b)))) +#define RPY_DBG_ASSERT_GT(a, b) \ + RPY_DBG_ASSERT((::rpy::compare_greater((a), (b)))) +#define RPY_DBG_ASSERT_LE(a, b) \ + RPY_DBG_ASSERT((::rpy::compare_less_equal((a), (b)))) +#define RPY_DBG_ASSERT_GE(a, b) \ + RPY_DBG_ASSERT((::rpy::compare_greater_equal((a), (b)))) + +#endif// ROUGHPY_CORE_DEBUG_ASSERTION_H diff --git a/core/include/roughpy/core/macros.h b/core/include/roughpy/core/macros.h index 26cfd11c..3320c893 100644 --- a/core/include/roughpy/core/macros.h +++ b/core/include/roughpy/core/macros.h @@ -60,6 +60,28 @@ # define RPY_HAS_INCLUDE(x) 0 #endif +/* + * The C++ preprocessor is very basic, but one can accomplish fairly advanced + * patterns by exploiting multiple passes of the preprocessor. The following + * macros help with the indirection needed to achieve everything one might want + * to do. One of main consumers of this set of utilities are the RPY_CHECK and + * RPY_DBG_ASSERT macros in Core. + * + * See https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms + * and the C++template metaprogramming book by David Abrahams and + * Aleksey Gurtovoy + */ +#define RPY_STRINGIFY_IMPL(ARG) #ARG +#define RPY_STRINGIFY(ARG) RPY_STRINGIFY_IMPL(ARG) + +#define RPY_JOIN_IMPL_IMPL(LHS, RHS) LHS##RHS +#define RPY_JOIN_IMPL(LHS, RHS) RPY_JOIN_IMPL_IMPL(LHS, RHS) +#define RPY_JOIN(LHS, RHS) RPY_JOIN_IMPL(LHS, RHS) + +#define RPY_IDENTITY(ARG) ARG +#define RPY_EMPTY(...) +#define RPY_DEFER(...) __VA_ARGS__ + /* * MSVC has a bug where it treats __VA_ARGS__ as a single token in argument * lists. To combat this, we use RPY_INVOKE_VA to invoke the overload macro with @@ -72,15 +94,6 @@ #define RPY_COUNT_ARGS(...) \ RPY_INVOKE_VA(RPY_COUNT_ARGS_1, (__VA_ARGS__, 4, 3, 2, 1, 0)) -#define RPY_STRINGIFY_IMPL(ARG) #ARG -#define RPY_STRINGIFY(ARG) RPY_STRINGIFY_IMPL(ARG) - -#define RPY_JOIN_IMPL_IMPL(LHS, RHS) LHS##RHS -#define RPY_JOIN_IMPL(LHS, RHS) RPY_JOIN_IMPL_IMPL(LHS, RHS) -#define RPY_JOIN(LHS, RHS) RPY_JOIN_IMPL(LHS, RHS) - -#define RPY_IDENTITY(ARG) ARG - #if defined(_MSC_VER) && defined(_MSVC_LANG) diff --git a/core/include/roughpy/core/strings.h b/core/include/roughpy/core/string_utils.h similarity index 100% rename from core/include/roughpy/core/strings.h rename to core/include/roughpy/core/string_utils.h diff --git a/core/src/CMakeLists.txt b/core/src/CMakeLists.txt new file mode 100644 index 00000000..8a765869 --- /dev/null +++ b/core/src/CMakeLists.txt @@ -0,0 +1,19 @@ + + + + +if(ROUGHPY_BUILD_TESTS) + + + add_executable(test_core + test_check_macros.cpp) + + + target_include_directories(test_core PRIVATE ${ROUGHPY_CORE_INCLUDE}) + + target_link_libraries(test_core PRIVATE RoughPy::Core GTest::gtest) + + setup_roughpy_cpp_tests(test_core) + + +endif() \ No newline at end of file diff --git a/core/src/test_check_macros.cpp b/core/src/test_check_macros.cpp new file mode 100644 index 00000000..8f7df0ba --- /dev/null +++ b/core/src/test_check_macros.cpp @@ -0,0 +1,102 @@ +// +// Created by sammorley on 15/11/24. +// + + + + +#include + +#include "check.h" + + +TEST(CheckMacros, TestCheckFailStandard) +{ + EXPECT_THROW(RPY_CHECK(false), std::runtime_error); +} + +TEST(CheckMacros, TestCHeckWithSpecifiedException) +{ + EXPECT_THROW( + RPY_CHECK(false, "message", std::invalid_argument), + std::invalid_argument + ); +} + +TEST(CheckMacros, TestCheckEqFails) +{ + EXPECT_THROW(RPY_CHECK_EQ(1, 2), std::runtime_error); +} + +TEST(CheckMacros, TestCheckEqFailsCustomException) +{ + EXPECT_THROW( + RPY_CHECK_EQ(1, 2, std::invalid_argument), + std::invalid_argument + ); +} + +TEST(CheckMacros, TestCheckNeFails) +{ + EXPECT_THROW(RPY_CHECK_NE(1, 1), std::runtime_error); +} + +TEST(CheckMacros, TestCheckNeFailsCustomException) +{ + EXPECT_THROW( + RPY_CHECK_NE(1, 1, std::invalid_argument), + std::invalid_argument + ); +} + +TEST(CheckMacros, TestCheckLtFails) +{ + EXPECT_THROW(RPY_CHECK_LT(2, 1), std::runtime_error); +} + +TEST(CheckMacros, TestCheckLtFailsCustomException) +{ + EXPECT_THROW( + RPY_CHECK_LT(2, 1, std::invalid_argument), + std::invalid_argument + ); +} + +TEST(CheckMacros, TestCheckLeFails) +{ + EXPECT_THROW(RPY_CHECK_LE(2, 1), std::runtime_error); +} + +TEST(CheckMacros, TestCheckLeFailsCustomException) +{ + EXPECT_THROW( + RPY_CHECK_LE(2, 1, std::invalid_argument), + std::invalid_argument + ); +} + +TEST(CheckMacros, TestCheckGtFails) +{ + EXPECT_THROW(RPY_CHECK_GT(1, 2), std::runtime_error); +} + +TEST(CheckMacros, TestCheckGtFailsCustomException) +{ + EXPECT_THROW( + RPY_CHECK_GT(1, 2, std::invalid_argument), + std::invalid_argument + ); +} + +TEST(CheckMacros, TestCheckGeFails) +{ + EXPECT_THROW(RPY_CHECK_GE(1, 2), std::runtime_error); +} + +TEST(CheckMacros, TestCheckGeFailsCustomException) +{ + EXPECT_THROW( + RPY_CHECK_GE(1, 2, std::invalid_argument), + std::invalid_argument + ); +} \ No newline at end of file