diff options
| -rw-r--r-- | modules/core/c++/abstract/common.hpp | 154 | ||||
| -rw-r--r-- | modules/core/c++/abstract/error.hpp | 324 |
2 files changed, 478 insertions, 0 deletions
diff --git a/modules/core/c++/abstract/common.hpp b/modules/core/c++/abstract/common.hpp new file mode 100644 index 0000000..408946c --- /dev/null +++ b/modules/core/c++/abstract/common.hpp @@ -0,0 +1,154 @@ +#pragma once + +#include <cstdint> +#include <memory> +#include <optional> +#include <utility> + +namespace saw { + +#define KEL_CONCAT_(x, y) x##y +#define KEL_CONCAT(x, y) KEL_CONCAT_(x, y) +#define KEL_UNIQUE_NAME(prefix) KEL_CONCAT(prefix, __LINE__) + +#define KEL_FORBID_COPY(classname) \ + classname(const classname &) = delete; \ + classname &operator=(const classname &) = delete + +#define KEL_FORBID_MOVE(classname) \ + classname(classname &&) = delete; \ + classname &operator=(classname &&) = delete + +#define KEL_DEFAULT_COPY(classname) \ + classname(const classname &) = default; \ + classname &operator=(const classname &) = default + +#define KEL_DEFAULT_MOVE(classname) \ + classname(classname &&) = default; \ + classname &operator=(classname &&) = default + +// In case of C++20 +#define KEL_ASSERT(expression) \ + assert(expression); \ + if (!(expression)) [[unlikely]] + +template <typename T> using maybe = std::optional<T>; + +template <typename T> using own = std::unique_ptr<T>; + +template <typename T> using our = std::shared_ptr<T>; + +template <typename T> using lent = std::weak_ptr<T>; + +template<typename T> +class ptr { +private: + T* ptr_; + +public: + ptr(): + ptr_{nullptr} + {} + + ptr(T& ptr__): + ptr_{&ptr__} + {} + + KEL_DEFAULT_COPY(ptr); + KEL_DEFAULT_MOVE(ptr); + + T& operator()(){ + return *ptr_; + } + + const T& operator()() const { + return *ptr_; + } + + bool is_valid() const { + return ptr_ != nullptr; + } +}; + +/** + * Reference class for easier distinction + * of references and its referenced types. + */ +template <typename T> +class ref { +private: + /** + * Referenced type + */ + T* ref_; + + /** + * We don't want to move since the would invalidate the type. + */ + KEL_FORBID_MOVE(ref); +public: + /** + * Main constructor. + */ + constexpr ref(T& ref__): + ref_{&ref__} + {} + + KEL_DEFAULT_COPY(ref); + + /** + * Operator retrieving the itself. + */ + constexpr T& operator()(){ + return *ref_; + } + + /** + * Operator retrieving the itself. + */ + constexpr const T& operator()() const { + return *ref_; + } +}; + +template <typename T, class... Args> own<T> heap(Args &&...args) { + return own<T>(new T(std::forward<Args>(args)...)); +} + +template <typename T, class... Args> our<T> share(Args &&...args) { + return std::make_shared<T>(std::forward<Args>(args)...); +} + +template <typename T> T instance() noexcept; + +template <typename Func, typename T> struct return_type_helper { + typedef decltype(instance<Func>()(instance<T>())) Type; +}; +template <typename Func> struct return_type_helper<Func, void> { + typedef decltype(instance<Func>()()) Type; +}; + +template <typename Func, typename T> +using return_type = typename return_type_helper<Func, T>::Type; + +/** + * Struct describing a void type + */ +struct void_t {}; + +/** + * Helper function since changing returns between void_t{} and make_error<...>() is a bit annoying. + */ +void_t make_void(); + +template <typename T> struct void_fix { typedef T Type; }; +template <> struct void_fix<void> { typedef void_t Type; }; +template <typename T> using fix_void = typename void_fix<T>::Type; + +template <typename T> struct void_unfix { typedef T Type; }; +template <> struct void_unfix<void_t> { typedef void Type; }; +template <typename T> using unfix_void = typename void_unfix<T>::Type; + +template <typename... T> constexpr bool always_false = false; + +} // namespace saw diff --git a/modules/core/c++/abstract/error.hpp b/modules/core/c++/abstract/error.hpp new file mode 100644 index 0000000..19917de --- /dev/null +++ b/modules/core/c++/abstract/error.hpp @@ -0,0 +1,324 @@ +#pragma once + +#include <algorithm> +#include <limits> +#include <functional> +#include <string> +#include <string_view> +#include <variant> +#include <vector> + +#include <cassert> + +#include "common.hpp" + +namespace saw { +/** + * Utility class for generating errors. Has a base distinction between + * critical and recoverable errors. Additional code ids can be provided to the + * constructor if additional distinctions are necessary. + */ +class error final { +public: + /** + * Type alias + */ + using code = uint32_t; +private: + /** + * Type representing the error code used to distinguish the different error types + */ + code error_code_; + /** + * Helper variable for ease of checking if it's a critical error. + */ + bool is_critical_; + /** + * Member holding the message. It's a variant for edge cases where we are out of memory. + */ + std::variant<std::string_view, std::string> error_message_; + +public: + error(error::code id, bool is_critical); + error(error::code id, bool is_critical, const std::string_view &msg); + error(error::code id, bool is_critical, std::string&& msg); + error(error &&error); + + KEL_FORBID_COPY(error); + + error &operator=(error &&) = default; + + /** + * Returns a string view to the message + */ + const std::string_view get_message() const; + + /** + * Returns a string view which describes which error this is. + */ + const std::string_view get_category() const; + + /** + * Checks if it's an error at all, since 0 is reserved for no error. + */ + bool failed() const; + + /** + * Checks if the error is critical. + */ + bool is_critical() const; + + /** + * Checks if the error is recoverable. + */ + bool is_recoverable() const; + + /** + * Replaces the copy constructor. We need this since we want to explicitly copy and not implicitly + */ + error copy_error() const; + + /** + * Return the id. + */ + code get_id() const; + + /** + * Provide an error struct and check if it matches the type + */ + template<typename T> + bool is_type() const; +}; + +/** + * Class which is basically a variant. Replaces exceptions + */ +template<typename T> +class error_or; + +namespace impl { + +/** + * Internal registry for all error types. Is generated dynamically. + */ +class error_registry final { +private: + struct error_info { + error_info() = delete; + error_info(const std::string_view& d_, bool critical_):description{d_}, is_critical{critical_}{} + + std::string_view description; + bool is_critical; + }; + + std::vector<error_info> infos_; +public: + error_registry(); + + KEL_FORBID_COPY(error_registry); + KEL_FORBID_MOVE(error_registry); + + error_or<const std::string_view> search_category(const error::code& id) const; + + error_or<error::code> search_id(const std::string_view& desc) const; + + error_or<error::code> search_or_register_id(const std::string_view& desc, bool is_critical); + + bool is_critical(error::code cd) const; +}; + +error_registry& get_error_registry(); + +template<typename T> +error::code get_template_error_id(){ + static error::code id = std::numeric_limits<error::code>::max(); + + if(id == std::numeric_limits<error::code>::max()){ + auto& reg = get_error_registry(); + + auto err_or_id = reg.search_or_register_id(T::description, T::is_critical); + if(err_or_id.is_error()){ + return std::numeric_limits<error::code>::max(); + } + + id = err_or_id.get_value(); + } + + return id; +} +} + +error make_error(error::code code, const std::string_view &generic); + +template<typename T> error make_error(const std::string_view& generic){ + error::code id = impl::get_template_error_id<T>(); + // Keep error, don't use make_error + return error{id, T::is_critical, generic}; +} + +template<typename T> error make_error(){ + error::code id = impl::get_template_error_id<T>(); + // Keep error, don't use make_error + return error{id, T::is_critical}; +} + +struct error_builder { +private: + std::function<std::string()> func_; +public: + template<typename Func> + error_builder(Func&& func__): + func_{std::move(func__)} + {} + + template<typename T> + error make_error(const std::string_view& generic){ + try{ + std::string err_str = func_(); + error::code id = impl::get_template_error_id<T>(); + func_ = [](){return "";}; + return error{id, T::is_critical, std::move(err_str)}; + }catch(const std::exception&){} + return make_error<T>(generic); + } +}; + +template<typename Func> +error_builder build_error(Func&& func){ + return error_builder{std::move(func)}; +} + +namespace err { +struct no_error { + static constexpr std::string_view description = "No error has occured"; + static constexpr bool is_critical = false; +}; + +struct recoverable { + static constexpr std::string_view description = "Generic recoverable error"; + static constexpr bool is_critical = false; +}; + +struct critical { + static constexpr std::string_view description = "Generic critical error"; + static constexpr bool is_critical = true; +}; + +struct buffer_exhausted { + static constexpr std::string_view description = "Buffer Exhausted"; + static constexpr bool is_critical = false; +}; + +struct not_found { + static constexpr std::string_view description = "Not found"; + static constexpr bool is_critical = false; +}; + +struct out_of_memory { + static constexpr std::string_view description = "Out of memory"; + static constexpr bool is_critical = true; +}; + +struct invalid_state { + static constexpr std::string_view description = "Invalid state"; + static constexpr bool is_critical = true; +}; + +struct not_supported { + static constexpr std::string_view description = "Not supported"; + static constexpr bool is_critical = false; +}; + +struct not_implemented { + static constexpr std::string_view description = "Not implemented"; + static constexpr bool is_critical = true; +}; + +struct already_exists { + static constexpr std::string_view description = "Already exists"; + static constexpr bool is_critical = false; +}; +} + +/** + * Shorthand for no error happened + */ +error no_error(); + +/** + * Exception alternative. Since I code without exceptions this class is + * essentially a kind of exception replacement. + */ +template <typename T> class error_or; + +class error_or_value { +public: + virtual ~error_or_value() = default; + + template <typename T> error_or<unfix_void<T>> &as() { + return static_cast<error_or<unfix_void<T>> &>(*this); + } + + template <typename T> const error_or<unfix_void<T>> &as() const { + return static_cast<const error_or<unfix_void<T>> &>(*this); + } +}; + +template <typename T> class error_or final : public error_or_value { +private: + std::variant<error, fix_void<T>> value_or_error_; + + static_assert(!std::is_same_v<T, void_t>, + "Don't use internal private types"); + +public: + constexpr error_or():value_or_error_{make_error<err::invalid_state>("Default assignement for error_or constructor.")}{} + constexpr error_or(const fix_void<T> &value) : value_or_error_{value} {} + + constexpr error_or(fix_void<T> &&value) : value_or_error_{std::move(value)} {} + + constexpr error_or(const error &error) : value_or_error_{error} {} + constexpr error_or(error &&error) : value_or_error_{std::move(error)} {} + + constexpr bool is_value() const { + return std::holds_alternative<fix_void<T>>(value_or_error_); + } + + constexpr bool is_error() const { + return std::holds_alternative<class error>(value_or_error_); + } + + constexpr class error &get_error() { + return std::get<class error>(value_or_error_); + } + + constexpr const class error &get_error() const { + return std::get<class error>(value_or_error_); + } + + constexpr fix_void<T> &get_value() { return std::get<fix_void<T>>(value_or_error_); } + + constexpr const fix_void<T> &get_value() const { + return std::get<fix_void<T>>(value_or_error_); + } +}; + +/** + * This tries to catch cases where error starts including itself as a type which can happen in more complicated cases. + * So this acts as a type safe guard. + */ +template <typename T> class error_or<error_or<T>> { +private: + error_or() = delete; +}; + +template<typename T> +bool error::is_type() const { + + return error_code_ == impl::get_template_error_id<T>(); +} + +} // namespace saw + +#define KEL_MAKE_ERROR(ErrType, ErrMsg) \ + return saw::make_error<ErrType>(ErrMsg) |
