audited all the library using gemini, openssl support is still pending
This commit is contained in:
+89
-68
@@ -1,108 +1,129 @@
|
||||
#pragma once
|
||||
|
||||
#include <ckitty/memory/primitives.hpp>
|
||||
#include <ckitty/memory/ptr.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
|
||||
namespace ckitty {
|
||||
|
||||
/**
|
||||
* @brief Represents a dynamically created,
|
||||
* fixed size container. Useful to move data
|
||||
* around, when the data itself has a fixed size.
|
||||
* @brief Represents a dynamically allocated, fixed-size array container
|
||||
* using shared ownership semantics.
|
||||
*
|
||||
* C++ does not have a standard library equivalent.
|
||||
* Closest to it are vector and std::array. However,
|
||||
* vector does not have a constant length and array
|
||||
* is not dynamically created.
|
||||
* @tparam T Element type stored in the array.
|
||||
*/
|
||||
template<typename T>
|
||||
class array {
|
||||
public:
|
||||
// Standard STL type definitions
|
||||
using value_type = T;
|
||||
using size_type = usize;
|
||||
using difference_type = usize;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using iterator = T*;
|
||||
using const_iterator = const T*;
|
||||
|
||||
protected:
|
||||
|
||||
ptr<T[]> _data;
|
||||
isize _length;
|
||||
|
||||
public:
|
||||
usize _length{ 0 };
|
||||
|
||||
private:
|
||||
/**
|
||||
* Inherits a pointer, and takes ownership of it.
|
||||
* @brief Private constructor used by factory methods to avoid unnecessary allocations.
|
||||
*/
|
||||
static array<T> inherit(T* data, isize length) {
|
||||
array<T> arr = array<T>();
|
||||
arr._data = ptr<T[]>(data);
|
||||
arr._length = length;
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Borrows a modifiable part of a buffer to
|
||||
* this array. The array does not delete it.
|
||||
*/
|
||||
static array<T> borrow(T* data, isize length) {
|
||||
array<T> arr = array<T>();
|
||||
arr._data = ptr<T[]>(data, []([[maybe_unused]] T* _ptr) {});
|
||||
arr._length = length;
|
||||
return arr;
|
||||
array(ptr<T[]> data, const usize length) noexcept
|
||||
: _data(std::move(data)), _length(length) {
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Takes ownership of a raw pointer.
|
||||
*/
|
||||
static array<T> inherit(T* data, const usize length) {
|
||||
return array<T>(ptr<T[]>(data), length);
|
||||
}
|
||||
|
||||
array(const isize length = 0)
|
||||
: _data(new T[length]), _length(length) {}
|
||||
/**
|
||||
* @brief Borrows a buffer without taking deletion ownership.
|
||||
*/
|
||||
static array<T> borrow(T* data, const usize length) {
|
||||
return array<T>(ptr<T[]>(data, []([[maybe_unused]] T* p) {}), length);
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new fixed-size array (value-initialized).
|
||||
* @param length Number of elements to allocate.
|
||||
*/
|
||||
explicit array(const usize length = 0)
|
||||
: _data(length > 0 ? new T[length]() : nullptr), _length(length) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct array by copying from a range [begin, end).
|
||||
*/
|
||||
array(const T* begin, const T* end)
|
||||
: _data(new T[end - begin]), _length(isize(end - begin)) {
|
||||
std::copy(begin, end, this->begin());
|
||||
: _data(end > begin ? new T[end - begin] : nullptr),
|
||||
_length(end > begin ? static_cast<usize>(end - begin) : 0) {
|
||||
if (_length > 0) {
|
||||
std::copy(begin, end, _data.get());
|
||||
}
|
||||
}
|
||||
|
||||
array(const array& copy) : _data(copy._data), _length(copy._length) {}
|
||||
// Shared ownership copy constructor and assignment
|
||||
array(const array& copy) noexcept = default;
|
||||
array& operator=(const array& copy) noexcept = default;
|
||||
|
||||
array(array&& move) : _data(std::move(move._data)), _length(move._length) {
|
||||
// move._data = nullptr
|
||||
move._length = 0;
|
||||
// Move constructor and assignment
|
||||
array(array&& move) noexcept
|
||||
: _data(std::move(move._data)), _length(std::exchange(move._length, 0)) {
|
||||
}
|
||||
|
||||
array& operator=(const array& copy) {
|
||||
this->_data = copy._data;
|
||||
this->_length = copy._length;
|
||||
array& operator=(array&& move) noexcept {
|
||||
if (this != &move) {
|
||||
_data = std::move(move._data);
|
||||
_length = std::exchange(move._length, 0);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
array& operator=(array&& move) {
|
||||
this->_data = std::move(move._data);
|
||||
this->_length = move._length;
|
||||
// move._data = nullptr
|
||||
move._length = 0;
|
||||
return *this;
|
||||
~array() = default;
|
||||
|
||||
// Element Access
|
||||
[[nodiscard]] T& operator[](const usize i) noexcept {
|
||||
assert(i >= 0 && i < _length && "Index out of bounds");
|
||||
return _data.get()[i];
|
||||
}
|
||||
|
||||
T& operator[](const isize i) {
|
||||
return *(_data.get() + i);
|
||||
[[nodiscard]] const T& operator[](const usize i) const noexcept {
|
||||
assert(i >= 0 && i < _length && "Index out of bounds");
|
||||
return _data.get()[i];
|
||||
}
|
||||
|
||||
const T& operator[](const isize i) const {
|
||||
return *(_data.get() + i);
|
||||
}
|
||||
// Capacity
|
||||
[[nodiscard]] usize length() const noexcept { return _length; }
|
||||
[[nodiscard]] usize size() const noexcept { return _length; }
|
||||
[[nodiscard]] bool empty() const noexcept { return _length == 0; }
|
||||
|
||||
isize length() const {
|
||||
return _length;
|
||||
}
|
||||
// Data pointer
|
||||
[[nodiscard]] T* data() noexcept { return _data.get(); }
|
||||
[[nodiscard]] const T* data() const noexcept { return _data.get(); }
|
||||
|
||||
T* begin() {
|
||||
return _data.get();
|
||||
}
|
||||
// Iterators
|
||||
[[nodiscard]] T* begin() noexcept { return _data.get(); }
|
||||
[[nodiscard]] T* end() noexcept { return _data.get() + _length; }
|
||||
|
||||
T* end() {
|
||||
return _data.get() + _length;
|
||||
}
|
||||
|
||||
const T* begin() const {
|
||||
return _data.get();
|
||||
}
|
||||
|
||||
const T* end() const {
|
||||
return _data.get() + _length;
|
||||
}
|
||||
[[nodiscard]] const T* begin() const noexcept { return _data.get(); }
|
||||
[[nodiscard]] const T* end() const noexcept { return _data.get() + _length; }
|
||||
|
||||
[[nodiscard]] const T* cbegin() const noexcept { return _data.get(); }
|
||||
[[nodiscard]] const T* cend() const noexcept { return _data.get() + _length; }
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace ckitty
|
||||
+113
-23
@@ -1,58 +1,148 @@
|
||||
#pragma once
|
||||
|
||||
#include <ckitty/memory/primitives.hpp>
|
||||
#include <ckitty/memory/array.hpp>
|
||||
#include <ckitty/memory/primitives.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
|
||||
namespace ckitty {
|
||||
|
||||
/**
|
||||
* @brief Endianness specification for packing and unpacking operations.
|
||||
*/
|
||||
enum class endian {
|
||||
little,
|
||||
big
|
||||
};
|
||||
|
||||
namespace bits {
|
||||
|
||||
/**
|
||||
* Unpacks a value into bytes.
|
||||
* @brief Unpacks an integral value into an array of bytes.
|
||||
* @tparam T Integral type to unpack.
|
||||
* @param value The value to convert into bytes.
|
||||
* @param order Byte order (default: endian::little).
|
||||
*/
|
||||
template<typename T>
|
||||
inline ckitty::array<u8> unpack(const T value, bool endianness) {
|
||||
const size bytelen = sizeof(T);
|
||||
ckitty::array<u8> bytes(bytelen);
|
||||
for(size i = 0; i < bytelen; i++) {
|
||||
size j = endianness ? bytelen - i - 1 : i;
|
||||
bytes[i] = (value >> (j * 8)) & 0xFF;
|
||||
[[nodiscard]] inline ckitty::array<u8> unpack(const T value, const endian order = endian::little) {
|
||||
static_assert(std::is_integral_v<T>, "bits::unpack requires an integral type");
|
||||
|
||||
constexpr usize bytelen = sizeof(T);
|
||||
ckitty::array<u8> bytes(static_cast<isize>(bytelen));
|
||||
|
||||
for (usize i = 0; i < bytelen; ++i) {
|
||||
const usize shift_bytes = (order == endian::big) ? (bytelen - 1 - i) : i;
|
||||
bytes[static_cast<isize>(i)] = static_cast<u8>((value >> (shift_bytes * 8)) & 0xFF);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Packs bytes into a value.
|
||||
* @brief Packs a raw byte buffer into an integral value.
|
||||
* @tparam T Integral destination type.
|
||||
* @param bytes Pointer to the byte buffer.
|
||||
* @param length Number of bytes available in the buffer.
|
||||
* @param order Byte order (default: endian::little).
|
||||
*/
|
||||
template<typename T>
|
||||
inline T pack(const u8* bytes, bool endianness) {
|
||||
const size bytelen = sizeof(T);
|
||||
T type(0);
|
||||
for(size i = 0; i < bytelen; i++) {
|
||||
size j = endianness ? bytelen - i - 1 : i;
|
||||
type |= T(bytes[i] << (j * 8)) & 0xFF;
|
||||
}
|
||||
return type;
|
||||
[[nodiscard]] inline T pack(const u8* bytes, const usize length, const endian order = endian::little) noexcept {
|
||||
static_assert(std::is_integral_v<T>, "bits::pack requires an integral type");
|
||||
|
||||
if (!bytes) {
|
||||
return T(0);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool read(const T value, isize bitno) {
|
||||
return (value >> bitno) & 1;
|
||||
constexpr usize bytelen = sizeof(T);
|
||||
const usize count = (length < bytelen) ? length : bytelen;
|
||||
T result{0};
|
||||
|
||||
for (usize i = 0; i < count; ++i) {
|
||||
const usize shift_bytes = (order == endian::big) ? (bytelen - 1 - i) : i;
|
||||
result |= (static_cast<T>(bytes[i]) << (shift_bytes * 8));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Packs a ckitty::array of bytes into an integral value.
|
||||
*/
|
||||
template<typename T>
|
||||
inline T write(const T value, isize bitno, bool bitval) {
|
||||
size mask = T(1) << bitno;
|
||||
[[nodiscard]] inline T pack(const ckitty::array<u8>& bytes, const endian order = endian::little) noexcept {
|
||||
return pack<T>(bytes.begin(), static_cast<usize>(bytes.length()), order);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads a single bit state at the specified index.
|
||||
*/
|
||||
template<typename T>
|
||||
[[nodiscard]] inline bool read(const T value, const usize bitno) noexcept {
|
||||
static_assert(std::is_integral_v<T>, "bits::read requires an integral type");
|
||||
return (value >> bitno) & static_cast<T>(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets or clears a single bit at the specified index, returning the modified value.
|
||||
*/
|
||||
template<typename T>
|
||||
[[nodiscard]] inline T write(const T value, const usize bitno, const bool bitval) noexcept {
|
||||
static_assert(std::is_integral_v<T>, "bits::write requires an integral type");
|
||||
const T mask = static_cast<T>(1) << bitno;
|
||||
return bitval ? (value | mask) : (value & ~mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Modifies a bit in-place at the specified index.
|
||||
*/
|
||||
template<typename T>
|
||||
inline void write_r(T& value, isize bitno, bool bitval) {
|
||||
inline void write_inplace(T& value, const usize bitno, const bool bitval) noexcept {
|
||||
value = write(value, bitno, bitval);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets a bit to 1 at the specified index.
|
||||
*/
|
||||
template<typename T>
|
||||
[[nodiscard]] inline T set(const T value, const usize bitno) noexcept {
|
||||
return write(value, bitno, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clears a bit to 0 at the specified index.
|
||||
*/
|
||||
template<typename T>
|
||||
[[nodiscard]] inline T clear(const T value, const usize bitno) noexcept {
|
||||
return write(value, bitno, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inverts a bit at the specified index.
|
||||
*/
|
||||
template<typename T>
|
||||
[[nodiscard]] inline T toggle(const T value, const usize bitno) noexcept {
|
||||
static_assert(std::is_integral_v<T>, "bits::toggle requires an integral type");
|
||||
return value ^ (static_cast<T>(1) << bitno);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reverses the byte order of an integral value.
|
||||
*/
|
||||
template<typename T>
|
||||
[[nodiscard]] inline T byteswap(const T value) noexcept {
|
||||
static_assert(std::is_integral_v<T>, "bits::byteswap requires an integral type");
|
||||
constexpr usize bytelen = sizeof(T);
|
||||
T result{0};
|
||||
|
||||
for (usize i = 0; i < bytelen; ++i) {
|
||||
const u8 byte = static_cast<u8>((value >> (i * 8)) & 0xFF);
|
||||
result |= (static_cast<T>(byte) << ((bytelen - 1 - i) * 8));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace bits
|
||||
|
||||
} // namespace ckitty
|
||||
+107
-128
@@ -2,70 +2,87 @@
|
||||
|
||||
#include <ckitty/memory/array.hpp>
|
||||
#include <ckitty/memory/data_view.hpp>
|
||||
#include <ckitty/memory/primitives.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <iomanip>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace ckitty {
|
||||
|
||||
/**
|
||||
* Define a buffer as an array of u8.
|
||||
* @brief A byte buffer represented as a contiguous fixed-size array of u8.
|
||||
*/
|
||||
using buffer = array<u8>;
|
||||
|
||||
namespace buffers {
|
||||
|
||||
/**
|
||||
* @brief The sole purpose of this function
|
||||
* is to stop me from confusing the signs.
|
||||
* @brief Checks whether a given index falls within bounds.
|
||||
*/
|
||||
inline bool fits(isize index, isize length) {
|
||||
return index < length;
|
||||
[[nodiscard]] constexpr inline bool fits(const isize index, const isize length) noexcept {
|
||||
return index >= 0 && index < length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if some data fits in the
|
||||
* provided space.
|
||||
* @brief Checks if a requested payload size fits starting at `offset`.
|
||||
*/
|
||||
inline bool fits(isize offset, isize length, isize test) {
|
||||
return (offset < length) && ((length - offset) >= test);
|
||||
[[nodiscard]] constexpr inline bool fits(const isize offset, const isize length, const isize test_length) noexcept {
|
||||
return offset >= 0 && test_length >= 0 && length >= 0 &&
|
||||
offset <= length && test_length <= (length - offset);
|
||||
}
|
||||
|
||||
inline data_view view(const buffer& buff) {
|
||||
/**
|
||||
* @brief Creates a data_view over an existing buffer.
|
||||
*/
|
||||
[[nodiscard]] inline data_view view(const buffer& buff) noexcept {
|
||||
return data_view(buff.begin(), buff.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Simple match agains data view.
|
||||
* @brief Creates a data_view over a string.
|
||||
*/
|
||||
inline bool matches(data_view d1, data_view d2) {
|
||||
if(d1.length() != d2.length()) return false;
|
||||
for(isize i = 0; i < d1.length(); i++) {
|
||||
if(d1[i] != d2[i]) return false;
|
||||
}
|
||||
return true;
|
||||
[[nodiscard]] inline data_view view(std::string_view str) noexcept {
|
||||
return data_view(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Matches if the data starts with a prefix
|
||||
* @brief Tests if two byte views contain identical content.
|
||||
*/
|
||||
inline bool startsWith(data_view d1, data_view pre) {
|
||||
if(d1.length() < pre.length()) return false;
|
||||
for(isize i = 0; i < pre.length(); i++) {
|
||||
if(d1[i] != pre[i]) return false;
|
||||
[[nodiscard]] constexpr inline bool matches(const data_view d1, const data_view d2) noexcept {
|
||||
return d1 == d2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a view starts with the given prefix.
|
||||
*/
|
||||
[[nodiscard]] constexpr inline bool starts_with(const data_view data, const data_view prefix) noexcept {
|
||||
if (data.length() < prefix.length()) return false;
|
||||
return data.sub_off(0, prefix.length()) == prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Searches for a subview pattern inside data.
|
||||
* @param data Buffer view to search within.
|
||||
* @param test Pattern view to locate.
|
||||
* @param[out] out_index Set to the match start index if found.
|
||||
* @return true if pattern was found, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] inline bool find(const data_view data, const data_view test, isize& out_index) noexcept {
|
||||
if (test.empty()) {
|
||||
out_index = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool find(data_view data, data_view test, isize& index) {
|
||||
// loop until we find match
|
||||
for(isize off = 0; off < data.length(); off++) {
|
||||
// fits?
|
||||
if(!fits(off, data.length(), test.length())) return false;
|
||||
|
||||
// test for equality
|
||||
if(matches(data_view(data.begin() + off, test.length()), test)) {
|
||||
index = off;
|
||||
const isize max_offset = data.length() - test.length();
|
||||
for (isize off = 0; off <= max_offset; ++off) {
|
||||
if (data.sub_off(off, test.length()) == test) {
|
||||
out_index = off;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -73,135 +90,97 @@ namespace ckitty {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if an index is in between
|
||||
* min and max, inclusively.
|
||||
* @brief Checks if an index is within [min, max] inclusively.
|
||||
*/
|
||||
inline bool in_range(isize min, isize index, isize max) {
|
||||
return min <= index && index <= max;
|
||||
[[nodiscard]] constexpr inline bool in_range(const isize min_val, const isize index, const isize max_val) noexcept {
|
||||
return min_val <= index && index <= max_val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Moves the buffer to a new
|
||||
* one, and restores the size in the
|
||||
* previous one.
|
||||
* @brief Moves buffer ownership out while replacing the source buffer with a fresh allocation.
|
||||
*/
|
||||
inline buffer moveRealocate(buffer& buff) {
|
||||
isize len = buff.length();
|
||||
buffer out = std::move(buff);
|
||||
[[nodiscard]] inline buffer move_and_reallocate(buffer& buff) {
|
||||
const isize len = buff.length();
|
||||
buffer old_buffer = std::move(buff);
|
||||
buff = buffer(len);
|
||||
return out;
|
||||
return old_buffer;
|
||||
}
|
||||
|
||||
inline data_view view(std::string& str) {
|
||||
return data_view(reinterpret_cast<const u8*>(str.c_str()), str.length());
|
||||
/**
|
||||
* @brief Copies data from a data_view into a buffer at `index`, advancing `index`.
|
||||
*/
|
||||
inline void copy(buffer& buff, isize& index, const data_view src) noexcept {
|
||||
assert(index >= 0 && "Index cannot be negative");
|
||||
assert(index + src.length() <= buff.length() && "Buffer overflow during copy operation");
|
||||
|
||||
std::copy(src.begin(), src.end(), buff.begin() + index);
|
||||
index += src.length();
|
||||
}
|
||||
|
||||
inline void copy(buffer& buff, isize& index, const std::string& str) {
|
||||
std::copy(str.begin(), str.end(), buff.begin() + index);
|
||||
index += str.size();
|
||||
/**
|
||||
* @brief Construct a new owned buffer populated with bytes from a string.
|
||||
*/
|
||||
[[nodiscard]] inline buffer from(std::string_view str) {
|
||||
return buffer(reinterpret_cast<const u8*>(str.data()),
|
||||
reinterpret_cast<const u8*>(str.data() + str.size()));
|
||||
}
|
||||
|
||||
inline void copy(buffer& buff, isize& index, const buffer& data) {
|
||||
std::copy(data.begin(), data.end(), buff.begin() + index);
|
||||
index += data.length();
|
||||
}
|
||||
/**
|
||||
* @brief Prints a formatted hexadecimal and ASCII view of a buffer to a stream.
|
||||
*/
|
||||
inline void hexdump(const data_view view, std::ostream& os, std::string_view prefix = "") {
|
||||
constexpr char hex_lookup[] = "0123456789ABCDEF";
|
||||
|
||||
inline void hexdump(data_view view, std::ostream& os, std::string prefix = "") {
|
||||
// format:
|
||||
// FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
|
||||
// -
|
||||
char chars[] = {
|
||||
'0', '1', '2', '3',
|
||||
'4', '5', '6', '7',
|
||||
'8', '9', 'A', 'B',
|
||||
'C', 'D', 'E', 'F'
|
||||
};
|
||||
|
||||
// how many rows chars to use?
|
||||
double logv = std::log(std::max<isize>(1, view.length()));
|
||||
double spaces = std::floor(logv / std::log(16) + 1);
|
||||
isize rowchars = std::max<isize>(2, isize(spaces)) - 1;
|
||||
|
||||
// print header
|
||||
os << prefix << std::string(rowchars, ' ') << " X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 XA XB XC XD XE XF | -------- --------\n";
|
||||
|
||||
// check zero data
|
||||
if(view.length() == 0) {
|
||||
os << prefix << std::string(rowchars, ' ') << " (NO DATA)\n";
|
||||
os.flush();
|
||||
if (view.empty()) {
|
||||
os << prefix << "(NO DATA)\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// add prefix
|
||||
os << prefix << "\n";
|
||||
// Calculate address width in hex digits integer-wise
|
||||
isize max_addr = view.length();
|
||||
int addr_width = 2;
|
||||
while (max_addr > 0xFF) {
|
||||
addr_width += 2;
|
||||
max_addr >>= 8;
|
||||
}
|
||||
|
||||
// loop through the bytes
|
||||
isize index = 0, row = 0;
|
||||
while(index < view.length()) {
|
||||
// ascii buffer
|
||||
std::string ascii;
|
||||
// Print Header
|
||||
os << prefix << std::string(addr_width, ' ')
|
||||
<< " X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 XA XB XC XD XE XF | ASCII\n";
|
||||
|
||||
// add header
|
||||
const isize total = view.length();
|
||||
for (isize i = 0; i < total; i += 16) {
|
||||
os << prefix;
|
||||
os << std::hex << std::setfill('0') << std::setw(int(rowchars)) << row << std::dec << "X ";
|
||||
os << std::hex << std::setfill('0') << std::setw(addr_width) << i << std::dec << " ";
|
||||
|
||||
// print a line of the characters
|
||||
for(int g = 0; g < 2; g++) {
|
||||
// print half a row
|
||||
for(int i = 0; i < 8; i++) {
|
||||
// end of file?
|
||||
u8 value, high, low;
|
||||
std::string ascii_part;
|
||||
ascii_part.reserve(16);
|
||||
|
||||
// compute vars
|
||||
if(index < view.length()) {
|
||||
// get the byte value
|
||||
value = view[index++];
|
||||
high = (value >> 4) & 0xF;
|
||||
low = (value >> 0) & 0xF;
|
||||
for (isize j = 0; j < 16; ++j) {
|
||||
const isize idx = i + j;
|
||||
if (idx < total) {
|
||||
const u8 byte = view[idx];
|
||||
os << hex_lookup[(byte >> 4) & 0x0F] << hex_lookup[byte & 0x0F];
|
||||
|
||||
// log value
|
||||
os << chars[high] << chars[low];
|
||||
const unsigned char ubyte = static_cast<unsigned char>(byte);
|
||||
ascii_part += std::isgraph(ubyte) ? static_cast<char>(ubyte) : '.';
|
||||
} else {
|
||||
os << " ";
|
||||
value = ' ';
|
||||
}
|
||||
|
||||
// add to ascii?
|
||||
char ch = char(value);
|
||||
if(!std::isgraph(ch)) ch = ' ';
|
||||
ascii += ch;
|
||||
|
||||
// space condition
|
||||
if(i < 7) {
|
||||
os << ' ';
|
||||
continue;
|
||||
}
|
||||
if(g == 0) {
|
||||
if (j == 7) {
|
||||
os << " ";
|
||||
ascii += " ";
|
||||
continue;
|
||||
} else if (j < 15) {
|
||||
os << ' ';
|
||||
}
|
||||
}
|
||||
// <-
|
||||
}
|
||||
|
||||
// add the "ASCII" version
|
||||
os << " | " << ascii;
|
||||
row++;
|
||||
|
||||
// end of line
|
||||
os << '\n';
|
||||
os.flush();
|
||||
os << " | " << ascii_part << '\n';
|
||||
}
|
||||
|
||||
// undo manips
|
||||
os << std::setfill(' ');
|
||||
}
|
||||
|
||||
inline buffer from(const std::string& str) {
|
||||
return buffer(reinterpret_cast<const u8*>(str.begin().base()), reinterpret_cast<const u8*>(str.end().base()));
|
||||
}
|
||||
} // namespace buffers
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace ckitty
|
||||
+101
-28
@@ -1,55 +1,128 @@
|
||||
#pragma once
|
||||
|
||||
#include <ckitty/memory/array.hpp>
|
||||
#include <ckitty/memory/primitives.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
namespace ckitty {
|
||||
|
||||
/**
|
||||
* @brief Represents a piece of a shared buffer that
|
||||
* is not owned by this object.
|
||||
* @brief A non-owning view over a contiguous sequence of immutable bytes.
|
||||
* Lightweight wrapper equivalent to a std::span<const u8>.
|
||||
*/
|
||||
class data_view {
|
||||
private:
|
||||
public:
|
||||
using value_type = u8;
|
||||
using size_type = isize;
|
||||
using difference_type = isize;
|
||||
using reference = const u8&;
|
||||
using const_reference = const u8&;
|
||||
using pointer = const u8*;
|
||||
using const_pointer = const u8*;
|
||||
using iterator = const u8*;
|
||||
using const_iterator = const u8*;
|
||||
|
||||
const u8* _begin;
|
||||
const u8* _end;
|
||||
private:
|
||||
const u8* _begin{ nullptr };
|
||||
const u8* _end{ nullptr };
|
||||
|
||||
public:
|
||||
// Constructors
|
||||
constexpr data_view() noexcept = default;
|
||||
|
||||
data_view(const u8* begin, const u8* end)
|
||||
: _begin(begin), _end(end) {}
|
||||
|
||||
data_view(const u8* data, const isize length)
|
||||
: _begin(data), _end(data + length) {}
|
||||
|
||||
data_view(const std::string& str)
|
||||
: _begin(reinterpret_cast<const u8*>(str.begin().base())),
|
||||
_end(reinterpret_cast<const u8*>(str.end().base())) {}
|
||||
|
||||
const u8* begin() const {
|
||||
return _begin;
|
||||
constexpr data_view(const u8* begin, const u8* end) noexcept
|
||||
: _begin(begin), _end(end) {
|
||||
assert((_begin <= _end) && "Invalid range: begin > end");
|
||||
}
|
||||
|
||||
const u8* end() const {
|
||||
return _end;
|
||||
constexpr data_view(const u8* data, const isize length) noexcept
|
||||
: _begin(data), _end(data&& length > 0 ? data + length : data) {
|
||||
assert(length >= 0 && "Length cannot be negative");
|
||||
}
|
||||
|
||||
u8 operator[](isize i) const {
|
||||
return *(_begin + i);
|
||||
// String / String View interoperability
|
||||
data_view(const std::string& str) noexcept
|
||||
: _begin(reinterpret_cast<const u8*>(str.data())),
|
||||
_end(reinterpret_cast<const u8*>(str.data() + str.size())) {
|
||||
}
|
||||
|
||||
isize length() const {
|
||||
return isize(_end - _begin);
|
||||
constexpr data_view(std::string_view sv) noexcept
|
||||
: _begin(reinterpret_cast<const u8*>(sv.data())),
|
||||
_end(reinterpret_cast<const u8*>(sv.data() + sv.size())) {
|
||||
}
|
||||
|
||||
const data_view sub_off(isize offset, isize length) const {
|
||||
return data_view(_begin + offset, _begin + offset + length);
|
||||
// Construct from char array / C-string with explicit length
|
||||
constexpr data_view(const char* data, const isize length) noexcept
|
||||
: data_view(reinterpret_cast<const u8*>(data), length) {
|
||||
}
|
||||
|
||||
const data_view sub_abs(isize begin, isize end) const {
|
||||
return data_view(_begin + begin, _begin + end);
|
||||
// Interoperability with ckitty::array<u8>
|
||||
data_view(const ckitty::array<u8>& arr) noexcept
|
||||
: _begin(arr.begin()), _end(arr.end()) {
|
||||
}
|
||||
|
||||
// Default copy/move semantics
|
||||
constexpr data_view(const data_view&) noexcept = default;
|
||||
constexpr data_view(data_view&&) noexcept = default;
|
||||
constexpr data_view& operator=(const data_view&) noexcept = default;
|
||||
constexpr data_view& operator=(data_view&&) noexcept = default;
|
||||
~data_view() = default;
|
||||
|
||||
// Element Access
|
||||
[[nodiscard]] constexpr u8 operator[](const isize i) const noexcept {
|
||||
assert(i >= 0 && i < length() && "Index out of bounds");
|
||||
return _begin[i];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const u8* data() const noexcept { return _begin; }
|
||||
|
||||
// Iterators
|
||||
[[nodiscard]] constexpr const u8* begin() const noexcept { return _begin; }
|
||||
[[nodiscard]] constexpr const u8* end() const noexcept { return _end; }
|
||||
[[nodiscard]] constexpr const u8* cbegin() const noexcept { return _begin; }
|
||||
[[nodiscard]] constexpr const u8* cend() const noexcept { return _end; }
|
||||
|
||||
// Capacity
|
||||
[[nodiscard]] constexpr isize length() const noexcept { return static_cast<isize>(_end - _begin); }
|
||||
[[nodiscard]] constexpr isize size() const noexcept { return length(); }
|
||||
[[nodiscard]] constexpr bool empty() const noexcept { return _begin == _end; }
|
||||
|
||||
// Subviews
|
||||
/**
|
||||
* @brief Returns a subview starting at `offset` with `length` bytes.
|
||||
*/
|
||||
[[nodiscard]] constexpr data_view sub_off(const isize offset, const isize length) const noexcept {
|
||||
assert(offset >= 0 && offset <= this->length() && "Offset out of bounds");
|
||||
assert(length >= 0 && (offset + length) <= this->length() && "Length out of bounds");
|
||||
return data_view(_begin + offset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a subview in range [_begin + start, _begin + end).
|
||||
*/
|
||||
[[nodiscard]] constexpr data_view sub_abs(const isize start, const isize end) const noexcept {
|
||||
assert(start >= 0 && start <= length() && "Start index out of bounds");
|
||||
assert(end >= start && end <= length() && "End index out of bounds");
|
||||
return data_view(_begin + start, _begin + end);
|
||||
}
|
||||
|
||||
// Comparison Operators
|
||||
[[nodiscard]] friend constexpr bool operator==(const data_view& lhs, const data_view& rhs) noexcept {
|
||||
if (lhs.length() != rhs.length()) return false;
|
||||
for (isize i = 0; i < lhs.length(); ++i) {
|
||||
if (lhs[i] != rhs[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] friend constexpr bool operator!=(const data_view& lhs, const data_view& rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace ckitty
|
||||
+112
-10
@@ -1,33 +1,135 @@
|
||||
#pragma once
|
||||
|
||||
#include <ckitty/memory/primitives.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace ckitty {
|
||||
|
||||
namespace it {
|
||||
|
||||
template<typename T, typename V>
|
||||
bool contains(const T& cont, const V& value) {
|
||||
namespace detail {
|
||||
// SFINAE helper to check for member .find() or .contains()
|
||||
template<typename C, typename V, typename = void>
|
||||
struct has_member_find : std::false_type {};
|
||||
|
||||
template<typename C, typename V>
|
||||
struct has_member_find<C, V, std::void_t<decltype(std::declval<const C&>().find(std::declval<const V&>()))>> : std::true_type {};
|
||||
|
||||
template<typename C, typename V, typename = void>
|
||||
struct has_member_erase : std::false_type {};
|
||||
|
||||
template<typename C, typename V>
|
||||
struct has_member_erase<C, V, std::void_t<decltype(std::declval<C&>().erase(std::declval<const V&>()))>> : std::true_type {};
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* @brief Checks if a container contains a specific value.
|
||||
* Automatically dispatches to member .find() / .contains() if available (O(1) / O(log N)),
|
||||
* falling back to standard linear search O(N) for sequence containers.
|
||||
*/
|
||||
template<typename Container, typename Value>
|
||||
[[nodiscard]] inline bool contains(const Container& cont, const Value& value) {
|
||||
if constexpr (detail::has_member_find<Container, Value>::value) {
|
||||
return cont.find(value) != cont.end();
|
||||
} else {
|
||||
return std::find(cont.begin(), cont.end(), value) != cont.end();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename V>
|
||||
bool erase(T& cont, const V& value) {
|
||||
/**
|
||||
* @brief Checks if any element in a container satisfies a predicate.
|
||||
*/
|
||||
template<typename Container, typename Predicate>
|
||||
[[nodiscard]] inline bool contains_if(const Container& cont, Predicate&& pred) {
|
||||
return std::find_if(cont.begin(), cont.end(), std::forward<Predicate>(pred)) != cont.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds the zero-based index of the first occurrence of a value.
|
||||
* @return Index if found, or -1 (isize) if not found.
|
||||
*/
|
||||
template<typename Container, typename Value>
|
||||
[[nodiscard]] inline isize index_of(const Container& cont, const Value& value) {
|
||||
auto it = std::find(cont.begin(), cont.end(), value);
|
||||
if (it == cont.end()) return -1;
|
||||
return static_cast<isize>(std::distance(cont.begin(), it));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erases the FIRST occurrence of a value from a sequence container.
|
||||
* @return true if an element was erased, false otherwise.
|
||||
*/
|
||||
template<typename Container, typename Value>
|
||||
inline bool erase_first(Container& cont, const Value& value) {
|
||||
auto it = std::find(cont.begin(), cont.end(), value);
|
||||
if (it == cont.end()) return false;
|
||||
cont.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T, typename V>
|
||||
bool map_erase(T& map, const V& value) {
|
||||
auto it = map.find(value);
|
||||
if (it == map.end()) return false;
|
||||
map.erase(it);
|
||||
return true;
|
||||
/**
|
||||
* @brief Erases ALL occurrences of a value from a container.
|
||||
* Dispatches to member .erase(key) for associative maps/sets, or erase-remove for sequences.
|
||||
* @return Number of elements removed.
|
||||
*/
|
||||
template<typename Container, typename Value>
|
||||
inline usize erase_all(Container& cont, const Value& value) {
|
||||
if constexpr (detail::has_member_erase<Container, Value>::value) {
|
||||
return static_cast<usize>(cont.erase(value));
|
||||
} else {
|
||||
const auto old_size = cont.size();
|
||||
cont.erase(std::remove(cont.begin(), cont.end(), value), cont.end());
|
||||
return static_cast<usize>(old_size - cont.size());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erases elements that satisfy a predicate.
|
||||
* @return Number of elements removed.
|
||||
*/
|
||||
template<typename Container, typename Predicate>
|
||||
inline usize erase_if(Container& cont, Predicate&& pred) {
|
||||
const auto old_size = cont.size();
|
||||
cont.erase(std::remove_if(cont.begin(), cont.end(), std::forward<Predicate>(pred)), cont.end());
|
||||
return static_cast<usize>(old_size - cont.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Specialized single-key erase for maps and sets (single lookup O(log N) / O(1)).
|
||||
*/
|
||||
template<typename Map, typename Key>
|
||||
inline bool map_erase(Map& map, const Key& key) {
|
||||
return map.erase(key) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if all elements satisfy the predicate.
|
||||
*/
|
||||
template<typename Container, typename Predicate>
|
||||
[[nodiscard]] inline bool all_of(const Container& cont, Predicate&& pred) {
|
||||
return std::all_of(cont.begin(), cont.end(), std::forward<Predicate>(pred));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if any element satisfies the predicate.
|
||||
*/
|
||||
template<typename Container, typename Predicate>
|
||||
[[nodiscard]] inline bool any_of(const Container& cont, Predicate&& pred) {
|
||||
return std::any_of(cont.begin(), cont.end(), std::forward<Predicate>(pred));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if no elements satisfy the predicate.
|
||||
*/
|
||||
template<typename Container, typename Predicate>
|
||||
[[nodiscard]] inline bool none_of(const Container& cont, Predicate&& pred) {
|
||||
return std::none_of(cont.begin(), cont.end(), std::forward<Predicate>(pred));
|
||||
}
|
||||
|
||||
} // namespace it
|
||||
|
||||
} // namespace ckitty
|
||||
@@ -1,40 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <deque>
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
|
||||
namespace ckitty {
|
||||
|
||||
typedef std::uint8_t u8;
|
||||
typedef std::uint16_t u16;
|
||||
typedef std::uint32_t u32;
|
||||
typedef std::uint64_t u64;
|
||||
// Fixed-width integer types
|
||||
using u8 = std::uint8_t;
|
||||
using u16 = std::uint16_t;
|
||||
using u32 = std::uint32_t;
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
typedef std::int8_t i8;
|
||||
typedef std::int16_t i16;
|
||||
typedef std::int32_t i32;
|
||||
typedef std::int64_t i64;
|
||||
using i8 = std::int8_t;
|
||||
using i16 = std::int16_t;
|
||||
using i32 = std::int32_t;
|
||||
using i64 = std::int64_t;
|
||||
|
||||
typedef std::size_t isize;
|
||||
// Pointer-sized integer types
|
||||
using usize = std::size_t; // Unsigned size type
|
||||
using isize = std::ptrdiff_t; // Signed size type
|
||||
|
||||
typedef float f32;
|
||||
typedef double f64;
|
||||
// Floating-point types
|
||||
using f32 = float;
|
||||
using f64 = double;
|
||||
|
||||
// Common standard library type aliases
|
||||
using string = std::string;
|
||||
|
||||
using std::vector;
|
||||
using std::map;
|
||||
using std::deque;
|
||||
using std::map;
|
||||
using std::optional;
|
||||
using std::vector;
|
||||
|
||||
template<typename T> using ptr = std::shared_ptr<T>;
|
||||
template<typename T> using uptr = std::unique_ptr<T>;
|
||||
template<typename T> using wptr = std::weak_ptr<T>;
|
||||
// Smart pointer type aliases
|
||||
|
||||
template<typename T> using ilist = std::initializer_list<T>;
|
||||
template<typename T, typename Deleter = std::default_delete<T>>
|
||||
using uptr = std::unique_ptr<T, Deleter>;
|
||||
|
||||
}
|
||||
template<typename T>
|
||||
using sptr = std::shared_ptr<T>;
|
||||
|
||||
template<typename T>
|
||||
using wptr = std::weak_ptr<T>;
|
||||
|
||||
// Utility aliases
|
||||
template<typename T>
|
||||
using ilist = std::initializer_list<T>;
|
||||
|
||||
} // namespace ckitty
|
||||
@@ -0,0 +1,145 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace ckitty {
|
||||
|
||||
/**
|
||||
* @brief Non-atomic, reference-counted smart pointer (equivalent to Rust's Rc<T>).
|
||||
* Designed for single-threaded usage or single threads protected by external mutexes.
|
||||
*
|
||||
* @tparam T Type of object managed. Supports array types like ptr<T[]>.
|
||||
*/
|
||||
template<typename T>
|
||||
class ptr {
|
||||
public:
|
||||
using element_type = std::remove_extent_t<T>;
|
||||
using deleter_type = std::function<void(element_type*)>;
|
||||
|
||||
private:
|
||||
struct ControlBlock {
|
||||
element_type* raw_ptr{nullptr};
|
||||
std::size_t ref_count{1};
|
||||
deleter_type deleter;
|
||||
|
||||
ControlBlock(element_type* p, deleter_type d)
|
||||
: raw_ptr(p), ref_count(1), deleter(std::move(d)) {}
|
||||
};
|
||||
|
||||
ControlBlock* _cb{nullptr};
|
||||
|
||||
void release() noexcept {
|
||||
if (_cb) {
|
||||
--_cb->ref_count;
|
||||
if (_cb->ref_count == 0) {
|
||||
if (_cb->deleter) {
|
||||
_cb->deleter(_cb->raw_ptr);
|
||||
} else {
|
||||
if constexpr (std::is_array_v<T>) {
|
||||
delete[] _cb->raw_ptr;
|
||||
} else {
|
||||
delete _cb->raw_ptr;
|
||||
}
|
||||
}
|
||||
delete _cb;
|
||||
}
|
||||
_cb = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr ptr() noexcept = default;
|
||||
constexpr ptr(std::nullptr_t) noexcept {}
|
||||
|
||||
/**
|
||||
* @brief Takes ownership of a raw pointer.
|
||||
* @param raw Pointer to manage.
|
||||
* @param deleter Custom cleanup function (optional).
|
||||
*/
|
||||
explicit ptr(element_type* raw, deleter_type deleter = nullptr) {
|
||||
if (raw) {
|
||||
_cb = new ControlBlock(raw, std::move(deleter));
|
||||
}
|
||||
}
|
||||
|
||||
~ptr() {
|
||||
release();
|
||||
}
|
||||
|
||||
// Copy semantics: non-atomic ref-count increment
|
||||
ptr(const ptr& other) noexcept : _cb(other._cb) {
|
||||
if (_cb) {
|
||||
++_cb->ref_count;
|
||||
}
|
||||
}
|
||||
|
||||
ptr& operator=(const ptr& other) noexcept {
|
||||
if (this != &other) {
|
||||
release();
|
||||
_cb = other._cb;
|
||||
if (_cb) {
|
||||
++_cb->ref_count;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Move semantics: transfer ownership without ref-count change
|
||||
ptr(ptr&& other) noexcept : _cb(other._cb) {
|
||||
other._cb = nullptr;
|
||||
}
|
||||
|
||||
ptr& operator=(ptr&& other) noexcept {
|
||||
if (this != &other) {
|
||||
release();
|
||||
_cb = std::exchange(other._cb, nullptr);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Accessors
|
||||
[[nodiscard]] element_type* get() const noexcept {
|
||||
return _cb ? _cb->raw_ptr : nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t use_count() const noexcept {
|
||||
return _cb ? _cb->ref_count : 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] explicit operator bool() const noexcept {
|
||||
return get() != nullptr;
|
||||
}
|
||||
|
||||
// Pointer dereference operators
|
||||
[[nodiscard]] element_type& operator*() const noexcept {
|
||||
return *get();
|
||||
}
|
||||
|
||||
[[nodiscard]] element_type* operator->() const noexcept {
|
||||
return get();
|
||||
}
|
||||
|
||||
// Array indexing operator (enabled only for array types T[])
|
||||
template<typename U = T, typename = std::enable_if_t<std::is_array_v<U>>>
|
||||
[[nodiscard]] element_type& operator[](const std::ptrdiff_t index) const noexcept {
|
||||
return get()[index];
|
||||
}
|
||||
|
||||
void reset(element_type* raw = nullptr, deleter_type deleter = nullptr) {
|
||||
release();
|
||||
if (raw) {
|
||||
_cb = new ControlBlock(raw, std::move(deleter));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function equivalent to std::make_shared
|
||||
template<typename T, typename... Args>
|
||||
[[nodiscard]] ptr<T> make_ptr(Args&&... args) {
|
||||
return ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
} // namespace ckitty
|
||||
@@ -0,0 +1,135 @@
|
||||
#pragma once
|
||||
|
||||
#include <ckitty/memory/primitives.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
|
||||
namespace ckitty {
|
||||
|
||||
/**
|
||||
* @brief Lazy sequence generator that mimics Python's range() function.
|
||||
* Evaluates values on the fly without pre-allocating memory.
|
||||
*
|
||||
* @tparam T Integral element type (defaults to isize).
|
||||
*/
|
||||
template<typename T = isize>
|
||||
class range {
|
||||
static_assert(std::is_integral_v<T>, "ckitty::range requires an integral type");
|
||||
|
||||
private:
|
||||
T _start{0};
|
||||
T _stop{0};
|
||||
T _step{1};
|
||||
|
||||
public:
|
||||
class iterator {
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = T;
|
||||
using difference_type = isize;
|
||||
using pointer = const T*;
|
||||
using reference = const T&;
|
||||
|
||||
private:
|
||||
T _current{0};
|
||||
T _step{1};
|
||||
|
||||
public:
|
||||
constexpr iterator() noexcept = default;
|
||||
constexpr iterator(const T current, const T step) noexcept
|
||||
: _current(current), _step(step) {}
|
||||
|
||||
[[nodiscard]] constexpr T operator*() const noexcept { return _current; }
|
||||
|
||||
constexpr iterator& operator++() noexcept {
|
||||
_current += _step;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr iterator operator++(int) noexcept {
|
||||
iterator temp = *this;
|
||||
_current += _step;
|
||||
return temp;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr friend bool operator==(const iterator& lhs, const iterator& rhs) noexcept {
|
||||
if (lhs._step > 0) return lhs._current >= rhs._current;
|
||||
if (lhs._step < 0) return lhs._current <= rhs._current;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr friend bool operator!=(const iterator& lhs, const iterator& rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct range(stop) -> [0, stop) with step = 1.
|
||||
*/
|
||||
constexpr explicit range(const T stop) noexcept
|
||||
: _start(0), _stop(stop), _step(1) {}
|
||||
|
||||
/**
|
||||
* @brief Construct range(start, stop, step = 1).
|
||||
*/
|
||||
constexpr range(const T start, const T stop, const T step = 1) noexcept
|
||||
: _start(start), _stop(stop), _step(step) {
|
||||
assert(step != 0 && "ckitty::range step cannot be zero");
|
||||
}
|
||||
|
||||
// Accessors
|
||||
[[nodiscard]] constexpr T start() const noexcept { return _start; }
|
||||
[[nodiscard]] constexpr T stop() const noexcept { return _stop; }
|
||||
[[nodiscard]] constexpr T step() const noexcept { return _step; }
|
||||
|
||||
// Iterators
|
||||
[[nodiscard]] constexpr iterator begin() const noexcept { return iterator(_start, _step); }
|
||||
[[nodiscard]] constexpr iterator end() const noexcept { return iterator(_stop, _step); }
|
||||
|
||||
// Capacity
|
||||
[[nodiscard]] constexpr isize size() const noexcept {
|
||||
if (_step > 0 && _start < _stop) {
|
||||
return static_cast<isize>((_stop - _start + _step - 1) / _step);
|
||||
}
|
||||
if (_step < 0 && _start > _stop) {
|
||||
return static_cast<isize>((_start - _stop - _step - 1) / (-_step));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr isize length() const noexcept { return size(); }
|
||||
[[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; }
|
||||
|
||||
// Sequence Access
|
||||
[[nodiscard]] constexpr T operator[](const isize index) const noexcept {
|
||||
assert(index >= 0 && index < size() && "ckitty::range index out of bounds");
|
||||
return static_cast<T>(_start + (index * _step));
|
||||
}
|
||||
|
||||
// Membership check (O(1))
|
||||
[[nodiscard]] constexpr bool contains(const T value) const noexcept {
|
||||
if (_step > 0) {
|
||||
return value >= _start && value < _stop && ((value - _start) % _step == 0);
|
||||
}
|
||||
if (_step < 0) {
|
||||
return value <= _start && value > _stop && ((_start - value) % (-_step) == 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Class Template Argument Deduction (CTAD)
|
||||
template<typename T>
|
||||
range(T) -> range<T>;
|
||||
|
||||
template<typename T>
|
||||
range(T, T) -> range<T>;
|
||||
|
||||
template<typename T>
|
||||
range(T, T, T) -> range<T>;
|
||||
|
||||
} // namespace ckitty
|
||||
@@ -0,0 +1,266 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <ckitty/memory/primitives.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace ckitty {
|
||||
|
||||
namespace unicode {
|
||||
|
||||
// --------------------- //
|
||||
// UTF-8 Sequence Length //
|
||||
// --------------------- //
|
||||
|
||||
constexpr isize seqlen(u8 c) {
|
||||
if ((c & 0x80) == 0x00) return 1;
|
||||
if ((c & 0xE0) == 0xC0) return 2;
|
||||
if ((c & 0xF0) == 0xE0) return 3;
|
||||
if ((c & 0xF8) == 0xF0) return 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
constexpr bool isCont(u8 c) {
|
||||
return (c & 0xC0) == 0x80;
|
||||
}
|
||||
|
||||
// ----------------- //
|
||||
// UTF-8 into UTF-32 //
|
||||
// ----------------- //
|
||||
|
||||
inline bool decodeArr(const char* src, isize chlen, u32& out) {
|
||||
// Check character length
|
||||
if (chlen < 1 || chlen > 4) return false;
|
||||
|
||||
// map of masks, starts at 1
|
||||
static constexpr u8 firstMask[5] = {
|
||||
0x00, // unused
|
||||
0x7F, // 0xxxxxxx
|
||||
0x1F, // 110xxxxx
|
||||
0x0F, // 1110xxxx
|
||||
0x07 // 11110xxx
|
||||
};
|
||||
|
||||
u32 result = u8(src[0]) & firstMask[chlen];
|
||||
for (isize i = 1; i < chlen; ++i) {
|
||||
if (!isCont(u8(src[i]))) return false;
|
||||
result = (result << 6) | (u8(src[i]) & 0x3F);
|
||||
}
|
||||
|
||||
// Security & Constraints Validation
|
||||
// So, as it turns out, you can define UTF-8
|
||||
// characters to be longer than they should.
|
||||
// This protects against the (albeit niche) hack
|
||||
// of sending sensitive chars like "\", "<", etc.
|
||||
// and have a parser not target them.
|
||||
// So take this: ALWAYS use UTF-32 to make comparisons!
|
||||
// Byte-sequences are NOT to be trusted NEVER.
|
||||
// Still, this ensures it's DOUBLE correct.
|
||||
if (chlen == 2 && result < 0x80) return false; // Overlong 2-byte
|
||||
if (chlen == 3 && result < 0x0800) return false; // Overlong 3-byte
|
||||
if (chlen == 4 && result < 0x10000) return false; // Overlong 4-byte
|
||||
if (result >= 0xD800 && result <= 0xDFFF) return false; // UTF-16 Surrogates
|
||||
if (result > 0x10FFFF) return false; // Out of Unicode bounds
|
||||
|
||||
out = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline isize decode(const char* src, isize len, u32& out) {
|
||||
if (len <= 0) return 0;
|
||||
|
||||
isize m = seqlen(u8(src[0]));
|
||||
if (m == 0 || m > len) return 0;
|
||||
|
||||
if (decodeArr(src, m, out)) return m;
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline bool toUTF32(std::string_view str, std::u32string& out) {
|
||||
isize _i = 0;
|
||||
isize csize = str.size();
|
||||
const char* data = str.data();
|
||||
|
||||
while (_i < csize) {
|
||||
u32 codepoint;
|
||||
isize _size = decode(data + _i, csize - _i, codepoint);
|
||||
if (_size == 0) return false;
|
||||
|
||||
out += char32_t(codepoint);
|
||||
_i += _size;
|
||||
}
|
||||
|
||||
return _i == csize;
|
||||
}
|
||||
|
||||
// ----------------- //
|
||||
// UTF-32 into UTF-8 //
|
||||
// ----------------- //
|
||||
|
||||
inline void append_utf32_to_utf8(u32 code_point, std::string& out) {
|
||||
if (code_point <= 0x7F) {
|
||||
// 1-byte sequence (ASCII)
|
||||
out.push_back(static_cast<char>(code_point));
|
||||
} else if (code_point <= 0x7FF) {
|
||||
// 2-byte sequence
|
||||
out.push_back(static_cast<char>(0xC0 | ((code_point >> 6) & 0x1F)));
|
||||
out.push_back(static_cast<char>(0x80 | (code_point & 0x3F)));
|
||||
} else if (code_point <= 0xFFFF) {
|
||||
// 3-byte sequence
|
||||
// Filter out surrogate pairs (U+D800 to U+DFFF) as they are invalid Unicode scalar values
|
||||
if (code_point >= 0xD800 && code_point <= 0xDFFF) {
|
||||
code_point = 0xFFFD; // Replacement character
|
||||
}
|
||||
out.push_back(static_cast<char>(0xE0 | ((code_point >> 12) & 0x0F)));
|
||||
out.push_back(static_cast<char>(0x80 | ((code_point >> 6) & 0x3F)));
|
||||
out.push_back(static_cast<char>(0x80 | (code_point & 0x3F)));
|
||||
} else if (code_point <= 0x10FFFF) {
|
||||
// 4-byte sequence
|
||||
out.push_back(static_cast<char>(0xF0 | ((code_point >> 18) & 0x07)));
|
||||
out.push_back(static_cast<char>(0x80 | ((code_point >> 12) & 0x3F)));
|
||||
out.push_back(static_cast<char>(0x80 | ((code_point >> 6) & 0x3F)));
|
||||
out.push_back(static_cast<char>(0x80 | (code_point & 0x3F)));
|
||||
} else {
|
||||
// Code point out of Unicode range -> insert UTF-8 replacement character U+FFFD
|
||||
append_utf32_to_utf8(0xFFFD, out);
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string toUTF8(u32 cp) {
|
||||
std::string s;
|
||||
append_utf32_to_utf8(cp, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string toUTF8(const std::u32string& str) {
|
||||
std::string s;
|
||||
for (u32 ch : str) append_utf32_to_utf8(ch, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
// ----------------- //
|
||||
// STRINGS //
|
||||
// ----------------- //
|
||||
|
||||
inline const char* getControlCharName(u8 c) {
|
||||
static const char* names[32] = {
|
||||
"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
|
||||
"BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI",
|
||||
"DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
|
||||
"CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US"
|
||||
};
|
||||
if (c < 32) return names[c];
|
||||
if (c == 127) return "DEL";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct pos {
|
||||
isize byteoff;
|
||||
isize line;
|
||||
isize col;
|
||||
pos(isize _byteoff = 0, isize _line = 1, isize _col = 1)
|
||||
: byteoff(_byteoff), line(_line), col(_col) {
|
||||
}
|
||||
};
|
||||
|
||||
inline void hexdump_utf8(const char* data, isize length, pos at, std::ostream& ostr) {
|
||||
auto old_flags = ostr.flags();
|
||||
auto old_fill = ostr.fill();
|
||||
|
||||
isize i = 0;
|
||||
while (i < length) {
|
||||
u8 lead = u8(data[i]);
|
||||
isize m = seqlen(lead);
|
||||
|
||||
// Byte, Line, Col
|
||||
ostr << std::setfill('0') << std::hex << std::uppercase;
|
||||
ostr << "0x" << std::setw(8) << (at.byteoff + i);
|
||||
|
||||
ostr << std::setfill(' ') << std::dec;
|
||||
ostr << " (" << std::setw(3) << at.line << ", " << std::setw(3) << at.col << ") : ";
|
||||
|
||||
// 1. Invalid Lead Byte handling
|
||||
if (m == 0) {
|
||||
ostr << std::setfill('0') << std::hex << std::uppercase;
|
||||
ostr << "0x" << std::setw(2) << u32(lead);
|
||||
ostr << std::setw(15) << std::setfill(' ') << " "; // Pad to match normal spacing
|
||||
ostr << " : INVALID LEAD\n";
|
||||
if (lead == '\n') { at.line++; at.col = 1; } else { at.col++; }
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. Truncated Sequence handling (Not enough bytes left in buffer)
|
||||
if ((i + m) > length) {
|
||||
isize available = length - i;
|
||||
|
||||
// Print what we can see
|
||||
for (isize j = 0; j < available; ++j) {
|
||||
ostr << "0x" << std::setw(2) << std::setfill('0') << std::hex << int(u8(data[i + j])) << " ";
|
||||
}
|
||||
// Fill missing bytes with ??
|
||||
for (isize j = available; j < m; ++j) {
|
||||
ostr << "0x?? ";
|
||||
}
|
||||
|
||||
// Pad the rest of the column width
|
||||
isize spaces_to_print = 20 - (m * 5);
|
||||
if (spaces_to_print) ostr << std::setw(i32(spaces_to_print)) << std::setfill(' ') << " ";
|
||||
ostr << ": TRUNC SEQ\n";
|
||||
|
||||
// Update row tracking based on what we actually processed
|
||||
for (isize j = 0; j < available; ++j) {
|
||||
if (u8(data[i + j]) == '\n') { at.line++; at.col = 1; } else { at.col++; }
|
||||
}
|
||||
i += available;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. Fully Available Sequence Processing
|
||||
u32 codepoint = 0;
|
||||
bool valid = decodeArr(data + i, m, codepoint);
|
||||
|
||||
for (isize j = 0; j < m; ++j) {
|
||||
ostr << "0x" << std::setw(2) << std::setfill('0') << std::hex << int(u8(data[i + j])) << " ";
|
||||
}
|
||||
|
||||
isize spaces_to_print = 20 - (m * 5);
|
||||
if (spaces_to_print) ostr << std::setw(i32(spaces_to_print)) << std::setfill(' ') << " ";
|
||||
|
||||
if (valid) {
|
||||
ostr << ": U+" << std::setw(5) << std::setfill('0') << std::uppercase << std::hex << codepoint << " ";
|
||||
|
||||
// Check for control characters
|
||||
const char* ctrlName = getControlCharName(lead); // Multi-byte UTF-8 can't be ASCII control chars
|
||||
if (m == 1 && ctrlName != nullptr) {
|
||||
ostr << "(" << ctrlName << ")\n";
|
||||
} else {
|
||||
ostr.write(data + i, i64(m));
|
||||
ostr << "\n";
|
||||
}
|
||||
|
||||
// Track position updates
|
||||
if (m == 1 && lead == '\n') {
|
||||
at.line++;
|
||||
at.col = 1;
|
||||
} else {
|
||||
at.col++;
|
||||
}
|
||||
} else {
|
||||
ostr << ": INVALID SEQ\n";
|
||||
at.col++;
|
||||
}
|
||||
|
||||
i += m;
|
||||
}
|
||||
|
||||
ostr.flags(old_flags);
|
||||
ostr.fill(old_fill);
|
||||
at.byteoff += length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+119
-38
@@ -1,78 +1,159 @@
|
||||
#pragma once
|
||||
|
||||
#include <ckitty/memory/primitives.hpp>
|
||||
#include <string>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <locale>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace ckitty {
|
||||
|
||||
namespace strings {
|
||||
|
||||
// trim spaces
|
||||
// --- In-place Trimming ---
|
||||
|
||||
/**
|
||||
* @brief Trims leading whitespace in-place.
|
||||
*/
|
||||
inline void ltrim(std::string& s) {
|
||||
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
|
||||
[](unsigned char ch) {
|
||||
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
|
||||
return !std::isspace(ch);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Trims trailing whitespace in-place.
|
||||
*/
|
||||
inline void rtrim(std::string& s) {
|
||||
s.erase(std::find_if(s.rbegin(), s.rend(),
|
||||
[](unsigned char ch) {
|
||||
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
|
||||
return !std::isspace(ch);
|
||||
}).base(), s.end());
|
||||
}
|
||||
|
||||
inline void trim(std::string& str) {
|
||||
ltrim(str);
|
||||
rtrim(str);
|
||||
/**
|
||||
* @brief Trims leading and trailing whitespace in-place without double shifting.
|
||||
*/
|
||||
inline void trim(std::string& s) {
|
||||
auto first = std::find_if(s.begin(), s.end(), [](unsigned char ch) {
|
||||
return !std::isspace(ch);
|
||||
});
|
||||
if (first == s.end()) {
|
||||
s.clear();
|
||||
return;
|
||||
}
|
||||
auto last = std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
|
||||
return !std::isspace(ch);
|
||||
}).base();
|
||||
|
||||
s.assign(first, last);
|
||||
}
|
||||
|
||||
inline std::string trimc(const std::string& str) {
|
||||
std::string copy = str;
|
||||
trim(copy);
|
||||
return copy;
|
||||
// --- Non-mutating Trimming (Returns std::string_view / std::string) ---
|
||||
|
||||
/**
|
||||
* @brief Returns a string_view with leading and trailing whitespace removed (Zero Allocations).
|
||||
*/
|
||||
[[nodiscard]] constexpr inline std::string_view trim_view(std::string_view sv) noexcept {
|
||||
const auto start = sv.find_first_not_of(" \t\n\r\f\v");
|
||||
if (start == std::string_view::npos) return {};
|
||||
const auto end = sv.find_last_not_of(" \t\n\r\f\v");
|
||||
return sv.substr(start, end - start + 1);
|
||||
}
|
||||
|
||||
inline std::string ltrimc(const std::string& str) {
|
||||
std::string copy = str;
|
||||
ltrim(copy);
|
||||
return copy;
|
||||
[[nodiscard]] inline std::string trimmed(std::string_view sv) {
|
||||
return std::string(trim_view(sv));
|
||||
}
|
||||
|
||||
inline std::string rtrimc(const std::string& str) {
|
||||
std::string copy = str;
|
||||
rtrim(copy);
|
||||
return copy;
|
||||
[[nodiscard]] inline std::string ltrimmed(std::string_view sv) {
|
||||
const auto start = sv.find_first_not_of(" \t\n\r\f\v");
|
||||
return (start == std::string_view::npos) ? "" : std::string(sv.substr(start));
|
||||
}
|
||||
|
||||
// ascii upper & lower
|
||||
|
||||
inline void upper(std::string& str) {
|
||||
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
|
||||
[[nodiscard]] inline std::string rtrimmed(std::string_view sv) {
|
||||
const auto end = sv.find_last_not_of(" \t\n\r\f\v");
|
||||
return (end == std::string_view::npos) ? "" : std::string(sv.substr(0, end + 1));
|
||||
}
|
||||
|
||||
inline void lower(std::string& str) {
|
||||
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
|
||||
// --- Case Conversion ---
|
||||
|
||||
/**
|
||||
* @brief Converts string to uppercase in-place (ASCII safe).
|
||||
*/
|
||||
inline void upper(std::string& str) noexcept {
|
||||
std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) {
|
||||
return static_cast<char>(std::toupper(c));
|
||||
});
|
||||
}
|
||||
|
||||
inline std::string copy_upper(const std::string& str) {
|
||||
std::string copy = str;
|
||||
upper(copy);
|
||||
return copy;
|
||||
/**
|
||||
* @brief Converts string to lowercase in-place (ASCII safe).
|
||||
*/
|
||||
inline void lower(std::string& str) noexcept {
|
||||
std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) {
|
||||
return static_cast<char>(std::tolower(c));
|
||||
});
|
||||
}
|
||||
|
||||
inline std::string copy_lower(const std::string& str) {
|
||||
std::string copy = str;
|
||||
lower(copy);
|
||||
return copy;
|
||||
[[nodiscard]] inline std::string to_upper(std::string_view sv) {
|
||||
std::string result(sv);
|
||||
upper(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
[[nodiscard]] inline std::string to_lower(std::string_view sv) {
|
||||
std::string result(sv);
|
||||
lower(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- Common Utilities ---
|
||||
|
||||
[[nodiscard]] constexpr inline bool starts_with(std::string_view sv, std::string_view prefix) noexcept {
|
||||
return sv.length() >= prefix.length() && sv.substr(0, prefix.length()) == prefix;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr inline bool ends_with(std::string_view sv, std::string_view suffix) noexcept {
|
||||
return sv.length() >= suffix.length() && sv.substr(sv.length() - suffix.length()) == suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Splits a string by a delimiter into a list of string_views.
|
||||
*/
|
||||
[[nodiscard]] inline std::vector<std::string_view> split(std::string_view sv, char delimiter) {
|
||||
std::vector<std::string_view> tokens;
|
||||
std::size_t start = 0;
|
||||
std::size_t end = sv.find(delimiter);
|
||||
|
||||
while (end != std::string_view::npos) {
|
||||
tokens.push_back(sv.substr(start, end - start));
|
||||
start = end + 1;
|
||||
end = sv.find(delimiter, start);
|
||||
}
|
||||
tokens.push_back(sv.substr(start));
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Joins a container of string-like objects into a single string.
|
||||
*/
|
||||
template<typename Container>
|
||||
[[nodiscard]] inline std::string join(const Container& container, std::string_view delimiter) {
|
||||
std::string result;
|
||||
auto it = container.begin();
|
||||
if (it != container.end()) {
|
||||
result += *it;
|
||||
++it;
|
||||
}
|
||||
for (; it != container.end(); ++it) {
|
||||
result += delimiter;
|
||||
result += *it;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace strings
|
||||
|
||||
} // namespace ckitty
|
||||
@@ -1,42 +1,122 @@
|
||||
#pragma once
|
||||
|
||||
#include <ckitty/memory/primitives.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <cstddef>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace ckitty {
|
||||
|
||||
namespace chrono {
|
||||
|
||||
using namespace std::chrono;
|
||||
namespace detail {
|
||||
/**
|
||||
* @brief Thread-safe cross-platform wrapper for localtime.
|
||||
*/
|
||||
inline bool safe_localtime(const std::time_t& time, std::tm& result) noexcept {
|
||||
#if defined(_WIN32)
|
||||
return localtime_s(&result, &time) == 0;
|
||||
#else
|
||||
return localtime_r(&time, &result) != nullptr;
|
||||
#endif
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
inline u64 millis() {
|
||||
auto now = high_resolution_clock::now().time_since_epoch();
|
||||
return duration_cast<milliseconds>(now).count();
|
||||
// --- Monotonic & System Timestamps ---
|
||||
|
||||
/**
|
||||
* @brief Returns system wall-clock epoch time in milliseconds.
|
||||
*/
|
||||
[[nodiscard]] inline u64 millis() noexcept {
|
||||
const auto now = std::chrono::system_clock::now().time_since_epoch();
|
||||
return static_cast<u64>(std::chrono::duration_cast<std::chrono::milliseconds>(now).count());
|
||||
}
|
||||
|
||||
inline u64 micros() {
|
||||
auto now = high_resolution_clock::now().time_since_epoch();
|
||||
return duration_cast<microseconds>(now).count();
|
||||
/**
|
||||
* @brief Returns system wall-clock epoch time in microseconds.
|
||||
*/
|
||||
[[nodiscard]] inline u64 micros() noexcept {
|
||||
const auto now = std::chrono::system_clock::now().time_since_epoch();
|
||||
return static_cast<u64>(std::chrono::duration_cast<std::chrono::microseconds>(now).count());
|
||||
}
|
||||
|
||||
// Date Time String // Could be done better but this is neat too
|
||||
/**
|
||||
* @brief Returns monotonic time in nanoseconds (ideal for benchmarks).
|
||||
*/
|
||||
[[nodiscard]] inline u64 steady_nanos() noexcept {
|
||||
const auto now = std::chrono::steady_clock::now().time_since_epoch();
|
||||
return static_cast<u64>(std::chrono::duration_cast<std::chrono::nanoseconds>(now).count());
|
||||
}
|
||||
|
||||
inline std::string dateTimeString(const std::string& format, const std::tm time) {
|
||||
// --- Date & Time Formatting ---
|
||||
|
||||
/**
|
||||
* @brief Formats a std::tm structure according to a format string.
|
||||
*/
|
||||
[[nodiscard]] inline std::string date_time_string(std::string_view format, const std::tm& time) {
|
||||
std::ostringstream oss;
|
||||
oss << std::put_time(&time, format.c_str());
|
||||
oss << std::put_time(&time, std::string(format).c_str());
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
inline std::string dateTimeString(const std::string& format = "%d-%m-%Y %H-%M-%S") {
|
||||
/**
|
||||
* @brief Formats current local time according to a strftime format string.
|
||||
*/
|
||||
[[nodiscard]] inline std::string date_time_string(std::string_view format = "%Y-%m-%d %H:%M:%S") {
|
||||
const std::time_t t = std::time(nullptr);
|
||||
std::tm tm = *std::localtime(&t);
|
||||
return dateTimeString(format, tm);
|
||||
std::tm tm_buf{};
|
||||
if (!detail::safe_localtime(t, tm_buf)) {
|
||||
return "";
|
||||
}
|
||||
return date_time_string(format, tm_buf);
|
||||
}
|
||||
|
||||
// Backward-compatible camelCase alias
|
||||
[[nodiscard]] inline std::string dateTimeString(std::string_view format = "%Y-%m-%d %H:%M:%S") {
|
||||
return date_time_string(format);
|
||||
}
|
||||
|
||||
// --- Stopwatch Utility ---
|
||||
|
||||
/**
|
||||
* @brief High-precision monotonic timer for benchmarking and measuring elapsed time.
|
||||
*/
|
||||
class Stopwatch {
|
||||
private:
|
||||
std::chrono::steady_clock::time_point _start{std::chrono::steady_clock::now()};
|
||||
|
||||
public:
|
||||
constexpr Stopwatch() noexcept = default;
|
||||
|
||||
/// Resets the starting time point to now.
|
||||
void reset() noexcept {
|
||||
_start = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
/// Returns elapsed time in milliseconds.
|
||||
[[nodiscard]] double elapsed_ms() const noexcept {
|
||||
const auto end = std::chrono::steady_clock::now();
|
||||
return std::chrono::duration<double, std::milli>(end - _start).count();
|
||||
}
|
||||
|
||||
/// Returns elapsed time in microseconds.
|
||||
[[nodiscard]] double elapsed_us() const noexcept {
|
||||
const auto end = std::chrono::steady_clock::now();
|
||||
return std::chrono::duration<double, std::micro>(end - _start).count();
|
||||
}
|
||||
|
||||
/// Returns elapsed time in seconds.
|
||||
[[nodiscard]] double elapsed_sec() const noexcept {
|
||||
const auto end = std::chrono::steady_clock::now();
|
||||
return std::chrono::duration<double>(end - _start).count();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace chrono
|
||||
|
||||
} // namespace ckitty
|
||||
@@ -2,67 +2,161 @@
|
||||
|
||||
#include <ckitty/memory/primitives.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
|
||||
// --- OS & Platform Preprocessor Detection ---
|
||||
|
||||
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
|
||||
#define CKITTY_OS_WINDOWS 1
|
||||
#define CKITTY_DEVICE_DESKTOP 1
|
||||
#elif defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
|
||||
#define CKITTY_OS_IOS 1
|
||||
#define CKITTY_DEVICE_MOBILE 1
|
||||
#elif defined(TARGET_OS_MAC) && TARGET_OS_MAC
|
||||
#define CKITTY_OS_MACOS 1
|
||||
#define CKITTY_DEVICE_DESKTOP 1
|
||||
#else
|
||||
#error "Unknown Apple platform"
|
||||
#endif
|
||||
#elif defined(__ANDROID__)
|
||||
#define CKITTY_OS_ANDROID 1
|
||||
#define CKITTY_DEVICE_MOBILE 1
|
||||
#elif defined(__linux__) || defined(__unix__)
|
||||
#define CKITTY_OS_LINUX 1
|
||||
#define CKITTY_DEVICE_DESKTOP 1
|
||||
#elif defined(__EMSCRIPTEN__) || defined(__wasm__) || defined(__wasm)
|
||||
#define CKITTY_OS_WASM 1
|
||||
#define CKITTY_DEVICE_BROWSER 1
|
||||
#else
|
||||
#error "Unknown operating system or target environment."
|
||||
#endif
|
||||
|
||||
// --- CPU Architecture Detection ---
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
#define CKITTY_ARCH_X86_64 1
|
||||
#elif defined(__i386__) || defined(_M_IX86)
|
||||
#define CKITTY_ARCH_X86 1
|
||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
||||
#define CKITTY_ARCH_ARM64 1
|
||||
#elif defined(__arm__) || defined(_M_ARM)
|
||||
#define CKITTY_ARCH_ARM 1
|
||||
#elif defined(__wasm32__) || defined(__wasm64__)
|
||||
#define CKITTY_ARCH_WASM 1
|
||||
#else
|
||||
#define CKITTY_ARCH_UNKNOWN 1
|
||||
#endif
|
||||
|
||||
// --- Build Mode Detection ---
|
||||
|
||||
#if defined(NDEBUG) && !defined(_DEBUG)
|
||||
#define CKITTY_BUILD_RELEASE 1
|
||||
#else
|
||||
#define CKITTY_BUILD_DEBUG 1
|
||||
#endif
|
||||
|
||||
namespace ckitty {
|
||||
|
||||
namespace environment {
|
||||
|
||||
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
|
||||
//define something for Windows (32-bit and 64-bit, this part is common)
|
||||
// Do not bother to differentiate 32 bits from 64 bits.
|
||||
inline const std::string OS_NAME = "Windows";
|
||||
inline const std::string DEVICE = "Desktop";
|
||||
inline const std::string CODE = "desktop/windows";
|
||||
#define CKITTY_OS_WINDOWS
|
||||
#define CKITTY_DEVICE_DESKTOP
|
||||
#elif __APPLE__
|
||||
#include <TargetConditionals.h>
|
||||
// iOS, tvOS, or watchOS Simulator
|
||||
// #elif TARGET_OS_MACCATALYST
|
||||
// Mac's Catalyst (ports iOS API into Mac, like UIKit).
|
||||
#if defined(TARGET_IPHONE_SIMULATOR) || defined(TARGET_OS_IPHONE)
|
||||
// iOS, tvOS, or watchOS device
|
||||
inline const std::string OS_NAME = "iOS";
|
||||
inline const std::string DEVICE = "Mobile";
|
||||
inline const std::string CODE = "mobile/ios";
|
||||
#define CKITTY_OS_IOS
|
||||
#define CKITTY_DEVICE_MOBILE
|
||||
#elif TARGET_OS_MAC
|
||||
// Other kinds of Apple platforms
|
||||
inline const std::string OS_NAME = "MacOS";
|
||||
inline const std::string DEVICE = "Desktop";
|
||||
inline const std::string CODE = "desktop/macos";
|
||||
#define CKITTY_OS_MACOS
|
||||
#define CKITTY_DEVICE_DESKTOP
|
||||
#else
|
||||
# error "Unknown Apple platform"
|
||||
#endif
|
||||
#elif __ANDROID__
|
||||
// Below __linux__ check should be enough to handle Android,
|
||||
// but something may be unique to Android.
|
||||
// all unices not caught above
|
||||
inline const std::string OS_NAME = "Android";
|
||||
inline const std::string DEVICE = "Mobile";
|
||||
inline const std::string CODE = "mobile/android";
|
||||
#define CKITTY_OS_ANDROID
|
||||
#define CKITTY_DEVICE_MOBILE
|
||||
#elif defined(__linux__) || defined(__unix__)
|
||||
// linux / unix
|
||||
inline const std::string OS_NAME = "Linux";
|
||||
inline const std::string DEVICE = "Desktop";
|
||||
inline const std::string CODE = "desktop/linux";
|
||||
#define CKITTY_OS_LINUX
|
||||
#define CKITTY_DEVICE_DESKTOP
|
||||
#elif defined(__EMSCRIPTEN__) || defined(__wasm__) || defined(__wasm)
|
||||
// WASM
|
||||
inline const std::string OS_NAME = "WASM";
|
||||
inline const std::string DEVICE = "Browser";
|
||||
inline const std::string CODE = "browser/wasm";
|
||||
#define CKITTY_OS_WASM
|
||||
#define CKITTY_DEVICE_BROWSER
|
||||
#else
|
||||
# error "Unknown compiler/system."
|
||||
// --- OS String Constants ---
|
||||
|
||||
#if defined(CKITTY_OS_WINDOWS)
|
||||
constexpr std::string_view OS_NAME = "Windows";
|
||||
constexpr std::string_view DEVICE = "Desktop";
|
||||
constexpr std::string_view CODE = "desktop/windows";
|
||||
#elif defined(CKITTY_OS_IOS)
|
||||
constexpr std::string_view OS_NAME = "iOS";
|
||||
constexpr std::string_view DEVICE = "Mobile";
|
||||
constexpr std::string_view CODE = "mobile/ios";
|
||||
#elif defined(CKITTY_OS_MACOS)
|
||||
constexpr std::string_view OS_NAME = "MacOS";
|
||||
constexpr std::string_view DEVICE = "Desktop";
|
||||
constexpr std::string_view CODE = "desktop/macos";
|
||||
#elif defined(CKITTY_OS_ANDROID)
|
||||
constexpr std::string_view OS_NAME = "Android";
|
||||
constexpr std::string_view DEVICE = "Mobile";
|
||||
constexpr std::string_view CODE = "mobile/android";
|
||||
#elif defined(CKITTY_OS_LINUX)
|
||||
constexpr std::string_view OS_NAME = "Linux";
|
||||
constexpr std::string_view DEVICE = "Desktop";
|
||||
constexpr std::string_view CODE = "desktop/linux";
|
||||
#elif defined(CKITTY_OS_WASM)
|
||||
constexpr std::string_view OS_NAME = "WASM";
|
||||
constexpr std::string_view DEVICE = "Browser";
|
||||
constexpr std::string_view CODE = "browser/wasm";
|
||||
#endif
|
||||
|
||||
}
|
||||
// --- Compile-Time Boolean Flags (C++17 if constexpr ready) ---
|
||||
|
||||
}
|
||||
#if defined(CKITTY_OS_WINDOWS)
|
||||
constexpr bool is_windows = true;
|
||||
#else
|
||||
constexpr bool is_windows = false;
|
||||
#endif
|
||||
|
||||
#if defined(CKITTY_OS_MACOS)
|
||||
constexpr bool is_macos = true;
|
||||
#else
|
||||
constexpr bool is_macos = false;
|
||||
#endif
|
||||
|
||||
#if defined(CKITTY_OS_IOS)
|
||||
constexpr bool is_ios = true;
|
||||
#else
|
||||
constexpr bool is_ios = false;
|
||||
#endif
|
||||
|
||||
#if defined(CKITTY_OS_LINUX)
|
||||
constexpr bool is_linux = true;
|
||||
#else
|
||||
constexpr bool is_linux = false;
|
||||
#endif
|
||||
|
||||
#if defined(CKITTY_OS_ANDROID)
|
||||
constexpr bool is_android = true;
|
||||
#else
|
||||
constexpr bool is_android = false;
|
||||
#endif
|
||||
|
||||
#if defined(CKITTY_OS_WASM)
|
||||
constexpr bool is_wasm = true;
|
||||
#else
|
||||
constexpr bool is_wasm = false;
|
||||
#endif
|
||||
|
||||
// Device Type Flags
|
||||
constexpr bool is_desktop = (is_windows || is_macos || is_linux);
|
||||
constexpr bool is_mobile = (is_ios || is_android);
|
||||
constexpr bool is_browser = is_wasm;
|
||||
|
||||
// Architecture Flags
|
||||
#if defined(CKITTY_ARCH_X86_64)
|
||||
constexpr bool is_x86_64 = true;
|
||||
constexpr std::string_view ARCH_NAME = "x86_64";
|
||||
#else
|
||||
constexpr bool is_x86_64 = false;
|
||||
#endif
|
||||
|
||||
#if defined(CKITTY_ARCH_ARM64)
|
||||
constexpr bool is_arm64 = true;
|
||||
constexpr std::string_view ARCH_NAME = "arm64";
|
||||
#else
|
||||
constexpr bool is_arm64 = false;
|
||||
#endif
|
||||
|
||||
// Build Mode Flags
|
||||
#if defined(CKITTY_BUILD_DEBUG)
|
||||
constexpr bool is_debug = true;
|
||||
constexpr bool is_release = false;
|
||||
#else
|
||||
constexpr bool is_debug = false;
|
||||
constexpr bool is_release = true;
|
||||
#endif
|
||||
|
||||
} // namespace environment
|
||||
|
||||
} // namespace ckitty
|
||||
+133
-41
@@ -1,62 +1,154 @@
|
||||
#pragma once
|
||||
|
||||
#include <ckitty/memory/primitives.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
#include <source_location>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace ckitty {
|
||||
|
||||
struct error {
|
||||
// --- Single Stack Frame ---
|
||||
|
||||
i32 code;
|
||||
struct error_frame {
|
||||
i32 code{0};
|
||||
std::string message;
|
||||
std::source_location location;
|
||||
|
||||
error(i32 code = 0, const std::string& msg = "")
|
||||
: code(code), message(msg) {
|
||||
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);
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return code;
|
||||
[[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;
|
||||
}
|
||||
|
||||
operator std::string() const {
|
||||
[[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 << "[" << std::hex << std::showbase << std::uppercase << std::setw(2) << std::setfill('0') << code << "] ";
|
||||
oss << (message.empty() ? "(no info)" : message);
|
||||
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();
|
||||
}
|
||||
|
||||
error& operator+=(const error& err) {
|
||||
auto err1 = (*this) + err;
|
||||
code = err1.code;
|
||||
message = err1.message;
|
||||
return *this;
|
||||
}
|
||||
|
||||
error operator+(const error& err) const {
|
||||
// If this error has no code (no error), return the other error
|
||||
if (code == 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// If the other error has no code, return this error
|
||||
if (err.code == 0) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Both errors have codes, combine them
|
||||
error result = *this;
|
||||
|
||||
// Combine messages
|
||||
if (!result.message.empty()) {
|
||||
result.message += "\n ";
|
||||
}
|
||||
|
||||
// add next error to the message
|
||||
result.message += err.operator std::string();
|
||||
return result;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user