#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ckitty { // --- Single Stack Frame --- struct error_frame { i32 code{0}; std::string message; std::source_location location; error_frame(i32 code, std::string msg, std::source_location loc = std::source_location::current()) : code(code), message(std::move(msg)), location(loc) {} }; // --- Stacked Error Representation --- class error { private: std::vector _frames; public: error() = default; /// Construct a single error frame with automatic source location capture error(i32 code, std::string message, std::source_location loc = std::source_location::current()) { _frames.emplace_back(code, std::move(message), loc); } [[nodiscard]] bool empty() const noexcept { return _frames.empty(); } [[nodiscard]] explicit operator bool() const noexcept { return !_frames.empty(); } [[nodiscard]] i32 code() const noexcept { return _frames.empty() ? 0 : _frames.front().code; } [[nodiscard]] const std::vector& frames() const noexcept { return _frames; } /// Pushes a new contextual frame to the top of the stack error& context(std::string_view message, std::source_location loc = std::source_location::current()) { _frames.emplace_back(code(), std::string(message), loc); return *this; } /// Formats the error stack into a multi-line visual trace [[nodiscard]] std::string to_string() const { if (_frames.empty()) return "[OK] No Error"; std::ostringstream oss; oss << "Error Stack Trace (Root Code: 0x" << std::hex << code() << std::dec << "):\n"; for (size_t i = 0; i < _frames.size(); ++i) { const auto& f = _frames[i]; oss << " [" << i << "] " << f.message << "\n"; oss << " at " << f.location.file_name() << ":" << f.location.line() << " in " << f.location.function_name() << "\n"; } return oss.str(); } explicit operator std::string() const { return to_string(); } }; // --- Failable (Expected) Template --- struct unexpected { error err; explicit unexpected(error e) : err(std::move(e)) {} explicit unexpected(i32 code, std::string message) : err(code, message) {} }; template class failable { private: std::variant _storage; public: // Success constructors constexpr failable(const T& val) : _storage(val) {} constexpr failable(T&& val) noexcept(std::is_nothrow_move_constructible_v) : _storage(std::move(val)) {} // Failure constructors constexpr failable(error err) : _storage(std::move(err)) {} constexpr failable(unexpected unex) : _storage(std::move(unex.err)) {} // Status queries [[nodiscard]] bool has_value() const noexcept { return std::holds_alternative(_storage); } [[nodiscard]] explicit operator bool() const noexcept { return has_value(); } // Value Access [[nodiscard]] T& value() & { return std::get(_storage); } [[nodiscard]] const T& value() const& { return std::get(_storage); } [[nodiscard]] T&& value() && { return std::get(std::move(_storage)); } [[nodiscard]] T value_or(T&& fallback) const& { return has_value() ? std::get(_storage) : std::forward(fallback); } // Error Access [[nodiscard]] error& error_cause() & { return std::get(_storage); } [[nodiscard]] const error& error_cause() const& { return std::get(_storage); } /// Attaches context to the contained error if in a failed state failable& context(std::string_view msg, std::source_location loc = std::source_location::current()) { if (!has_value()) { std::get(_storage).context(msg, loc); } return *this; } }; // --- Void Specialization --- template<> class failable { private: std::optional _err; public: constexpr failable() noexcept = default; constexpr failable(error err) : _err(std::move(err)) {} failable(unexpected unex) : _err(std::move(unex.err)) {} [[nodiscard]] bool has_value() const noexcept { return !_err.has_value(); } [[nodiscard]] explicit operator bool() const noexcept { return has_value(); } [[nodiscard]] const error& error_cause() const { return *_err; } failable& context(std::string_view msg, std::source_location loc = std::source_location::current()) { if (_err) { _err->context(msg, loc); } return *this; } }; } // namespace ckitty