155 lines
5.2 KiB
C++
155 lines
5.2 KiB
C++
#pragma once
|
|
|
|
#include <ckitty/memory/primitives.hpp>
|
|
|
|
#include <cstdint>
|
|
#include <format>
|
|
#include <initializer_list>
|
|
#include <optional>
|
|
#include <source_location>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
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<error_frame> _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<error_frame>& 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<typename T = void>
|
|
class failable {
|
|
private:
|
|
std::variant<T, error> _storage;
|
|
|
|
public:
|
|
// Success constructors
|
|
constexpr failable(const T& val) : _storage(val) {}
|
|
constexpr failable(T&& val) noexcept(std::is_nothrow_move_constructible_v<T>)
|
|
: _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<T>(_storage); }
|
|
[[nodiscard]] explicit operator bool() const noexcept { return has_value(); }
|
|
|
|
// Value Access
|
|
[[nodiscard]] T& value() & { return std::get<T>(_storage); }
|
|
[[nodiscard]] const T& value() const& { return std::get<T>(_storage); }
|
|
[[nodiscard]] T&& value() && { return std::get<T>(std::move(_storage)); }
|
|
|
|
[[nodiscard]] T value_or(T&& fallback) const& {
|
|
return has_value() ? std::get<T>(_storage) : std::forward<T>(fallback);
|
|
}
|
|
|
|
// Error Access
|
|
[[nodiscard]] error& error_cause() & { return std::get<error>(_storage); }
|
|
[[nodiscard]] const error& error_cause() const& { return std::get<error>(_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<error>(_storage).context(msg, loc);
|
|
}
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
// --- Void Specialization ---
|
|
|
|
template<>
|
|
class failable<void> {
|
|
private:
|
|
std::optional<error> _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
|
|
|