// ===================================================================================================================== // fennec, a free and open source game engine // Copyright © 2025 Medusa Slockbower // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // ===================================================================================================================== #ifndef FENNEC_FPROC_STRINGS_STRING_H #define FENNEC_FPROC_STRINGS_STRING_H #include #include #include #include #include #include namespace fennec { // Forward def template> struct _string; // Alias for default allocator using string = _string<>; /// /// \brief Struct for wrapping c-style strings /// /// \details behaviour guarantees that the underlying string is null-terminated template struct _string { public: static constexpr size_t npos = -1; using alloc_t = allocation; // Constructors ======================================================================================================== /// /// \brief Default Constructor, initializes empty string constexpr _string() : _str() { } /// /// \brief Sized Constructor, initializes a null-terminated string of size `n` with null characters /// \param n the number of characters /// /// \details adds additional character for null termination. constexpr _string(size_t n) : _string('\0', n) { } /// /// \brief Sized Constructor, initializes a null-terminated string of size `n` filled with the character `c` /// \param c the character to fill with /// \param n the number of characters /// /// \details adds additional character for null termination. constexpr _string(char c, size_t n) : _str(n + 1) { fennec::memset(_str.data(), c, n); _str[n] = '\0'; } /// /// \brief Buffer Copy Constructor /// \param str the buffer to copy /// \tparam n number of characters in the buffer /// /// \details adds additional character for null termination. Ignores whether str is null-terminated. /// This constructor makes the assumption that `len` is the intended number of characters. template constexpr _string(const char str[n]) : _str(str, n + 1) { _str[n] = '\0'; } /// /// \brief Buffer Copy Constructor /// \param str the buffer to copy /// \param n number of characters in the buffer /// /// \details adds additional character for null termination. Ignores whether str is null-terminated. /// This constructor makes the assumption that `n` is the intended number of characters. constexpr _string(const char* str, size_t n) : _str(n + 1) { fennec::memcpy(_str.data(), str, n); _str[n] = '\0'; } /// /// \brief Buffer Copy Constructor /// \param str the buffer to copy constexpr _string(const cstring& str) : _str(str, str.size() + 1) { } /// /// \brief String Copy Constructor /// \param str the string to copy constexpr _string(const _string& str) : _str(str._str) { } constexpr _string(_string&& str) noexcept : _str(fennec::move(str._str)) { } /// /// \brief String Destructor, cleans up the underlying allocation constexpr ~_string() = default; // allocation cleans up itself // Properties ========================================================================================================== /// /// \returns The size of the string excluding null terminator constexpr size_t size() const { return _str.capacity() - 1; } constexpr bool empty() const { return size() == 0; } // Access ============================================================================================================== /// /// \brief Array Access Operator /// \param i the index to access /// \returns a reference to the character constexpr char& operator[](int i) { return _str[i]; } /// /// \brief Const-Array Access Operator /// \param i the index to access /// \returns a copy of the character constexpr char operator[](int i) const { assertd(i >= 0 && (size_t)i < size(), "Array Out of Bounds"); return _str[i]; } /// /// \brief Dereference Operator /// \returns A const qualified pointer to the underlying allocation constexpr const char* operator*() const { return _str.data(); } /// /// \brief Implicit Dereference Cast constexpr operator const char*() const { return _str.data(); } // Examination ========================================================================================================= /// /// \returns The length of the string to the first null-terminator constexpr size_t length() const { return find('\0'); } /// /// \brief String Comparison /// \param ostr the string to compare against /// \returns Zero if both strings are equal, otherwise a negative value if lhs appears before rhs according to the /// current locale, otherwise a positive value. constexpr int compare(const cstring& str, size_t i = 0, size_t n = npos) const { if (i >= size()) { // bounds check return -1; } n = fennec::min(n, fennec::max(_str, str.size()) + 1); return ::strncmp(_str.data() + i, str, n); } /// /// \brief String Comparison /// \param ostr the string to compare against /// \returns Zero if both strings are equal, otherwise a negative value if lhs appears before rhs according to the /// current locale, otherwise a positive value. constexpr int compare(const string& str, size_t i = 0, size_t n = npos) const { if (i >= size()) { // bounds check return -1; } n = min(n, max(size(), str.size()) + 1); return ::strncmp(_str.data() + i, str, n); } constexpr bool operator==(const _string& str) const { return compare(str) == 0; } /// /// \brief Finds the index of the first occurrence of `c` in the string /// \param c the character to find /// \returns The index of `c` if it occurs in the string, otherwise returns `size()` constexpr size_t find(char c, size_t i = 0) const { if (i >= size()) { // bounds check return size(); } const char* loc = ::strchr(_str.data() + i, c); // get location using strchr return loc ? loc - _str.data() : size(); // return size if not found } /// /// \brief Finds the index of the first occurrence of `str` in the string. /// \param str the string to find /// \returns The index of `str` if it occurs in the string, otherwise returns `size()` constexpr size_t find(const string& str, size_t i = 0) const { // bounds check if (i >= size()) { // bounds check return size(); } const char* loc = ::strstr(_str, str); return loc ? loc - _str : size(); } /// /// \brief Finds the index of the first occurrence of `str` in the string. /// \param str the string to find /// \returns The index of `str` if it occurs in the string, otherwise returns `size()` constexpr size_t find(const cstring& str, size_t i = 0) const { if (i + str.size() > size()) { // bounds check return size(); } const char* loc = ::strstr(_str + i, str); // get location using strstr return loc ? loc - _str : size(); // return size if not found } /// /// \brief Finds the index of the last occurrence of `c` in the string. /// \param c the string to find /// \param i the index to start at /// \returns The index of `c` if it occurs in the string, otherwise returns `size()` constexpr size_t rfind(char c, size_t i = npos) const { if (size() == 0) { return size(); } i = min(i, size() - 1); // clamp i to bounds do { if (_str[i] == c) { // loop backwards looking for c return i; } } while (i--); return size(); // base case } /// /// \brief Finds the index of the last occurrence of `str` in the string. /// \param str the string to find /// \param i the index to start at /// \returns The index of `str` if it occurs in the string, otherwise returns `size()` constexpr size_t rfind(const cstring& str, size_t i = npos) const { if (size() == 0) { return size(); } const char first = str[0]; i = min(i, size() - str.size()); do { if(_str[i] == first) { if (compare(str, i) == 0) { return i; } } } while (i--); return size(); // base case } /// /// \brief Finds the index of the last occurrence of `str` in the string. /// \param str the string to find /// \param i the index to start at /// \returns The index of `str` if it occurs in the string, otherwise returns `size()` constexpr size_t rfind(const string& str, size_t i = npos) const { if (size() == 0) { return size(); } const char first = str[0]; i = min(i, size() - str.size()); do { if(_str[i] == first) { if (compare(str, i) == 0) { // loop backwards looking for str return i; } } } while (i--); return size(); // base case } // Manipulation ======================================================================================================== /// /// \brief Resize the string, filling additional bytes with `'\0'` /// \param n the new size of the string constexpr void resize(size_t n) { size_t i = size(); _str.reallocate(n + 1); if (n > i) fennec::memset(_str.data() + i, '\0', n + 1 - i); } /// /// \brief Copy Assignment Operator /// \param str the string to copy /// \returns a reference to `this` constexpr _string& operator=(const cstring& str) { resize(str.size()); fennec::memcpy(_str.data(), str, str.size()); _str[str.size()] = '\0'; return *this; } /// /// \brief Copy Assignment Operator /// \param str the string to copy /// \returns a reference to `this` constexpr _string& operator=(const string& str) { resize(str.size()); fennec::memcpy(_str.data(), str, str.size()); _str[str.size()] = '\0'; return *this; } /// /// \brief Move Assignment Operator /// \param str the string to move /// \returns a reference to `this` constexpr _string& operator=(string&& str) noexcept { _str = move(str._str); return *this; } /// /// \brief Replace all instances of `x` with `y` /// \param x the character to search for /// \param y the character to replace with void replace(char x, char y) { size_t i = 0; while ((i = find(x, 0)) != size()) { _str[i] = y; } } /// /// \brief Retrieve a substring of a string /// \param i the start index /// \param n the number of characters /// \return constexpr _string substring(size_t i, size_t n = npos) const { if (i >= size()) { return _string(""); } n = min(n, size() - i); return _string(_str.data() + i, n); } /// /// \brief Returns a string with `c` appended to it /// \param c /// \returns constexpr _string operator+(char c) const { // Copy contents with one additional byte. _string res(_str.data(), _str.size()); res[size()] = c; // Set the last character to c return res; } friend constexpr _string operator+(char c, _string& str) { return _string(c, 1) + str; } constexpr _string operator+(const cstring& str) const { _string res(size() + str.size()); // Make a new string with the size of this + str fennec::memcpy(&res[0], _str.data(), size()); // Copy the contents of this fennec::memcpy(&res[size()], str, str.size()); // Append the contents of str return res; } constexpr _string operator+(const _string& str) const { _string res(size() + str.size()); // Make a new string with the size of this + str fennec::memcpy(&res[0], _str.data(), size()); // Copy the contents of this fennec::memcpy(&res[size()], str, str.size()); // Append the contents of str return res; } constexpr _string& operator+=(char c) { size_t x = size(); resize(x + 1); _str[x] = c; return *this; } constexpr _string& operator+=(const cstring& str) { size_t x = size(); resize(x + str.size()); fennec::memcpy(&_str[x], str, str.size()); return *this; } constexpr _string& operator+=(const _string& str) { size_t x = size(); resize(x + str.size()); fennec::memcpy(&_str[x], str, str.size()); return *this; } private: alloc_t _str; }; template<> struct hash : hash { constexpr size_t operator()(const string& str) const { return hash::operator()(byte_array(*str, str.size())); } }; } #endif // FENNEC_FPROC_STRINGS_STRING_H