diff --git a/ckittylib.code-workspace b/ckittylib.code-workspace new file mode 100644 index 0000000..61e510d --- /dev/null +++ b/ckittylib.code-workspace @@ -0,0 +1,9 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + } +} \ No newline at end of file diff --git a/src/ckitty/crypto/UUID.hpp b/src/ckitty/crypto/UUID.hpp new file mode 100644 index 0000000..0dbac9a --- /dev/null +++ b/src/ckitty/crypto/UUID.hpp @@ -0,0 +1,161 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace ckitty { + + /** + * @brief An UUID v4. + */ + class UUID { + private: + + u8 bytes[16]; + + public: + + UUID() { + std::fill(bytes, bytes + sizeof(bytes), 0); + } + + UUID(const UUID& uuid) { + for (u32 i = 0; i < 16; i++) { + bytes[i] = uuid.bytes[i]; + } + } + + i32 compare(const UUID& uuid) const { + for (u32 i = 0; i < 16; i++) { + u32 j = 15 - i; + i32 r = i32(bytes[j]) - i32(uuid.bytes[j]); + if (r < 0) return -1; + if (r > 0) return 1; + } + return 0; + } + + bool operator==(const UUID& uuid) const { + return compare(uuid) == 0; + } + + bool operator!=(const UUID& uuid) const { + return compare(uuid) != 0; + } + + bool operator<(const UUID& uuid) const { + return compare(uuid) < 0; + } + + bool operator<=(const UUID& uuid) const { + return compare(uuid) <= 0; + } + + bool operator>(const UUID& uuid) const { + return compare(uuid) > 0; + } + + bool operator>=(const UUID& uuid) const { + return compare(uuid) >= 0; + } + + public: + + std::string toString() const { + // %s%s-%s-%s-%s-%s%s%s + // convert to UUID format + char chars[] = { + '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'A', 'B', + 'C', 'D', 'E', 'F' + }; + std::string out = "00000000-0000-0000-0000-000000000000"; + + // 8 starting chars + u32 i = 0, j = 0; + while (j < 16) { + // parse that byte into 2 hex chars + for (u32 k = 0; k < 2; k++) { + out[i++] = chars[(bytes[j] >> 4) & 0xF]; + out[i++] = chars[(bytes[j] >> 0) & 0xF]; + j++; + } + + // move by one? + if (i < out.size() && out[i] == '-') i++; + } + + // send string + return out; + } + + public: + + static UUID create() { + // get random bytes + UUID uuid; + openssl::randomBytes(uuid.bytes, sizeof(uuid.bytes)); + + // Set version to 0100 + uuid.bytes[6] = (uuid.bytes[6] & 0x0F) | 0x40; + + // Set bits 6-7 to 10 + uuid.bytes[8] = (uuid.bytes[8] & 0x3F) | 0x80; + return uuid; + } + + /** + * @brief Creates an empty UUID. + */ + static UUID empty() { + UUID id = {}; + for(int i = 0; i < 16; i++) id.bytes[i] = 0; + return id; + } + + /** + * @brief Attempts to parse an UUID. + */ + static UUID fromString(const std::string& str) { + // must be 36 characters long + if(str.length() != 36) throw std::runtime_error(""); + + // 00000000-0000-0000-0000-000000000000 + std::array hyphens = {8, 13, 18, 23}; + UUID id; + size j = 0; + + // for loop the chars + for(size i = 0; i < 36; i += 2) { + // check hypen + if(str[i] == '-') { + if(!it::contains(hyphens, i)) + throw std::runtime_error("Error: Hyphen at unexpected position " + std::to_string(i) + "."); + i++; + } + + // parse... + u8 write = 0; + for(size n = 0; n < 2; n++) { + int hex = strings::hexChar(str[i + n]) << ((1 - n) * 4); + if (hex == -1) throw std::runtime_error("Error: Unexpected character \"" + strings::display(str[i + n]) + "\" position " + std::to_string(i + n) + "."); + write |= hex & 0xFF; + } + + // write a byte + id.bytes[j++] = write; + } + + // return the id now! + return id; + } + + }; + +} \ No newline at end of file diff --git a/src/ckitty/crypto/base64.hpp b/src/ckitty/crypto/base64.hpp new file mode 100644 index 0000000..466e0e9 --- /dev/null +++ b/src/ckitty/crypto/base64.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +namespace ckitty { + + namespace base64 { + + inline std::string encode(const u8* data, const size length) { + u8 buffer[length * 4 / 3 + 2]; + size strlen = EVP_EncodeBlock(buffer, data, length); + return std::string((const char*) buffer, strlen); + } + + inline buffer decode(const std::string& str) { + u8 buff[str.length() * 3 / 4 + 3]; + int n = EVP_DecodeBlock(buff, (const u8*) str.c_str(), str.length()); + if(n == -1) throw std::runtime_error("Invalid base 64 string."); + return buffer(buff, buff + n); + } + + // TODO: dedicated encoder & decoder have limited application right now. + + /** + * @brief Object to encode base64 data in chunks. + */ + class Encoder { + public: + + Encoder() {} + + }; + + /** + * @brief Object to decode base64 data in chunks. + */ + class Decoder { + public: + + Decoder() {} + + }; + + } + +} \ No newline at end of file diff --git a/src/ckitty/crypto/hash.hpp b/src/ckitty/crypto/hash.hpp new file mode 100644 index 0000000..fad3dae --- /dev/null +++ b/src/ckitty/crypto/hash.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include + +namespace ckitty { + + namespace hash { + + /** + * @brief SHA-1 implementation by OpenSSL + */ + class sha1 { + public: + + static buffer digestBuffer(data_view view) { + buffer buff(SHA_DIGEST_LENGTH); + SHA1(view.begin(), view.length(), buff.begin()); + return buff; + } + + public: + + sha1() {} + + }; + + /** + * @brief SHA-256 implementation by OpenSSL + */ + class sha256 { + public: + + static buffer digestBuffer(data_view view) { + buffer buff(SHA_DIGEST_LENGTH); + SHA256(view.begin(), view.length(), buff.begin()); + return buff; + } + + public: + + sha256() {} + + }; + + } + +} \ No newline at end of file diff --git a/src/ckitty/crypto/openssl.hpp b/src/ckitty/crypto/openssl.hpp new file mode 100644 index 0000000..46680ba --- /dev/null +++ b/src/ckitty/crypto/openssl.hpp @@ -0,0 +1,39 @@ +#pragma once + +#define CKITTY_OPENSSL +#define OPENSSL_NO_DEPRECATED + +#include +#include +#include +#include +#include + +namespace ckitty { + + namespace openssl { + + // NOTE: THIS SECTION REQUIRES AN ALREADY INITIALIZED OPENSSL LIBRARY! + + inline std::string getError() { + char str[2048]; + auto err = ERR_get_error(); + ERR_error_string_n(err, str, sizeof(str)); + return std::string(str); + } + + /** + * Generates random bytes. + * Use public (private = false) if the bytes are general purpose. + * Use private if the bytes are meant to be secure, i.e. UUID. + */ + inline void randomBytes(u8* bytes, size length, bool usePrivate = false) { + int r; + if (usePrivate) r = RAND_priv_bytes(bytes, length); + else r = RAND_bytes(bytes, length); + if (r != 1) throw std::runtime_error("Error: Could not create UUID: " + getError()); + } + + } + +} diff --git a/src/ckitty/memory/array.hpp b/src/ckitty/memory/array.hpp new file mode 100644 index 0000000..88e947d --- /dev/null +++ b/src/ckitty/memory/array.hpp @@ -0,0 +1,108 @@ +#pragma once + +#include + +namespace ckitty { + + /** + * @brief Represents a dynamically created, + * fixed size container. Useful to move data + * around, when the data itself has a fixed size. + * + * 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. + */ + template + class array { + protected: + + ptr _data; + size _length; + + public: + + /** + * Inherits a pointer, and takes ownership of it. + */ + static array inherit(T* data, size length) { + array arr = array(); + arr._data = ptr(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 borrow(T* data, size length) { + array arr = array(); + arr._data = ptr(data, []([[maybe_unused]] T* _ptr) {}); + arr._length = length; + return arr; + } + + public: + + array(const size length = 0) + : _data(new T[length]), _length(length) {} + + array(const T* begin, const T* end) + : _data(new T[end - begin]), _length(end - begin) { + std::copy(begin, end, this->begin()); + } + + array(const array& copy) : _data(copy._data), _length(copy._length) {} + + array(array&& move) : _data(std::move(move._data)), _length(move._length) { + // move._data = nullptr + move._length = 0; + } + + array& operator=(const array& copy) { + this->_data = copy._data; + this->_length = copy._length; + return *this; + } + + array& operator=(array&& move) { + this->_data = std::move(move._data); + this->_length = move._length; + // move._data = nullptr + move._length = 0; + return *this; + } + + T& operator[](const size i) { + return *(_data.get() + i); + } + + const T& operator[](const size i) const { + return *(_data.get() + i); + } + + size length() const { + return _length; + } + + T* begin() { + return _data.get(); + } + + T* end() { + return _data.get() + _length; + } + + const T* begin() const { + return _data.get(); + } + + const T* end() const { + return _data.get() + _length; + } + + }; + +} \ No newline at end of file diff --git a/src/ckitty/memory/bits.hpp b/src/ckitty/memory/bits.hpp new file mode 100644 index 0000000..5838970 --- /dev/null +++ b/src/ckitty/memory/bits.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#include + +namespace ckitty { + + namespace bits { + + /** + * Unpacks a value into bytes. + */ + template + inline ckitty::array unpack(const T value, bool endianness) { + const size bytelen = sizeof(T); + ckitty::array bytes(bytelen); + for(size i = 0; i < bytelen; i++) { + size j = endianness ? bytelen - i - 1 : i; + bytes[i] = (value >> (j * 8)) & 0xFF; + } + return bytes; + } + + /** + * Packs bytes into a value. + */ + template + 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; + } + + template + inline bool read(const T value, size bitno) { + return (value >> bitno) & 1; + } + + template + inline T write(const T value, size bitno, bool bitval) { + size mask = T(1) << bitno; + return bitval ? (value | mask) : (value & ~mask); + } + + template + inline void write_r(T& value, size bitno, bool bitval) { + value = write(value, bitno, bitval); + } + + } + +} diff --git a/src/ckitty/memory/buffers.hpp b/src/ckitty/memory/buffers.hpp new file mode 100644 index 0000000..69a2ed3 --- /dev/null +++ b/src/ckitty/memory/buffers.hpp @@ -0,0 +1,221 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace ckitty { + + /** + * Define a buffer as an array of u8. + */ + using buffer = array; + + namespace buffers { + + /** + * @brief The sole purpose of this function + * is to stop me from confusing the signs. + */ + inline bool fits(size index, size length) { + return index < length; + } + + /** + * @brief Returns true if some data fits in the + * provided space. + */ + inline bool fits(size offset, size length, size test) { + return (offset < length) && ((length - offset) >= test); + } + + inline data_view view(const buffer& buff) { + return data_view(buff.begin(), buff.end()); + } + + /** + * @brief Simple match agains data view. + */ + inline bool matches(data_view d1, data_view d2) { + if(d1.length() != d2.length()) return false; + for(size i = 0; i < d1.length(); i++) { + if(d1[i] != d2[i]) return false; + } + return true; + } + + /** + * @brief Matches if the data starts with a prefix + */ + inline bool startsWith(data_view d1, data_view pre) { + if(d1.length() < pre.length()) return false; + for(size i = 0; i < pre.length(); i++) { + if(d1[i] != pre[i]) return false; + } + return true; + } + + inline bool find(data_view data, data_view test, size& index) { + // loop until we find match + for(size 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; + return true; + } + } + return false; + } + + /** + * @brief Checks if an index is in between + * min and max, inclusively. + */ + inline bool in_range(size min, size index, size max) { + return min <= index && index <= max; + } + + /** + * @brief Moves the buffer to a new + * one, and restores the size in the + * previous one. + */ + inline buffer moveRealocate(buffer& buff) { + size len = buff.length(); + buffer out = std::move(buff); + buff = buffer(len); + return out; + } + + inline data_view view(std::string& str) { + return data_view((const u8*) str.c_str(), str.length()); + } + + inline void copy(buffer& buff, size& index, const std::string& str) { + std::copy(str.begin(), str.end(), buff.begin() + index); + index += str.size(); + } + + inline void copy(buffer& buff, size& index, const buffer& data) { + std::copy(data.begin(), data.end(), buff.begin() + index); + index += data.length(); + } + + inline void hex(data_view view, std::ostream& os) { + char chars[] = { + '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'A', 'B', + 'C', 'D', 'E', 'F' + }; + + for(u8 c : view) { + os << chars[ (c >> 4) & 0xF ]; + os << chars[ c & 0xF ]; + } + } + + 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(1, view.length())); + double spaces = std::floor((long double) (logv / std::log(16) + 1)); + size rowchars = std::max(2, 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(); + return; + } + + // add prefix + os << prefix << "\n"; + + // loop through the bytes + size index = 0, row = 0; + while(index < view.length()) { + // ascii buffer + std::string ascii; + + // add header + os << prefix; + os << std::hex << std::setfill('0') << std::setw(rowchars) << row << std::dec << "X "; + + // 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; + + // compute vars + if(index < view.length()) { + // get the byte value + value = view[index++]; + high = (value >> 4) & 0xF; + low = (value >> 0) & 0xF; + + // log value + os << chars[high] << chars[low]; + } else { + os << " "; + value = ' '; + } + + // add to ascii? + char ch = value; + if(!std::isgraph(ch)) ch = ' '; + ascii += ch; + + // space condition + if(i < 7) { + os << ' '; + continue; + } + if(g == 0) { + os << " "; + ascii += " "; + continue; + } + } + // <- + } + + // add the "ASCII" version + os << " | " << ascii; + row++; + + // end of line + os << '\n'; + os.flush(); + } + + // undo manips + os << std::setfill(' '); + } + + inline buffer from(const std::string& str) { + return buffer((const u8*) str.begin().base(), (const u8*) str.end().base()); + } + + } + +} \ No newline at end of file diff --git a/src/ckitty/memory/data_view.hpp b/src/ckitty/memory/data_view.hpp new file mode 100644 index 0000000..80af3ca --- /dev/null +++ b/src/ckitty/memory/data_view.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include + +namespace ckitty { + + /** + * @brief Represents a piece of a shared buffer that + * is not owned by this object. + */ + class data_view { + private: + + const u8* _begin; + const u8* _end; + + public: + + data_view(const u8* begin, const u8* end) + : _begin(begin), _end(end) {} + + data_view(const u8* data, const size length) + : _begin(data), _end(data + length) {} + + data_view(const std::string& str) + : _begin((const u8*) str.begin().base()), + _end((const u8*) str.end().base()) {} + + const u8* begin() const { + return _begin; + } + + const u8* end() const { + return _end; + } + + u8 operator[](size i) const { + return *(_begin + i); + } + + size length() const { + return _end - _begin; + } + + const data_view sub_off(size offset, size length) const { + return data_view(_begin + offset, _begin + offset + length); + } + + const data_view sub_abs(size begin, size end) const { + return data_view(_begin + begin, _begin + end); + } + + }; + +} \ No newline at end of file diff --git a/src/ckitty/memory/iterators.hpp b/src/ckitty/memory/iterators.hpp new file mode 100644 index 0000000..bfd4a38 --- /dev/null +++ b/src/ckitty/memory/iterators.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +namespace ckitty { + + namespace it { + + template + bool contains(const T& cont, const V& value) { + return std::find(cont.begin(), cont.end(), value) != cont.end(); + } + + template + bool erase(T& cont, const V& value) { + auto it = std::find(cont.begin(), cont.end(), value); + if (it == cont.end()) return false; + cont.erase(it); + return true; + } + + template + bool map_erase(T& map, const V& value) { + auto it = map.find(value); + if (it == map.end()) return false; + map.erase(it); + return true; + } + + } + +} diff --git a/src/ckitty/memory/primitives.hpp b/src/ckitty/memory/primitives.hpp new file mode 100644 index 0000000..6090a98 --- /dev/null +++ b/src/ckitty/memory/primitives.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ckitty { + + typedef std::uint8_t u8; + typedef std::uint16_t u16; + typedef std::uint32_t u32; + typedef std::uint64_t u64; + + typedef std::int8_t i8; + typedef std::int16_t i16; + typedef std::int32_t i32; + typedef std::int64_t i64; + + typedef std::size_t size; + + typedef float f32; + typedef double f64; + + using std::vector; + using std::map; + using std::deque; + using std::optional; + + template using ptr = std::shared_ptr; + template using uptr = std::unique_ptr; + template using wptr = std::weak_ptr; + + template using ilist = std::initializer_list; + +} \ No newline at end of file diff --git a/src/ckitty/system/chrono.hpp b/src/ckitty/system/chrono.hpp new file mode 100644 index 0000000..890005c --- /dev/null +++ b/src/ckitty/system/chrono.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace ckitty { + + namespace chrono { + + using namespace std::chrono; + + inline u64 millis() { + auto now = high_resolution_clock::now().time_since_epoch(); + return duration_cast(now).count(); + } + + inline u64 micros() { + auto now = high_resolution_clock::now().time_since_epoch(); + return duration_cast(now).count(); + } + + // Date Time String // Could be done better but this is neat too + + inline std::string dateTimeString(const std::string& format, const std::tm time) { + std::ostringstream oss; + oss << std::put_time(&time, format.c_str()); + return oss.str(); + } + + inline std::string dateTimeString(const std::string& format = "%d-%m-%Y %H-%M-%S") { + const std::time_t t = std::time(nullptr); + std::tm tm = *std::localtime(&t); + return dateTimeString(format, tm); + } + + } + +} \ No newline at end of file diff --git a/src/ckitty/system/environment.hpp b/src/ckitty/system/environment.hpp new file mode 100644 index 0000000..bb4b9e4 --- /dev/null +++ b/src/ckitty/system/environment.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include + +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 + // 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." +#endif + + } + +} \ No newline at end of file diff --git a/src/ckitty/system/error.hpp b/src/ckitty/system/error.hpp new file mode 100644 index 0000000..3cc12d1 --- /dev/null +++ b/src/ckitty/system/error.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +namespace ckitty { + + struct error { + + i32 code; + std::string message; + + error(i32 code = 0, const std::string& msg = "") + : code(code), message(msg) { + } + + operator bool() const { + return code; + } + + operator std::string() const { + std::ostringstream oss; + oss << "[" << std::hex << std::showbase << std::uppercase << std::setw(2) << std::setfill('0') << code << "] "; + oss << (message.empty() ? "(no info)" : message); + 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; + } + + }; + +} \ No newline at end of file diff --git a/src/ckitty/system/strings.hpp b/src/ckitty/system/strings.hpp new file mode 100644 index 0000000..205c65f --- /dev/null +++ b/src/ckitty/system/strings.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace ckitty { + + namespace strings { + + // trim spaces + + 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) { + return !std::isspace(ch); + }).base(), s.end()); + } + + inline void trim(std::string& str) { + ltrim(str); + rtrim(str); + } + + inline std::string trimc(const std::string& str) { + std::string copy = str; + trim(copy); + return copy; + } + + inline std::string ltrimc(const std::string& str) { + std::string copy = str; + ltrim(copy); + return copy; + } + + inline std::string rtrimc(const std::string& str) { + std::string copy = str; + rtrim(copy); + return copy; + } + + /** + * Returns a displayable representation of the character. + */ + inline std::string display(char c) { + if(std::isprint(c)) return std::string(1, c); + + char chars[] = { + '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'A', 'B', + 'C', 'D', 'E', 'F' + }; + + std::string str = "0x00"; + str[2] = chars[ (c >> 4) & 0xF ]; + str[3] = chars[ c & 0xF ]; + return str; + } + + inline int hexChar(char c) { + if('0' <= c && c <= '9') return c - '0'; + if('a' <= c && c <= 'f') return c - 'a' + 10; + if('A' <= c && c <= 'F') return c - 'A' + 10; + return -1; + } + + // ascii upper & lower + + inline void upper(std::string& str) { + std::transform(str.begin(), str.end(), str.begin(), ::toupper); + } + + inline void lower(std::string& str) { + std::transform(str.begin(), str.end(), str.begin(), ::tolower); + } + + inline std::string copy_upper(const std::string& str) { + std::string copy = str; + upper(copy); + return copy; + } + + inline std::string copy_lower(const std::string& str) { + std::string copy = str; + lower(copy); + return copy; + } + + // ... + + } + +} \ No newline at end of file diff --git a/src/ckitty/system/unicode.hpp b/src/ckitty/system/unicode.hpp new file mode 100644 index 0000000..a11961a --- /dev/null +++ b/src/ckitty/system/unicode.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include +#include + +/* + * NOTICE: This is the standalone unicode library for ckittylib. + * If using ICU, use the /unicode folder's headers. +*/ + +namespace ckitty { + + namespace unicode { + + using sequences = std::string; + using string = std::u32string; + + /** + * Returns the amount of bytes needed to + * represent an UTF-8 character given + * the first byte of the sequence. + * + * Returns 0 for invalid sequences. + */ + inline u32 sequenceLength(const u8 head) { + return (head - 160 >> 20 - head / 16) + 2; + } + + /** + * Encodes a UTF-32 character into a UTF-8 sequence. + */ + inline sequences encodeChar(const u32 cha) {} + + /** + * Decodes a UTF-8 sequence (the first one) into a UTF-32 character. + * Invalid sequences return 0 and raise a flag. + */ + inline u32 decodeChar(const sequences& cha, bool* error = nullptr) {} + + inline sequences encodeChar(const u32 cha) {} + + struct decode_result { + // Was the result valid? + bool valid = false; + // First character error. + u64 errCharIndex = 0; + // Byte index of character. + u64 errByteIndex = 0; + // UTF32 result + string output; + }; + + /** + * Decodes all UTF-8 sequences into 32 bit chars. + * Invalid ones are read as one byte. + */ + inline decode_result decodeUTF8(const sequences& str) { + decode_result r; + return r; + } + + /** + * Encodes UTF32 into UTF-8 byte sequences. + */ + inline sequences encodeUTF8(const string& str) {} + + struct validate_result { + // Was the result valid? + bool valid = false; + // First character error. + u64 errCharIndex = 0; + // Byte index of character. + u64 errByteIndex = 0; + }; + + /** + * Validates that a std::string is made out of valid UTF8 sequences. + */ + inline validate_result validateUTF8(const sequences& str) { + validate_result r; + return r; + } + + } + +} \ No newline at end of file