diff --git a/CMake/ystdlib-cpp-config.cmake.in b/CMake/ystdlib-cpp-config.cmake.in new file mode 100644 index 0000000..dc5c1d7 --- /dev/null +++ b/CMake/ystdlib-cpp-config.cmake.in @@ -0,0 +1,7 @@ +# ystdlib-cpp CMake configuration file + +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@TARGET_EXPORT_NAME@.cmake") + +# TODO: check required dependencies diff --git a/CMake/ystdlib-cpp-helpers.cmake b/CMake/ystdlib-cpp-helpers.cmake new file mode 100644 index 0000000..0daa178 --- /dev/null +++ b/CMake/ystdlib-cpp-helpers.cmake @@ -0,0 +1,76 @@ +# check_if_header_only_library() +# +# @param source_list_var The list of source files that a target library uses +# @param is_header_only_var Returns whether the target library only contains header files +function(check_if_header_only_library source_list_var is_header_only_var) + set(local_source_list "${${source_list_var}}") + foreach(src_file IN LISTS local_source_list) + if(${src_file} MATCHES ".*\\.(h|inc)") + list(REMOVE_ITEM local_source_list "${src_file}") + endif() + endforeach() + + if(local_source_list STREQUAL "") + set(${is_header_only_var} 1 PARENT_SCOPE) + else() + set(${is_header_only_var} 0 PARENT_SCOPE) + endif() +endfunction() + +# ystdlib_cpp_library() +# +# CMake function to imitate Bazel's cc_library rule. +# +# Parameters: +# NAME: name of the target +# HDRS: List of public header files for the library +# SRCS: List of source files for the library +# DEPS: List of other libraries to be linked in to the binary targets +function(ystdlib_cpp_library) + set(options "") + set(oneValueArgs NAME) + set(multiValueArgs HDRS SRCS DEPS) + cmake_parse_arguments(arg_ystdlib_cpp_lib + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + + if(YSTDLIB_CPP_ENABLE_INSTALL) + set(_TARGET_LIB_NAME "${arg_ystdlib_cpp_lib_NAME}") + else() + set(_TARGET_LIB_NAME "ystdlib_${arg_ystdlib_cpp_lib_NAME}") + endif() + + check_if_header_only_library(arg_ystdlib_cpp_lib_SRCS _TARGET_LIB_IS_INTERFACE) + + if (_TARGET_LIB_IS_INTERFACE) + add_library(${_TARGET_LIB_NAME} INTERFACE) + target_include_directories(${_TARGET_LIB_NAME} INTERFACE + "$" + "$" + ) + else() + message(FATAL_ERROR "Non-interface library is currently not supported.") + endif() + + target_link_libraries(${_TARGET_LIB_NAME} + PUBLIC ${arg_ystdlib_cpp_lib_DEPS} + ) + + add_library(ystdlib::${arg_ystdlib_cpp_lib_NAME} ALIAS ${_TARGET_LIB_NAME}) + + if(YSTDLIB_CPP_ENABLE_INSTALL) + install( + FILES ${arg_ystdlib_cpp_lib_HDRS} + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ystdlib/${arg_ystdlib_cpp_lib_NAME}" + ) + install(TARGETS ${_TARGET_LIB_NAME} + EXPORT ${TARGET_EXPORT_NAME} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) + endif() +endfunction() diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fb28bcf --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,77 @@ +cmake_minimum_required(VERSION 3.16.3) + +list(APPEND CMAKE_MODULE_PATH + "${CMAKE_CURRENT_LIST_DIR}/CMake" +) +include(ystdlib-cpp-helpers) + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# Package information +set(PKG_NAME "ystdlib-cpp") +set(PKG_VERS "0.0.1") +set(PKG_STR "${PKG_NAME}-${PKG_VERS}") +set(TARGET_EXPORT_NAME "${PKG_NAME}-targets") +set(PKG_CONF_NAME "${PKG_NAME}-config") +set(PKG_CONF_VERS_NAME "${PKG_NAME}-config-version") + +project(${PKG_NAME} LANGUAGES CXX) + +# Static everything for now +option(BUILD_SHARED_LIBS OFF) +option(YSTDLIB_CPP_USE_STATIC_LIBS ON) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + message(STATUS "setting C++ standard to C++${CMAKE_CXX_STANDARD}") +endif() + +if(NOT CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + option(YSTDLIB_CPP_ENABLE_INSTALL "${PKG_NAME} is the master project. Enable installation." OFF) +else() + option(YSTDLIB_CPP_ENABLE_INSTALL "${PKG_NAME} is the master project. Enable installation." ON) +endif() + +# All paths are relative to CMAKE_INSTALL_PREFIX +set(CMAKE_INSTALL_LIBDIR "lib") +set(CMAKE_INSTALL_BINDIR "bin") +set(CMAKE_INSTALL_INCLUDEDIR "include/${PKG_STR}") +set(CMAKE_INSTALL_PKGCONFDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PKG_STR}") + +list(APPEND YSTDLIB_CPP_BUILD_INCLUDE_DIRS + "${PROJECT_SOURCE_DIR}" + "${PROJECT_SOURCE_DIR}/submodules" +) + +if(YSTDLIB_CPP_ENABLE_INSTALL) + list(APPEND YSTDLIB_CPP_INSTALL_INCLUDE_DIRS ${CMAKE_INSTALL_INCLUDEDIR}) +else() + list(APPEND YSTDLIB_CPP_INSTALL_INCLUDE_DIRS ${YSTDLIB_CPP_BUILD_INCLUDE_DIRS}) +endif() + +add_subdirectory(ystdlib) + +if(YSTDLIB_CPP_ENABLE_INSTALL) + include(CMakePackageConfigHelpers) + + install(EXPORT ${TARGET_EXPORT_NAME} + FILE ${TARGET_EXPORT_NAME}.cmake + NAMESPACE ystdlib:: + DESTINATION ${CMAKE_INSTALL_PKGCONFDIR} + ) + configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/CMake/${PKG_CONF_NAME}.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/${PKG_CONF_NAME}.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_PKGCONFDIR} + ) + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${PKG_CONF_VERS_NAME}.cmake" + VERSION ${PKG_VERS} + COMPATIBILITY AnyNewerVersion + ) + install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${PKG_CONF_NAME}.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PKG_CONF_VERS_NAME}.cmake" + DESTINATION ${CMAKE_INSTALL_PKGCONFDIR} + ) +endif() diff --git a/README.md b/README.md index e69de29..f3383e9 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,3 @@ +ystdlib-cpp +=================================== +An open-source C++ library developed and used at YScope. diff --git a/ystdlib/CMakeLists.txt b/ystdlib/CMakeLists.txt new file mode 100644 index 0000000..764b94a --- /dev/null +++ b/ystdlib/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(error_handling) diff --git a/ystdlib/error_handling/CMakeLists.txt b/ystdlib/error_handling/CMakeLists.txt new file mode 100644 index 0000000..408a19a --- /dev/null +++ b/ystdlib/error_handling/CMakeLists.txt @@ -0,0 +1,6 @@ +ystdlib_cpp_library( + NAME + error_handling + HDRS + "ErrorCode.hpp" +) diff --git a/ystdlib/error_handling/ErrorCode.hpp b/ystdlib/error_handling/ErrorCode.hpp new file mode 100644 index 0000000..a77b50f --- /dev/null +++ b/ystdlib/error_handling/ErrorCode.hpp @@ -0,0 +1,150 @@ +#ifndef YSTDLIB_ERROR_HANDLING_ERRORCODE_HPP +#define YSTDLIB_ERROR_HANDLING_ERRORCODE_HPP + +#include +#include +#include +#include + +namespace ystdlib::error_handling { +/** + * Concept that defines a template parameter of an integer-based error code enumeration. + * @tparam Type + */ +template +concept ErrorCodeEnumType = std::is_enum_v && requires(Type type) { + { + static_cast>(type) + } -> std::convertible_to; +}; + +/** + * Template that defines a `std::error_category` of the given set of error code enumeration. + * @tparam ErrorCodeEnum + */ +template +class ErrorCategory : public std::error_category { +public: + // Methods implementing `std::error_category` + /** + * Gets the error category name. + * Note: A specialization must be explicitly implemented for each valid `ErrorCodeEnum`. + * @return The name of the error category. + */ + [[nodiscard]] auto name() const noexcept -> char const* override; + + /** + * Gets the descriptive message associated with the given error. + * @param error_num + * @return The descriptive message for the error. + */ + [[nodiscard]] auto message(int error_num) const -> std::string override { + return message(static_cast(error_num)); + } + + /** + * @param error_num + * @param condition + * @return Whether the error condition of the given error matches the given condition. + */ + [[nodiscard]] auto equivalent( + int error_num, + std::error_condition const& condition + ) const noexcept -> bool override { + return equivalent(static_cast(error_num), condition); + } + + // Methods + /** + * Gets the descriptive message associated with the given error. + * Note: A specialization must be explicitly implemented for each valid `ErrorCodeEnum`. + * @param error_enum. + * @return The descriptive message for the error. + */ + [[nodiscard]] auto message(ErrorCodeEnum error_enum) const -> std::string; + + /** + * Note: A specialization can be implemented to create error enum to error condition mappings. + * @param error_num + * @param condition + * @return Whether the error condition of the given error matches the given condition. + */ + [[nodiscard]] auto equivalent( + ErrorCodeEnum error_enum, + std::error_condition const& condition + ) const noexcept -> bool; +}; + +/** + * Template class that defines an error code. An error code is represented by a error enum value and + * the associated error category. This template class is designed to be `std::error_code` + * compatible, meaning that every instance of this class can be used to construct a corresponded + * `std::error_code` instance, or compare with a `std::error_code` instance to inspect a specific + * error. + * @tparam ErrorCodeEnum + */ +template +class ErrorCode { +public: + // Constructor + ErrorCode(ErrorCodeEnum error) : m_error{error} {} + + /** + * @return The underlying error code enum. + */ + [[nodiscard]] auto get_error() const -> ErrorCodeEnum { return m_error; } + + /** + * @return The error code as an error number. + */ + [[nodiscard]] auto get_error_num() const -> int { return static_cast(m_error); } + + /** + * @return The reference to the singleton of the corresponded error category. + */ + [[nodiscard]] constexpr static auto get_category() -> ErrorCategory const& { + return cCategory; + } + +private: + static inline ErrorCategory const cCategory; + + ErrorCodeEnum m_error; +}; + +/** + * @tparam ErrorCodeEnum + * @param error + * @return Constructed `std::error_code` from the given `ErrorCode` instance. + */ +template +[[nodiscard]] auto make_error_code(ErrorCode error) -> std::error_code; + +template +auto ErrorCategory::equivalent( + ErrorCodeEnum error_enum, + std::error_condition const& condition +) const noexcept -> bool { + return std::error_category::default_error_condition(static_cast(error_enum)) == condition; +} + +template +auto make_error_code(ErrorCode error) -> std::error_code { + return {error.get_error_num(), ErrorCode::get_category()}; +} +} // namespace ystdlib::error_handling + +/** + * The macro to create a specialization of `std::is_error_code_enum` for a given type T. Only types + * that are marked with this macro will be considered as a valid YSTDLIB error code enum, and thus + * used to specialize `ErrorCode` and `ErrorCategory` templates. + */ +// NOLINTBEGIN(bugprone-macro-parentheses, cppcoreguidelines-macro-usage) +#define YSTDLIB_ERROR_HANDLING_MARK_AS_ERROR_CODE_ENUM(T) \ + template <> \ + struct std::is_error_code_enum> : std::true_type { \ + static_assert(std::is_enum_v); \ + }; +// NOLINTEND(bugprone-macro-parentheses, cppcoreguidelines-macro-usage) + +#endif // YSTDLIB_ERROR_HANDLING_ERRORCODE_HPP