audited all the library using gemini, openssl support is still pending

This commit is contained in:
2026-08-07 20:08:28 -06:00
parent 32c21781d9
commit d0b17d48e7
13 changed files with 1631 additions and 456 deletions
+89 -68
View File
@@ -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
+114 -24
View File
@@ -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
View File
@@ -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
View File
@@ -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
+113 -11
View File
@@ -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
+38 -21
View File
@@ -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
+145
View File
@@ -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
+135
View File
@@ -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
+266
View File
@@ -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;
}
}
}
+122 -41
View File
@@ -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 ---
inline void ltrim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
[](unsigned char ch) {
/**
* @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) {
return !std::isspace(ch);
}));
}
inline void rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(),
[](unsigned char 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) {
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
+96 -16
View File
@@ -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
+151 -57
View File
@@ -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
+134 -42
View File
@@ -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